import gi
import struct
import os

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

class BrightnessManager:
    def __init__(self):
        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

        # LED state
        self.led_val = 0
        self.led_dir = 5  # Step size for smooth ramping
        self.color_index = 0  # 0: Red, 1: Green, 2: Blue
                                
        self.setup_led()

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

    def update_led_ramp(self):
        # Calculate next brightness step
        self.led_val += self.led_dir

        # If we hit 255 or 0, reverse direction
        if self.led_val >= 255:
            self.led_val = 255
            self.led_dir = -5

        elif self.led_val <= 0:
            self.led_val = 0
            self.led_dir = 5
            # Move to the next color once we finish a down-ramp
            self.color_index = (self.color_index + 1) % 3

        # Construct the RGB string
        rgb = [0, 0, 0]
        rgb[self.color_index] = self.led_val

        try:
            with open(self.led_multi_path, 'w') as f:
                f.write(f"{rgb[0]} {rgb[1]} {rgb[2]}")
        except:
            pass
        
        return True # Keep the GLib timer running

    def cycle_backlight(self):
        """Standard backlight cycle on touch"""
        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:
            with open(self.backlight_path, 'w') as f:
                f.write(str(self.bl_current))
            print(f"Touch! Backlight: {self.bl_current}")    
            
        except Exception as e:
            print(f"Backlight Error: {e}")

def handle_touch(source, condition, manager):
    # Linux input_event struct: long, long, unsigned short, unsigned short, unsigned int
    # We read 24 bytes (standard for 64-bit systems like i.MX8M)
    EVENT_SIZE = 24
    data = source.read(EVENT_SIZE)
    if len(data) < EVENT_SIZE:
        return True

    # Unpack the binary data
    # type (H), code (H), value (I) are the last 3 fields
    _, _, ev_type, ev_code, ev_value = struct.unpack('qqHHI', data)
    
    # EV_KEY (1) and BTN_TOUCH (330). ev_value 1 is press, 0 is release.
    # We trigger the change only on the 'press' (1)
    if ev_type == 1 and ev_code == 330 and ev_value == 1:
        manager.cycle_backlight()

    return True

def play_kiosk_video():
    Gst.init(None)

    brightness_manager = BrightnessManager()

    VIDEO_PATH = "file:///root/python/vid.mp4"

    SINK_STR = (
        "videoconvert ! "
        "video/x-raw,format=BGRx ! "
        "kmssink driver-name=mxsfb-drm connector-id=37 plane-id=31 can-scale=false" )

    # Create the playbin element
    pipeline = Gst.ElementFactory.make("playbin", "player")
    pipeline.set_property("uri", VIDEO_PATH)
                
    # Set the custom video sink
    video_sink = Gst.parse_bin_from_description(SINK_STR, True)
    pipeline.set_property("video-sink", video_sink)
                                
    # Set audio to fakesink (prevents errors if no audio hardware is found)
    audio_sink = Gst.ElementFactory.make("fakesink", "audio-fake")
    pipeline.set_property("audio-sink", audio_sink)

    # --- INPUT MONITORING ---
    try:
        # Open the touch device in binary read mode
        touch_fd = open("/dev/input/event2", "rb")
        # Add to GLib loop so it doesn't block the video
        GLib.io_add_watch(touch_fd, GLib.IO_IN, handle_touch, brightness_manager)
    except FileNotFoundError:
        print("Warning: /dev/input/event2 not found. Brightness control disabled.")
    
    GLib.timeout_add(20, brightness_manager.update_led_ramp)

    # --- LOOPING LOGIC ---
    bus = pipeline.get_bus()
    bus.add_signal_watch()

    def on_message(bus, msg):
        t = msg.type
        if t == Gst.MessageType.EOS:
            print("End of video. Looping...")
            pipeline.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 0)
        elif t == Gst.MessageType.ERROR:
            err, debug = msg.parse_error()
            print(f"Error: {err}")
            loop.quit()
        return True

    bus.connect("message", on_message)

    # Start playback
    print(f"Starting playback: {VIDEO_PATH}")
    pipeline.set_state(Gst.State.PLAYING)

    loop = GLib.MainLoop()
    try:
        loop.run()
    except KeyboardInterrupt:
        print("\nStopping player...")
    finally:
        pipeline.set_state(Gst.State.NULL)

if __name__ == "__main__":
    play_kiosk_video()
