'''
hid_lib.py

Helper functions for connecting to the Beyond over USB HID

'''
import time
import enum
import struct
from typing import Callable
import hid # NOTE: Uses hidapi, not hid. https://pypi.org/project/hidapi/

BIGSCREEN_VID = 0x35BD
BEYOND_PID = 0x0101
DEFAULT_HID_TIMEOUT = 10

class HIDCommands(enum.IntEnum):
    SW_VER                  = ord('*')
    SERIAL_NUM              = ord('%')
    HMD_SERIAL_NUM          = ord('&')
    TRACKING_SERIAL         = ord('~')
    USAGE_TIMER_GET         = ord('Z')
    USAGE_TIMER_SET         = ord('z')
    STEREO                  = ord('S')
    GAIN                    = ord('G')
    FRAC_GAIN               = ord('g')
    FAN                     = ord('F')
    FAN_DEFERRED            = ord('f')
    RATE                    = ord('R')
    OLED_FLIP               = ord('P')
    OLED_BRIGHNTESS         = ord('I')
    OLED_ID                 = ord('^')
    BOOTLOADER              = ord('B')
    RESET                   = ord('C')
    READ_SIG                = ord('U')
    RGB_LED                 = ord('L')
    WRITE_SIG               = ord('W')
    SAVE_SIG                = ord('V')
    PROX_SETTINGS           = ord('M')
    CHECK_VXR_CONNECTION    = ord('X')
    VXR_CHECKSUM            = ord('K')
    VXR_TAGS                = ord('T')
    VXR_DELETE              = ord('D')
    VXR_PROGRAM             = ord('A')
    VXR_RESET               = ord('Y')
    VXR_FWNAME              = ord('N')
    HW_TEST                 = ord('J')
    FATP_MODE               = ord('@')
    OLED_COMMAND            = ord('o')
    EDID_SWITCH             = ord('d')
    PROX_DISABLE            = ord('p')
    PROX_ENABLE             = ord('[')
    COLORBARPATTERN         = ord('=')
    STACK_LEVELS            = ord('s')
    OLED_POWER_DISABLE      = ord('-')
    OLED_POWER_ENABLE       = ord('+')
    FPGA_COMMANDS           = ord('e')

class HIDReplies(enum.IntEnum):
    DATA                    = ord('#')
    SUCCESS                 = ord('$')
    ERROR                   = ord('E')

class HIDData:
    def __init__(self, fan:int, prox:int, cc1:int, cc2:int, board_temp:float, dispL_temp:float, dispR_temp:float, disp_bright:int, disp_mode:bytes):
        self.fan_speed = fan
        self.prox_distance = prox
        self.cc1 = cc1
        self.cc2 = cc2
        self.board_temp = board_temp
        self.dispL_temp = dispL_temp
        self.dispR_temp = dispR_temp
        self.disp_bright = disp_bright
        self.disp_mode = disp_mode


def connect_beyond() -> hid.device:
    '''
    Connects to the Beyond via USB HID and returns the hid.device object
    '''
    beyond_hid = hid.device()
    beyond_hid.open(vendor_id = BIGSCREEN_VID, product_id = BEYOND_PID)
    return beyond_hid


def wait_for_response(hmd_device: hid.device, command: bytes, message_types:list, timeout_ms: int = 1000) -> bytes:
    '''
    Sends a query or command using HID set feature report
    Waits for a response using a list of valid keys (first byte of response)
    Times out after waiting timeout_ms milliseconds. If times out, returns empty bytes.
    '''
    sent_length = hmd_device.send_feature_report(command)
    if sent_length != len(command):
        return b''
    start_time = time.monotonic_ns()
    
    while( (start_time + (timeout_ms*1000000)) > time.monotonic_ns()):
        bytesout = bytes(hmd_device.read(65, timeout_ms=DEFAULT_HID_TIMEOUT))
        if(len(bytesout) > 0):
            if(bytesout[0] in message_types):
                return bytesout
    
    return b''

def decode_data(ret_bytes: bytes) -> HIDData:
    # Periodic report length = 24 bytes
    # Fan speed: 2 bytes (uint16, MSB first)
    # Proximity distance: 2 bytes (uint16, MSB first)
    # CC1 adc value: 2 bytes (uint16, MSB first)
    # CC2 adc value: 2 bytes (uint16, MSB first)
    # Board temperature: 4 bytes (packed float, LSB first)
    # Display 1 temperature: 4 bytes (packed float, LSB first)
    # Display 2 temperature: 4 bytes (packed float, LSB first)
    # Display brightness (duty cycle): 2 bytes (uint16, MSB first)
    # Display resolution/framerate: 2 bytes (uint16 [actually packed bits], MSB first)

    if len(ret_bytes) > 0:
        if ret_bytes[0] == HIDReplies.DATA and ret_bytes[1] == 24:
            # correct code and correct number of bytes
            fan,prox,cc1,cc2 = struct.unpack('>HHHH',ret_bytes[2:10])
            board_t, dispL_t, dispR_t = struct.unpack('<fff',ret_bytes[10:22])
            disp_bright = struct.unpack('>H', ret_bytes[22:24])
            return HIDData(fan, prox, cc1, cc2, board_t, dispL_t, dispR_t, disp_bright, ret_bytes[24:26])
        
    return HIDData(0,0,0,0,0,0,0,0,b'\x00\x00')
