import time
import threading
from typing import List, Optional
from iowp.special import SpecialModeFunction
from iowp.devices import IowDevice
from iowp import iowkit

class Monitor:
    def __init__(self):
        self._current_device = None
        self._lock = threading.Lock() # Das Herzstück für Thread-Sicherheit
        self.DEBUG = False

    def get_current_device(self):
        with self._lock:
            return self._current_device

    def begin_transmission(self, dev):
        """Versucht den Bus für ein Gerät zu reservieren."""
        trials = 128
        while trials > 0:
            if self._set_current_device(dev):
                if self.DEBUG: print(f"Monitor: {dev} hat den Bus reserviert.")
                return True
            
            time.sleep(0.1) # 100ms warten wie im Java-Code
            trials -= 1
            if self.DEBUG: print(f"Monitor: Bus belegt, {dev} wartet... ({trials})")
        
        raise RuntimeError(f"[{self._current_device}] Unable to begin a transmission for {dev}!")

    def end_transmission(self, dev):
        """Gibt den Bus wieder frei."""
        self._reset_current_device(dev)
        if self.DEBUG: print(f"Monitor: {dev} hat den Bus freigegeben.")

    def abort_transmission(self, dev):
        self.end_transmission(dev)

    def _set_current_device(self, dev):
        with self._lock:
            if self._current_device is None:
                self._current_device = dev
                return True
            return False

    def _reset_current_device(self, dev):
        with self._lock:
            if self._current_device is None:
                raise RuntimeError("No transmission pending!")
            if self._current_device != dev:
                raise RuntimeError(f"Owner is {self._current_device}, not {dev}!")
            self._current_device = None

    def __str__(self):
        return f"Monitor[Owner={self._current_device}]"


class I2C(SpecialModeFunction):
    I2C_SPEED_100KHZ = 0
    I2C_SPEED_400KHZ = 1
    I2C_SPEED_50KHZ = 2

    GENERATE_START = 0x80
    GENERATE_STOP = 0x40

    def __init__(self, speed=I2C_SPEED_100KHZ):
        super().__init__()  # Initialize SpecialModeFunction base class
        self.i2c_speed = speed
        self.devices = []
        self.monitor = Monitor()
        
        # Queues für Reports vom Background-Thread
        # Der Background-Thread in devices.py liest Pipe 1 und verteilt via report_received()
        import queue
        self._write_ack_queue = queue.Queue()
        self._read_queue = queue.Queue()
        

    def get_name(self) -> str:
        return "I2C"

    def get_special_mode_function_id(self) -> int:
        return self.SMF_I2C_ID

    def get_report_ids(self) -> List[int]:
        return [0x02, 0x03]

    def report_received(self, read_buffer: List[int]):
        # Called by background thread in devices.py when I2C report arrives
        if read_buffer[0] == 0x02:
            self._write_ack_queue.put(read_buffer)
        elif read_buffer[0] == 0x03:
            self._read_queue.put(read_buffer)

    def get_enable_report(self) -> List[int]:
        return [0x01, 0x01, self.i2c_speed]

    def get_disable_report(self) -> List[int]:
        return [0x01, 0x00]

    def get_iow_special_bits(self, product_id: int) -> List[int]:
        if product_id == IowDevice.IOW24ID:
            return [0x06]
        elif product_id == IowDevice.IOW56ID:
            return [0, 0xA0]
        else: # IOW40 / IOW28 / IOW100
            return [0xC0]

    def add_i2c_device(self, dev):
        if dev not in self.devices:
            self.devices.append(dev)
            dev.set_i2c(self)
        else:
            raise ValueError(f"Slave address {dev.dev_addr} already assigned!")

    def write_i2c(self, dev, data):
        """Entspricht deiner Java writeI2C Methode."""
        # IOW56 und IOW100 brauchen 64-Byte Reports mit bis zu 62 Bytes Daten
        if self.iow.product_id in [IowDevice.IOW56ID, IowDevice.IOW100ID]:
            self._write_i2c_64byte(dev, data)
            return

        # Standard I2C für IOW24/40/28: 8-Byte Reports, max 6 Bytes Daten
        self.monitor.begin_transmission(dev)
        
        try:
            length = len(data)
            remain = length
            pointer = 0
            first = True

            while remain > 0:
                flags = 0
                if remain > 6:
                    part_length = 6
                    if first:
                        flags |= self.GENERATE_START
                        first = False
                    flags |= part_length
                else:
                    part_length = remain
                    if first:
                        flags |= self.GENERATE_START
                    flags |= (self.GENERATE_STOP | part_length)

                # Report bauen: ID=2, Flags, Data... (8 Bytes total)
                report = [0x02, flags] + [0] * 6
                for i in range(part_length):
                    report[i + 2] = data[pointer]
                    pointer += 1
                
                remain -= part_length
                
                # Schreiben und auf ACK warten
                self.iow.write_report(1, report)
                
                # Warte auf ACK vom Background-Thread via Queue
                try:
                    answer = self._write_ack_queue.get(timeout=1.0)
                except:
                    raise Exception("I2C Write Error: Timeout waiting for ACK")
                
                if not answer or (answer[1] & 0x80):
                    raise Exception(f"I2C Write Error: NACK or Error Bit set!")
                
                if (answer[1] & 0x07) != part_length:
                    raise Exception("Not all bytes acknowledged!")

            self.monitor.end_transmission(dev)

        except Exception as e:
            self.monitor.abort_transmission(dev)
            raise e

    def _write_i2c_64byte(self, dev, data):
        """I2C Write für IOW56/IOW100 mit 64-Byte Reports und max 62 Bytes Daten."""
        self.monitor.begin_transmission(dev)
        
        try:
            length = len(data)
            remain = length
            pointer = 0
            first = True

            while remain > 0:
                flags = 0
                if remain > 62:
                    part_length = 62
                    if first:
                        flags |= self.GENERATE_START
                        first = False
                    flags |= part_length
                else:
                    part_length = remain
                    if first:
                        flags |= self.GENERATE_START
                    flags |= (self.GENERATE_STOP | part_length)

                # Report bauen: 64 Bytes total für IOW56/IOW100
                report = [0x02, flags] + [0] * 62
                for i in range(part_length):
                    report[i + 2] = data[pointer]
                    pointer += 1
                
                remain -= part_length
                
                # Schreiben und auf ACK warten
                self.iow.write_report(1, report)
                
                # Warte auf ACK vom Background-Thread via Queue
                try:
                    answer = self._write_ack_queue.get(timeout=1.0)
                except:
                    raise Exception("I2C Write Error: Timeout waiting for ACK")
                
                if not answer or (answer[1] & 0x80):
                    raise Exception(f"I2C Write Error: NACK or Error Bit set!")
                
                # IOW56/IOW100 nutzt 7 Bits für die Länge (0x7F statt 0x07)
                if (answer[1] & 0x7F) != part_length:
                    raise Exception("Not all bytes acknowledged!")

            self.monitor.end_transmission(dev)

        except Exception as e:
            self.monitor.abort_transmission(dev)
            raise e


    def read_i2c(self, dev, data_to_write, expected_length):
        """Entspricht deiner Java readI2C Methode."""
        self.monitor.begin_transmission(dev)
        
        try:
            # Report ID 3 für Read
            # wbuf = { 3, expected, data[0], data[1] ... }
            report = [0x03, expected_length] + [0] * 6
            for i in range(len(data_to_write)):
                report[i + 2] = data_to_write[i]
            
            self.iow.write_report(1, report)
            
            result = []
            remain = expected_length
            
            while remain > 0:
                # Warte auf Antwort vom Background-Thread via Queue
                try:
                    answer = self._read_queue.get(timeout=1.0)
                except:
                    raise Exception("I2C Read Error: Timeout waiting for data")
                
                if not answer or (answer[1] & 0x80):
                    raise Exception("I2C Read Error: Error Bit set!")
                
                count = answer[1] & 0x07
                for i in range(count):
                    result.append(answer[i + 2])
                
                remain -= count
                
            self.monitor.end_transmission(dev)
            return result

        except Exception as e:
            self.monitor.abort_transmission(dev)
            raise e