import time
import json
import logging
import asyncio

from hbmqtt.client import MQTTClient
from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2

from .nl_serial import NooliteSerial
from .utils import Singleton

from uuid import uuid1
from socket import gethostname

logger = logging.getLogger(__name__)

client_id = gethostname()+'-'+str(uuid1())

INPUT_TOPIC = '%s/send'
OUTPUT_TOPIC = '%s/receive'

class MqttDriver(metaclass=Singleton):
    def __init__(self, mtrf_tty_name, loop, mqtt_uri='mqtt://127.0.0.1/', mqtt_topic='noolite', commands_delay=0.1):
        self.mqtt_client = MQTTClient(client_id=client_id,config={'auto_reconnect': True})
        self.mqtt_uri = mqtt_uri
        self.commands_delay = commands_delay
        self.noolite_serial = NooliteSerial(loop=loop, tty_name=mtrf_tty_name,
                                            input_command_callback_method=self.input_serial_data)
        self.commands_to_send_queue = asyncio.Queue()
        self.read_topic = INPUT_TOPIC % mqtt_topic
        self.subscriptions = [
            ( self.read_topic+'/#', QOS_0),
        ]
        self.write_topic = OUTPUT_TOPIC % mqtt_topic
        loop.create_task(self.send_command_to_noolite())

    async def run(self):
        await self.mqtt_client.connect(self.mqtt_uri)
        await self.mqtt_client.subscribe(self.subscriptions)

        while True:
            logger.info('Waiting messages from mqtt...')
            message = await self.mqtt_client.deliver_message()

            topic = message.topic
            payload = message.publish_packet.payload.data

            logger.info('In message: {}\n{}'.format(topic, payload))

            if topic.startswith(self.read_topic):
              subtopic = topic[len(self.read_topic)+1:]
              print(subtopic)

              if subtopic == '':
                try:
                  payload = json.loads(payload.decode())
                except Exception as e:
                  logger.exception(e)
                  continue
                await self.commands_to_send_queue.put(payload)
              else:
                try:
                  address = subtopic.split('/')
                  if len(address)==2:
                    channel = int(address[0])
                    command = address[1]
                    id = None
                  elif len(address)==3:
                    channel = int(address[0])
                    command = address[2]
                    id = address[1]
                  elif len(address)==1:
                    command = address[0]
                    channel = None
                    id = None

                  command = command.lower()  

                  print("%s: %s (%s)" % (command,channel,id))

                  mtrf_command = { "ch": channel }
                  if id == '.' or id == 'TX-F':
                    mtrf_command["mode"] = 2
                  elif id =='TX':
                    mtrf_command["mode"] = 0
                  elif id =='RX':
                    mtrf_command["mode"] = 1
                  elif id =='RX-F':
                    mtrf_command["mode"] = 3
                  elif id:
                    mtrf_command["mode"] = 2
                    mtrf_command["ctr"] = 8
                    mtrf_command["id0"] = int(id[0:2],16)
                    mtrf_command["id1"] = int(id[2:4],16)
                    mtrf_command["id2"] = int(id[4:6],16)
                    mtrf_command["id3"] = int(id[6:8],16)
                  else:
                    mtrf_command["mode"] = 0                    

                  if command == "power":
                    payload = payload.decode('utf-8').lower()
                    print( "command: POWER " + payload )
                    if payload == "off" or payload == "0":
                      mtrf_command["cmd"] = 0
                    else:  
                      mtrf_command["cmd"] = 2

                  elif command == "on":
                    mtrf_command["cmd"] = 2

                  elif command == "off":
                    mtrf_command["cmd"] = 0

                  elif command == "brightness":
                    mtrf_command["cmd"] = 6
                    mtrf_command["d0"] = int(float(payload))
                      
                  elif command == "dimmer":
                    mtrf_command["cmd"] = 6
                    mtrf_command["d0"] = int(round(float(payload)*255/100))

                  elif command == "state":
                    mtrf_command["cmd"] = 128

                  elif command == "load_preset":
                    mtrf_command["cmd"] = 7

                  elif command == "save_preset":
                    mtrf_command["cmd"] = 8

                  elif command == "temp_on":
                    delay = (int(payload) + 3)//5
                    mtrf_command["cmd"] = 25
                    mtrf_command["d0"] = delay % 256
                    mtrf_command["d1"] = delay // 256
                    mtfr_command["fmt"] = 6

                  elif command == "bind":
                    mtrf_command["cmd"] = 15

                  elif command == "unbind":
                    mtrf_command["cmd"] = 9

                  elif command == "service":
                    mtrf_command["cmd"] = 131

                  elif command == "start_bind":
                    mtrf_command["ctr"] = 3
                    mtrf_command["cmd"] = 0
                    
                  elif command == "stop_bind":
                    mtrf_command["ctr"] = 4
                    mtrf_command["cmd"] = 0

                  elif command == "clear_bind":
                    mtrf_command["ctr"] = 5
                    mtrf_command["cmd"] = 0

                  elif command == "unbind_addr":
                    mtrf_command["ctr"] = 7
                    mtrf_command["cmd"] = 0

                except Exception as e:
                  logger.exception(e)
                  continue
                await self.commands_to_send_queue.put(mtrf_command)
                 

    async def send_command_to_noolite(self):
        last_command_send_time = 0
        while True:
            logger.info('Waiting commands to send...')
            payload = await self.commands_to_send_queue.get()
            logger.info('Get command from queue: {}'.format(payload))

            # Формируем и отправляем команду к noolite
            noolite_cmd = self.payload_to_noolite_command(payload)

            if time.time() - last_command_send_time < self.commands_delay:
                logger.info('Wait before send next command: {}'.format(
                    self.commands_delay - (time.time() - last_command_send_time)))
                await asyncio.sleep(self.commands_delay - (time.time() - last_command_send_time))

            try:
                await self.noolite_serial.send_command(**noolite_cmd)
            except TypeError as e:
                logger.exception(str(e))
            last_command_send_time = time.time()

    async def input_serial_data(self, command):
        logger.info('Pub command: {}'.format(command))
        command = self.noolite_response_to_payload(command.to_list())
        try:
          topic = "%s/%s/%s" % (self.write_topic, command['ch'], command['id'])
        except:
          topic = "%s/%s" % (self.write_topic, command['ch'])  
        await self.mqtt_client.publish(topic=topic, message=json.dumps(command).encode())

    @staticmethod        
    def payload_to_noolite_command(payload):
        return payload

    @staticmethod
    def noolite_response_to_payload(payload):

        message = {}

        try:
          mode = [ 'TX', 'RX', 'TX-F', 'RX-F', 'SERVICE', 'FIRMWARE' ] [payload[1]]
          message['mode'] = mode
        finally:
          None  

        try:
          message['ctr'] = [ 'OK', 'NORESP', 'ERROR', 'BOUND' ] [payload[2]]
        finally:
          None  

        ch = payload[4]
        message['ch'] = ch

        cmd = payload[5]
        message['cmd'] = cmd

        fmt = payload[6]
        message['fmt'] = fmt

        data = payload[7:11]
        message['data'] = data

        if payload[1] >= 2:
          message['id'] = '%0.2X%0.2X%0.2X%0.2X' % (payload[11], payload[12], payload[13], payload[14])

        if cmd == 0:
          message['command'] = 'OFF'
        elif cmd == 1: 
          message['command'] = 'BRIGHT_DOWN'
        elif cmd == 2: 
          message['command'] = 'ON'
        elif cmd == 3: 
          message['command'] = 'BRIGHT_UP'
        elif cmd == 4: 
          message['command'] = 'SWITCH'
        elif cmd == 5: 
          message['command'] = 'BRIGHT_BACK'
        elif cmd == 6: 
          message['command'] = 'SET_BRIGHTNESS'
        elif cmd == 7: 
          message['command'] = 'LOAD_PRESET'
        elif cmd == 8: 
          message['command'] = 'SAVE_PRESET'
        elif cmd == 9: 
          message['command'] = 'UNBIND'
        elif cmd == 10: 
          message['command'] = 'STOP_REG'
        elif cmd == 11: 
          message['command'] = 'BRIGHTNESS_STEP_DOWN'
        elif cmd == 12: 
          message['command'] = 'BRIGHTNESS_STEP_UP'
        elif cmd == 13: 
          message['command'] = 'BRIGHT_REG'
        elif cmd == 15: 
          message['command'] = 'BIND'
        elif cmd == 16: 
          message['command'] = 'ROLL_COLOUR'
        elif cmd == 17: 
          message['command'] = 'SWITCH_COLOUR'
        elif cmd == 18: 
          message['command'] = 'SWITCH_MODE'
        elif cmd == 19: 
          message['command'] = 'SPEED_MODE_BACK'
        elif cmd == 20: 
          message['command'] = 'BATTERY_LOW'
        elif cmd == 21: 
          message['command'] = 'SENS_TEMP_HUMI'
          t = data[0] + 256*(data[1] % 16)
          if (data[1] % 16) // 8:
            t = -(4096 - t )
          t = t / 10  
          message['t'] = t
          dev_type = (data[1] // 16) % 8
          try:
            message['dev_type'] = [ 'RESERVED', 'PT112', 'PT111' ][dev_type]
          finally:
            None  
          message['dev_battery_low'] = (data[1] // 128)
          if dev_type == 2:
            h = data[2]
            message['h'] = h
          message['aux'] = data[3]  
        elif cmd == 25: 
          message['command'] = 'TEMPORARY_ON'
          if fmt == 5:
            message['delay'] = data[0] * 5
          elif fmt == 6:
            message['delay'] = data[0] * 5 + data[1]*5*256
        elif cmd == 26: 
          message['command'] = 'MODES'
        elif cmd == 128: 
          message['command'] = 'READ_STATE'
        elif cmd == 129: 
          message['command'] = 'WRITE_STATE'
        elif cmd == 130: 
          message['command'] = 'SEND_STATE'
          dev_type = data[0]
          if dev_type == 5:
            message['dev_type'] = 'SLU-1-300'
          message['dev_firmware'] = data[1]
          if fmt == 0:
            dev_state = data[2] % 16
            try:
              message['dev_state'] = [ 'OFF', 'ON', 'TEMPORARY_ON' ][dev_state]
              message['POWER'] = message['dev_state']
            finally:
              None
            dev_mode = data[2] // 128
            if dev_mode:
              message['dev_binding'] = 'ON'
            message['brightness'] = data[3]
            message['DIMMER'] = int(round(data[3]*100/255))
          elif fmt == 1:
            message['dev_aux'] = data[2]
            message['dev_legacy'] = data[3]
          elif fmt == 2:
            message['dev_free'] = data[3]
            message['dev_free_legacy'] = data[2]    
        elif cmd == 131: 
          message['command'] = 'SERVICE'
        elif cmd == 132: 
          message['command'] = 'CLEAR_MEMORY'
          
        return message
        
