#!python
"""
Name: onionApp
Title: Onion Application
Author: Cooper
Date: 18/03/2019
Modified: 28/04/2020

Desc:  This is the main entry point for all Onion based IP based protocols (Lookup Redundant Acronym Syndrome) that
are going to run on the onion.  This script deals with all the common stuff which includes:
- Parsing the configuration file
- Finding out what the host is
- Finally going into the required mode

This was written to be as generic/agnostic as possible so that adding new protocols should be straight forward.

This has since been rewritten so that it no longer needs updating if a new protocol is added.  This was getting rather
bloated with all the network and several modes included since its inception, so it has been split up.

All network stuff is done with networkConfigurator, the decision to go into a specific mode is now decided by the
appropriate hw application, one for console and one for signs


"""
import os
import sys
import time
import argparse
import platform
import logging
from logging.handlers import RotatingFileHandler

from hanip.onionip import hwDetermine
from hanip.itxpt import DNS_SD
from hanip.onionip.sign import sign_firmloader

# sys.path.insert(0, r"/usr/share/python")

from hanip.onionip import onionConfig

# This import brings in the version number.
from hanip.onion_ver import onionVersion
desc = "Onion HanIP Software.  Version %s" % onionVersion

parser = argparse.ArgumentParser(desc)
parser.add_argument("--conf", nargs="?", default=r"/etc/hanip")
parser.add_argument("--data", nargs="?", default=r"/usr/share")

args = parser.parse_args()

class OnionApplication(object):
    def __init__(self):
        """
        Initialises the following member variables:

        config_dir: Path where configuration files are expected. Passed in
        as a command line argument.

        data_dir: Directory where sign data (eric.bin) can be found. Passed
        in as a command line argument.

        onion_config: Configuration parser. Used to make various checks and
        fallbacks if needed.

        network_config: Network configuration helper class

        hw_determine: HardwareDeterminer class to find out what equipment the s/w
        is running on.

        """
        self.config_dir = args.conf
        self.data_dir = args.data

        print(desc)

        self.system_platform = platform.machine()

    def load_default_configuration(self):
        """
        This is what was the default.cfg file.  To simplify image creation matters and deciding that having a default
        config is rather pointless...
        :return:
        """

        default_oconf = {
            "CONFIG_name": "NF/Factory",
            "MODE_service_mode": "factory",
            "NETWORK_dhcp_server": False,
            "NETWORK_dhcp_client": True,
            "NETWORK_local_link_address": False,
            "NETWORK_fallback_timeout": 3,
            "NETWORK_static_ip": "192.168.9.10",
            "NETWORK_wait_for_network": False,
            "NETWORK_unix_device": "br-lan",
            "NETWORK_console_override": True,
            "SERIAL_baudrate_host_console": 115200,
            "SERIAL_baudrate_host_sign": 38400,
            "SERIAL_baudrate_rs485": 38400,
            "SERIAL_unix_comport": "/dev/ttyS1",
            "SERIAL_rs485_comport": "/dev/ttyS2",
            "EOF_complete": 1
        }

        return default_oconf

    def load_configuration(self):
        """
        Looks in the configuration directory for a file called "config.cfg". If it
        is there, uses it to obtain configuration values, and if not, uses the
        file "default.cfg".

        Previous version(s) had a different behaviour for signs as they used to be exclusively itxpt only, they are
        now unified in their behaviour.

        Checks whether the network has already been configured by looking for a
        file "network.done" and sets the network_configured flag accordingly.

        There is now support for cloud credentials on the config file, this is meant for signs only, but would also
        apply to consoles

        """
        config_files = os.listdir(self.config_dir)
        logging.debug("/etc/hanip files:" + "".join(config_files))
        self.default_config = False

        if "config.cfg" in config_files:
            logging.info("Config file found!")
            self.oconf = self.onion_config.parse_configs_dict(self.config_dir, "config.cfg")
            self.check_config_valid()
        elif "cloud.cfg" in config_files:
            self.cloud_conf = self.onion_config.parse_configs_dict(self.config_dir, "cloud.cfg")
            #Need to add in the cloud config parameters into the main config
            self.oconf = self.onion_config.merge_configs(self.oconf, self.cloud_conf)
        elif os.path.exists("/etc/itxpt/"):
            logging.info("Previous itxpt_oslo config found, generating new config")
            config_generator = onionConfig.ConsatConfiguration()
            config_generator.generate_consat_configuration(self.config_dir, "config.cfg")
            #Allow the app to exit so that the supervisor starts itself
            sys.exit(0)
        else:
            logging.info("Config file not found, using default")
            self.default_config = True
            self.oconf = self.load_default_configuration()

    def check_config_valid(self):
        if self.onion_config.valid_config:
            pass
        else:
            logging.warning("Invalid/Missing configuration file, reverting to default")
            self.default_config = True
            self.oconf = self.load_default_configuration()

    def look_for_repair_service(self):
        """
        Looks for the repair DNS-SD and deletes the configuration
        :return:
        """
        logging.info("OA: Looking for repair service")
        self.dns_sd = DNS_SD.DNSSD_Discover("_itxpt_http._tcp.local.", "Han_repair")
        self.dns_sd.run()

        time.sleep(0.5)

        if self.dns_sd.serviceIP == "":
            pass
        else:
            logging.info("OA: Found repair service, deleting config")

            try:
                os.remove(os.path.join(self.config_dir, "config.cfg"))
            except OSError:
                logging.exception("OA: Cannot delete file, does it exist?")
            else:
                logging.info("OA: File deleted, going back to factory mode!")

        self.dns_sd.close()

    def get_hardware(self):
        """
        Polls the hardware that the onion is connected to
        """
        logging.info("Polling Hardware")
        self.hw_dict = self.hw_determine.get_hardware_dict()
        self.hw_dict["onion_ver"] = onionVersion
        self.set_override_address()
        logging.info("Hardware Details:")
        logging.info(self.hw_dict)

    def set_override_address(self):
        """
        If for any reason the physical switch is wrong, we can override it in the configuration file.  This will replace
        the value set by hw_determine, and replace it with the value in the config.  The original value is retained should
        it be needed elsewhere.

        """
        override_address = self.oconf.get("SIGN_override_address", None)

        if override_address is None:
            return
        else:
            try:
                int(override_address, 16)
            except ValueError:
                logging.warning("OA: Invalid override address")
                return
            else:
                logging.info("OA: address has been overidden to %s" % override_address)
                self.hw_dict["address"] = override_address

    def run_sign_app(self):
        """
        Initialises and runs the sign application
        """
        from hanip.onionip.sign import signApp
        app = signApp.SignApplication(self.oconf, self.hw_dict, self.config_dir, self.data_dir)
        app.main()

    def run_console_app(self):
        """
        Initialises and runs the console application
        """
        from hanip.onionip.console import consoleApp
        app = consoleApp.ConsoleApplication(self.oconf, self.hw_dict, self.config_dir, self.data_dir)
        app.main()

    def main(self):
        """
        Main entry point into the hanip application
        :return:
        """
        self.look_for_repair_service()

        self.onion_config = onionConfig.OnionConfig()
        self.load_configuration()

        # setup_logger(self.oconf.get("LOGGING_enabled", False), self.oconf.get("LOGGING_level", "info"))

        self.hw_determine = hwDetermine.HardwareDeterminer(self.oconf.get("SERIAL_unix_comport", "/dev/ttyS1"),
                                                           self.oconf.get("SERIAL_baudrate_host_console", 115200),
                                                           self.oconf.get("SERIAL_baudrate_host_sign", 38400),
                                                           self.config_dir)

        self.get_hardware()

        if self.hw_dict["hw_type"] in ["ext", "int"]:
            self.run_sign_app()

        elif self.hw_dict["hw_type"] == "con":
            self.run_console_app()

        elif os.path.isfile(sign_firmloader.SignFirmloader(None).flag_path):
            #If the hardware cannot be identified but there is a flag
            logging.info("OA: Sign firmware flag present, loading signApp")
            self.run_sign_app()

        else:
            #Maybe not terminate and do something useful
            logging.warning("Hardware not identified. Terminating.")



if __name__ == "__main__":

    #Check for debug flag:
    if os.path.isfile("/tmp/oniondebug") or os.path.isfile("/etc/hanip/oniondebug"):
        debug_level = logging.DEBUG
    else:
        debug_level = logging.INFO


    log_file = "/tmp/onion.log"
    logger = logging.getLogger(__name__)

    logging.basicConfig(
        handlers=[RotatingFileHandler(log_file, mode='a', maxBytes=5*1024*1024, backupCount=3, encoding=None, delay=0)],
        format="'%(asctime)s %(levelname)s %(filename)s %(funcName)s %(message)s'",
        datefmt="%d-%m %H:%M:%S",
        level=debug_level
    )

    oa = OnionApplication()
    oa.main()
    print("Goodbye World, Supervisor will restart me hopefully! ")
