"""
Name: isiApp
Title: ISI Application
Author: Cooper
Date: 25/03/2019

Desc: This is the main ISI Application which interfaces over MQTT with the ISI interface developed by DT.

"""
import os
import json
import time
import logging
import _thread

from hanip.onionip.console import console_task
from hanip.onionip.console import sign_manager
from hanip.itxpt import mqtt_client

from hanip.onionip import hwDetermine
from hanip.isi import isi_msg_handler

class ISIApplication():
    def __init__(self, config_dict, hw_dict, conf_dir, data_dir):
        self.debug = False
        self.conf_dir = conf_dir
        self.data_dir = data_dir
        self.config_dict = config_dict
        self.hw_dict = hw_dict

        # Globals
        self.enable_cyclic_status = True
        self.enable_event_status = True
        self.status_frequency = 60

        self.brake_active = False

        self.isi_dict = None
        self.ftp_update = False
        #####

        # Data Store
        self.sign_timer = 0
        self.current_sign_data = None
        #####

        self.ct = console_task.ConsoleTask(self.config_dict, self.hw_dict, self.conf_dir, self.data_dir)
        self.sm = sign_manager.SignManagerConsole(self.config_dict, self.data_dir)
        self.isi_msg_hdlr = isi_msg_handler.ISIMessageHandler(self.config_dict, self.data_dir)
        self.hw_determine = hwDetermine.HardwareDeterminer(None, None, None, self.conf_dir)

    """
    ###################################################################################################################
    Setup Required Modules
    """
    def setup(self):
        """
        Initialises the modules that required for init isi
        """
        self.setup_mqtt_client()
        self.setup_init_isi()
        self.setup_ftp()
        self.setup_brake_message()

    def setup_mqtt_client(self):
        self.mqtt_sub = mqtt_client.MQTT_Client()
        self.mqtt_sub.set_broker_address("127.0.0.1")
        if self.mqtt_sub.connect_client():
            self.mqtt_sub.subscribe_to_topics([(self.config_dict["ISI_mqtt_isi_topic"], 0)])
            _thread.start_new_thread(self.mqtt_sub.run_client, ())

    def setup_init_isi(self):
        if self.config_dict.get("ISI_enable", False):
            from hanip.isi import init_interface
            self.dt_init_module = init_interface.INITInterface(self.config_dict)
            logging.info("ISIApp: Starting DT Init Module")
            _thread.start_new_thread(self.dt_init_module.run, ())

    def setup_brake_message(self):
        """
        This sets up the brake message to be shown on the rear sign
        """
        sign = self.config_dict.get("ISI_brake_sign", 4)
        message = self.config_dict.get("ISI_brake_message", "STOP!")
        brightness = self.config_dict.get("ISI_brake_brightness", 99)

        self.sm.configure_adhoc_message(signs=[sign], message=message, brightness=brightness)


    """
    ###################################################################################################################
    FTP
    """
    def setup_ftp(self):
        if self.config_dict.get("FTP_enable", False):
            from hanip.isi import init_ftp_client
            self.ftp_module = init_ftp_client.FTPClient(self.config_dict, self.conf_dir)
            logging.info("ISIApp: Starting FTP Client")
            _thread.start_new_thread(self.ftp_module.run, ())


    def check_ftp_state(self):
        """
        This checks the FTP module to see if an update is available.  This only needs to let CT know there is a
        file to do and what the file is called.

        It returns the status of the update via ftp_module.update_complete, where 0 means a successful update and 1 means
        unsuccessful, after which the FTP flags in the module are reset.  Only after a successful update will the
        FTP version number be stored
        """
        if self.config_dict["FTP_enable"]:
            if self.ftp_module.update_available:
                logging.info("ISIApp: FTP update available, status %s" % self.ct.data_update_status)
                if self.ct.data_update_status == "NTP":
                    self.ct.update_ftp_payload_details(os.path.join("/tmp", self.ftp_module.payload_file_name))
                elif self.ct.data_update_status == "WTP":
                    return
                elif self.ct.data_update_status == "PROC":
                    return
                elif self.ct.data_update_status == "XFER":
                    pass
                elif self.ct.data_update_status == "XFERD":
                    pass
                elif "ERR" in self.ct.data_update_status:
                    self.ct.remote_message = "ERROR: %s" % self.ct.data_update_statues.get(self.ct.data_update_status, "NA")
                    self.wait_for_ftp_module(1)
                elif self.ct.data_update_status == "FAIL":
                    self.wait_for_ftp_module(1)
                elif self.ct.data_update_status == "DONE":
                    self.wait_for_ftp_module(0)

    def wait_for_ftp_module(self, result):
        """
        Waits for the FTP module to reset it's flags so we don't reapply the same updates again
        """
        self.ftp_module.update_complete = result

        if result:
            logging.warning("ISIApp: FTP update failed")
        else:
            logging.info("ISIApp: FTP update successful")

        while self.ftp_module.update_available == True:
            logging.info("Waiting for FTP module")
            time.sleep(1)

        self.ct.reset_ftp_payload_details()

    """
    ###################################################################################################################
    BRAKE
    """
    def check_input_states(self):
        if self.ct.digital_input_values[0]:
            self.sm.assert_adhoc_message(True)
        else:
            self.sm.assert_adhoc_message(False)

    """
    ###################################################################################################################
    ISI Status Handling
    """
    def send_isi_status(self, status_type):
        sign_states = self.sm.sign_status

        isi_status = {
            "dg3_sw_ver": self.hw_dict["software_version"],
            "onion_app_ver": self.hw_dict["onion_ver"],
            "serial_number": self.hw_dict["serial_number"],
            "id": time.time(),
            "type": status_type
        }

        for sign in sign_states:
            state = sign_states[sign]

            if state == "-":
                continue
            elif state == "0":
                isi_status[sign] = {"status": 100}
            elif state == "4":
                isi_status[sign] = {"status": 300}
            else:
                isi_status[sign] = {"status": 300}

        status_json = json.dumps(isi_status, indent=4, separators=(',', ': '))
        # print(status_json)
        logging.info("ISIApp: Publishing MQTT status")
        #Note that if the sign_table JSON does not initially exist then the first status reply will contain no sign status
        #This is only ever the case when the system is first run so I won't bother to "correct" this.
        #Also there will be lag between this and the routine that polls every 30 seconds.

        self.mqtt_sub.publish_data(
            self.config_dict["ISI_mqtt_status_topic"],
            status_json
        )

    """
    ###################################################################################################################
    Data Handling
    """
    def check_mqtt(self):
        if self.mqtt_sub.newMsg:
            self.mqtt_sub.newMsg = False

            if self.mqtt_sub.rcvdTopic == self.config_dict.get("ISI_mqtt_isi_topic", "isi_journey"):
                try:
                    isi_dict = json.loads(self.mqtt_sub.payload)
                except json.decoder.JSONDecodeError:
                    logging.warning("ISIApp: Invalid MQTT payload")
                    return 0
                else:
                    #Automatically funnel the isi dict to isi_msg_handler
                    self.isi_msg_hdlr.update_isi_dict(isi_dict)
                    return 1

        return 0

    def get_isi_dest_codes(self):
        """
        Obtains the line nummber and destination codes supplied via ISI
        """
        remote_dest_code = self.isi_msg_hdlr.get_isi_journey_item("DestinationNo")
        remote_line_code = self.isi_msg_hdlr.get_isi_journey_item("LineNo")
        remote_code = "00" + remote_line_code.zfill(4) + remote_dest_code.zfill(4)
        remote_test_mode = self.isi_msg_hdlr.get_isi_test_mode()

        logging.info("Remote code: %s\tTest Mode: %s" % (remote_code, remote_test_mode))

        return remote_dest_code, remote_line_code, remote_code, remote_test_mode

    def get_status_parameters(self):
        """
        This obtains the status parameters used by ISI
        """
        status_parameters = self.isi_msg_hdlr.get_isi_status_paramters()
        if status_parameters != None:
            self.status_frequency = status_parameters[0]
            self.enable_cyclic_status = status_parameters[1]
            self.enable_event_status = status_parameters[2]

            logging.info("Status settings: Cy%s Fr%s Ev%s" % (
            self.enable_cyclic_status, self.status_frequency, self.enable_event_status))

    def set_destination_code(self, route, dest, test):
        """
        This updates console task with the codes obtained via ISI.

        Unfortunately we need a way to determine if the code set is valid otherwise
        """
        self.ct.update_remote_code(route_code=route, dest_code=dest)

    def update_dest_code(self):
        """

        """
        if self.check_mqtt():
            codes = self.get_isi_dest_codes()
            self.set_destination_code(route=codes[1], dest=codes[0], test=codes[3])
        elif self.isi_dict == None:
            pass


    def get_sign_data(self):
        """
        This is the new method that juggles between ISI data and DG3 data, as the Onion no longer stores a copy of the
        database the sign data itself is now obtained from the console directly.
        """
        code_valid = self.ct.remote_code_valid
        remote_code = self.ct.remote_code

        if code_valid and not remote_code:
            #This is local code mode
            logging.info("ISIApp: Local code mode")
            self.sm.update_data_from_console_task(self.ct.sign_messages)
            self.ct.show_remote_message(None, 0, 0)
        elif code_valid and remote_code:
            #If the code is valid, then use the
            logging.info("ISIApp: Remote code mode")
            self.sm.update_data_from_console_task(self.ct.sign_messages)
            self.ct.show_remote_message(None, 0, 0)
        else:
            #Use the text obtained from ISI
            logging.info("ISIApp: Remote text mode")
            sign_data = self.isi_msg_hdlr.process_isi_data(self.isi_dict, self.sm.sign_dictionary)
            self.sm.update_data_from_console_task(sign_data)
            self.ct.show_remote_message("RM: %s" % self.isi_msg_hdlr.console_display_text, 0, 5)
            #TODO test mode from ISI/MQTT

    """
    ###################################################################################################################
    Chow Main
    """
    def check_restart_required(self):
        """
        Checks if this app needs to restart but also whether it is allowed to i.e. other processes have completed
        """
        if self.config_dict["FTP_enable"]:
            if self.ct.data_update_available:
                return False

        if self.ct.reboot_required:
            return True
        if self.ct.stop:
            return True


    def run(self):
        self.ct.update_console_display("ISI App Ver: %s" % (self.hw_dict["onion_ver"]), 0, 5)

        _thread.start_new_thread(self.ct.run_sign_data_mode,())
        _thread.start_new_thread(self.sm.run, ())

        self.setup()
        oldTime_ss = time.time()    #Sign status reply

        while 1:
            self.sm.test_mode = self.ct.test_mode
            if self.ct.test_mode:
                continue

            self.check_input_states()
            self.check_ftp_state()

            if self.sm.sign_fault and self.enable_event_status:
                self.send_isi_status("event")
                self.sm.sign_fault = False

            #Update sign data here, now the sign data can come from two sources, INIT ISI and DG3.
            self.update_dest_code()
            self.get_sign_data()

            if time.time() - oldTime_ss > self.status_frequency:
                if self.enable_cyclic_status:
                    self.send_isi_status("cyclic")
                    oldTime_ss = time.time()

            if self.check_restart_required():
                return

            time.sleep(1)

if __name__ == "__main__":
 pass