# GstMini
# Copyright (C) 2025 Rafael Caricio <rafael@caricio.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.

import io
import tkinter as tk
from typing import Optional
from PIL import Image, ImageTk
import queue

from ..core.element import GstElement, GstState
from ..core.buffer import GstBuffer, BufferFlags
from ..core.pad import GstFlowReturn

# GstElement callled TkSink that renders video frames in a Tkinter window
# The frame content is expected to be raw bytes representing an image
class TkSink(GstElement):
    """
    Sink element that renders video frames in a Tkinter window.

    Demonstrates integration with GUI frameworks for video rendering.
    """

    def __init__(self, name: str, win_title: str, win_geometry: str = "640x480"):
        """
        Create a Tkinter sink.

        Args:
            name: Element name
            win_title: Window title
            win_geometry: Window geometry (e.g., "640x480")
        """
        super().__init__(name)

        self.win_title = win_title
        self.win_geometry = win_geometry

        self._tk_root: Optional[tk.Tk] = None
        self._tk_label: Optional[tk.Label] = None
        self._current_image: Optional[ImageTk.PhotoImage] = None
        self._frame_queue = queue.Queue(maxsize=2)  # Limit queue size to avoid memory issues

        self.sink_pad = self.create_sink_pad("sink")
        self.sink_pad.set_chain_function(self._chain)

    def _chain(self, buffer: GstBuffer) -> GstFlowReturn:
        """
        Chain function - receives buffers and renders them in the Tkinter label.

        Args:
            buffer: Incoming buffer

        Returns:
            GstFlowReturn status
        """
        if buffer.has_flag(BufferFlags.EOS):
            self.log("Received EOS")
            return GstFlowReturn.EOS

        pts = buffer.pts

        # Convert stream time to running time (gst_segment_to_running_time)
        # Corresponds to gstbasesink.c:2207
        running_time = self.segment.to_running_time(pts)

        if running_time < 0:
            self.log("Buffer outside segment, dropping")
            return GstFlowReturn.OK

        # Synchronization (gst_base_sink_do_sync)
        if self.state == GstState.PLAYING:
            # Calculate clock time: running_time + base_time
            # Corresponds to gstbasesink.c:2356
            clock_time = running_time + self.pipeline.base_time

            # Wait on clock (gst_base_sink_wait_clock at gstbasesink.c:2381)
            self.log(f"Waiting for clock_time={clock_time:.3f}s...")

            _, jitter = self.pipeline.clock.wait_until(clock_time)
            jitter_ms = jitter * 1000

            if jitter >= 0:
                self.log(f"Clock wait complete (jitter: {jitter_ms:+.1f}ms)")
            else:
                self.log(f"Buffer late (jitter: {jitter_ms:+.1f}ms)")

        return self.render_frame(buffer)

    def on_ready(self):
        """
        Integrate the sink with a Tkinter root window.
        This must be called from the main thread before starting the pipeline.

        Args:
            tk_root: Optional Tkinter root window. If None, creates a new one.

        Returns:
            The Tkinter root window
        """
        self._tk_root = tk.Tk()

        self._tk_root.title(self.win_title)
        self._tk_root.geometry(self.win_geometry)
        self._tk_label = tk.Label(self._tk_root, text="Waiting for video frames...", bg="black", fg="white")
        self._tk_label.pack(fill=tk.BOTH, expand=True)

    def on_playing(self):
        """Called when element transitions to PLAYING state."""
        # Start periodic frame updates
        self._update_frame()

    def _update_frame(self):
        """
        Check for new frames in the queue and update the display.
        This must run on the Tkinter main thread.
        """
        try:
            # Non-blocking get to check if there's a new frame
            frame_data = self._frame_queue.get_nowait()

            # Render the frame
            img = Image.open(io.BytesIO(frame_data))
            photo = ImageTk.PhotoImage(img)
            self._tk_label.config(image=photo)
            self._current_image = photo  # Keep a reference to avoid garbage collection
            self.log("Rendered new frame")
        except queue.Empty:
            # No new frame, that's okay
            pass
        except Exception as e:
            self.log(f"Error updating frame: {e}")
        finally:
            self._tk_root.after(16, self._update_frame)  # ~60 FPS

    def render_frame(self, buffer: GstBuffer) -> GstFlowReturn:
        """
        Render a video frame in the Tkinter label.

        Args:
            buffer: Incoming buffer containing frame data

        Returns:
            GstFlowReturn status
        """

        if buffer.has_flag(BufferFlags.DELTA_UNIT):
            self.log("Received a non-decodable buffer, skipping rendering")
            return GstFlowReturn.OK

        try:
            # Queue the buffer data for the Tk thread to process
            # Use non-blocking put with a short timeout to avoid blocking the chain function
            try:
                self._frame_queue.put(buffer.data, timeout=0.01)
                self.log("Queued frame for rendering")
            except queue.Full:
                self.log("Frame queue full, dropping frame")
        except Exception as e:
            self.log(f"Failed to queue frame: {e}")

        return GstFlowReturn.OK

    def tk_root(self) -> Optional[tk.Tk]:
        """
        Get the Tkinter root window.

        Returns:
            The Tkinter root window
        """
        return self._tk_root
