import logging
import struct
import subprocess
import time
from enum import Enum, auto
from threading import Thread

import zmq

from flandre.BusClient import BusClient
from flandre.config import C
from flandre.nodes.Node import Node
from flandre.utils.Msg import ImageArgMsg, KillMsg, SetDeviceConnectedMsg, SetDeviceEnabledMsg, DeviceEnabledMsg, \
    DeviceConnectedMsg, SetDeviceConfigMsg, DeviceOnlineMsg, DeviceConfigListMsg, RequestRfFrameMsg, \
    DeviceZero, SetDeviceSwitchMsg, DeviceSwitchMsg, SeqMetaMsg, RefreshDeviceMsg, RfFrameMsg
from flandre.utils.RfFrame import RfFrameMemory
from flandre.utils.RfMeta import RfFrameMeta, RfSequenceMeta

logger = logging.getLogger(__name__)


class DeviceCmd(Enum):
    Exit = 0

    GetName = auto()
    GetData = auto()
    GetConnection = auto()
    GetEnable = auto()

    SetNameAndFileOnly = auto()
    SetConnectionOn = auto()
    SetConnectionOff = auto()
    SetEnableOn = auto()
    SetEnableOff = auto()
    SetZero = auto()


class Device(Node):
    magic = 7355608
    topics = [SetDeviceConnectedMsg, SetDeviceEnabledMsg, SetDeviceConfigMsg, RequestRfFrameMsg, DeviceZero,
              DeviceSwitchMsg
              ]

    def __init__(self, level=logging.INFO):
        super(Device, self).__init__(level=level)
        self.arg = ImageArgMsg('', t_start=0, t_end=1499)
        self.seq_meta: RfSequenceMeta | None = None
        self.req_driver_socket: zmq.Socket = None
        self.rep_socket: zmq.Socket = None
        self.ok = b'ok\x00'
        self.loop2_t = None
        self.switch = False

    def loop2(self):
        while True:
            self.online()
            time.sleep(1)

    def device_cmd(self, cmd: DeviceCmd, v: bytes = b''):
        self.req_driver_socket.send(struct.pack('i', self.magic) + struct.pack('i', cmd.value) + v)

    def connect(self):
        temp_client = BusClient(SetDeviceSwitchMsg, RefreshDeviceMsg, poller=True)
        temp_client.poller.register(self.req_driver_socket, zmq.POLLIN)
        self.device_cmd(DeviceCmd.SetConnectionOn)
        logger.warning('onn')
        d = dict(temp_client.poller.poll())

        if self.req_driver_socket in d:
            rb = self.req_driver_socket.recv()
            if rb == self.ok:
                self.send(DeviceConnectedMsg(True))
            else:
                logger.error(f"Device msg: {rb}")
        if temp_client.sub in d:
            msg = temp_client.recv()
            if isinstance(msg, SetDeviceSwitchMsg):
                if not msg.value:
                    self.switch = False
                    logger.warning(f"interrupt connecting")
                    self.req_driver_socket.close()
                    self.context.term()
                    self.context = zmq.Context()
                    self.req_driver_socket = self.context.socket(zmq.REQ)
                    self.req_driver_socket.connect(C.live_rep_socket)
            if isinstance(msg, RefreshDeviceMsg):
                self.switch = False
                logger.warning(f"interrupt connecting")
                self.req_driver_socket.close()
                self.context.term()
                self.context = zmq.Context()
                self.req_driver_socket = self.context.socket(zmq.REQ)
                self.req_driver_socket.connect(C.live_rep_socket)

    def disconnect(self):
        self.device_cmd(DeviceCmd.SetConnectionOff)
        rb = self.req_driver_socket.recv()
        if rb == self.ok:
            self.send(DeviceConnectedMsg(False))
        else:
            logger.error(f"Device msg: {rb}")

    def enable(self):
        self.device_cmd(DeviceCmd.SetEnableOn)
        rb = self.req_driver_socket.recv()
        if rb == self.ok:
            self.send(DeviceEnabledMsg(True))
            return True
        else:
            logger.error(f"Device msg: {rb}")
            return False

    def disable(self):
        self.device_cmd(DeviceCmd.SetEnableOff)
        rb = self.req_driver_socket.recv()
        if rb == self.ok:
            self.send(DeviceEnabledMsg(False))
            return True
        else:
            logger.error(f"Device msg: {rb}")
            return False

    def online(self):
        code = subprocess.run(['curl', '-m', '1', C.live_rep_socket_http], stderr=subprocess.DEVNULL,
                              stdout=subprocess.DEVNULL).returncode
        logger.debug(f'detect curl code: {code}')
        match code:
            case 28 | 7:
                self.send(DeviceOnlineMsg(False))
                return False
            case _:
                self.send(DeviceOnlineMsg(True))
                return True

    def get_enable(self):
        self.device_cmd(DeviceCmd.GetEnable)
        rb = self.req_driver_socket.recv()
        match rb:
            case b'true':
                self.send(DeviceEnabledMsg(True))
                return True
            case b'false':
                self.send(DeviceEnabledMsg(False))
                return False
            case _:
                logger.error(f"Device msg: {rb}")

    def get_seq_meta_name(self):
        self.device_cmd(DeviceCmd.GetName)
        rb = self.req_driver_socket.recv()
        name = rb.decode()
        self.seq_meta = RfSequenceMeta.from_name(name)
        if rb != b'':
            self.send(SeqMetaMsg('live', name))

    def get_connection(self):
        self.device_cmd(DeviceCmd.GetConnection)
        rb = self.req_driver_socket.recv()
        match rb:
            case b'true':
                self.send(DeviceConnectedMsg(True))
                return True
            case b'false':
                self.send(DeviceConnectedMsg(False))
                return False
            case _:
                logger.error(f"Device msg: {rb}")

    def set_name_and_file(self, name: str, config_str: str):
        if self.get_enable():
            self.disable()
            self.set_name_and_file_only(name, config_str)
            self.enable()
        elif self.get_connection():
            self.set_name_and_file_only(name, config_str)
        else:
            logger.warning(f"Device not connect, cannot set config")

    def set_name_and_file_only(self, name: str, txt: str):
        name_encoded = name.encode()
        self.device_cmd(DeviceCmd.SetNameAndFileOnly,
                        struct.pack('I', name_encoded.__len__()) + name.encode() + txt.encode())
        rb = self.req_driver_socket.recv()
        if rb == self.ok:
            return True
        else:
            logger.error(f"Device msg: {rb}")
            return False

    def get_data(self):
        self.device_cmd(DeviceCmd.GetData)
        return self.req_driver_socket.recv()

    def set_zero(self):
        self.device_cmd(DeviceCmd.SetZero)
        return self.req_driver_socket.recv()

    def custom_setup(self):
        self.rep_socket = self.context.socket(zmq.REP)
        self.rep_socket.bind(f"tcp://localhost:{C.driver_rep_port}")
        self.c.poller.register(self.rep_socket, zmq.POLLIN)

        self.req_driver_socket = self.context.socket(zmq.REQ)
        self.req_driver_socket.connect(C.live_rep_socket)
        self.loop2_t = Thread(target=self.loop2)
        self.loop2_t.start()

    def loop(self):
        arr = []
        time.sleep(3)
        if self.online():
            self.get_connection()
            self.get_enable()
            self.get_seq_meta_name()
        for f in C.device_config_folder.glob('*.txt'):
            arr.append((f.stem, f.read_text()))
        self.send(DeviceConfigListMsg(arr))
        # if arr.__len__() > 0:
        #     self.setfile(arr[0][1])
        logger.debug(f'device start loop')
        while True:
            d = dict(self.c.poller.poll())
            if self.c.sub in d:
                msg = self.recv()
                if isinstance(msg, KillMsg):
                    if msg.name == '':
                        return
                elif isinstance(msg, SetDeviceEnabledMsg):
                    if msg.value:
                        self.enable()
                    else:
                        self.disable()
                elif isinstance(msg, SetDeviceConnectedMsg):
                    logger.info('connecting to device')
                    if msg.value:
                        self.connect()
                    else:
                        self.disconnect()
                elif isinstance(msg, SetDeviceConfigMsg):
                    self.set_name_and_file(msg.name, msg.txt)
                    self.seq_meta = RfSequenceMeta.from_name(msg.name)
                    self.send(SeqMetaMsg('live', msg.name))
                elif isinstance(msg, RequestRfFrameMsg):
                    if self.switch:
                        braw = self.get_data()
                        if braw == b'':
                            logger.warning('empty msg!')
                            continue
                        _, sequence_id, encoder = struct.unpack_from('=iqi', braw)
                        buffer = braw[4 + 8 + 4:]
                        if self.seq_meta is not None:
                            self.send(RfFrameMsg(0, RfFrameMemory(
                                RfFrameMeta(encoder=encoder, sequence_id=sequence_id),
                                self.seq_meta,
                                buffer,
                            )))
                        else:
                            logger.warning(f'no seq meta msg')
                    else:
                        logger.warning('device not online')
                elif isinstance(msg, DeviceZero):
                    self.set_zero()
                elif isinstance(msg, DeviceSwitchMsg):
                    self.switch = msg.value == 'GREEN'
                # elif isinstance(msg, SetDeviceSwitchMsg):
                #     logger.info(f'{msg.value}')
                #     if msg.value:
                #         self.device_rep_socket.connect(f"tcp://{LIVE_REP_SOCKET}")
                #     else:
                #         self.device_rep_socket.close()
            if self.rep_socket in d:
                self.rep_socket.recv()
                self.rep_socket.send(self.get_data())
