from iowp import iowkit
from typing import List, Optional
from iowp.devices import Iow24, Iow28, Iow40, Iow56, Iow100, IowDevice

class IowFactory:
    _instance = None

    @classmethod
    def get_instance(cls):
        return cls()

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(IowFactory, cls).__new__(cls, *args, **kwargs)
        return cls._instance

    def __init__(self):
        # Only initialize once as it's a singleton
        if not hasattr(self, '_devices'):
            self._devices: List[IowDevice] = []
            self.open_all_devices()

    def open_all_devices(self) -> int:
        """
        Opens all available devices and populates the internal list.
        """
        # iowkit.IowKitOpenDevice() opens all and returns the FIRST handle.
        # It now internally closes old handles in iowkit.py
        first_handle = iowkit.IowKitOpenDevice()
        # print(f"DEBUG: IowKitOpenDevice returned: {first_handle}")
        
        # Reset internal device list
        self._devices = []

        if not first_handle:
            return 0
            
        num_devs = iowkit.IowKitGetNumDevs()
        # print(f"DEBUG: IowKitGetNumDevs returned: {num_devs}")
        for i in range(1, num_devs + 1):
            handle = iowkit.IowKitGetDeviceHandle(i)
            if not handle:
                continue
            pid = iowkit.IowKitGetProductId(handle)
            
            new_dev = None
            if pid == IowDevice.IOW24ID:
                new_dev = Iow24(handle)
            elif pid == IowDevice.IOW28ID or pid == IowDevice.IOW28LID:
                new_dev = Iow28(handle)
            elif pid == IowDevice.IOW40ID:
                new_dev = Iow40(handle)
            elif pid == IowDevice.IOW56ID:
                new_dev = Iow56(handle)
            elif pid == IowDevice.IOW100ID:
                new_dev = Iow100(handle)
            else:
                new_dev = IowDevice(handle)
            
            if new_dev:
                # print(f"DEBUG: Found {new_dev.name} (Serial={new_dev.serial})")
                self._devices.append(new_dev)
                
        return len(self._devices)

    def close_all_devices(self):
        for dev in self._devices:
            dev.close()
        self._devices = []
        
    def get_devices(self) -> List[IowDevice]:
        return self._devices

    def get_iow_device(self, num: int = 0) -> IowDevice:
        """Returns the first plugged IO-Warrior device or by index (0-based)."""
        if 0 <= num < len(self._devices):
            return self._devices[num]
        raise ValueError(f"Cannot find IO-Warrior device with num {num}")

    def get_iow24_device(self, serial: Optional[str] = None) -> Iow24:
        """Returns the first IOW24 device, or one with a matching serial."""
        for dev in self._devices:
            if isinstance(dev, Iow24):
                if serial is None or dev.serial == serial:
                    return dev
        raise ValueError("No IOW24 device found")

    def get_iow28_device(self, serial: Optional[str] = None) -> Iow28:
        """
        Sucht ein IOW28 Gerät in der aktuellen Instanz.

        Args:
            serial: Die Seriennummer des gesuchten Geräts. 
                    Falls None, wird das erste gefundene Gerät zurückgegeben.

        Returns:
            Das gefundene Iow28 Objekt.

        Raises:
            ValueError: Wenn kein passendes Gerät gefunden wurde.
        """
        for dev in self._devices:
            if isinstance(dev, Iow28):
                if serial is None or dev.serial == serial:
                    return dev
        raise ValueError("No IOW28 device found")

    def get_iow40_device(self, serial: Optional[str] = None) -> Iow40:
        """Returns the first IOW40 device, or one with a matching serial."""
        for dev in self._devices:
            if isinstance(dev, Iow40):
                if serial is None or dev.serial == serial:
                    return dev
        raise ValueError("No IOW40 device found")

    def get_iow56_device(self, serial: Optional[str] = None) -> Iow56:
        """Returns the first IOW56 device, or one with a matching serial."""
        for dev in self._devices:
            if isinstance(dev, Iow56):
                if serial is None or dev.serial == serial:
                    return dev
        raise ValueError("No IOW56 device found")

    def get_iow100_device(self, serial: Optional[str] = None) -> Iow100:
        """Returns the first IOW100 device, or one with a matching serial."""
        for dev in self._devices:
            if isinstance(dev, Iow100):
                if serial is None or dev.serial == serial:
                    return dev
        raise ValueError("No IOW100 device found")

    def __str__(self):
        """Equivalent to Java's toString() method."""
        msg = [f"Number of plugged IO-Warrior device(s): {len(self._devices)}"]
        for i, dev in enumerate(self._devices):
            msg.append(f"Device{i}: {str(dev)}")
        return "\n".join(msg) + "\n"