import hid
import time
import typing
import struct
from enum import IntEnum

DEFAULT_HID_TIMEOUT = 10
BIGSCREEN_VID = 0x35BD
BEYOND_PID = 0x0101

class Hid_Message(IntEnum):
    FAN_SPEED = ord('F')
    REPORT_RATE = ord('R')
    DATA_MESSAGE = ord('#')
    SUCCESS = ord('$')
    ERROR = ord('E')

def find_beyond_device():
    devices = hid.enumerate()
    beyond_devices = [d for d in devices if d['vendor_id'] == BIGSCREEN_VID and d['product_id'] == BEYOND_PID]
    if beyond_devices:
        return beyond_devices[0]['path']
    return None

def wait_for_response(beyond: hid.device, message_types: list[int], timeout_ms: int = 100) -> bytes:
    start_time = time.monotonic_ns()
    
    while( (start_time + (timeout_ms*1000000)) > time.monotonic_ns()):
        bytesout = beyond.read(65)
        if(len(bytesout) > 0):
            if(bytesout[0] in message_types):
                return bytes(bytesout)
    
    return b''

def set_fan_speed(fan_speed: int) -> bool:
    if(fan_speed > 100):
        fan_speed = 100
    if(fan_speed < 0):
        fan_speed = 0

    device_path = find_beyond_device()
    if not device_path:
        return False
    beyond = hid.device()
    beyond.open_path(device_path)
    beyond.send_feature_report(bytes([0, Hid_Message.FAN_SPEED, fan_speed]))
    hid_reply = wait_for_response(beyond, [Hid_Message.SUCCESS, Hid_Message.ERROR])
    beyond.close()

    if(len(hid_reply) > 0):
        if(hid_reply[0] == Hid_Message.SUCCESS):
            return True
    return False

def get_fan_speed(timeout_ms: int = 100) -> int:
    fan_speed = -1
    device_path = find_beyond_device()
    if not device_path:
        return fan_speed
    beyond = hid.device()
    beyond.open_path(device_path)
    # first, speed up the report rate to 50ms
    beyond.send_feature_report(bytes([0, Hid_Message.REPORT_RATE, 0, 50]))
    hid_reply = wait_for_response(beyond, [Hid_Message.SUCCESS], timeout_ms)
    hid_reply = wait_for_response(beyond, [Hid_Message.DATA_MESSAGE], timeout_ms)
    beyond.close()

    if(len(hid_reply) > 0):
        if(hid_reply[0] == Hid_Message.DATA_MESSAGE):
            # extract fan speed from bytes 2 and 3 (MSB first)
            fan_speed = struct.unpack('>H',bytes(hid_reply[2:4]))[0]

    return fan_speed

# test code
if __name__ == "__main__":
    print(get_fan_speed())
    print(set_fan_speed(50))
    print(get_fan_speed())
    print(set_fan_speed(100))
    print(get_fan_speed())
    print(set_fan_speed(0))
    print(get_fan_speed())