import serial
from serial.tools import list_ports
import time

ADAPTER_NEEDLES = ('CH340', 'CH341', 'CP210', 'Silicon Labs')

class ch340:
    RELEASE, BTN_L, BTN_R, BTN_M, BTN_X1, BTN_X2 = 0x00, 0x01, 0x02, 0x04, 0x08, 0x10

    def __init__(self, port=None):
        self.port = port
        self.ch340 = None
    
    @staticmethod
    def findPort():
        for info in list_ports.comports():
            hay = f'{info.description} {info.manufacturer or ""}'
            if any(n.lower() in hay.lower() for n in ADAPTER_NEEDLES):
                return info.device
        return None

    @staticmethod
    def normalizePort(port):
        port = str(port).strip().upper()

        if not port or not port[-1].isdigit():
            return False
        
        number = port[3:] if port.startswith('COM') else port
        if not number.isdigit():
            return False
        
        return f'COM{number}'

    def isConnected(self):
        return self.ch340 is not None and self.ch340.is_open

    def open(self):
        if self.port is None:
            self.port = self.findPort()
            if not self.port:
                print('CH340 not found. Do you have the driver installed?')
                return False
            
        self.port = self.normalizePort(self.port)

        if not self.port:
            print('Invalid port format. Correct examples: "COM3", "3"')
            return False

        if self.ch340 is not None:
            self.close()

        try:
            self.ch340 = serial.Serial(port=self.port, baudrate=921600, timeout=0)
            return True
        except serial.SerialException as e:
            print(f'Failed to connect to CH340 on port: {self.port}. Full error: {e}')
            return False

    def close(self):
        if self.ch340:
            self.ch340.close()
            self.ch340 = None
        return True

    def move(self, x, y):
        if not self.isConnected(): return
        if (x == 0 and y == 0): return

        x = max(-32767, min(32767, x))
        y = max(-32767, min(32767, y))

        buf = bytearray(5)
        buf[0] = 0x01
        buf[1] = x & 0xFF
        buf[2] = (x >> 8) & 0xFF
        buf[3] = y & 0xFF
        buf[4] = (y >> 8) & 0xFF

        self.ch340.write(buf)

    def setButtons(self, mask):
        if not self.isConnected(): return
        buf = bytearray(2)
        buf[0] = 0x02
        buf[1] = mask & 0x1F
        self.ch340.write(buf)

    def click(self, mask, down_ms=30):
        self.setButtons(mask)
        time.sleep(down_ms / 1000)
        self.setButtons(0x00)