import argparse
import gi
import os
import socket
import struct
import sys

gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib

SWITCH_UDP_PORT = 5001

class KioskManager:
    def __init__(self, pipeline):
        self.pipeline = pipeline
        self.videos = [
            "file:///root/python/vid.mp4",
            "file:///root/python/vid2.mp4"
        ]
        self.current_video_index = 0
        
        # Hardware Paths
        self.backlight_path = "/sys/class/backlight/main_backlight/brightness"        
        self.led_multi_path = "/sys/class/leds/display-indicator/multi_intensity"
        self.led_trig_path = "/sys/class/leds/display-indicator/trigger"
        self.led_br_path = "/sys/class/leds/display-indicator/brightness"

        # Backlight State
        self.bl_current = 10
        self.bl_direction = 1  # 1 for increasing, -1 for decreasing

        # Hex to RGB Tuples
        self.colours = [
            (95, 1, 111),   # #5F016F
            (255, 51, 187), # #FF33BB
            (255, 128, 212),# #FF80D4
            (255, 173, 228) # #FFADE4
        ]
        self.colour_index = 0
        
        self.setup_led()

    def setup_led(self):
        try:
            if os.path.exists(self.led_trig_path):
                with open(self.led_trig_path, 'w') as f: f.write("none")
                with open(self.led_br_path, 'w') as f: f.write("255")
                self.change_led_colour()
        except Exception as e:
            print(f"LED Setup Error: {e}")

    def change_led_colour(self):
        """Cycles the next LED colour"""
        r, g, b = self.colours[self.colour_index]
        try:
            if os.path.exists(self.led_multi_path):
                with open(self.led_multi_path, 'w') as f:
                    f.write(f"{r} {g} {b}")
            print(f"Loop/Switch! LED: {r} {g} {b}")
            self.colour_index = (self.colour_index + 1) % len(self.colours)
        except Exception as e:
            print(f"Failed to write LED colour: {e}")

    def cycle_backlight(self):
        """Touch Logic: Adjusts screen brightness and cycles LED"""
        self.bl_current += self.bl_direction
        if self.bl_current >= 20:
            self.bl_current = 20
            self.bl_direction = -1
        elif self.bl_current <= 0:
            self.bl_current = 0
            self.bl_direction = 1
            
        try:
            if os.path.exists(self.backlight_path):
                with open(self.backlight_path, 'w') as f:
                    f.write(str(self.bl_current))
                print(f"Touch Event: Brightness now {self.bl_current}")

        except Exception as e:
            print(f"Backlight Error: {e}")           

    def switch_video(self):
        """Manual trigger to swap between vid.mp4 and vid2.mp4"""
        self.pipeline.set_state(Gst.State.READY)
        self.current_video_index = (self.current_video_index + 1) % len(self.videos)
        new_uri = self.videos[self.current_video_index]
        
        print(f"Button Trigger: Switching to {new_uri}")
        self.pipeline.set_property("uri", new_uri)
        self.pipeline.set_state(Gst.State.PLAYING)

def handle_touch(source, condition, manager):
    """Listens to /dev/input/event2"""
    EVENT_SIZE = 24
    data = source.read(EVENT_SIZE)
    if not data or len(data) < EVENT_SIZE:
        return True
    
    _, _, ev_type, ev_code, ev_value = struct.unpack('qqHHI', data)

    # EV_KEY (1) and BTN_TOUCH (330), value 1 is Press
    if ev_type == 1 and ev_code == 330 and ev_value == 1:
        manager.cycle_backlight()
    return True        
    

def handle_button(source, condition, manager):
    """Lestens to /dev/input/event1"""
    EVENT_SIZE = 24
    data = source.read(EVENT_SIZE)
    if not data or len(data) < EVENT_SIZE:
        return True

    _, _, ev_type, ev_code, ev_value = struct.unpack('qqHHI', data)

    # EV_KEY (1), value 1 is press
    if ev_type == 1 and ev_value == 1:
        manager.switch_video()

    return True


def handle_udp_switch(sock, condition, manager):
    """Receives 'switch' datagrams from device_server.py and cycles the video."""
    try:
        data, _ = sock.recvfrom(64)
    except BlockingIOError:
        return True
    if data.strip() == b"switch":
        print("UDP Trigger: switch")
        manager.switch_video()
    return True


def _resolve_start_index(start_name: str, videos: list) -> int:
    """Map a basename like 'vid.mp4' to its index in the videos list."""
    target = os.path.basename(start_name).lower()
    for i, uri in enumerate(videos):
        if os.path.basename(uri).lower() == target:
            return i
    raise SystemExit(
        f"--start {start_name!r} not in kiosk video list: "
        + ", ".join(os.path.basename(u) for u in videos)
    )


def play_kiosk(start_index: int = 0):
    Gst.init(None)

    pipeline = Gst.ElementFactory.make("playbin", "player")
    
    SINK_STR = ("videoconvert ! video/x-raw,format=BGRx ! "
                "kmssink driver-name=mxsfb-drm connector-id=37 plane-id=31 can-scale=false")
    video_sink = Gst.parse_bin_from_description(SINK_STR, True)
    pipeline.set_property("video-sink", video_sink)
    pipeline.set_property("audio-sink", Gst.ElementFactory.make("fakesink"))

    manager = KioskManager(pipeline)
    manager.current_video_index = start_index
    pipeline.set_property("uri", manager.videos[start_index])
    print(f"Starting on: {manager.videos[start_index]}")

    # --- UDP SWITCH LISTENER ---
    # device_server.py sends b'switch' to 127.0.0.1:5001 to cycle videos remotely.
    try:
        udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        udp_sock.setblocking(False)
        udp_sock.bind(("127.0.0.1", SWITCH_UDP_PORT))
        GLib.io_add_watch(udp_sock, GLib.IO_IN, handle_udp_switch, manager)
    except Exception as e:
        print(f"UDP Listener Error: {e}")

    # --- INPUT MONITORING ---
    try:
        btn_fd = open("/dev/input/event1", "rb")
        GLib.io_add_watch(btn_fd, GLib.IO_IN, handle_button, manager)
    except Exception as e: print(f"Button Node Error: {e}")
    
    try:
        # Touchscreen assigned to event2
        tch_fd = open("/dev/input/event2", "rb")
        GLib.io_add_watch(tch_fd, GLib.IO_IN, handle_touch, manager)
    except Exception as e: print(f"Touch Node Error: {e}")
    
    # --- BUS MESSAGES ---
    bus = pipeline.get_bus()
    bus.add_signal_watch()

    def on_message(bus, msg, manager_instance):
        if msg.type == Gst.MessageType.EOS:
            # Video ended. Cycle LED and advance to the next video in the list.
            manager_instance.change_led_colour()
            manager_instance.switch_video()
        elif msg.type == Gst.MessageType.ERROR:
            err, debug = msg.parse_error()
            print(f"GStreamer Error: {err}")
            loop.quit

    bus.connect("message", on_message, manager)

    pipeline.set_state(Gst.State.PLAYING)
    loop = GLib.MainLoop()
    
    try:
        loop.run()
    except KeyboardInterrupt:
        pipeline.set_state(Gst.State.NULL)

if __name__ == "__main__":
    p = argparse.ArgumentParser(description="Kiosk video player")
    p.add_argument("--start", default="vid.mp4",
                   help="Initial video filename (basename match against kiosk list)")
    # parse_known_args so legacy flags like --static-pink don't crash the kiosk
    args, _unknown = p.parse_known_args()

    # We need the video list to resolve --start, so we recreate it here (must
    # stay in sync with KioskManager.videos).
    _videos = [
        "file:///root/python/vid.mp4",
        "file:///root/python/vid2.mp4",
    ]
    start_index = _resolve_start_index(args.start, _videos)
    play_kiosk(start_index=start_index)
