"""
Name: consoleApp
Title: 
Author: Cooper
Date: 28/04/2020

Desc:

"""
import os
import sys
import time
import logging
import subprocess

from hanip.onionip import hano1
from hanip.onionip import networkConfigurator

class ConsoleApplication(object):
    def __init__(self, config_dict, hw_dict, conf_dir, data_dir):
        self.config_dict = config_dict
        self.hw_dict = hw_dict
        self.config_dir = conf_dir
        self.data_dir = data_dir

        self.debug = False
        self.restart = False
        self.onion_mode = False
        self.prod_mode = False

        comport = self.config_dict.get("SERIAL_unix_comport", "/dev/ttyS1")
        baud = self.config_dict.get("SERIAL_baudrate_host_console", 115200)

        self.network_conf = networkConfigurator.NetworkConfigurator(self.config_dict, self.config_dir)
        self.hano1 = hano1.HANO1(comport, baud, True, None)

    """
    ###################################################################################################################
    Console specific commands
    """
    def update_console_display(self, message, line, time):
        self.hano1.showOnConsole(message, line, time)

    def poll_console(self):
        """
        This is intended to be a get out of jail free card when an invalid config is loaded, allows the user to delete
        the dodgy config via the terminal so that a new one can be loaded when the unit reboots into terminal mode.
        :return:
        """
        status, keyspressed = self.hano1.poll_console()

        if status == 2:
            logging.info("Config deletion requested...")
            try:
                os.remove(os.path.join(self.config_dir, "config.cfg"))
                self.update_console_display("Deleting config!", 0, 5)
            except OSError:
                pass

            return 1

        return 0

    def check_console_firmware(self):
        """
        This checks whether the enchanced Onion commands are available so that the software can decide how it talks
        to the console
        :return:
        """
        if "onion" in self.hw_dict["software_version"].lower():
            self.onion_mode = True
        elif "ibis" in self.hw_dict["software_version"].lower():
            self.onion_mode = True
        elif self.config_dict.get("MODE_force_onion_mode", False):
            self.onion_mode = True

    """
    ###################################################################################################################
    Network Stuff
    """
    def network_blocking_override(self):
        """
        Implemented as part of ONION-56, this function checks to see if any network related stuff is to be ignored
        as Onion based consoles are being fitted with no network connectivity.
        """
        blocking_override = self.config_dict.get("NETWORK_blocking_override", False)

        if blocking_override:
            logging.warning("CA: Network blocking override is enabled!")
            self.config_dict["MODE_service_mode"] = "muted"
            return 0
        else:
            return 1

    def configure_network(self):
        if self.onion_mode:
            console_override = self.config_dict.get("NETWORK_console_override", False)

            if not console_override:
                logging.info("Obtaining Console network parameters")
                self.obtain_network_params_from_console()
            else:
                logging.info("Using oconf network parameters")

        wait_for_link_status = self.wait_for_link()

        if wait_for_link_status == 1:
            self.network_conf.configure_network()
            self.network_conf.getIPAddress()

            if not self.network_conf.network_status == 0:
                if self.config_dict.get("NETWORK_wait_for_network", True) or self.network_conf.fallback_enable:
                    self.wait_for_network()

        elif wait_for_link_status == 2:
            self.hano1.show_on_terminal("0", "C", "Going to muted mode")
            self.config_dict["MODE_service_mode"] = "muted"
        else:
            #No point in waiting for DHCP if theres no link and it was forably exited
            self.prod_mode = True

    def obtain_network_params_from_console(self):
        """
        This obtains all the relevant network parameters from the console such as:
        DHCP CLIENT, IP, GATEWAY

        Other parameters that aren't available in the console front panel will still rely on Onion configconfig
        :return:
        """
        oconf_map = {
            "NETWORK_static_ip": "IP_ADDRESS",
            "NETWORK_subnet_mask": "SUBNET_MASK",
            "NETWORK_gateway": "DEFAULT_GATEWAY",
            "NETWORK_dhcp_server": "DHCP_SERVER",
            "NETWORK_dhcp_client": "DHCP_CLIENT",
            "NETWORK_local_link_address": "LINK_LOCAL",
            "NETWORK_dns_server": "DNS_SERVER"
        }

        for oconf_key, console_key in oconf_map.items():
            print(oconf_key, console_key)
            console_param = self.hano1.get_parameter_value(console_key)
            if console_param == None:
                print("\t%s (oconf val)" % self.config_dict[oconf_key])
                #If the value returned from the console is unknown use the value in ofconfig
                continue
            elif console_param == "0":
                self.config_dict[oconf_key] = False
            elif console_param == "1":
                self.config_dict[oconf_key] = True
            else:
                self.config_dict[oconf_key] = console_param

            print("\t%s" % self.config_dict[oconf_key])

        #Update network conf dict
        self.network_conf.config_dict = self.config_dict

    def wait_for_link(self):
        """
        Sits in a loop and waits for the sign to get a network link
        """
        self.hano1.start_terminal_mode()
        self.hano1.clear_terminal()

        while 1:
            link_status = self.network_conf.get_link_status()

            if link_status:
                break
            else:
                logging.error("SA: No network link")
                self.hano1.show_on_terminal("0", "C", "No network")
                self.hano1.show_on_terminal("1", "C", "Waiting for link")

                key = self.wait_for_user_input()
                if key == 0:    # Left arrow
                    break
                elif key == 2:  # Down arrow:
                    return 2

            time.sleep(1)

        self.hano1.end_terminal_mode()
        return link_status


    def wait_for_network(self):
        initial_time = time.time()

        self.hano1.start_terminal_mode()
        self.hano1.clear_terminal()

        while 1:
            self.network_conf.getIPAddress()

            if self.network_conf.network_status == 0 and not self.debug:
            # if 0:
                break
            else:
                self.hano1.show_on_terminal("0", "C", "No network")
                self.hano1.show_on_terminal("1", "C", "Waiting for DHCP")
                time.sleep(1)

                key = self.wait_for_user_input()
                if key == 0:
                    self.hano1.show_on_terminal("1", "C", "Exiting wait loop")
                    #Setting this allows the console to go into prod mode which will either allow a new config or
                    #different network settings.
                    self.prod_mode = True
                    time.sleep(1)
                    break

            if self.network_conf.fallback_enable:
            # if 1:
                if (time.time() - initial_time) > int(self.config_dict["NETWORK_fallback_timeout"]):
                    self.hano1.show_on_terminal("0", "C", "Backup DHCP Server:")
                    self.hano1.show_on_terminal("1", "C", "Activate?")
                    self.hano1.show_on_terminal("2", "L", "Yes     No")
                    key = self.wait_for_user_input()

                    if key == 0:
                        initial_time = time.time()
                        self.hano1.clear_terminal()
                        self.hano1.show_on_terminal("0", "C", "No network")
                        self.hano1.show_on_terminal("1", "C", "Waiting for DHCP")
                        continue
                    elif key == 1:
                        if self.network_conf.network_status == 0:
                            self.hano1.show_on_terminal("0", "C", "IP Obtained")
                            self.hano1.show_on_terminal("1", "C", self.network_conf.ip_address)
                        else:
                            self.hano1.clear_terminal()
                            self.hano1.show_on_terminal("0", "C", "Reconfiguring network")

                            static_ip_address = self.config_dict["NETWORK_static_ip"]
                            netmask = self.config_dict["NETWORK_subnet_mask"]
                            gateway = self.config_dict["NETWORK_gateway"]
                            self.hano1.show_on_terminal("1", "C", static_ip_address)
                            self.network_conf.set_static_ip(static_ip_address, netmask, gateway)
                            self.network_conf.dhcp_server(1)
                            self.hano1.show_on_terminal("0", "C", "Restarting network")
                            self.network_conf.restart_network()
                            self.network_conf.restart_dhcp()

                        # We need to delete the network flag so that the next boot it goes into DHCP mode again
                        self.network_conf.delete_network_flag()
                    elif key == 2:
                        self.hano1.end_terminal_mode()
                        from hanip.onionip.console import console_task
                        mode = console_task.ConsoleTask(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
                        mode.run_prod_mode()

                        self.restart = True
                        return
                    else:
                        continue

        self.hano1.end_terminal_mode()

    def update_host_name(self):
        device_host_name = self.hw_dict["model"]

        commands = [
            ["uci", "set", "system.@system[0].hostname=%s" % device_host_name],
            ["uci", "commit"],
            ["/etc/init.d/system", "restart"]
                    ]

        logging.info("Setting device hostname")

        for command in commands:
            # print(command)
            if sys.platform == "linux":
                process = subprocess.Popen(command, stdout=subprocess.PIPE)
                output, error = process.communicate()

    def wait_for_user_input(self):
        """
        When the timer has exceeded, the console should prompt the driver to enable the DHCP server.  In this state
        the DG3 needs to be in the Onion Terminal mode

        #TODO Sort out EG3 keypresses, but only FARA uses this perverse setup.
        :return:
        """
        key = self.hano1.wait_for_keypress(5)
        # print(key)
        console = self.hw_dict["model"].lower()

        if key == None:
            return None
        elif key == "\x0A" or key == "\x0D":
            return 1
        elif key == "\x0C" or key == "<":
            if "dg3" in console:
                return 0
            else:
                return 1
        elif key == ">":
            return 0
        elif key == "-":
            return 2
        elif key == "+":
            return 3

    """
    ###################################################################################################################
    Main entry point and Mode selector
    """
    def main(self):
        # This part allows the app to delete the config if required via the console front panel if a dodgy config causes
        # it to be a in reboot loop
        if self.poll_console():
            return

        self.check_console_firmware()
        if self.network_blocking_override():
            self.configure_network()

        #If for whatever reason terminal mode was not closed beforehand, this will ensure it does so that the
        #application can progress normally.
        self.hano1.end_terminal_mode()

        if self.restart:
            return

        self.hw_dict["unit_IP"] = self.network_conf.ip_address
        self.hw_dict["unit_MAC"] = self.network_conf.mac_address

        if self.hw_dict["serial_number"] == "":
            try:
                self.hw_dict["serial_number"] = self.network_conf.mac_address.replace(":", "")
            except AttributeError:
                self.hw_dict["serial_number"] = "not_available"

        if self.prod_mode:
            service_mode = "factory"
        else:
            service_mode = self.config_dict.get("MODE_service_mode", "factory")

        self.update_console_display("MODE: %s" % service_mode, 0, 3)
        self.update_console_display("ver: %s" % self.hw_dict["onion_ver"], 1, 3)

        self.hano1.closeSerial()

        print("SERVICE MODE: %s" % service_mode)

        if service_mode == "factory":
            from hanip.onionip.console import console_task
            mode = console_task.ConsoleTask(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run_prod_mode()

        elif service_mode == "muted":
            from hanip.onionip.console import console_task
            mode = console_task.ConsoleTask(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run_muted_mode()

        elif service_mode == "mqtt":
            from hanip.itxpt import itxpt_mqtt
            mode = itxpt_mqtt.ITxPTMQTT(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run()

        elif service_mode == "avms":
            from hanip.itxpt import itxpt_avms
            mode = ""

        elif service_mode == "keolis_orleans":
            from hanip.itxpt import keolis_avms
            mode = keolis_avms.KeolisAVMS(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run()

        elif service_mode == "hanover_mqtt":
            from hanip.itxpt import hanover_mqtt
            mode = hanover_mqtt.HanoverMQTT(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run()

        elif service_mode == "laus":
            from hanip.itxpt import lausanne_mqtt
            app = lausanne_mqtt.Lausanne_ConsoleApp(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            app.run()

        elif service_mode == "ad-isi":
            from hanip.isi import isiApp
            mode = isiApp.ISIApplication(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run()

        elif service_mode == "ibis-ip":
            from hanip.ibis_ip import ibisipApp
            mode = ibisipApp.IBISIPConsoleApplication(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run()

        elif service_mode == "passthrough":
            from hanip.onionip.console import passthrough_mode
            mode = passthrough_mode.PassthroughMode(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run()

        elif service_mode == "dev":
            from hanip.onionip.console import console_task
            mode = console_task.ConsoleTask(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run_dev_mode()

        elif service_mode == "webcontroller":
            from hanip.web_controller import wifi_controller
            mode = wifi_controller.WebAppConsole(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run()
        else:
            print("%s not supported" % service_mode)
            print("Going into factory mode")
            from hanip.onionip.console import console_task
            mode = console_task.ConsoleTask(self.config_dict, self.hw_dict, self.config_dir, self.data_dir)
            mode.run_prod_mode()

if __name__ == "__main__":
    pass
