"""
Name: mqtt_client
Title: 
Author: Cooper
Date: 29/08/2018

Desc:  This client now acts as both a publisher and subscriber, previous implementation split the behaviour into two
separate modules but to simplify matters this is all in one now.  It is also more robust in the sense it will raise an
exception when there is an issue connecting instead of crashing.

This module was originally written to interface with Consat, but since then there are different brokers to connect to now
although there are some Consatish things in here still it can be used as a generic client, it just needs to be fed the
right sort of details.

Note
----
Reference on the API can be found at https://pypi.org/project/paho-mqtt/

This now contains the class which deals with the MQTT discovery and connection handling as well, takes in a dictionary:
        mqtt_conn_dict = {
            "version": 311,
            "port": 1883,
            "username": "",
            "password": "",
            "certificate_path": "",
            "enable_tls": False,
            "discover": False,
            "timeout": 0,
            "disconnect_timeout", 5,
            "service_type": "",
            "hostname": "",
            "fallback_address": "",
            "broker_topic": "",
            "reply_topic": "",
            "status_topic": "",
            "payload_queue_size": 0,
            "subscription_topics": ["root/topic1", "root/topic2"],
        }

NB:  broker_topic is for the legacy/original MQTT support which a lot of devices are currently using.  This needs to be
maintained for backwards compatibility but going forwards we would need a better way to handle subscriptions to multiple
topics in order to support the path that Hanover is heading towards.

As such there is a new parameter in the mqtt_conn_dict:  "subscription_topics".
This will be a list of topics.  Previously there was only one topic to pay attention to so it was easy to manage with a flag
This is not practical when multiple topics are involved, so the callback will need changing to support this.

When a payload is received, it needs to then be put into a queue (FIFO)

Full documentation here: https://eclipse.dev/paho/files/paho.mqtt.python/html/client.html

"""
from hanip.itxpt import DNS_SD

import paho.mqtt.client as mqtt
import time
import _thread
import logging
import ssl
import os
import subprocess
import queue
import hashlib

logger = logging.getLogger("mqtt_client")

class MQTT_Client(object):
    
    """
    Class implementing MQTT client functionality.
    Members
    -------
    """

    def __init__(self, client_id=None, version: int = 311, queue_size: int = 0) -> None:
        """
        Constructor. Creates an MQTT Client and sets its callback members and some initial flag values.

        Parameters
        ----------
        version: int
            Version of MQTT to be used
        """
        logging.info("MQTT protocol version %s" % version)
        if version == 31:
            _version = mqtt.MQTTv31
        elif version == 311:
            _version = mqtt.MQTTv311
        elif version == 5:
            _version = mqtt.MQTTv5
        else:
            _version = mqtt.MQTTv311

        self.client_id = client_id
        logging.info("MQTT client_id: %s", self.client_id)
        self.mqttc = mqtt.Client(client_id=self.client_id, protocol=_version)
        self.brokerAddress = None

        self.mqttc.on_message = self.on_message
        self.mqttc.on_connect = self.on_connect
        self.mqttc.on_subscribe = self.on_subscribe
        self.mqttc.on_disconnect = self.on_disconnect
        self.mqttc.on_publish = self.on_publish

        self.payload = ""
        self.newMsg = False
        self.rcvdTopic = None
        self.payload_md5 = ""
        self.brokerConnected = False
        self.queuing_enabled = False

        if queue_size > 0:
            logging.info("MQTTC: Queue enabled")
            self.queuing_enabled = True
            self.payload_queue = queue.Queue(maxsize=queue_size)

        self.broker_configurator = MQTTBrokerConfigurator()

        if os.path.isfile("/tmp/oniondebug_mqttc"):
            self.debug = True
        else:
            self.debug = False

    """
    ###################################################################################################################
    MQTT Callbacks
    """

    def on_connect(self, client, userdata, flags, rc):
        """
        Callback invoked when a connection is established. Subscribes to a list of topics
        and reports the connection attempt's result.

        Parameters
        ----------
        client: mqtt.Client
            The client calling the callback.
        userdata: unspecified
            User-defined data that may have been defined when the client was
            created or when explicitly set using user_data_set(userdata).
        flags: dict
            flags is a dict that contains response flags from the broker:
            flags['session present'] - this flag is useful for clients that are
                using clean session set to 0 only. If a client with clean
                session=0, that reconnects to a broker that it has previously
                connected to, this flag indicates whether the broker still has the
                session information for the client. If 1, the session still exists.
        rc: int
            Result Code as follows:
            0: Connection successful
            1: Connection refused - incorrect protocol version
            2: Connection refused - invalid client identifier
            3: Connection refused - server unavailable
            4: Connection refused - bad username or password
            5: Connection refused -
            6-255: Currently unused.
        """
        mqtt_result_code = {
            0: "Successful",
            1: "Refused: incorrect protocol version",
            2: "Refused: invalid client identifier",
            3: "Refused: server unavailable",
            4: "Refused: bad username or password",
            5: "Refused: not authorised",
            6: "No idea"
        }

        result = mqtt_result_code[rc]

        if rc == 0:
            self.brokerConnected = True
            logging.info("Connected")
        else:
            logging.warning(result)

            # Result code 5 usually means the broker is not setup correctly.
            if rc == 5:
                self.broker_configurator.check_mqtt_broker()

    def on_disconnect(self, client, userdata, rc):
        """
        Called back after the client has been disconnected. Simply reports and resets
        the self.brokerConnected flag.

        Parameters
        ----------
        client: mqtt.Client
            The client calling the callback.
        userdata: unspecified
            User-defined data that may have been defined when the client was
            created or when explicitly set using user_data_set(userdata).
        rc: int
            Result Code as follows:
            0: Disconnection was initiated by the client.
            Any other value: Disconnection unexpected, from another cause.

        """

        logging.warning("Disconnect From Server - code: %s" % rc)
        self.brokerConnected = False

    def on_subscribe(self, client, userdata, mid, granted_qos):
        """
        Callback for when a subscription has been registered with a broker,
        listing the topics currently subscribed to and setting a flag
        brokerConnected.

        Parameters
        ----------
        client: mqtt.Client
            The client calling the callback.
        userdata: unspecified
            User-defined data that may have been defined when the client was
            created or when explicitly set using user_data_set(userdata).
        mid: Message ID
            value from the call to subscribe()
        granted_qos: list of integers
            The QoS level the broker has granted for each subscription request.

        """
        logging.info("Subscribed: " + str(mid) + " " + str(granted_qos))

        # for topic in self.topic:
        #     logging.info("MQTT Topic: %s" % topic[0])

    def on_message(self, client, userdata, msg):
        """
        Called whenever a message is received on a topic that the client subscribes to.
        The message passed in is used to populate a payload and accompanying topic. A flag
        newMsg is set to True when a message has a non-empty payload.

        Parameters
        ----------
        client: mqtt.Client
            The client calling the callback.
        userdata: unspecified
            User-defined data that may have been defined when the client was
            created or when explicitly set using user_data_set(userdata).
        msg: MQTTMessage
            describes all of the message parameters.
            This has members topic, payload, qos, retain.

        """
        if msg.payload:
            payload = msg.payload.decode("utf-8", "ignore")
            topic = str(msg.topic)
            md5_hash = self.generate_md5(msg.payload)

            self.payload = payload
            self.rcvdTopic = topic
            self.newMsg = True

            if self.payload_md5 != md5_hash:
                self.payload_md5 = md5_hash
                logging.debug(msg.topic + ":" + self.payload)
            else:
                logging.debug(msg.topic + ":" + self.payload_md5)

            if self.queuing_enabled:
                try:
                    content = (topic, payload)
                    self.payload_queue.put(content, block=False)
                except queue.Full:
                    logging.warning("MQTTC: Queue is full, please pop something out")


    def on_publish(self, client, userdata, mid):
        """
        Called when a message that was to be sent using the publish() call has 
        completed transmission to the broker. Only for messages using QoS levels 1 & 2
        does this mean the broker has actually published the material.

        Note
        ----
        Currently just reports the published message ID. Probably should do more when
        using QoS 0.

        Parameters
        ----------
        client: mqtt.Client
            The client calling the callback.
        userdata: unspecified
            User-defined data that may have been defined when the client was
            created or when explicitly set using user_data_set(userdata).
        mid: Message ID
            value from the call to publish()

        """
        pass
        # logging.debug("Published, ID: %s" % str(mid))

    """
    ###################################################################################################################
    MQTT Client Functions
    """
    def set_broker_address(self, address, check_config= True) -> None:
        """
        Sets the IP address of the MQTT broker

        Checks whether anonymous connections are allowed when the broker address is local host.
        """
        self.brokerAddress = address

        if (address == "127.0.0.1" or address == "localhost") and check_config:
            self.broker_configurator.check_mqtt_broker()

    def set_timeouts(self, min_value: int, max_value: int) -> None:
        """
        Sets the timeout for connections
        """
        self.mqttc.reconnect_delay_set(min_value, max_value)

    def connect_client(self, username: str = "", password: str = "", port: int = 1883,
                       enable_tls: bool = False, certificate_path: str = ""
                       ) -> int:
        """
        Attempts to connect this client to the broker at address self.brokerAddress.
        Catches and reports any exceptions that occur, and returns 0 to indicate failure.
        On success, returns 1.

        """
        if enable_tls and certificate_path != "":
            # Calling specific ssl tls protocol versions has been depreciated, now call PROTOCOL_TLS_CLIENT instead
            try:
                self.mqttc.tls_set(certificate_path, tls_version=ssl.PROTOCOL_TLS_CLIENT)
            except FileNotFoundError:
                logging.warning("MQTT certificate cannot be found...")
                return 0
            # For production code this should always be TRUE
            insecure_set = False if self.debug else True
            self.mqttc.tls_insecure_set(insecure_set)
            logging.debug("MQTT: tls_insecure_set=%s" % insecure_set)
            logging.info("MQTT: Secure channel enabled")

        try:
            logging.info("MQTT Connecting to: %s:%s" % (self.brokerAddress, port))
            if username != "":
                logging.debug("MQTT: Connecting with username %s" % username)
                self.mqttc.username_pw_set(username, password)
            self.mqttc.connect(self.brokerAddress, port=port)
        except ConnectionError as e:
            logging.error("MQTT Connection Error: %s" % e)
            return 0
        except TimeoutError as e:
            logging.error("MQTT Timeout Error: %s" % e)
            return 0
        except OSError as e:
            logging.error("MQTT Network unreachable: %s" % self.brokerAddress)
            return 0

        return 1

    def disconnect_client(self) -> None:
        """
        Disconnects the client,  note that this according to the documentation also stops the loop
        """
        try:
            self.mqttc.disconnect()
        except Exception as e:
            logging.error("MQTT Disconnect error: %s" % e)

    def run_client(self) -> None:
        """
        Uses the client's loop_forever() function which is a blocking function.

        If a disconnect is called, this will return, or kill the thread if run in one.
        If this gets disconnected for whatever reason it will retry forever.

        """
        logging.info("MQTT: Running MQTT Loop")
        self.mqttc.loop_forever()

    def subscribe_to_topics(self, topic):
        """
        topic: Can be in one of the following formats it is why there is no typecasting.
        Simple string and integer - subscribe("my/topic", 2)
        String and integer tuple - subscribe(("my/topic", 1))
        List of string and integer tuples - subscribe([("my/topic", 0), ("another/topic", 2)])

        Topics used to be subscribed to automatically on connection but there are times when this isnt desirable or
        strictly necessary.  This will now need to be called separately.
        """
        self.mqttc.subscribe(topic)

    def publish_data(self, topic: str, payload: str, qos: int = 0, retain: bool = False) -> None:
        """
        Called to publish a given topic's payload.

        Note
        ----
        Should probably do more in terms of error-checking here. Also, maybe we should
        attend to the QoS and retain parameters of the Client's publish() method.

        Parameters
        ----------
        topic:
            The topic to be published.
        payload:
            The message paylod to publish.
            :param qos:
            :param retain:
        """
        self.mqttc.publish(topic, payload, qos, retain)

    def generate_md5(self, raw_payload: bytes):
        """
        This generates the MD5 of the payload for logging purposes
        """
        res = hashlib.md5(raw_payload)
        md5_hash = res.hexdigest()

        return md5_hash


"""
########################################################################################################################
###########################################################:)###########################################################
########################################################################################################################
"""

class MQTTConnectionHandler(object):
    """
    For lack of better name this class handles discovery of a service, and handles connecting to it.
    By bring it out of the main SignMQTT class, we can easily upscale this to several MQTT brokers, but the intention
    for now is to connect to two, not sure why anyone would want more.

    This is currently very sign centric as it was pulled from signmqtt.
    """
    def __init__(self, instance: str, MQTT_conn_dict: dict):
        self.instance = instance
        self.mqtt_conn_dict = MQTT_conn_dict
        self.protocol_version = self.mqtt_conn_dict.get("version", 311)
        self.client_id = self.mqtt_conn_dict.get("client_id", None)

        self.service_ip = ""
        self.service_found = False
        self.service_connected = False
        self.status = "Disconnected"

        self.subscription_topics = None
        self.new_payload = False
        self.current_payload = None

        self.queue_size = self.obtain_queue_size()

        # Init has been moved here
        self.mqtt_sub = MQTT_Client(self.client_id, self.protocol_version, self.queue_size)

        # Set shorter timeouts so that it isn't forever waiting to try again.
        self.mqtt_sub.set_timeouts(
            self.mqtt_conn_dict.get("timeout_min", 1),
            self.mqtt_conn_dict.get("timeout_max", 3)
        )

    def obtain_queue_size(self):
        """
        Incase a queue size is not set, this will determine whether the contents of "subscription_topics" means a queue
        is needed
        """
        queue_size = self.mqtt_conn_dict.get("queue_size", 0)

        if queue_size == 0:
            subscription_topics = self.mqtt_conn_dict.get("subscription_topics", None)
            if subscription_topics is not None:
                queue_size = 10
            else:
                queue_size = 0

        logging.info("MQTTC: Queue size %s" % queue_size)
        return queue_size


    """
    ###################################################################################################################
    Service Discovery Stuff
    """

    def start_service_discovery(self) -> None:
        servicetype = self.mqtt_conn_dict["service_type"]
        hostname = self.mqtt_conn_dict["hostname"]

        self.service_discover = DNS_SD.DNSSD_Discover(servicetype, hostname)
        self.service_discover.run()
        logging.info("Instance %s: Service discovery running!" % self.instance)

    def discover_services(self) -> None:
        self.start_service_discovery()

        service_timeout = time.time()
        timeout_val = int(self.mqtt_conn_dict["timeout"])

        while 1:
            if self.service_discover.serviceIP == "":
                self.status = "Discovering"

                if timeout_val == 0:
                    time.sleep(0.5)
                    continue
                else:
                    if (time.time() - service_timeout) > timeout_val:
                        logging.info("Instance %s: Discover timeout, attempting fallback" % self.instance)
                        self.service_ip = self.mqtt_conn_dict["fallback_address"]
                        self.service_found = True
                        self.service_discover.close()
                        self.status = "Fallback"
                        break

                time.sleep(1)

            else:
                self.status = "Service Found"
                self.service_found = True
                self.service_ip = self.service_discover.serviceIP
                self.service_discover.close()
                logging.info("Instance %s: Service found: %s" % (self.instance, self.service_ip))
                break

    """
    ###################################################################################################################
    MQTT Connectivity
    """
    def discover_and_connect_to_service(self, run_client: bool = True):
        """
        For lack of better name at the moment, the functions here have been taken out of the main run loop because we
        need to be able to restart the whole service discovery and the client connection stuff should we need to.

        The issue with the original method is that, should the client disconnect from the broker, it will attempt to
        reconnect to the same broker using the previously obtained IP address.  This usually isn't an issue, but if the
        broker has a new IP assigned to it then it wouldn't know.

        This is exclusively for use for signs looking for a sign data broker, so skip all this then just use connect_to_service
        """
        while 1:
            if self.mqtt_conn_dict["discover"]:
                self.discover_services()
            else:
                self.service_found = True
                self.service_ip = self.mqtt_conn_dict["fallback_address"]

            self.mqtt_sub.set_broker_address(self.service_ip)

            if self.connect_to_service():
                self.status = "Connected"
                # give time for the client to settle
                time.sleep(0.5)
                break
            else:
                logging.warning("Instance %s: MQTT couldn't connect, restarting process..." % self.instance)

                if not self.mqtt_conn_dict["discover"]:
                    time.sleep(1)

        if run_client:
            logging.info("Instance %s: Running MQTT Thread" % self.instance)
            self.subscription_topics = self.process_topics()
            self.mqtt_sub.subscribe_to_topics(self.subscription_topics)

            # Run client is only appropriate when we want to subscribe to topics, otherwise not really needed
            _thread.start_new_thread(self.mqtt_sub.run_client, ())

    def connect_to_service(self) -> int:
        """
        This routine will need to be able to sit and wait for a broker, previously it only tried once and if it failed
        would not reattempt to connect.

        As for now there is no need for any secure MQTT connections for the sign data/status'.  But there is provision
        for it already.
        """
        logging.info("Instance %s: Connecting to broker" % self.instance)
        for retries in range(5):
            status = self.mqtt_sub.connect_client(
                username=self.mqtt_conn_dict["username"],
                password=self.mqtt_conn_dict["password"],
                port=self.mqtt_conn_dict["port"],
                enable_tls=self.mqtt_conn_dict.get("enable_tls", False),
                certificate_path=self.mqtt_conn_dict["certificate_path"]
            )
            logging.info("Instance %s: MQTT Status: %s" % (self.instance, status))

            if status:
                self.service_connected = True
                return 1
            else:
                time.sleep(5)       #Delay to stop spamming connections
                continue

        return 0

    def check_connection_status(self):
        """
        This routine checks the connection status to the broker.

        If the broker is disconnected then wait a certain amount of time before kicking off the discovery process again
        If the broker is reconnected before the timeout expires
        """
        # Setup disconnect timers
        mqtt_disconnect_timeout = self.mqtt_conn_dict.get("disconnect_timeout", 60)     #Disconnect time in seconds
        mqtt_disconnect_timer = 0

        if not self.mqtt_sub.brokerConnected:
            logging.warning("Instance %s: Lost connection to broker" % self.instance)
            self.service_connected = False
            self.new_payload = False
            self.current_payload = None
            self.status = "Disconnected"

            while 1:
                logging.info("Instance %s: Waiting for reconnection, timeout:%s/%s" % (self.instance, mqtt_disconnect_timer, mqtt_disconnect_timeout))
                if self.mqtt_sub.brokerConnected:
                    time.sleep(3)   #Allow client to settle
                    self.mqtt_sub.subscribe_to_topics(self.subscription_topics)
                    logging.info("Instance %s: Connection Restablished!!" % self.instance)
                    self.service_connected = True
                    self.status = "Connected"
                    return 0

                if mqtt_disconnect_timeout != 0:
                    if mqtt_disconnect_timer >= mqtt_disconnect_timeout:
                        logging.info("Instance %s: Connection lost, starting connection process again" % self.instance)
                        # Call this so that the existing thread (if any) is killed too
                        self.disconnect_client()
                        return 1
                    else:
                        mqtt_disconnect_timer += 1

                time.sleep(1)


    def setup_last_will(self, topic: str, payload: str, qos: int = 0, retain: bool = False):
        """
        This sets the last will of the subscriber if required which is automatically published if the client isn't
        disconnected in the conventional way
        """
        self.mqtt_sub.mqttc.will_set(topic=topic, payload=payload, qos=qos, retain=retain)

    # def get_last_will(self):
    #     """
    #     This obtains the currently set last will topic and payload
    #     It seems that these functions are not supported this current version of paho-mqtt
    #     """
    #     payload = self.mqtt_sub.mqttc.will_payload()
    #     topic = self.mqtt_sub.mqttc.will_topic()
    #
    #     return topic, payload

    def clear_last_will(self):
        """
        Clears the last will
        """
        self.mqtt_sub.mqttc.will_clear()

    def disconnect_client(self):
        """
        Disconnects the client
        """
        self.mqtt_sub.mqttc.disconnect()

    """
    ###################################################################################################################
    MQTT topic handling
    """
    def process_topics(self) -> list:
        """
        Routine for gathering and modifying the topics as necessary e.g. substituting in the sign address/serial,
        although this really only applies to signs
        :return:
        """
        topics = []
        # These are the basic topics for Hanip do not modify
        broker_topic = self.mqtt_conn_dict.get("broker_topic", None)
        reply_topic = self.mqtt_conn_dict.get("reply_topic", None)
        status_topic = self.mqtt_conn_dict.get("status_topic", None)
        sign_address = self.mqtt_conn_dict.get("address", "0")

        if broker_topic is not None:
            self.message_topic = broker_topic.replace("#", sign_address)
            self.message_all_topic = broker_topic.replace("#", "all")
            topics = [(self.message_topic, 0), (self.message_all_topic, 0)]
        else:
            self.message_all_topic = self.message_topic = ""

        if reply_topic is not None:
            self.reply_topic = self.mqtt_conn_dict["reply_topic"].replace("#", sign_address)\
                .replace("$SER", self.mqtt_conn_dict["serial"])

        if status_topic is not None:
            self.status_topic = self.mqtt_conn_dict["status_topic"].replace("#", sign_address)\
                .replace("$SER", self.mqtt_conn_dict["serial"])

        #Deal with additional topics here
        subscription_topics = self.mqtt_conn_dict.get("subscription_topics", None)
        if subscription_topics is not None and type(subscription_topics) is list:
            for topic in self.mqtt_conn_dict["subscription_topics"]:
                _topic = topic.replace("$ADDR", sign_address)
                topics.append((_topic, 0))

        logging.info(topics)
        return topics

    """
    ###################################################################################################################
    MQTT external calls
    """

    def poll_new_data(self) -> None:
        """
        This grabs the payload from the MQTT client class if there is new data to be grabbed that is of the correct
        topic.  The filter only applies to whatever topic is in broker_topic which is only used by hanip.
        The whole point of this is to allow existing functionality/behaviour with original hanip modules whilst allowing
        other things to use this call.
        :return:
        """
        if self.message_topic == "" or self.message_topic is None:
            topic_filter = False
        else:
            topic_filter = True

        if self.mqtt_sub.newMsg:
            self.mqtt_sub.newMsg = False

            if topic_filter:
                #Check if it's the all message topic
                if not self.mqtt_sub.rcvdTopic == self.message_all_topic:
                    #Check if it's the "normal" topic
                    if not self.mqtt_sub.rcvdTopic == self.message_topic:
                        # Return if no topics match
                        return

            logging.debug("Instance %s: payload available" % self.instance)

            if self.mqtt_sub.payload == self.current_payload:
                pass
            else:
                logging.info("Instance %s: new payload" % self.instance)
                self.current_payload = self.mqtt_sub.payload
                self.new_payload = True

    def poll_mqtt_queue(self):
        """
        This is the new method for polling items from the queue.  Items are automatically placed in a queue so no need
        for the connection handler to populate it.
        """
        if self.mqtt_sub.queuing_enabled:
            try:
                payload = self.mqtt_sub.payload_queue.get(block=False)
            except queue.Empty:
                # logging.info("MQTTC: Payload queue is empty")
                return None
            except Exception as e:
                logging.warning("MQTTC: %s" % e)
                return None
            else:
                return payload
        else:
            logging.info("MQTTC: Queueing not enabled")

    def send_reply(self, payload: str) -> None:
        if self.service_connected and self.mqtt_conn_dict["reply_topic"] is not None:
            logging.info("Instance %s: Sending reply to %s %s" % (self.instance, self.reply_topic, self.service_ip))
            self.mqtt_sub.publish_data(self.reply_topic, payload)
        else:
            logging.warning("Instance %s: No broker connection" % self.instance)

    def send_status(self, payload: str) -> None:
        if self.service_connected and self.mqtt_conn_dict["status_topic"] is not None:
            logging.info("Instance %s: Sending status to %s %s" % (self.instance, self.status_topic, self.service_ip))
            self.mqtt_sub.publish_data(self.status_topic, payload)
        else:
            logging.warning("Instance %s: No broker connection" % self.instance)

    def send_message(self, topic: str, payload: str, qos: int = 0, retain: bool = False) -> None:
        if self.service_connected:
            logging.info("Instance %s: Sending message to %s %s" % (self.instance, topic, self.service_ip))
            self.mqtt_sub.publish_data(topic, payload, qos, retain)
        else:
            logging.warning("Instance %s: No broker connection" % self.instance)

    def run(self) -> None:
        """
        Main entry point into this for signs
        """
        while 1:
            self.discover_and_connect_to_service()

            #Perhaps add a delay here so that retained messages are heard.
            time.sleep(1)

            while 1:
                if self.check_connection_status():
                    break
                else:
                    self.poll_new_data()
                    time.sleep(1)

    """
    ###################################################################################################################
    MQTT Console related calls
    """
    def poll_status_data(self):
        """
        Placeholder for sign status subscriptions
        """
        pass

class MQTTBrokerConfigurator(object):
    def __init__(self):
        pass

    def check_mqtt_broker(self):
        """
        This checks the mosquitto configuration to see if anonymous clients are permitted.  Unfortunately this
        isn't configured via UCI.

        It is likely the config will change when secure MQTT is implemented.
        """
        mosquitto_config_path = r"/etc/mosquitto/mosquitto.conf"

        if not os.path.isfile(mosquitto_config_path):
            logging.error("BrokerConfig: mqtt config file does not exist")
            return

        mosquitto_config_contents = [
            "bind_address 0.0.0.0",
            "port 1883",
            "protocol mqtt",
            "log_type information",
            "listener 9001 0.0.0.0",
            "allow_anonymous true"
        ]

        anonymous_setting = False

        with open(mosquitto_config_path, "r") as mosquitto_config:
            for line in mosquitto_config:
                if "allow_anonymous true" in line:
                    anonymous_setting = True
                    # Don't break?

        if not anonymous_setting:
            logging.info("BrokerConfig: Setting MQTT broker config: allow_anonymous true")
            with open(mosquitto_config_path, "w") as mosquitto_config:
                for line in mosquitto_config_contents:
                    mosquitto_config.write(line + "\n")

            # Restart MQTT broker here:
            command = ["/etc/init.d/mosquitto", "restart"]

            process = subprocess.Popen(command, stdout=subprocess.PIPE)
            output, error = process.communicate()

            time.sleep(3)
        else:
            logging.info("BrokerConfig: Broker config correct")

"""
###################################################################################################################
Testing
"""


def main():
    logging.basicConfig(level=logging.DEBUG)

    mqtt_conn_dict = {
        "version": 311,
        "port": 1883,
        "username": "",
        "password": "",
        "certificate_path": "",
        "enable_tls": False,
        "discover": False,
        "timeout": 0,
        "disconnect_timeout": 5,
        "service_type": "",
        "hostname": "",
        "fallback_address": "127.0.0.1",
        #"broker_topic": "root/topic0",
        "reply_topic": "",
        "status_topic": "",
        # "payload_queue_size": 10,
        "subscription_topics": ["root/topic1", "root/topic2"],

        "address": "0",
        "serial": "ABC1234456789",
        "mac": "00:00:00:00:00:00:00"
    }

    mqtt_handler = MQTTConnectionHandler("Client1", mqtt_conn_dict)
    _thread.start_new_thread(mqtt_handler.run, ())

    while 1:
        if mqtt_handler.queue_size > 0:
            print(mqtt_handler.poll_mqtt_queue())

        if mqtt_handler.new_payload:
            mqtt_handler.new_payload = False
            print(mqtt_handler.current_payload)

        time.sleep(1)


def client_only_test():
    topics = [("isi_journey", 0), ("infohub/dpi/sign/request/2/json", 0)]
    mqttsub = MQTT_Client()
    mqttsub.set_broker_address("127.0.0.1")
    if mqttsub.connect_client():
        mqttsub.subscribe_to_topics(topics)
        _thread.start_new_thread(mqttsub.run_client, ())

    while 1:
        logging.debug(mqttsub.payload)
        time.sleep(1)

if __name__ == "__main__":
    main()
    # client_test()
