# -*- coding: utf-8 -*-
"""
Contains drivers for:
1. SyringePump: Runze Fluid SY-03B (ASCII)
2. EmmMotor: Emm V5.0 Closed-loop Stepper (Modbus-RTU variant)
3. XKCSensor: XKC Non-contact Level Sensor (Modbus-RTU)
"""

import socket
import serial
import time
import threading
import struct
import re
import traceback
import queue
from typing import Optional, Dict, List, Any

try:
    from unilabos.device_comms.universal_driver import UniversalDriver
except ImportError:
    import logging
    class UniversalDriver:
        def __init__(self):
            self.logger = logging.getLogger(self.__class__.__name__)

        def execute_command_from_outer(self, command: str):
            pass

# ==============================================================================
# 1. Transport Layer (通信层)
# ==============================================================================

class TransportManager:
    """
    统一通信管理类。
    自动识别 串口 (Serial) 或 网络 (TCP) 连接。
    """
    def __init__(self, port: str, baudrate: int = 9600, timeout: float = 3.0, logger=None):
        self.port = port
        self.baudrate = baudrate
        self.timeout = timeout
        self.logger = logger
        self.lock = threading.RLock() # 线程锁，确保多设备共用一个连接时不冲突

        self.is_tcp = False
        self.serial = None
        self.socket = None

        # 简单判断: 如果包含 ':' (如 192.168.1.1:8899) 或者看起来像 IP，则认为是 TCP
        if ':' in self.port or (self.port.count('.') == 3 and not self.port.startswith('/')):
            self.is_tcp = True
            self._connect_tcp()
        else:
            self._connect_serial()

    def _log(self, msg):
        if self.logger:
             pass
            # self.logger.debug(f"[Transport] {msg}")

    def _connect_tcp(self):
        try:
            if ':' in self.port:
                host, p = self.port.split(':')
                self.tcp_host = host
                self.tcp_port = int(p)
            else:
                self.tcp_host = self.port
                self.tcp_port = 8899 # 默认端口

            # if self.logger: self.logger.info(f"Connecting TCP {self.tcp_host}:{self.tcp_port} ...")
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket.settimeout(self.timeout)
            self.socket.connect((self.tcp_host, self.tcp_port))
        except Exception as e:
            raise ConnectionError(f"TCP connection failed: {e}")

    def _connect_serial(self):
        try:
            # if self.logger: self.logger.info(f"Opening Serial {self.port} (Baud: {self.baudrate}) ...")
            self.serial = serial.Serial(
                port=self.port,
                baudrate=self.baudrate,
                timeout=self.timeout
            )
        except Exception as e:
            raise ConnectionError(f"Serial open failed: {e}")

    def close(self):
        """关闭连接"""
        if self.is_tcp and self.socket:
            try: self.socket.close()
            except: pass
        elif not self.is_tcp and self.serial and self.serial.is_open:
            self.serial.close()

    def clear_buffer(self):
        """清空缓冲区 (Thread-safe)"""
        with self.lock:
            if self.is_tcp:
                self.socket.setblocking(False)
                try:
                    while True:
                        if not self.socket.recv(1024): break
                except: pass
                finally: self.socket.settimeout(self.timeout)
            else:
                self.serial.reset_input_buffer()

    def write(self, data: bytes):
        """发送原始字节"""
        with self.lock:
            if self.is_tcp:
                self.socket.sendall(data)
            else:
                self.serial.write(data)

    def read(self, size: int) -> bytes:
        """读取指定长度字节"""
        if self.is_tcp:
            data = b''
            start = time.time()
            while len(data) < size:
                if time.time() - start > self.timeout: break
                try:
                    chunk = self.socket.recv(size - len(data))
                    if not chunk: break
                    data += chunk
                except socket.timeout: break
            return data
        else:
            return self.serial.read(size)

    def send_ascii_command(self, command: str) -> str:
        """
        发送 ASCII 字符串命令 (如注射泵指令)，读取直到 '\r'。
        """
        with self.lock:
            data = command.encode('ascii') if isinstance(command, str) else command
            self.clear_buffer()
            self.write(data)

            # Read until \r
            if self.is_tcp:
                resp = b''
                start = time.time()
                while True:
                    if time.time() - start > self.timeout: break
                    try:
                        char = self.socket.recv(1)
                        if not char: break
                        resp += char
                        if char == b'\r': break
                    except: break
                return resp.decode('ascii', errors='ignore').strip()
            else:
                return self.serial.read_until(b'\r').decode('ascii', errors='ignore').strip()

# ==============================================================================
# 2. Syringe Pump Driver (注射泵)
# ==============================================================================

class SyringePump:
    """SY-03B 注射泵驱动 (ASCII协议)"""

    CMD_INITIALIZE = "Z{speed},{drain_port},{output_port}R"
    CMD_SWITCH_VALVE = "I{port}R"
    CMD_ASPIRATE = "P{vol}R"
    CMD_DISPENSE = "D{vol}R"
    CMD_DISPENSE_ALL = "A0R"
    CMD_STOP = "TR"
    CMD_QUERY_STATUS = "Q"
    CMD_QUERY_PLUNGER = "?0"

    def __init__(self, device_id: int, transport: TransportManager):
        if not 1 <= device_id <= 15:
            pass # Allow all IDs for now
        self.id = str(device_id)
        self.transport = transport

    def _send(self, template: str, **kwargs) -> str:
        cmd = f"/{self.id}" + template.format(**kwargs) + "\r"
        return self.transport.send_ascii_command(cmd)

    def is_busy(self) -> bool:
        """查询繁忙状态"""
        resp = self._send(self.CMD_QUERY_STATUS)
        # 响应如 /0` (Ready, 0x60) 或 /0@ (Busy, 0x40)
        if len(resp) >= 3:
            status_byte = ord(resp[2])
            # Bit 5: 1=Ready, 0=Busy
            return (status_byte & 0x20) == 0
        return False

    def wait_until_idle(self, timeout=30):
        """阻塞等待直到空闲"""
        start = time.time()
        while time.time() - start < timeout:
            if not self.is_busy(): return
            time.sleep(0.5)
        # raise TimeoutError(f"Pump {self.id} wait idle timeout")
        pass

    def initialize(self, drain_port=0, output_port=0, speed=10):
        """初始化"""
        self._send(self.CMD_INITIALIZE, speed=speed, drain_port=drain_port, output_port=output_port)

    def switch_valve(self, port: int):
        """切换阀门 (1-8)"""
        self._send(self.CMD_SWITCH_VALVE, port=port)

    def aspirate(self, steps: int):
        """吸液 (相对步数)"""
        self._send(self.CMD_ASPIRATE, vol=steps)

    def dispense(self, steps: int):
        """排液 (相对步数)"""
        self._send(self.CMD_DISPENSE, vol=steps)

    def stop(self):
        """停止"""
        self._send(self.CMD_STOP)

    def get_position(self) -> int:
        """获取柱塞位置 (步数)"""
        resp = self._send(self.CMD_QUERY_PLUNGER)
        m = re.search(r'\d+', resp)
        return int(m.group()) if m else -1

# ==============================================================================
# 3. Stepper Motor Driver (步进电机)
# ==============================================================================

class EmmMotor:
    """Emm V5.0 闭环步进电机驱动"""

    def __init__(self, device_id: int, transport: TransportManager):
        self.id = device_id
        self.transport = transport

    def _send(self, func_code: int, payload: list) -> bytes:
        with self.transport.lock:
            self.transport.clear_buffer()
            # 格式: [ID] [Func] [Data...] [Check=0x6B]
            body = [self.id, func_code] + payload
            body.append(0x6B) # Checksum
            self.transport.write(bytes(body))

            # 根据指令不同，读取不同长度响应
            read_len = 10 if func_code in [0x31, 0x32, 0x35, 0x24, 0x27] else 4
            return self.transport.read(read_len)

    def enable(self, on=True):
        """使能 (True=锁轴, False=松轴)"""
        state = 1 if on else 0
        self._send(0xF3, [0xAB, state, 0])

    def run_speed(self, speed_rpm: int, direction=0, acc=10):
        """速度模式运行"""
        sp = struct.pack('>H', int(speed_rpm))
        self._send(0xF6, [direction, sp[0], sp[1], acc, 0])

    def run_position(self, pulses: int, speed_rpm: int, direction=0, acc=10, absolute=False):
        """位置模式运行"""
        sp = struct.pack('>H', int(speed_rpm))
        pl = struct.pack('>I', int(pulses))
        is_abs = 1 if absolute else 0
        self._send(0xFD, [direction, sp[0], sp[1], acc, pl[0], pl[1], pl[2], pl[3], is_abs, 0])

    def stop(self):
        """停止"""
        self._send(0xFE, [0x98, 0])

    def set_zero(self):
        """清零位置"""
        self._send(0x0A, [])

    def get_position(self) -> int:
        """获取当前脉冲位置"""
        resp = self._send(0x32, [])
        if len(resp) >= 8:
            sign = resp[2]
            val = struct.unpack('>I', resp[3:7])[0]
            return -val if sign == 1 else val
        return 0

# ==============================================================================
# 4. Liquid Sensor Driver (液位传感器)
# ==============================================================================

class XKCSensor:
    """XKC RS485 液位传感器 (Modbus RTU)"""

    def __init__(self, device_id: int, transport: TransportManager, threshold: int = 300):
        self.id = device_id
        self.transport = transport
        self.threshold = threshold

    def _crc(self, data: bytes) -> bytes:
        crc = 0xFFFF
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x0001: crc = (crc >> 1) ^ 0xA001
                else: crc >>= 1
        return struct.pack('<H', crc)

    def read_level(self) -> Optional[Dict[str, Any]]:
        """
        读取液位。
        返回: {'level': bool, 'rssi': int}
        """
        with self.transport.lock:
            self.transport.clear_buffer()
            # Modbus Read Registers: 01 03 00 01 00 02 CRC
            payload = struct.pack('>HH', 0x0001, 0x0002)
            msg = struct.pack('BB', self.id, 0x03) + payload
            msg += self._crc(msg)
            self.transport.write(msg)

            # Read header
            h = self.transport.read(3) # Addr, Func, Len
            if len(h) < 3: return None
            length = h[2]

            # Read body + CRC
            body = self.transport.read(length + 2)
            if len(body) < length + 2:
                # Firmware bug fix specific to some modules
                if len(body) == 4 and length == 4:
                    pass
                else:
                    return None

            data = body[:-2]
            if len(data) == 2:
                rssi = data[1]
            elif len(data) >= 4:
                rssi = (data[2] << 8) | data[3]
            else:
                return None

            return {
                'level': rssi > self.threshold,
                'rssi': rssi
            }

# ==============================================================================
# 5. Main Device Class (ChinweDevice)
# ==============================================================================

class ChinweDevice(UniversalDriver):
    """
    ChinWe 工作站主驱动
    继承自 UniversalDriver，管理所有子设备（泵、电机、传感器）
    """

    def __init__(self, port: str = "192.168.1.200:8899", baudrate: int = 9600,
                 pump_ids: List[int] = None, motor_ids: List[int] = None,
                 sensor_id: int = 6, sensor_threshold: int = 300,
                 timeout: float = 10.0):
        """
        初始化 ChinWe 工作站
        :param port: 串口号 或 IP:Port
        :param baudrate: 串口波特率
        :param pump_ids: 注射泵 ID列表 (默认 [1, 2, 3])
        :param motor_ids: 步进电机 ID列表 (默认 [4, 5])
        :param sensor_id: 液位传感器 ID (默认 6)
        :param sensor_threshold: 传感器液位判定阈值
        :param timeout: 通信超时时间 (默认 10秒)
        """
        super().__init__()
        self.port = port
        self.baudrate = baudrate
        self.timeout = timeout
        self.mgr = None
        self._is_connected = False

        # 默认配置
        if pump_ids is None: pump_ids = [1, 2, 3]
        if motor_ids is None: motor_ids = [4, 5]

        # 配置信息
        self.pump_ids = pump_ids
        self.motor_ids = motor_ids
        self.sensor_id = sensor_id
        self.sensor_threshold = sensor_threshold

        # 子设备实例容器
        self.pumps: Dict[int, SyringePump] = {}
        self.motors: Dict[int, EmmMotor] = {}
        self.sensor: Optional[XKCSensor] = None

        # 轮询线程控制
        self._stop_event = threading.Event()
        self._poll_thread = None

        # 实时状态缓存
        self.status_cache = {
             "sensor_rssi": 0,
             "sensor_level": False,
             "connected": False
        }

        # 自动连接
        if self.port:
            self.connect()

    def connect(self) -> bool:
        if self._is_connected: return True
        try:
            self.logger.info(f"Connecting to {self.port} (timeout={self.timeout})...")
            self.mgr = TransportManager(self.port, baudrate=self.baudrate, timeout=self.timeout, logger=self.logger)

            # 初始化所有泵
            for pid in self.pump_ids:
                self.pumps[pid] = SyringePump(pid, self.mgr)

            # 初始化所有电机
            for mid in self.motor_ids:
                self.motors[mid] = EmmMotor(mid, self.mgr)

            # 初始化传感器
            self.sensor = XKCSensor(self.sensor_id, self.mgr, self.sensor_threshold)

            self._is_connected = True
            self.status_cache["connected"] = True

            # 启动轮询线程
            self._start_polling()
            return True
        except Exception as e:
            self.logger.error(f"Connection failed: {e}")
            self._is_connected = False
            self.status_cache["connected"] = False
            return False

    def disconnect(self):
        self._stop_event.set()
        if self._poll_thread:
            self._poll_thread.join(timeout=2.0)

        if self.mgr:
            self.mgr.close()

        self._is_connected = False
        self.status_cache["connected"] = False
        self.logger.info("Disconnected.")

    def _start_polling(self):
        """启动传感器轮询线程"""
        if self._poll_thread and self._poll_thread.is_alive():
            return

        self._stop_event.clear()
        self._poll_thread = threading.Thread(target=self._polling_loop, daemon=True, name="ChinwePoll")
        self._poll_thread.start()

    def _polling_loop(self):
        """轮询主循环"""
        self.logger.info("Sensor polling started.")
        error_count = 0
        while not self._stop_event.is_set():
            if not self._is_connected or not self.sensor:
                time.sleep(1)
                continue

            try:
                # 获取传感器数据
                data = self.sensor.read_level()
                if data:
                    self.status_cache["sensor_rssi"] = data['rssi']
                    self.status_cache["sensor_level"] = data['level']
                    error_count = 0
                else:
                    error_count += 1

                # 降低轮询频率防止总线拥塞
                time.sleep(0.2)

            except Exception as e:
                error_count += 1
                if error_count > 10: # 连续错误记录日志
                    # self.logger.error(f"Polling error: {e}")
                    error_count = 0
                time.sleep(1)

    # --- 对外暴露属性 (Properties) ---

    @property
    def sensor_level(self) -> bool:
        return self.status_cache["sensor_level"]

    @property
    def sensor_rssi(self) -> int:
        return self.status_cache["sensor_rssi"]

    @property
    def is_connected(self) -> bool:
        return self._is_connected

    # --- 对外功能指令 (Actions) ---

    def pump_initialize(self, pump_id: int, drain_port=0, output_port=0, speed=10):
        """指定泵初始化"""
        pump_id = int(pump_id)
        if pump_id in self.pumps:
            self.pumps[pump_id].initialize(drain_port, output_port, speed)
            self.pumps[pump_id].wait_until_idle()
            return True
        return False

    def pump_aspirate(self, pump_id: int, volume: int, valve_port: int):
        """
        泵吸液 (阻塞)
        :param valve_port: 阀门端口 (1-8)
        """
        pump_id = int(pump_id)
        valve_port = int(valve_port)
        if pump_id in self.pumps:
            pump = self.pumps[pump_id]
            # 1. 切换阀门
            pump.switch_valve(valve_port)
            pump.wait_until_idle()
            # 2. 吸液
            pump.aspirate(volume)
            pump.wait_until_idle()
            return True
        return False

    def pump_dispense(self, pump_id: int, volume: int, valve_port: int):
        """
        泵排液 (阻塞)
        :param valve_port: 阀门端口 (1-8)
        """
        pump_id = int(pump_id)
        valve_port = int(valve_port)
        if pump_id in self.pumps:
            pump = self.pumps[pump_id]
            # 1. 切换阀门
            pump.switch_valve(valve_port)
            pump.wait_until_idle()
            # 2. 排液
            pump.dispense(volume)
            pump.wait_until_idle()
            return True
        return False

    def pump_valve(self, pump_id: int, port: int):
        """泵切换阀门 (阻塞)"""
        pump_id = int(pump_id)
        port = int(port)
        if pump_id in self.pumps:
            pump = self.pumps[pump_id]
            pump.switch_valve(port)
            pump.wait_until_idle()
            return True
        return False

    def motor_run_continuous(self, motor_id: int, speed: int, direction: str = "顺时针"):
        """
        电机一直旋转 (速度模式)
        :param direction: "顺时针" or "逆时针"
        """
        motor_id = int(motor_id)
        if motor_id not in self.motors: return False

        dir_val = 0 if direction == "顺时针" else 1
        self.motors[motor_id].run_speed(speed, dir_val)
        return True

    def motor_rotate_quarter(self, motor_id: int, speed: int = 60, direction: str = "顺时针"):
        """
        电机旋转1/4圈 (阻塞)
        假设电机设置为 3200 脉冲/圈，1/4圈 = 800脉冲
        """
        motor_id = int(motor_id)
        if motor_id not in self.motors: return False

        pulses = 800
        dir_val = 0 if direction == "顺时针" else 1

        self.motors[motor_id].run_position(pulses, speed, dir_val, absolute=False)

        # 预估时间阻塞 (单位: 分钟 -> 秒)
        # Time(s) = revs / (RPM/60). revs = 0.25. time = 15 / RPM.
        estimated_time = 15.0 / max(1, speed)
        time.sleep(estimated_time + 0.5)

        return True

    def motor_stop(self, motor_id: int):
        """电机停止"""
        motor_id = int(motor_id)
        if motor_id in self.motors:
            self.motors[motor_id].stop()
            return True
        return False

    def wait_sensor_level(self, target_state: str = "有液", timeout: int = 30) -> bool:
        """
        等待传感器达到指定电平
        :param target_state: "有液" or "无液"
        """
        target_bool = True if target_state == "有液" else False

        self.logger.info(f"Wait sensor: {target_state} ({target_bool}), timeout: {timeout}")
        start = time.time()
        while time.time() - start < timeout:
            if self.sensor_level == target_bool:
                return True
            time.sleep(0.1)
        self.logger.warning("Wait sensor level timeout")
        return False

    def wait_time(self, duration: int) -> bool:
        """
        等待指定时间 (秒)
        :param duration: 秒
        """
        self.logger.info(f"Waiting for {duration} seconds...")
        time.sleep(duration)
        return True

    def execute_command_from_outer(self, command_dict: Dict[str, Any]) -> bool:
        """支持标准 JSON 指令调用"""
        return super().execute_command_from_outer(command_dict)

if __name__ == "__main__":
    # Test
    logging.basicConfig(level=logging.INFO)
    dev = ChinweDevice(port="192.168.31.201:8899")
    try:
        if dev.is_connected:
            print(f"Status: Level={dev.sensor_level}, RSSI={dev.sensor_rssi}")

            # Test pump 1
            # dev.pump_valve(1, 1)
            # dev.pump_move(1, 1000, "aspirate")

            # Test motor 4
             # dev.motor_run(4, 60, 0, 2)

            for _ in range(5):
                print(f"Level={dev.sensor_level}, RSSI={dev.sensor_rssi}")
                time.sleep(1)
    finally:
        dev.disconnect()
