import queue

import can
import usb
import time
import struct
import threading
from dataclasses import dataclass
from typing import Dict, List, Tuple, Optional
from queue import Queue

from can_msg_builder import *
from can_msg_parser import *


class SenderThread(threading.Thread):
    """
    每个“逻辑channel(共4个)”一个发送线程：
    - 等主线程 tick_event
    - barrier 同步放行
    - 加 bus_lock 后 send（同一个设备的两个channel共享一个bus对象，必须加锁）
    """
    def __init__(
        self,
        name: str,
        bus: can.BusABC,
        bus_lock: threading.Lock,
        channel_index: int,         # 0 or 1
        tick_event: threading.Event,
        barrier: threading.Barrier,
        stop_event: threading.Event,
        idle_hz: float = 500.0,
        idle_timeout_s: float = 0.01,  # 多久没控制算“空闲”
    ):
        super().__init__(name=name)
        self.bus = bus
        self.bus_lock = bus_lock
        self.channel_index = int(channel_index)
        self.tick_event = tick_event
        self.barrier = barrier
        self.stop_event = stop_event
        self.queue = Queue()
        self.idle_period = 1.0 / float(idle_hz)
        self.idle_timeout_s = float(idle_timeout_s)
        self._last_cmd_ts = time.perf_counter()
        self._next_idle_ts = time.perf_counter() + self.idle_period

    def turn_on(self):
        pass

    def turn_off(self):
        pass

    def stop(self):
        pass

    def clear_error(self):
        pass

    def set_zero(self):
        pass

    def set_torq(self, torque: List[float]):
        pass

    def set_vel(self, vel: List[float]):
        pass

    def set_pos(self, pos: List[float]):
        pass

    def _mark_cmd(self):
        self._last_cmd_ts = time.perf_counter()

    def _drain_msgs(self):
        msgs = []
        while True:
            try:
                msgs.append(self.queue.get_nowait())
            except queue.Empty:
                break
        return msgs

    def _send_msgs(self, msgs):
        for msg in msgs:
            msg.channel = self.channel_index
            with self.bus_lock:
                self.bus.send(msg)

    def _thread_idle(self):
        pass

    def run(self):
        while not self.stop_event.is_set():
            now = time.perf_counter()

            # 1) 优先处理控制 tick（有控制指令）
            if self.tick_event.is_set():
                self._mark_cmd()
                try:
                    self.barrier.wait(timeout=0.2)  # 0.05 太激进，建议放宽
                except threading.BrokenBarrierError:
                    # barrier broken 时也别死循环，稍退避
                    time.sleep(0.001)
                    continue

                try:
                    msgs = self._drain_msgs()
                    if msgs:
                        with self.bus_lock:
                            for msg in msgs:
                                msg.channel = self.channel_index
                                self.bus.send(msg)
                except usb.core.USBTimeoutError:
                    # USB 写超时：退避，避免连环打爆
                    time.sleep(0.005)
                except can.CanError:
                    pass

                # 等主线程清 tick_event，避免同 tick 重发
                while self.tick_event.is_set() and not self.stop_event.is_set():
                    time.sleep(0)

                # 控制刚发完，顺手把 idle 定时器往后推，避免“控制后马上 query”
                self._next_idle_ts = time.perf_counter() + self.idle_period
                continue

            # 2) 没控制 tick：进入空闲轮询（满足空闲时间阈值）
            if (now - self._last_cmd_ts) >= self.idle_timeout_s:
                if now >= self._next_idle_ts:
                    try:
                        self._thread_idle()
                    except usb.core.USBTimeoutError:
                        time.sleep(0.005)
                    except can.CanError:
                        pass
                    # 维持均匀节拍
                    self._next_idle_ts += self.idle_period
                else:
                    # 睡到下一次 idle
                    time.sleep(min(0.001, self._next_idle_ts - now))
            else:
                # 刚停止控制不久：短睡，等进入 idle
                time.sleep(0.001)



class BroadcastSenderThread(SenderThread):
    def __init__(
            self,
            name: str,
            bus: can.BusABC,
            bus_lock: threading.Lock,
            channel_index: int,  # 0 or 1
            tick_event: threading.Event,
            barrier: threading.Barrier,
            stop_event: threading.Event,
    ):
        super().__init__(name, bus, bus_lock, channel_index, tick_event, barrier, stop_event)

    def turn_on(self):
        self.queue.put(build_turn_on_broadcast())

    def turn_off(self):
        self.queue.put(build_turn_off_broadcast())

    def stop(self):
        self.queue.put(build_stop_broadcast())

    def clear_error(self):
        self.queue.put(build_clear_error_broadcast())

    def set_zero(self):
        pass

    def set_torq(self, torques):
        assert len(torques) <= 4
        self.queue.put(build_torque_broadcast(torques))

    def set_vel(self, vel: List[float]):
        assert len(vel) <= 4
        self.queue.put(build_velocity_broadcast(vel))

    def set_pos(self, pos):
        assert len(pos) <= 4
        self.queue.put(build_position_broadcast(pos))

    def _thread_idle(self):
        msg = build_get_state_broadcast()
        msg.channel = self.channel_index
        try:
            with self.bus_lock:
                self.bus.send(msg)
        except can.CanError:
            pass

class SingleSenderThread(SenderThread):
    def __init__(
            self,
            name: str,
            bus: can.BusABC,
            bus_lock: threading.Lock,
            channel_index: int,  # 0 or 1
            tick_event: threading.Event,
            barrier: threading.Barrier,
            stop_event: threading.Event,
            ms_index: List[int]
    ):
        super().__init__(name, bus, bus_lock, channel_index, tick_event, barrier, stop_event)
        self.ms_index = ms_index

    def turn_on(self):
        for i in range(1, 5):
            self.queue.put(build_turn_on_single(i))

    def turn_off(self):
        for i in range(1, 5):
            self.queue.put(build_turn_off_single(i))

    def stop(self):
        for i in range(1, 5):
            self.queue.put(build_stop_single(i))

    def clear_error(self):
        for i in range(1, 5):
            self.queue.put(build_clear_error_single(i))

    def set_zero(self):
        pass

    def set_torq(self, torques):
        for i in range(1, 5):
            if i in self.ms_index:
                self.queue.put(build_torque_single(i, torques[i-1], True))
            else:
                self.queue.put(build_torque_single(i, torques[i-1], False))

    def set_vel(self, vel: List[float]):
        for i in range(1, 5):
            self.queue.put(build_velocity_single(i, vel[i - 1]))

    def set_pos(self, pos):
        for i in range(1, 5):
            self.queue.put(build_set_multiturn_position_single(i, pos[i - 1]))

    def _thread_idle(self):
        for i in range(1, 5):
            msg = build_get_multiturn_position_single(i)
            msg.channel = self.channel_index
            try:
                with self.bus_lock:
                    self.bus.send(msg)
            except can.CanError:
                pass
            except usb.core.USBTimeoutError:
                time.sleep(0.001)



@dataclass
class RxFrame:
    t: float = 0.0
    arb: int = 0
    data: bytes = b""


@dataclass
class MotorState:
    stamp: float = 0.0
    temp: float = 0.0
    single_turn_rad: float = 0.0
    multi_turn_rad: float = 0.0
    vel: float = 0.0
    power_raw: float = 0.0


class RxCache4Ch(can.Listener):
    """
    一个 Notifier 监听一个 “设备Bus(包含两个channel)”，按 msg.channel 分流到 ch0/ch1 的缓存。
    """
    def __init__(self, joint_map: Dict[int, List[str]]) -> None:
        super().__init__()
        self._lock = threading.Lock()
        self.joint_state = {}
        self.ch_mid_idx = {}
        for ch, joint_names in joint_map.items():
            for i, joint_name in enumerate(joint_names):
                self.ch_mid_idx[(ch, i+1)] = joint_name
                self.joint_state[joint_name] = MotorState()

    def on_message_received(self, msg: can.Message):
        # python-can Message.channel：对 canalystii 多通道会填 0/1
        ch = getattr(msg, "channel", None)
        if ch is None:
            # 没有 channel 信息就无法分流，直接忽略或当作 ch0
            ch = 0
        try:
            ch = int(ch)
        except Exception:
            ch = 0

        arb = msg.arbitration_id
        if arb < 0x141 or arb > 0x160:
            return
        mid = arb - 0x140

        joint_name = self.ch_mid_idx[(ch, mid)]
        if joint_name is None:
            return

        if msg.data[0] in [0xA0, 0xA1, 0x9C]:
            res = parse_single_turn_state(msg.data)
            # print(res)
            with self._lock:
                self.joint_state[joint_name].stamp = time.time()
                self.joint_state[joint_name].temp = res["temperature_C"]
                self.joint_state[joint_name].power_raw = res["iq_or_power_raw"]
                self.joint_state[joint_name].vel = res["speed_dps"]
                self.joint_state[joint_name].single_turn_rad = res["rad"]
        elif msg.data[0] in [0x92]:
            res = parse_multi_turn_state(msg.data)
            with self._lock:
                self.joint_state[joint_name].multi_turn_rad =res["rad"]
        else:
            print(msg.data[0])

    def snapshot(self):
        with self._lock:
            return self.joint_state
    
    