try:  #local script
    from lib.datatypes import PictureMatrix, RGB, FusionLightData, FusionLightEffect, FusionLightColor, FusionLightDirection
    from lib.device import get_device
except ImportError:  #as a module
    from .lib.datatypes import PictureMatrix, RGB, FusionLightData, FusionLightEffect, FusionLightColor, FusionLightDirection 
    from .lib.device import get_device

from cv2.typing import MatLike
from typing import List
import sys, cv2, time, numpy, atexit

#10 is actually not transient - it is persistent as a secondary profile but for our purposes we can treat it as transient
#since nobody should have the need to write to a profile number this high (switching between profiles require os interaction anyways so might as well write a new image)
TRANSIENT_ID = 10

if len(sys.argv) < 2:
    print(f'Usage: {__file__} <video path> [profile id]')
    exit(-1)

if len(sys.argv) >= 3:
    try:
        profile = int(sys.argv[2])
        if profile not in range(5):
            raise TypeError()
    except:
        print('Invalid profile id given, defaulting to transience (will be reverted after play (or reboot if interrupted))...')
        profile = TRANSIENT_ID
else:
    profile = TRANSIENT_ID

dev = get_device()
orig_effect = dev.get_simple_light_effect()

#if transient, set it back to the old profile
if profile == TRANSIENT_ID:
    def reset():
        if orig_effect.fusion_effect < FusionLightEffect.Custom1:
            dev.set_simple_light_effect(orig_effect)
        else:
            load = PictureMatrix.from_bytes(dev.get_custom_light_effect(orig_effect.fusion_effect - FusionLightEffect.Custom1))
            dev.set_custom_light_effect(profile, load)
    atexit.register(reset)


#change to profile for the image stream first (technically only need to change if not transient and current profile is non custom, but we revert afterwards anyway)
#to avoid any flashing set to black first (mainly when we are in simple modes which means last seen image will be shown right when set_simple_light_effect is called)
dev.set_custom_light_effect(profile, PictureMatrix([RGB(0,0,0) for i in range(105)]))
#need to set it to one of the "proper" profiles for it to know to update
dev.set_simple_light_effect(FusionLightData(FusionLightEffect.Custom1 + profile if profile != TRANSIENT_ID else FusionLightEffect.Custom1, 1, 255, FusionLightColor.White, FusionLightDirection.Left2Right))


def set_frame(raw: MatLike, fps_data: List[float]) -> None:
    pre = cv2.resize(raw, (19, 6), interpolation=cv2.INTER_AREA)
    im = cv2.cvtColor(pre, cv2.COLOR_BGR2RGB)

    #display what's supposed to be encoded in the keyboard at this frame
    cv2.imshow('preview', cv2.resize(pre, (190*5, 60*5)))
    if len(fps_data) > 1:
        cv2.setWindowTitle('preview', f'Preview (FPS: {1/numpy.average(numpy.diff(fps_data))})')
    cv2.waitKey(1)

    #turns out even if i do translate this into a list of RGB instead of just directly send mats it still gives basically around the same fps
    pixels = im
    pixels = []

    h, w, _ = im.shape

    for y in range(h):
        for x in range(w):
            pixels.append(RGB(*im[y, x]))

    #apply any adjustment needed to give true colors
    dev.set_custom_light_effect(profile, PictureMatrix.pixel_matrix_to_keys(pixels), adjust=dev.ADJUST_RGB)



#apparently also works with static images
vid = cv2.VideoCapture(sys.argv[1])

#skip some frames so it's not too slow
#TODO make this less hardcoded - this value is from testing on my laptop with bad apple
AVG_FPS = 11
interval = vid.get(cv2.CAP_PROP_FPS) // AVG_FPS

actual_fps = []

#this actually doesnt help that much it just gives like 1 more fps and takes a while to buffer :(
PRECOMPUTE = False

if PRECOMPUTE:
    buf = []

    print('Buffering...')

    count = 0
    while vid.isOpened():
        
        ret, frame = vid.read()
        if ret:
            buf.append(frame)
            count += interval
            vid.set(cv2.CAP_PROP_POS_FRAMES, count)
        else:
            vid.release()
            break

    print('Done. Starting player...')

    for frame in buf:
        actual_fps.append(time.time())

        set_frame(frame, actual_fps)
        #get an averagable data before popping
        if len(actual_fps) > 10:
            actual_fps.pop(0)
else:
    count = 0
    while vid.isOpened():
        
        actual_fps.append(time.time())

        ret, frame = vid.read()
        if ret:
            set_frame(frame, actual_fps)
            count += interval
            vid.set(cv2.CAP_PROP_POS_FRAMES, count)
            #get an averagable data before popping
            if len(actual_fps) > 10:
                actual_fps.pop(0)
        else:
            vid.release()
            break


#keep last frame until we manually quit
cv2.waitKey(0)