"""
Name: Hanover MQTT
Title: Hanover MQTT
Author: Gianandrea Manfredi
Date: 19/01/2021
Last Modified: 27/01/2022
Desc: Hanover implementation of the ITxPT mqtt functionality for consoles and signs

#console
onionApp -> consoleApp -> hanover_mqtt -> console_task -> hano1
                                       -> itxpt_mqtt_connection
                                       -> itxpt_mqtt

#sign
onionApp -> signApp -> hanover_mqtt -> sign_task - hano1
                                    -> itxpt_mqtt_connection
                                    -> itxpt_mqtt
"""

import copy
import json
import _thread
import os
import threading
import time

from hanip.itxpt import module_inventory_service
from hanip.itxpt import itxpt_mqtt
from hanip.itxpt.itxpt_mqtt import ConsoleMQTT
from hanip.itxpt.itxpt_mqtt_connection import MQTTConnectionClass

from hanip.onionip import hwDetermine
from hanip.onionip import hcp

hanover_mqtt_app_ver = "0.9"


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

    def run(self):
        if self.hw_dict["hw_type"] == "con":
            print("HanMQTT: MQTT service running on Console")
            mqtt_service = ConsoleMQTT(self.config_dict, self.hw_dict, self.conf_dir, self.data_dir)
        else:
            print("HanMQTT: MQTT service running on Sign")
            mqtt_service = HanoverSignMQTT(self.config_dict, self.hw_dict, self.conf_dir, self.data_dir)

        mqtt_service.run()


"""
#######################################################################################################################
CONSOLE CLASS - Manages console functionality for hanover mqtt application
#######################################################################################################################
"""


class ConsoleMQTT(object):
    def __init__(self, config_dict, hw_dict, conf_dir, data_dir):
        """
        Console MQTT class managing the running of an MQTT based service on
        the console.

        Parameters
        ----------
        config_dict : dict
            application configuration dictionary
        hw_dict : dict
            hardware configuration dictionary
        conf_dir : dict

        data_dir : dict
            data dictionary
        """
        self.config_dict = config_dict
        self.hw_dict = hw_dict
        self.conf_dir = conf_dir
        self.data_dir = data_dir
        
        self.thread_event = threading.Event()

        self.connection = None
        self.legacy_mode = False

        self.TRM_MODE_WAIT = 100
        self.trm_rsp_timer = 0
        self.trm_wait = False

        self.conssta_interval = self.signsta_interval = self.dri_interval = self.text_interval = 0

        # connection topics - subscribes to all topics from sign and htc.
        topic_prefix = self.config_dict.get("MQTT_base_topic", "hanover/dpi/")
        if topic_prefix[-1] != "/":
            topic_prefix + "/"

        self.obc_topic = topic_prefix + "obc/#"
        self.sign_topic = topic_prefix + "sign/#"

        # topics that will be monitored for or sent
        self.data_send = "/send"
        self.data_ack = "/ack"
        self.data_prog = "/prog"

        self.dri_topic = topic_prefix + "%s/cur-dricode"            # dest,route,info
        self.text_topic = topic_prefix + "%s/ctext"                 # console text
        self.status_topic = topic_prefix + "%s/cstatus"             # console status
        self.all_sign_topic = topic_prefix + "sign/status/all"      # all sign status
        self.sign_topic = topic_prefix + "sign/status/#address"     # sel sign status
        self.test_topic = topic_prefix + "%s/stest/all"             # sign test
        self.trmode_topic = topic_prefix + "%s/tmode"               # console terminal mode
        self.trmode_key_topic = topic_prefix + "cons/keypress"      # trmode - console keypress
        self.db_update = topic_prefix + "%s/upd/data"               # console programming 

        self.console_status = {
            "ManualMode": True,
            "ConStatInterval": 3,
            "SignStatInterval": 5,
            "DRICodeInterval": 5,
            "ConTextInterval": 10,
        }

        self.topic_list = [
            topic_prefix + "obc/#",
            topic_prefix + "sign/#",
            topic_prefix + "cons/keypress"
        ]

        self.sign_status = [None]*15
        self.sign_count = 0

        self.last_rcvd_cons_txt = None

        self.update_status = None
        self.hwd = hwDetermine.HardwareDeterminer(None, None, None, self.conf_dir)
        self.legacy_mode = self.config_dict.get("MODE_legacy_data_mode", False)

    class _UpdateData(object):
        """
        Console Update class
        """
        _MAX_DB_DATA_ATTEMPTS = 5
        _DB_TIMEOUT = 40
        """db update status"""
        _DB_UPDATING = 0
        _DB_COMPLETE = 1
        _DB_FAILED = 2

        """db update type"""
        _ERICBIN = 1
        _valid_update_type = [_ERICBIN]

        _db_update_data = ""
        _db_attempts = 0
        _db_prev_block_no = 0
        _db_timeout = 0
        _updating_flag = False

    """
    ##############################################################################################
    Helpers
    """
    def Exit_Application_Gracefully(msg, code=0):
        """Exits application gracefully in the event of a fail state that
        will not allow the program to suceed to run correctly"""

        raise SystemExit(code)

    @staticmethod
    def get_sign_count(mapping_table):
        """Gets count of signs"""
        sign_count = 0

        if mapping_table is not None:
            for sign in mapping_table:
                if sign != "-":
                    sign_count += 1

        return sign_count

    @staticmethod
    def CRC16_check(data):
        """CRC Check for database update data"""
        check = 0

        data = bytearray.fromhex(data)

        for b in data:
            check = check ^ b

        return hex(check).replace('0x', "").upper().zfill(2)

    """
    ##############################################################################################
    Helpers - Workaround functions 
    """
    def wrk_restart_console_task(self):
        """
        Issue: When a database update occurs, the console will still return
        old data despite the update stops and restarts console task to reload
        data provided from the console
        """
        self.ct.stop = True
        self.setup_console_task()
        time.sleep(10)
    
    """
    ##############################################################################################
    Console set-up
    """
    def run_setup(self):
        """"Runs the setup routine for the console on application startup"""
        self.setup_console_mqtt()
        self.setup_console_task()
        self.show_terminal_status()
        self.displayVersion()
        self.itxpt = itxpt_mqtt.ConsoleMQTT(self.config_dict, self.hw_dict, self.config_dict, self.data_dir)
        self.itxpt.setup_template_gen()

    def setup_mis(self):
        """Runs module inventory service"""
        self.mis = module_inventory_service.ModuleInventoryService(self.config_dict, self.hw_dict)
        self.mis.run()

    def setup_template_gen(self):
        from hanip.onionip.sign import template_generator
        print("HanMQTT: Database init")
        self.itxpt.tg = template_generator.TemplateGenerator(self.config_dict, self.data_dir)
    
    def setup_console_task(self):
        """Runs console_task, which runs in a seperate thread to monitor
        and update console values"""
        from hanip.onionip.console import console_task
        self.ct = console_task.ConsoleTask(self.config_dict, self.hw_dict, self.conf_dir, self.data_dir)
        self.ct.delay_interval = 0.2
        self.ct.monitor_console_flag = True

        if self.legacy_mode:
            _thread.start_new_thread(self.ct.run, ())
        else:
            _thread.start_new_thread(self.ct.run_sign_data_mode, ())

    def show_terminal_status(self):
        """Shows terminal status on startup"""
        self.ct.show_terminal_status_old()

    def displayVersion(self):
        """Displays onion application version on the console"""
        if hanover_mqtt_app_ver == "":
            module_ver = self.hw_dict["onion_ver"]
        else:
            module_ver = "%s_%s" % (self.hw_dict["onion_ver"], hanover_mqtt_app_ver)

        self.ct.update_console_display("Hanover MQTT App Ver: %s" % (module_ver), 0, 5)

    """
    ##############################################################################################
    Console connection
    """
    def setup_console_mqtt(self):
        """Setup the mqtt services for the console. If the console is setup
        as a broker in the config it will also setup the broker services.
        All through MQTT connection class
        """
        self.connection = MQTTConnectionClass(0, self.config_dict, self.hw_dict, self.topic_list)
        _thread.start_new_thread(self.connection.run, ())

    """
    ##############################################################################################
    Console Serialization and Deserialization for HTC <-> Console tasks
    """
    def publish_dest_information(self):
        """Publishes DRI data for the dri topic

        Topic
        -----
        hanover/dpi/cons/cur-dricode
        """
        publish_topic = self.dri_topic % "cons"

        dest_data = {
            "CurrentDestCode": self.ct.dest_code,
            "CurrentRouteCode": self.ct.route_code,
            "CurrentInfoCode": self.ct.info_code,
            "ManualMode": not self.ct.remote_set_code
        }

        json_txt = json.dumps(dest_data, indent=4, separators=(',', ': '))
        self.connection.mqtt_msg_to_queue(publish_topic, json_txt)

    def get_dest_information(self, dri_data):
        """Parses DRI data received from the HTC dri topic and updates 
        variables and config. Setting of dest and info code requires
        for the correct setting of *remote priority* in the console
        configuration. Remote priority 3 enables change of full dri
        
        Topic
        -----
        hanover/dpi/obc/cur-dricode

        Parameters
        ----------
        dri_data :  str
            destination, route, info code data as string
        """
        valid = False
        dri_data = json.loads(dri_data)
        
        print("HanoverMQTT: Received new remote dri")
        
        if "CurrentDestCode" in dri_data and isinstance(dri_data["CurrentDestCode"], str) or dri_data["CurrentDestCode"] is None:
            dest_code = dri_data["CurrentDestCode"]
            if "CurrentRouteCode" in dri_data and (isinstance(dri_data["CurrentRouteCode"], str) or dri_data["CurrentRouteCode"] is None):
                route_code = dri_data["CurrentRouteCode"]
                if "CurrentInfoCode" in dri_data and (isinstance(dri_data["CurrentInfoCode"], str) or dri_data["CurrentInfoCode"] is None):
                    info_code = dri_data["CurrentInfoCode"]
                    if "ManualMode" in dri_data and isinstance(dri_data["ManualMode"], bool):
                        self.ct.set_dri_codes(dest_code, route_code, info_code)
                        valid = True

        if valid:
            return valid
        else:
            print("HanMQTT: dri payload invalid, internal data has not been updated")

    def publish_console_text(self):
        """Publishes the console text that is currently displayed on 
        the front panel
        
        Topic
        -----
        hanover/dpi/cons/ctext
        """

        publish_topic = self.text_topic % "cons"
        
        """
        This is the format the msg should take, but this data cannot be
        retrieved. so we send back what we got as it is restructured.

        console_text = {
            "DisplayTime": "",
            "DisplayInterval": 0,
            "Lines": {
                    "<line-name>": {
                    "Justify": "Position",
                    "Text": ""
                    }
            },
            "Cursor": True,
                "Audio": {
                "Sound": "",
                "Frequ": "",
                "Duration": ""
            }
        }
        """

        json_txt = json.dumps(self.last_rcvd_cons_txt, indent=4, separators=(',', ': '))
        self.connection.mqtt_msg_to_queue(publish_topic, json_txt)

    def get_console_text(self, console_text):
        """Parses text data from the HTC and updates the 
        console text accordingly
        
        Topic
        -----
        hanover/dpi/obc/ctext
        
        Parameters
        ----------
        console_text : str
            text to be set on the console
        """
        valid = audio_valid = False
        justify = "L"
        console_text = json.loads(console_text)

        # ♪♪♪ these can be ommited, but we save them if they exist ♬♬♬
        if "Cursor" in console_text and isinstance(console_text, bool):
            audio = console_text["Cursor"]
        if "Audio" in console_text and isinstance(console_text["Audio"], int):
            if "Sound" in console_text and isinstance(console_text["Sound"], bool):
                sound = console_text["Sound"]
                if "Frequ" in console_text and isinstance(console_text["Frequ"], int):
                    freq = console_text["Frequ"]
                    if "Duration" in console_text and isinstance(console_text["Duration"], int):
                        audio_duration = console_text["Duration"]
                        audio_valid = True

        if "DisplayTime" in console_text and isinstance(console_text["DisplayTime"], int):
            display_time = console_text["DisplayTime"]
        if "DisplayInterval" in console_text and isinstance(console_text["DisplayInterval"], int) and console_text["DisplayInterval"] > 0:
            interval = console_text["DisplayInterval"]
            if "Lines" in console_text:
                i = 0
                for line in console_text["Lines"]:
                    if "Text" in console_text["Lines"]["%s" % line]:
                        text = console_text["Lines"]["%s" % line]["Text"]
                        if "Justify" in console_text["Lines"]["%s" % line]:
                            justify = console_text["Lines"]["%s" % line]["Justify"]
                        # cannot currently justify the text whilst also declaring interval
                        self.ct.hano.showOnConsole(text, i+1, interval)
                    if i > 2:
                        break
                    i += 1

        if audio_valid and valid:
            pass
            # play sound / move cursor
            # TODO: This functionality is not currently available onion to 

    def get_sign_OK(self):

        sign_OK = False

        for sign in self.sign_status:
            if sign is not None:
                if "StatusByte" in sign and sign["StatusByte"] == '0':
                    sign_OK = True
                elif "StatusByte" in sign and sign["StatusByte"] != '0':
                    sign_OK = False
                    break

        return sign_OK

    def publish_console_status(self):
        """
        Publishes the console status in the hanover_mqtt formatting

        Topic
        -----
        Hanover/dpi/cons/cstatus
        """
        publish_topic = self.status_topic % "cons"

        data_updating = True if self.update_status is not None and self.update_status._updating_flag else False
        sign_status = self.get_sign_OK() if self.sign_status is not None else "NA"

        mapping_table = copy.deepcopy(self.ct.sign_mapping_table)
        mapping_table = str(mapping_table) if mapping_table is not None else None

        console_data = {
            "ManualMode": not self.ct.remote_set_code,
            "ConStatInterval": self.console_status["ConStatInterval"],
            "SignStatInterval": self.console_status["SignStatInterval"],
            "DRICodeInterval": self.console_status["DRICodeInterval"],
            "ConTextInterval": self.console_status["ConTextInterval"],
            "DataUpdating": data_updating,
            "DataVersion": self.ct.data_version,
            "DataOK": True if mapping_table is not None else False,
            "UpdateBlockSize": 1024,
            "IPAddress": self.hw_dict["unit_IP"],
            "MACAddress": self.hw_dict["unit_MAC"],
            "AppVersion": self.hw_dict["onion_ver"],
            "FirmwareVersion": self.hw_dict["software_version"],
            "SignsFitted": mapping_table,
            "AllSignsOK": sign_status
        }       

        json_txt = json.dumps(console_data, indent=4, separators=(',', ': '))
        self.connection.mqtt_msg_to_queue(publish_topic, json_txt)

    def get_console_status(self, console_status):
        """Parses console status data sent from the HTC and updates the 
        status variables and config

        Topic
        -----
        Hanover/dpi/obc/cstatus

        Parameters
        ----------
        console_status : str
            console status variables to be set
        """
        valid = False
        console_status = json.loads(console_status)

        if "ManualMode" in console_status and isinstance(console_status["ManualMode"], bool):
            manual_mode = console_status["ManualMode"]
            if "ConStatInterval" in console_status and isinstance(console_status["ConStatInterval"], int):
                console_status_interval = console_status["ConStatInterval"]
                if "SignStatInterval" in console_status and isinstance(console_status["SignStatInterval"], int):
                    sign_status_interval = console_status["SignStatInterval"]
                    if "DRICodeInterval" in console_status and isinstance(console_status["DRICodeInterval"], int):
                        dri_code_interval = console_status["DRICodeInterval"]
                        if "ConTextInterval" in console_status and isinstance(console_status["ConTextInterval"], int):
                            cons_text_interval = console_status["ConTextInterval"]
                            self.console_status["ManualMode"] = manual_mode
                            self.console_status["ConStatInterval"] = console_status_interval
                            self.console_status["SignStatInterval"] = sign_status_interval
                            self.console_status["DRICodeInterval"] = dri_code_interval
                            self.console_status["ConTextInterval"] = cons_text_interval
                            print("HanMQTT: updated status:", str(self.console_status))
                            valid = True

        if not valid:
            print("HanMQTT: error in cstatus payload, settings not updated")

    """
    ##############################################################################################
    Console Serialization and Deserialization for HTC | Console <-> Sign tasks
    """
    def cons_publish_sign_status(self):
        """Publishes hanover_mqtt format of extended sign status for
        the signs from the console for each enumerated sign

        The status_stringelement containsa representation of the
        standard Hanover Sign Short Status byteas a 2-character
        ASCII/UTF-8 string. Converted to a single binary byte, it
        is interpreted as follows:
        •Bit 0 & Bit 1 = Message status (0 = MsgOk, 1 = MsgConError,
        2 = MsgTxError, 3 = Lamp Failure)
        •Bit 3 = Sign Busy(0 = Sign Free, 1 = Sign Busy)
        •Bit 4 = Message Space Available (0 = Space Available, 1 = Space Full)
        
        Topic
        -----
        hanover/dpi/sign/status/all - console published
        """
        publish_topic = self.all_sign_topic
        mapping_table = copy.deepcopy(self.ct.sign_mapping_table)
        sign_count = self.get_sign_count(mapping_table)

        sign_data = {
                "SignCount": sign_count,
                "SignStats": {

                }
            }

        # add individual status' to sign_data
        if self.ct.sign_mapping_table is None:
            return
        else:
            for i, sign in enumerate(self.sign_status):
                if self.sign_status[i] is not None:
                    sign = {"Sign%s" % str(i): self.sign_status[i]}
                    sign_data["SignStats"].update(sign)

        json_txt = json.dumps(sign_data, indent=4, separators=(',', ': '))
        self.connection.mqtt_msg_to_queue(publish_topic, json_txt)

    def get_sign_status(self, sign_status, address):
        """saves a copy of the extended sign status for the sign into an array

        Topic
        -----
        hanover/dpi/sign/status/<hcp_address> - sign published

        Parameters
        ----------
        sign_status : str
            sign status data
        address : str
            sign address
        """
        print("HanMQTT: console saving sign status for address: " + address)
        self.sign_status[int(address)] = json.loads(sign_status)

    def publish_sign_test(self, start, enhanced=False):
        """Publishes the test all topic. When the signs receive this topic,
        they stop displaying their previous contents, if any, and either
        start or stop their test sequence.
        
        Topic
        -----
        hanover/dpi/cons/stest/all

        Parameters
        ----------
        start : bool
            Start sign test true or false
        enhanced : bool
            enhanced test true or false
        """
        publish_topic = self.test_topic % "cons"

        test_data = {
            "SignSelfTest": start,
            "Enhanced": enhanced
        }

        json_txt = json.dumps(test_data, indent=4, separators=(',', ': '))
        self.connection.mqtt_msg_to_queue(publish_topic, json_txt)

    def get_sign_test(self, data):
        """Processes the sign test message as sent by the obc"""

        data = json.loads(data)

        if "SignSelfTest" in data and isinstance(data["SignSelfTest"], bool):
            if "Enhanced" in data and isinstance(data["Enhanced"], bool):
                if data["SignSelfTest"]:
                    self.ct.remote_test_mode = True
                elif data["SignSelfTest"] is False:
                    self.ct.remote_test_mode = False
                    self.ct.disable_test_mode = True

    def publish_sign_display_rawhcp(self):
        """This  topic may be published  by both the  EG3 andthe HTC.
        As its name suggests, its  main function is  to control Hanover
        Signs using  the normal HCP protocol.
        
        Topic
        -----
        -hanover/dpi/sign/command/<hcp_addr>
        •“header”: 8-bit Message Start token, either 0x02for a master message,
        or 0x01for a slave message.
        •“command”:One of the supported HCPCommandsshown in Appendix B –
        HCP Commands.
        •“address”:The HCP Address of the equipment the command is being sent
        •“body”:The body of the message, expressed as a continuoussequence of
        8-bit bytes encoded aspairs of ASCII hex digits.
        •“tail”:8-bit Message End token, either 0x03 for a master message,
        or 0x04 for slave message,
        •“checksum”:a single  8-bit byte encoded as a pair of ASCII digits,
        expressing the LRC of every byte from the byte following the Message 
        Start Tokento the Message End Token. 
        
        Example
        -------
        '\x0209zM00013?\x03
        """
        if self.ct.sign_mapping_table is not None:

            while not self.connection.q_prioirity.empty():
                self.connection.q_prioirity.get()

            manual_code = copy.deepcopy(self.ct.manual_code)
            route_code = copy.deepcopy(self.ct.route_code)
            test_mode = copy.deepcopy(self.ct.test_mode)
            sign_messages = copy.deepcopy(self.ct.sign_messages)

            try:
                sign_data = []
                sign_data = self.itxpt.get_display_data(manual_code, route_code, test_mode, sign_messages, False)

                if sign_data is not None:
                    for addr, data in enumerate(sign_data, 1):
                        tmpdata = json.loads(data)

                        if "requestData" in tmpdata:
                            pass
                        else:
                            publish_topic = "hanover/dpi/sign/command/%s" % addr
                            self.connection.mqtt_msg_to_queue(publish_topic, data, True)
            except Exception as e:
                print("HanMQTT: Error sending sign msg")

    def get_sign_display_rawhcp(self, sign_data):
        """Parses sign display data. Its published and subscribed.
        
        Topic
        -----
        hanover/dpi/sign/rawstatus/<hcp_addr>

        Parameters
        ----------
        sign_data : str
            sign data
        """
        pass
    
    """
    ##############################################################################################
    Console Serialization and Deserialization for TERMINAL MODE
    In this mode, the Console is  used as a dumb terminal, with its  display
    being  controlled by the HTCand the Console sending back keypresses to
    the HTC in order tochange values which are under HTC control.In this mode,
    the  consolestops  driving the  signs.  This  is  because in  legacy
    systems, the  HTC and the  signs  are present on the same RS485
    communications bus.
    """
    def publish_terminal_mode(self, trm_mode=False):
        """
        Gets or sets terminal mode depending on the intiation of the function

        Once the topic is published there is 10 seconds for a response to be
        received before it is decided whether to proceed with the action or
        not.

        -hanover/dpi/"origin"/tmode

        Parameter
        ---------
        trm_mode : bool       
            get or set terminal mode topic payload
        """
        publish_topic = self.trmode_topic % "cons"

        data = {
            "TerminalMode": trm_mode
        }
    
        json_txt = json.dumps(data, indent=4, separators=(',', ': '))
        self.connection.mqtt_msg_to_queue(publish_topic, json_txt) 

    def get_terminal_mode(self, data):
        """
        Processes terminal mode messsage from obc

        Parameter
        ---------
        data : str
            terminal mode parameters
        """    
        data = json.loads(data)

        if "TerminalMode" in data and isinstance(data["TerminalMode"], bool):
            if data["TerminalMode"]:
                self.trm_wait = True
                self.trm_rsp_timer = time.time()
            elif not data["TerminalMode"]:
                self.ct.process_terminal_mode(False)

    def activate_terminal_mode(self):
        """"Activates terminal mode"""
        self.ct.process_terminal_mode(True)
        self.publish_terminal_mode(True)

    def publish_keypress(self, key):
        """Publishes topic on a console keypress event - hano1 is aware of
        this, but is not currently supported in console task.

        "Ent” : The Enter key
        “0”... “9” : One of the Numeric keys
        “D” | “I” | “R” | “X” | “Y” : One of the Alphabetic keys
        “+” : The UP arrow, “-“ : The DOWN arrow, “<” :
        The LEFT arrow, “>” : The RIGHT arrow

        Parameters
        ----------
        key : string
            the key which has been pressed on the console and to be retrieved 
            by remote controller
        """
        publish_topic = self.trmode_key_topic

        keypress = { 
            "Keypress": key
        }

        json_txt = json.dumps(keypress, indent=4, separators=(',', ': '))
        self.connection.mqtt_msg_to_queue(publish_topic, json_txt)

    def get_trm_mode_keys(self, data):
        """Processes remote sent keypresses
        
        Parameter
        ---------
        data : str
            terminal mode parameters
        """
        data = json.loads(data)
        key = "Invalid"

        if "Keypress" in data and isinstance(data["Keypress"], str):
            if self.ct.terminal_mode:
                key = data["Keypress"]
                self.ct.hano.sendKeyPress(data["Keypress"])
            else:
                print("HanMQTT:: console not in terminal mode, keypress rejected")

        return key

    """
    ##############################################################################################
    Console Serialization and Deserialization for HTC | Console <-> Sign tasks
    """
    def clear_database_update(self):
        """Clears all data relating to a database update"""
        self.update_status = None

    def get_database_filename(self, db):
        """Get the datbase name from db identifier

        Parameter
        ---------
        db : int
            value identifier for database type
        """
        if db == self.update_status._ERICBIN:
            path = "/usr/share/payload/"
            db_name = "eric.bin"
        else:
            path = None
            db_name = None

        return [path, db_name]

    def program_database(self, db=[None, None]):
        """
        Updates supported databases

        Parameter
        ---------
        db[] : str 
            string for path and file name to be updated
        """
        success = False

        # write bitstream to file
        if db[0] and db[1]:
            path = db[0]
            file = db[1]

            if os.path.exists(path + file):
                os.remove(path + file)

            f = open(path + file, 'x')
            f.close()
            f = open(path + file, 'w+b')  
            f.write(bytearray.fromhex(self.update_status._db_update_data))    
            f.close()

            print("HanMQTT: Attempt db update")
            self.publish_database_update_prog(self.update_status._DB_UPDATING)

            self.ct.console_updating = True

            if self.ct.transfer_to_console(path):

                print("CT: Waiting for console to reboot")
                for x in range(90):
                    if len(self.ct.hano.transmitMessage("a", True, 30)) > 0:
                        break
                    time.sleep(1)

                self.publish_database_update_prog(self.update_status._DB_COMPLETE)
                success = True

                self.wrk_restart_console_task()
            else:
                self.publish_database_update_prog(self.update_status._DB_FAILED)

            self.clear_database_update()
        else:
            print("HanMQTT: No database specified for upload")

        return success

    def publish_database_update_ack(self, blocknumber, blocksize, block_received):
        """Acknowledge message published following receipt of the latest update topic from the HTC
        
        Topic
        -----
        hanover/dpi/cons/upd/data/ack 
        
        Parameter
        ---------
        blocknumber: str 
            blocknumber to be written
        blocksize: str
            blocksize to be written
        block_received: bool
            block received
        """
        publish_topic = self.db_update%"cons" + self.data_ack

        if isinstance(blocknumber, int) and isinstance(blocksize, int) and isinstance(block_received, bool):
            update_ack = {
                "BlockNumber": blocknumber,
                "BlockSize": blocksize,
                "BlockReceived": block_received
            }
            json_txt = json.dumps(update_ack,indent=4, separators=(',', ': '))
            self.connection.mqtt_msg_to_queue(publish_topic, json_txt)
            print("HanMQTT: Published db data ack")
        else:
            print("HanMQTT: Error processing database update request")

    

    def database_update_req(self, sent_data):
        """Parses update database topic from the HTC and begins update process if the data
        is correct.
        “BlockNumber”: <int>,“BlockSize”: <int>,“BlockData”: <hex-data>,“CRC16”: <hex-data>
        
        Topic
        -----
        hanover/dpi/obc/upd/data/send
        
        Parameter
        ---------
        sent_data: dict
            program data sent with download database topic
        """
        valid = False
        sent_data = json.loads(sent_data)
        if self.update_status is None:
            self.update_status = ConsoleMQTT._UpdateData() 

        est_blockno = self.update_status._db_prev_block_no + 1
        blocknumber = 0
        blocksize = 0

        print("HanMQTT: Received request to send db data block")

        if time.time() - self.update_status._db_timeout < self.update_status._DB_TIMEOUT or self.update_status._db_prev_block_no == 1 or "BlockNumber" in sent_data and sent_data["BlockNumber"] == 1:
            if "BlockNumber" in sent_data and isinstance(sent_data["BlockNumber"], int) and sent_data["BlockNumber"] == est_blockno:
                blocknumber = sent_data["BlockNumber"]
                if "BlockSize" in sent_data and "BlockData" in sent_data and isinstance(sent_data["BlockSize"], int):
                    blocksize = sent_data["BlockSize"]
                    block_data = sent_data["BlockData"]
                    if "CRC16" in sent_data and len(block_data) /2 == int(blocksize):
                        crc = sent_data["CRC16"]
                        crc_check = self.CRC16_check(block_data) 
                        if crc == self.CRC16_check(block_data) :
                            self.update_status._db_timeout = time.time() # start timer to expect next block
                            self.update_status._db_update_data = self.update_status._db_update_data + block_data
                            self.update_status._db_prev_block_no = blocknumber
                            valid = True
                        else:
                            print("CRC CHECK FAIL: ", str(crc), str(crc_check))

        if valid:
            self.publish_database_update_ack(blocknumber, blocksize, True)
        else:
            self.publish_database_update_ack(blocknumber, blocksize, False)
            if self.update_status._db_attempts < self.update_status._MAX_DB_DATA_ATTEMPTS:
                self.update_status._db_attempts += 1
            else:
                self.clear_database_update()
                print("HanMQTT: max attempts to write database data reached, clearing cache")
                

    def publish_database_update_prog(self, status):
        """Publishes current status of updating of database to the HTC
        
        Topic
        -----
        hanover/dpi/cons/upd/data/prog
        """
        publish_topic = self.db_update%"cons" + self.data_prog

        update_status = {
            "UpdateStatus": status
        }

        json_txt = json.dumps(update_status,indent=4, separators=(',', ': '))
        self.connection.mqtt_msg_to_queue(publish_topic, json_txt)
        print("HanMQTT: database update progress message sent")

    def program_update(self, sent_data):
        """Parses program database topic from the HTC and begins update process if the data
        is correct.
        “StartUpdate”: true | false,“UpdateType”: <int>,“DataSize”: <int>,“NbOfBlock”: <int>

        Topic
        -----
        hanover/dpi/obc/upd/data/prog
        
        Parameter
        ---------
        sent_data: dict
            program data sent with program topic
        """
        valid = False

        print("HanMQTT: received request to update data")
        sent_data = json.loads(sent_data)

        if "StartUpdate" in sent_data and sent_data["StartUpdate"]:
            if "UpdateType" in sent_data and sent_data["UpdateType"] in self.update_status._valid_update_type:
                if "DataSize" in sent_data and sent_data["DataSize"] == len(self.update_status._db_update_data) /2: 
                    if "NbOfBlock" in sent_data and sent_data["NbOfBlock"] == self.update_status._db_prev_block_no:
                        valid = True
                
        if valid:
            db_file = self.get_database_filename(sent_data["UpdateType"])
            if db_file[1] is not None:  
                self.program_database(db_file)
            else:
                self.publish_database_update_prog(self.update_status._DB_FAILED)
                self.clear_database_update()
                print("HanMQTT: database type not recognised. Process cancelled")
        else:
            print("HanMQTT: database update cancelled by the obc, clearing database update cache")
            self.clear_database_update()
    
    """
    ##############################################################################################
    
    """
    def manage_msg_list(self, topic, data):
        """"This function will manage incoming messages and process them as appropriate. Resets auto status
        timers if a message as been requested.

        Parameters
        ----------
        topic : str
            mqtt topic 
        data : str
            mqtt payload
        """
        origin, cmd, cmd_info, add_info =  self.get_topic_info(topic)
        
        if "obc" == origin:
            if cmd == "cur-dricode":
                if self.get_dest_information(data):
                    if self.ct.test_mode:
                        self.ct.remote_test_mode = False
                        self.ct.disable_test_mode = True
                        time.sleep(2)
                    self.ct.test_mode = False
                    self.publish_dest_information()
                    self.publish_sign_display_rawhcp()
                    self.dri_interval = time.time()
            elif cmd == "ctext":
                self.get_console_text(data)
                self.publish_console_text()
                if self.trm_wait and  time.time() - self.trm_rsp_timer < self.TRM_MODE_WAIT:
                    self.activate_terminal_mode()
                self.text_interval = time.time()
            elif cmd == "cstatus":
                self.get_console_status(data)
                self.publish_console_status()
                self.conssta_interval = time.time()
            elif cmd == "stest":
                self.get_sign_test(data)
            elif cmd_info == "data" and add_info == "send":
                self.database_update_req(data)
                # response occurs in function
            elif cmd_info == "data" and add_info == "prog":
                self.program_update(data)
            elif cmd == "tmode":
                self.get_terminal_mode(data)
        elif "sign" == origin:
            if cmd == "status" and cmd_info.isnumeric():
                self.get_sign_status(data, cmd_info)
 

    def get_topic_info(self, rcvd_topic):
        """This function gets the origin of the received message

        Parameters
        ----------
        rcvd_topic : str
            the received topic which will be sliced
        """
        origin = cmd = cmd_info = add_info = ""
        if rcvd_topic[len(rcvd_topic) - 1] == "/":
            rcvd_topic.rstrip("/")

        str_len = len(rcvd_topic.split("/"))

        try:
            if str_len > 2:
                origin = rcvd_topic.split("/")[2]
            if str_len > 3:
                cmd = rcvd_topic.split("/")[3]
            if str_len > 4:
                cmd_info = rcvd_topic.split("/")[4]
            if str_len > 5:
                add_info = rcvd_topic.split("/")[5]
        except:
            print("HanoverMQTT: Application could not parse topic correctly")

        return origin, cmd, cmd_info, add_info

    """
    ##############################################################################################
    MAIN
    ##############################################################################################
    """   
    def run(self):
        """application main"""

        self.run_setup()

        test_mode = False
        sign_msg_delay = 1
        sign_interval = time.time()
        restart_ct_interval = 10
        restart_ct_timer = 0
        
        try:
            while 1:
                while not self.ct.work_done.wait(3):
                    pass
                self.ct.parent_done.clear()

                if self.ct.stop:
                    return
                
                print("HanoverMQTT: Process incoming msgs")    
                # if respond to htc mqtt requests
                if self.connection.new_payload or self.connection.q.empty() is False:
                    while not self.connection.q.empty():
                        item = self.connection.q.get()
                        # print("Queue Size: " + str(Queue.qsize(self.connection.q)))
                        self.manage_msg_list(item[0], item[1])
                        self.connection.new_payload = False

                # SR2378 6.38
                if time.time() - sign_interval >= sign_msg_delay:
                    if not self.ct.test_mode and not self.ct.remote_test_mode:
                        self.publish_sign_display_rawhcp()
                        sign_interval = time.time()

                while not self.connection.q_prioirity.empty():
                    self.connection.mqtt_msg_send(True)
                
                # keypresses
                while not self.ct.key_presses.empty():
                    key = self.ct.key_presses.get()
                    self.publish_keypress(key)
                    
                """
                while not self.connection.q_out.empty():
                    self.connection.mqtt_msg_send()
                """
                
                # publish console status
                if time.time() - self.conssta_interval >= self.console_status["ConStatInterval"]:
                    self.conssta_interval = time.time()
                    self.publish_console_status()

                # publish automatic sign status
                if time.time() - self.signsta_interval >= self.console_status["SignStatInterval"]:
                    self.signsta_interval = time.time()
                    self.cons_publish_sign_status()

                # publish automatic dri 
                if time.time() - self.dri_interval >= self.console_status["DRICodeInterval"]: 
                    self.dri_interval = time.time()
                    self.publish_dest_information() 

                # publish automatic console text - currently not possible
                if time.time() - self.text_interval >= self.console_status["ConTextInterval"]: 
                    pass
                
                print("HanoverMQTT: Process outgoing msgs") 
                while not self.connection.q_out.empty():
                    self.connection.mqtt_msg_send()

                if self.ct.test_mode and test_mode is False:
                    print("HanoverMQTT: Update test mode on") 
                    if not self.ct.remote_test_mode:
                        self.publish_sign_test(True)
                    test_mode = True
                elif test_mode and self.ct.test_mode is False:
                    print("HanoverMQTT: Update test mode off") 
                    self.publish_sign_test(False)
                    test_mode = False
                
                # terminal mode
                if self.trm_wait and not self.ct.terminal_mode:
                    if time.time() - self.trm_rsp_timer > self.TRM_MODE_WAIT:
                        print("HanoverMQTT: terminal mode wait timeout")
                        self.trm_wait = False
                    else:
                        print("HanoverMQTT: Waiting rsp to activate terminal mode %s..." % int(self.trm_rsp_timer - time.time()))
                elif self.ct.terminal_mode:
                    pass

                if self.ct.console_active is False:
                    restart_ct_timer = time.time()
                    if time.time() - restart_ct_timer > restart_ct_interval:
                        self.wrk_restart_console_task()
                else:
                    restart_ct_timer = time.time()
                
                self.ct.parent_done.set()

                time.sleep(0.2)
        except KeyboardInterrupt:
            self.ct.stop = True

class HanoverSignMQTT(object):
    def __init__(self, config_dict, hw_dict, conf_dir, data_dir):
        """
        Sign MQTT class managing the running of an MQTT based service on
        the signs.

        Parameters
        ----------
        config_dict : dict
            application configuration dictionary
        hw_dict : dict
            hardware configuration dictionary
        conf_dir : dict

        data_dir : dict
            data dictionary
        """
        self.config_dict = config_dict
        self.hw_dict = hw_dict
        self.conf_dir = conf_dir
        self.data_dir = data_dir

        topic_prefix = self.config_dict.get("base_topic", "hanover/dpi/")
        if topic_prefix[-1] != "/":
            topic_prefix + "/"

        self.sign_cmd_tpc = topic_prefix + "sign/command/%s" # addr or 0(all)
        self.sign_resrp_tpc = topic_prefix +"sign/rawstatus/%s" # addr or 0(all)
        self.sign_test_tpc = topic_prefix + "%s/stest/%s" # addr or all
        self.sign_status = topic_prefix + "sign/status/%s"

        self.current_sign_data = None
        self.previous_sign_data = None
        
        self.wrkrnd_delay_end_test = 0
        self.WRKRND_END_TEST_REQ = 3
        
        self.raw_status_timer = time.time()
        self.RAW_STATUS_INTERVAL = 3

        self.itxpt = itxpt_mqtt.SignMQTT(config_dict, hw_dict, conf_dir, data_dir)

    def sign_setup(self):
        """Function calling setup functions for the sign"""
        self.itxpt.setup_sign_task()
        self.itxpt.st.init_serial()
        self.itxpt.setup_config_updater()
        self.itxpt.setup_mis()
        
        self.setup_register_topics()
        self.setup_sign_mqtt()

        self.itxpt.setup_ecomode()
        self.itxpt.setup_consat_status()

        self.sign_status_interval = 10

        _thread.start_new_thread(self.itxpt.st.run, ())

    """
    ####################################################################################################################
    "Helpers"
    """
    def calculate_lrc(self, message):
        """Calculates the Longitudinal Redundancy Check"""
    
        lrc = 0
        for b in message[1:]:
            lrc ^= b
        
        return lrc

    """
    ####################################################################################################################
    "Setup"
    """
    def setup_register_topics(self):
        """
        Setups the topics to be subscribed to
        """
        self.topic_list = [
            self.sign_cmd_tpc%"all",
            self.sign_cmd_tpc%self.itxpt.st.address,
            self.sign_test_tpc%("obc", self.itxpt.st.address),
            self.sign_test_tpc%("obc", "all"),
            self.sign_test_tpc%("cons", self.itxpt.st.address),
            self.sign_test_tpc%("cons", "all"),
        ]

    def setup_sign_mqtt(self):
        """Setup the mqtt services for the console. If the console is setup as a broker in the
        config it will also setup the broker services. All through MQTT connection class
        """
        print("ITxPT: Staring primary data thread")
        self.connection = MQTTConnectionClass(0, self.config_dict, self.hw_dict, self.topic_list)
        _thread.start_new_thread(self.connection.run, ())
        time.sleep(1)

        if self.config_dict.get("MQTT_Secondary_enable", False):
            print("ITxPT: Staring secondary data thread")
            self.connection_two = MQTTConnectionClass(0, self.config_dict, self.hw_dict, self.topic_list, secondary_broker=True)
            _thread.start_new_thread(self.connection_two.run,())

        # Wait and deal with symbols here.
        while 1:
            self.itxpt.st.display_sign_graphic("TRI-UP")
            time.sleep(0.5)
            if self.connection.service_found:
                break
            if self.config_dict.get("MQTT_Secondary_enable", False):
                if self.connection_two.service_found:
                    break
            self.itxpt.st.clear_sign()
            time.sleep(0.5)

        while 1:
            self.itxpt.st.display_sign_graphic("TRI-DOWN")
            time.sleep(0.5)
            if self.connection.service_connected:
                break
            if self.config_dict.get("MQTT_Secondary_enable", False):
                if self.connection_two.service_connected:
                    break
            self.itxpt.st.clear_sign()
            time.sleep(0.5)

        self.itxpt.st.display_sign_graphic("SQUARE")
        time.sleep(0.5)
  
    """
    ####################################################################################################################
    "Serializer and deserialize data"
    """
    def send_status(self):
        """
        Publishes the extended sign data for the sign. Uses get ext_status from sign manager.
        """
        publish_topic = self.sign_status%self.itxpt.st.address

        sign_data = {
            "StatusByte":  self.itxpt.st.hw_dict["hw_status"], 
            "ExtendedStatus": {
                    "VariantName": self.itxpt.st.hw_dict["model"], 
                    "VersionNumber": self.itxpt.st.hw_dict["software_version"], 
                    "HCPAddress": self.itxpt.st.address,
                    "SignWidth": self.itxpt.st.hw_dict["sign_size"].split('x')[0],
                    "SignHeight": self.itxpt.st.hw_dict["sign_size"].split('x')[1],
                    "CurrentBrightness": 0,
                    "MaxBrightness": self.itxpt.st.config_dict["BRIGHTNESS_max_brightness"]
                    },
                    "ManufInfo": {
                        "MAC": self.hw_dict["unit_MAC"],
                        "ProductNumber": self.hw_dict.get("product_no", "NA"),
                        "SerialNumber": self.hw_dict["serial_number"],
                        "ProductType": self.hw_dict["model"],
                        "HardwareVersion": self.hw_dict["hardware_version"],
                        "DateOfManufacturer": self.hw_dict.get("manufacture_date", "NA"),
                }
        }
        
        json_txt = json.dumps(sign_data,indent=4, separators=(',', ': '))
        self.connection.mqtt_msg_to_queue(publish_topic, json_txt)

    def publish_current_display_data_hcp(self):
        """"Publishes the currently displayed sign data over mqtt in the json format
        representing HCP values
        •“header”: 8-bit Message Start token, either 0x02 for a master message, 
        or 0x01for a slave message.
        •“command”:One of the supported HCPCommands.
        •“address”:The HCP Address of the equipment the commandis being sent to.
        •“body”:The body of the message, expressed as a continuoussequence of 8-bit 
        bytes encoded as pairs of ASCII hex digits.
        •“tail”:8-bit Message End token, either 0x03 for a master message, 
        or 0x04 for slave message,
        •“checksum”:a single 8-bit byte encoded as a pair of ASCII digits, expressing 
        the LRC of every bytefrom the byte following the Message Start Tokento 
        the Message End Token.
        """
        publish_topic = self.sign_resrp_tpc%self.itxpt.st.address

        sign_data = {
            "header": self.current_sign_data["header"],
            "command": self.current_sign_data["command"],
            "address": self.itxpt.st.address,
            "body": self.current_sign_data["body"],
            "tail": self.current_sign_data["tail"],
            "checksum": self.current_sign_data["checksum"],
        }

        json_txt = json.dumps(sign_data,indent=4, separators=(',', ': '))
        self.connection.mqtt_msg_to_queue(publish_topic, json_txt)

    def get_sign_display_data(self, data):
            """Retrieves HCP data for data to display on signs
            
            Topic
            -----
            hanover/dpi/sign/command/<hcp_addr>
            
            Parameters
            ----------
            data : str
                mqtt payload
            """
            valid = False
            jsondata = json.loads(data)
            
            self.current_sign_data = jsondata

            if "header" in jsondata and isinstance(jsondata["header"], str):
                if "command" in jsondata and isinstance(jsondata["command"], int):
                    if "address" in jsondata and isinstance(jsondata["address"], int):
                        if "body" in jsondata and isinstance(jsondata["body"], str) or isinstance(jsondata["body"], hex):
                            if "tail" in jsondata and isinstance(jsondata["tail"], str):
                                if "checksum" in jsondata and isinstance(jsondata["checksum"], str):
                                    checksum = hcp.HCP.genCheckSum(self, str(jsondata["command"]) + str(jsondata["address"]) + jsondata["body"] + "\u0003")
                                    if checksum == jsondata["checksum"]:
                                        try:
                                            if data != self.previous_sign_data:
                                                self.itxpt.process_rawhmf_json(data, False)
                                            self.previous_sign_data = data
                                            valid = True
                                        except Exception as e:
                                            print("HanMQTT: Error processing hmf msg", e)
            if not valid:
                print("HanoverMQTT: HCP MQTT display sign msg invalid")
            
    def get_sign_test(self, data):
        """
        Processes sign test mqtt message
        
        Parameters
        ----------
        data : str
            mqtt payload
        """
        valid = False

        data = json.loads(data)

        if "SignSelfTest" in data and isinstance(data["SignSelfTest"], bool):
            if "Enhanced" in data and isinstance(data["Enhanced"], bool):
                if not data["SignSelfTest"]:
                    print("HanoverMQTT: Sign Test Disabled")
                    self.itxpt.st.test_mode =  False
                    self.itxpt.st.ext_test_mode = False
                    self.itxpt.st.clear_sign(True)
                elif data["Enhanced"]:
                    print("HanoverMQTT: Enhanced Sign Test Enabled")
                    self.itxpt.st.test_mode = False
                    self.itxpt.st.ext_test_mode = True
                elif not data["Enhanced"]:
                    print("HanoverMQTT: Sign Test Enabled")
                    self.itxpt.st.test_mode = True
                    self.itxpt.st.ext_test_mode = False
                valid = True

        if not valid:
            print("Sign test MQTT msg invalid")

    def manage_msg_list(self, topic, data):
        """"Manages incoming messages and process them as appropriate. Resets auto status
        timers if a message as been requested.

        Parameters
        ----------
        topic : str
            mqtt topic 
        data : str
            mqtt payload
        """
        origin, cmd, cmd_info, add_info =  self.get_topic_info(topic)

        if "obc" == origin or "cons" == origin:
            if cmd == "stest":
                self.previous_sign_data = None
                self.wrkrnd_delay_end_test = 0
                self.get_sign_test(data)
        elif "sign" == origin:
            if cmd == "command":
                if self.wrkrnd_delay_end_test < self.WRKRND_END_TEST_REQ and (self.itxpt.st.test_mode or self.itxpt.st.ext_test_mode):
                    self.wrkrnd_delay_end_test += 1
                else:
                    self.wrkrnd_delay_end_test = 0
                    if self.itxpt.st.test_mode or self.itxpt.st.ext_test_mode:
                        self.previous_sign_data = None
                        self.itxpt.st.test_mode =  False
                        self.itxpt.st.ext_test_mode = False
                        self.itxpt.st.clear_sign()
                    self.get_sign_display_data(data) 
                    if time.time() - self.raw_status_timer > self.RAW_STATUS_INTERVAL:
                        self.raw_status_timer = time.time()
                        self.publish_current_display_data_hcp()
            

    def get_topic_info(self, rcvd_topic):
        """Gets the origin of the received message

        Parameters
        ----------
        rcvd_topic : str
            the received topic which will be sliced
        """
        origin = cmd = cmd_info = add_info = ""
        if rcvd_topic[len(rcvd_topic) - 1] == "/":
            rcvd_topic.rstrip("/")

        str_len = len(rcvd_topic.split("/"))
        print(rcvd_topic)

        try:
            if str_len > 2:
                origin = rcvd_topic.split("/")[2]
            if str_len > 3:
                cmd = rcvd_topic.split("/")[3]
            if str_len > 4:
                cmd_info = rcvd_topic.split("/")[4]
            if str_len > 5:
                add_info = rcvd_topic.split("/")[5]
        except:
            print("HanoverMQTT: Application could not parse topic correctly")

        return origin, cmd, cmd_info, add_info

    def run(self):

        self.sign_setup()
        sign_status_interval = time.time()

        try:
            while 1:
                self.itxpt.handle_ecomode()

                while self.connection.q.empty() is False:
                    item = self.connection.q.get()
                    # print("HanoverMQTT: Queue Size: " + str(Queue.qsize(self.connection.q)))
                    self.manage_msg_list(item[0], item[1])
                    self.connection.new_payload = False

                while not self.connection.q_out.empty():
                    self.connection.mqtt_msg_send()
                
                if time.time() - sign_status_interval > self.sign_status_interval:
                    self.send_status()
                    sign_status_interval = time.time()

                if self.itxpt.check_config_updater():
                    self.itxpt.st.stop = True
                    return

                time.sleep(0.1)
        except KeyboardInterrupt:
            self.itxpt.st.stop = True

