from iowp.port import IowPort
from iowp import iowkit
import threading
import queue
from typing import List, Optional, Dict
from iowp.special import SpecialModeFunction

class IowDevice:
    IOW40ID  = 0x1500
    IOW24ID  = 0x1501
    IOW56ID  = 0x1503
    IOW28ID  = 0x1504
    IOW28LID = 0x1505 # Legacy support variant
    IOW100ID = 0x1506

    def __init__(self, handle):
        self._handle = handle
        self._id = iowkit.IowKitGetProductId(handle)
        self._rev = iowkit.IowKitGetRevision(handle)
        
        # Get Serial
        import ctypes
        serial_buf = ctypes.create_unicode_buffer(20)
        if iowkit.IowKitGetSerialNumber(handle, serial_buf):
            self._serial = serial_buf.value
        else:
            self._serial = "Unknown"
            
        self.ports = []
        self._port_count = 0
        self._smf_report_length = 8 # Default
        if self._id in [self.IOW56ID, self.IOW28ID, self.IOW28LID, self.IOW100ID]:
            self._smf_report_length = 64
            
        self._life = True
        self._is_autonomous = False
        self._special_mode = 0
        self._smf_implementations: List[SpecialModeFunction] = []
        self._current_pin_status_queue = queue.Queue()
        self._listeners = []
        self._last_io_value: Optional[int] = None

        # Threads
        self._stop_event = threading.Event()
        self._sm_thread = threading.Thread(target=self._special_mode_read_loop, daemon=True, name=f"SMRead-{self._serial}")
        self._pin_thread = threading.Thread(target=self._pin_read_loop, daemon=True, name=f"PinRead-{self._serial}")
        
        self._sm_thread.start()

    @property
    def handle(self):
        return self._handle

    @property
    def serial(self):
        return self._serial

    @property
    def product_id(self):
        return self._id

    @property
    def rev(self):
        return self._rev

    @property
    def name(self) -> str:
        return "Unknown"

    def set_write_timeout(self, timeout: int):
        iowkit.IowKitSetWriteTimeout(self._handle, timeout)

    def get_port(self, index: int) -> IowPort:
        if 0 <= index < len(self.ports):
            return self.ports[index]
        raise IndexError(f"Port {index} out of range")

    def set_port(self, index: int, data: int):
        self.get_port(index).data = data

    def set_direction(self, index: int, direction: int):
        self.get_port(index).direction = direction

    def get_direction(self, index: int) -> int:
        return self.get_port(index).direction

    def set_bit(self, port_idx: int, bit: int):
        self.get_port(port_idx).set_bit(bit)

    def clear_bit(self, port_idx: int, bit: int):
        self.get_port(port_idx).clear_bit(bit)

    def is_bit_set(self, port_idx: int, bit: int) -> bool:
        return self.get_port(port_idx).is_bit_set(bit)

    def close(self):
        self._stop_event.set()
        if self._handle:
            iowkit.IowKitCloseDevice(self._handle)
            self._handle = None
        self._life = False

    def write_report(self, pipe: int, report: List[int]) -> int:
        """Writes a report to the specified pipe, padding if necessary."""
        if not self._handle:
            return 0
            
        target_len = self._smf_report_length if pipe == 1 else (self._port_count + 1)
        if len(report) < target_len:
            report = list(report) + [0] * (target_len - len(report))
        elif len(report) > target_len:
            report = list(report)[:target_len]
            
        return iowkit.IowKitWrite(self._handle, pipe, report, len(report))

    def add_special_mode_function_impl(self, impl: SpecialModeFunction):
        if impl not in self._smf_implementations:
            # Set iow reference
            impl.set_iow_device(self)

            # Set related special mode bit
            self._special_mode = self._special_mode | impl.get_special_mode_function_id()

            # Enable it
            enable_report = impl.get_enable_report() 
            if enable_report:
                self.write_report(1, enable_report)
                
            # Add implemenation to list
            self._smf_implementations.append(impl)
            
            # Update port special bits
            bits = impl.get_iow_special_bits(self._id)
            for i, mask in enumerate(bits):
                if i < len(self.ports):
                    self.ports[i].set_special(self.ports[i].get_special() | mask)


    def remove_special_mode_function_impl(self, impl: SpecialModeFunction):
        if impl in self._smf_implementations:
            # Reset related special mode bit 
            self._special_mode = self._special_mode & ~impl.get_special_mode_function_id()
            # Disable special mode function
            disable_report = impl.get_disable_report()
            if disable_report:
                self.write_report(1, disable_report)
            
            # Reset special bits in bitmask
            bits = impl.get_iow_special_bits(self._id)
            for i, mask in enumerate(bits):
                if i < len(self.ports):
                    self.ports[i].set_special(self.ports[i].get_special() & ~mask)

            # Delete iow reference
            impl.set_iow_device(None)
            # Remove implemenation from list
            self._smf_implementations.remove(impl)

    def add_iow_change_listener(self, listener):
        if listener not in self._listeners:
            self._listeners.append(listener)

    def _notify_iow_listeners(self, value):
        for listener in self._listeners:
            import threading
            t = threading.Thread(target=self._iow_notifier_run, args=(listener, value), daemon=True)
            t.start()

    def _iow_notifier_run(self, listener, value):
        try:
            if hasattr(listener, 'iow_changed'):
                listener.iow_changed(value)
            elif callable(listener):
                listener(value)
        except Exception as e:
            print(f"Error in IowChangeListener ({self._serial}): {e}")

    def set_autonomous_mode(self, status: bool):
        if status:
            if not self._is_autonomous:
                self._is_autonomous = True
                # Start pin thread if not alive or recreate it if it was stopped
                if not self._pin_thread.is_alive():
                    # We might need to recreate the thread object if it finished
                    try:
                        self._pin_thread.start()
                    except RuntimeError:
                        self._pin_thread = threading.Thread(target=self._pin_read_loop, daemon=True, name=f"PinRead-{self._serial}")
                        self._pin_thread.start()
        else:
            self._is_autonomous = False

    def _special_mode_read_loop(self):
        """Background thread reading from Pipe 1 (Special Mode)."""
        while not self._stop_event.is_set():
            try:
                # Read from Pipe 1
                buf = [0] * self._smf_report_length
                res = iowkit.IowKitRead(self._handle, 1, buf, self._smf_report_length)
                
                if res > 0:
                    report_id = int(buf[0])
                    # Distribute to SMFs
                    handled = False
                    # Create a copy as a list of ints to satisfy the linter
                    rep_data: List[int] = [int(x) for x in buf[:res]]
                    for smf in self._smf_implementations:
                        if smf.match_report_id(report_id):
                            smf.report_received(rep_data)
                            handled = True
                            break
                    
                    if not handled and report_id == 0xFF:
                        # Special Pin Status Report (CPS)
                        self._current_pin_status_queue.put(rep_data)
                elif res == 0:
                    # Timeout or device gone
                    pass
            except Exception:
                if not self._stop_event.is_set():
                    # Possibly actual error
                    pass
                break

    def _pin_read_loop(self):
        """Background thread reading from Pipe 0 (IO Pins) if autonomous mode is on."""
        while not self._stop_event.is_set():
            if self._is_autonomous:
                try:
                    # Blocks until data changes or timeout
                    value = self.read_io_ports()
                    if value is not None and value != self._last_io_value:
                        # Only notify AND print if value actually changed
                        # print(f"DEBUG-TRC: PinReadThread received 0x{value:08X} - Notifying listeners")
                        self._last_io_value = value
                        self._notify_iow_listeners(value)
                except Exception as e:
                    print(f"Error in PinRead loop ({self._serial}): {e}")
                    if self._stop_event.wait(1.0): # Wait before retry on error
                        break
            else:
                self._stop_event.wait(0.1)

    def write_io_ports(self) -> int:
        """
        Writes the current port state to the device.
        Must be implemented by subclasses.
        """
        raise NotImplementedError

    def read_io_ports(self) -> Optional[int]:
        """
        Reads from port 0 (IO Pipe).
        Returns a long (int) representing all port states, or None on error.
        """
        # Buffer size: Report ID + data bytes. 
        # On some OS (Mac), Report ID 0 might be stripped by hidapi.
        size = self._port_count + 1 
            
        buf = bytearray(64) 
        res = iowkit.IowKitRead(self._handle, iowkit.IOW_PIPE_IO_PINS, buf, size)
        
        if res > 0:
            # Determine if we have a ReportID at the start.
            offset: int = 0
            if res == self._port_count:
                offset = 0
            else:
                offset = 1
                
            ret_value: int = 0
            for i in range(self._port_count):
                idx: int = i + offset
                if idx < res:
                    val: int = int(buf[idx]) & 0xFF
                    self.ports[i].set_data_from_read(val)
                    ret_value = ret_value | (val << (8 * i))
            
            return ret_value
        return None

    @property
    def sm_mode(self) -> str:
        """Returns a string representing the active special modes."""
        names = []
        for smf in self._smf_implementations:
            names.append(smf.get_name())
        return " ".join(names)

    def __repr__(self):
        return self.__str__()

class Iow24(IowDevice):
    def __init__(self, handle):
        super().__init__(handle)
        self._port_count = 2
        # Port 0: 8 bit, Port 1: 8 bit
        self.ports.append(IowPort(0))
        self.ports.append(IowPort(1))
        
    @property
    def name(self) -> str:
        return "IOW24"

    def __str__(self):
        res = [f"{self.name}:Handle[{id(self._handle)}],Id[{self._id}],Rev[{hex(self._rev)}],Serial[{self._serial}],Listener[{len(self._listeners)}],SpecialMode: {self.sm_mode}"]
        res.append(f"\t{str(self.ports[0])}")
        res.append(f"\t{str(self.ports[1])}")
        return "\n".join(res) + "\n"

    def write_io_ports(self) -> int:
        # Report ID 0, Port 0, Port 1 (3 Bytes)
        report = [0x00, 0x00, 0x00]
        report[1] = self.ports[0].get_data_to_write()
        report[2] = self.ports[1].get_data_to_write()
        return iowkit.IowKitWrite(self._handle, iowkit.IOW_PIPE_IO_PINS, report, 3)

class Iow40(IowDevice):
    def __init__(self, handle):
        super().__init__(handle)
        self._port_count = 4
        for i in range(4):
            self.ports.append(IowPort(i))
            
    @property
    def name(self) -> str:
        return "IOW40"

    def __str__(self):
        res = [f"{self.name}:Handle[{id(self._handle)}],Id[{self._id}],Rev[{hex(self._rev)}],Serial[{self._serial}],Listener[{len(self._listeners)}],SpecialMode: {self.sm_mode}"]
        for i in range(4):
            res.append(f"\t{str(self.ports[i])}")
        return "\n".join(res) + "\n"

    def write_io_ports(self) -> int:
        # Report ID 0 + 4 bytes
        report = [0x00] * 5
        for i in range(4):
            report[i+1] = self.ports[i].get_data_to_write()
        return iowkit.IowKitWrite(self._handle, iowkit.IOW_PIPE_IO_PINS, report, 5)

class Iow28(IowDevice):
    def __init__(self, handle):
        super().__init__(handle)
        self._port_count = 4
        for i in range(4):
            self.ports.append(IowPort(i))
            
        # Port 2 has only 2 bits (0,1)? (Check Java source: existence mask)
        self.ports[2].set_existence(0x03) 
        # Port 3 has only 1 bit (7)? (Check Java source: existence mask)
        self.ports[3].set_existence(0x80)

    @property
    def name(self) -> str:
        return "IOW28"

    def __str__(self):
        res = [f"{self.name}:Handle[{id(self._handle)}],Id[{self._id}],Rev[{hex(self._rev)}],Serial[{self._serial}],Listener[{len(self._listeners)}],SpecialMode: {self.sm_mode}"]
        for i in range(4):
            res.append(f"\t{str(self.ports[i])}")
        return "\n".join(res) + "\n"

    def write_io_ports(self) -> int:
        # Report ID 0 + 4 bytes
        report = [0x00] * 5
        for i in range(4):
            report[i+1] = self.ports[i].get_data_to_write()
        return iowkit.IowKitWrite(self._handle, iowkit.IOW_PIPE_IO_PINS, report, 5)

class Iow56(IowDevice):
    def __init__(self, handle):
        super().__init__(handle)
        self._port_count = 7
        for i in range(7):
            self.ports.append(IowPort(i))
            
    @property
    def name(self) -> str:
        return "IOW56"

    def __str__(self):
        res = [f"{self.name}:Handle[{id(self._handle)}],Id[{self._id}],Rev[{hex(self._rev)}],Serial[{self._serial}],Listener[{len(self._listeners)}],SpecialMode: {self.sm_mode}"]
        for i in range(7):
            res.append(f"\t{str(self.ports[i])}")
        return "\n".join(res) + "\n"

    def write_io_ports(self) -> int:
        # Report ID 0 + 7 bytes
        report = [0x00] * 8
        for i in range(7):
            report[i+1] = self.ports[i].get_data_to_write()
        return iowkit.IowKitWrite(self._handle, iowkit.IOW_PIPE_IO_PINS, report, 8)

class Iow100(IowDevice):
    def __init__(self, handle):
        super().__init__(handle)
        self._port_count = 12
        for i in range(12):
            self.ports.append(IowPort(i))
            
        # Existence masks from Iow100.java
        self.ports[1].set_existence(0x07)
        self.ports[10].set_existence(0x4f)
        self.ports[11].set_existence(0x06)

    @property
    def name(self) -> str:
        return "IOW100"

    def __str__(self):
        res = [f"{self.name}:Handle[{id(self._handle)}],Id[{self._id}],Rev[{hex(self._rev)}],Serial[{self._serial}],Listener[{len(self._listeners)}],SpecialMode: {self.sm_mode}"]
        for i in range(12):
            res.append(f"\t{str(self.ports[i])}")
        return "\n".join(res) + "\n"

    def write_io_ports(self) -> int:
        # Report ID 0 + 12 bytes
        report = [0x00] * 13
        for i in range(12):
            report[i+1] = self.ports[i].get_data_to_write()
        return iowkit.IowKitWrite(self._handle, iowkit.IOW_PIPE_IO_PINS, report, 13)
