"""SAR-only Tkinter viewer for VideoReceiverSFP.

Displays high-resolution SAR images and exposes simple image controls
(brightness, contrast, autocontrast) and a Save PNG toggle. API mirrors
the other viewers: `show_frame(frame)`, `run()`, `run_async()`, `stop()`.
"""
import threading
import time
import logging
import tkinter as tk
from tkinter import ttk
from typing import Any, Optional, Callable
import os

try:
    from PIL import Image, ImageTk, ImageOps, ImageEnhance
except Exception:
    Image = ImageTk = ImageOps = ImageEnhance = None


class SfpSarViewer:
    def __init__(self, window_title: str = "SFP SAR Viewer", on_sar_param_changed: Optional[Callable] = None, master: Optional[tk.Widget] = None, compact: bool = False):
        # Support embedding inside an existing master (Labelframe/frame). If no master provided,
        # create a standalone Tk root as before.
        if master is None:
            self._root = tk.Tk()
            self._owns_root = True
            self._root.title(window_title)
            self._root.geometry("1000x700")
        else:
            self._root = master
            self._owns_root = False

        self._on_sar_param_changed = on_sar_param_changed
        # Compact mode: hide controls by default and expose via right-click
        self._compact = bool(compact)

        # Build UI: image on left, controls on right
        main = ttk.Frame(self._root)
        main.pack(fill="both", expand=True, padx=6, pady=6)

        # Left image area - fixed 484x484, user can resize window
        img_frame = ttk.Frame(main)
        img_frame.grid(row=0, column=0, sticky="nsew")
        
        # Use Canvas to ensure fixed size display; we'll draw images into it
        self._img_canvas = tk.Canvas(img_frame, width=484, height=484, bg="black", highlightthickness=0)
        self._img_canvas.pack(fill="both", expand=True)
        self._canvas_image_id = None

        # Right controls (hidden in compact mode)
        self._brightness_var = tk.IntVar(value=0)
        self._contrast_var = tk.IntVar(value=0)
        self._save_png_var = tk.BooleanVar(value=False)
        if not self._compact:
            ctrl_frame = ttk.Labelframe(main, text="SAR Controls", padding=8)
            ctrl_frame.grid(row=0, column=1, sticky="nsew", padx=(8,0))

            # Brightness slider (-100..100)
            ttk.Label(ctrl_frame, text="Brightness").grid(row=0, column=0, sticky="w")
            b_slider = ttk.Scale(ctrl_frame, from_=-100, to=100, orient="horizontal",
                                 variable=self._brightness_var, command=self._on_brightness_changed)
            b_slider.grid(row=1, column=0, sticky="ew", pady=(0,6))

            # Contrast slider (-100..100)
            ttk.Label(ctrl_frame, text="Contrast").grid(row=2, column=0, sticky="w")
            c_slider = ttk.Scale(ctrl_frame, from_=-100, to=100, orient="horizontal",
                                 variable=self._contrast_var, command=self._on_contrast_changed)
            c_slider.grid(row=3, column=0, sticky="ew", pady=(0,6))

            # Autocontrast button
            auto_btn = ttk.Button(ctrl_frame, text="AutoContrast", command=self._on_autocontrast)
            auto_btn.grid(row=4, column=0, sticky="ew", pady=(4,8))

            # Show metadata button
            meta_btn = ttk.Button(ctrl_frame, text="Show Metadata", command=self._on_show_metadata)
            meta_btn.grid(row=5, column=0, sticky="ew", pady=(4,8))

            # Save current equalized image button
            save_img_btn = ttk.Button(ctrl_frame, text="Save Image", command=self._on_save_current_image)
            save_img_btn.grid(row=6, column=0, sticky="ew", pady=(4,8))

            # Save PNG toggle
            save_cb = ttk.Checkbutton(ctrl_frame, text="Save .png", variable=self._save_png_var, command=self._on_save_png_toggled)
            save_cb.grid(row=7, column=0, sticky="w", pady=(2,2))
        else:
            # Compact mode: provide context menu on canvas to open controls and metadata
            try:
                self._context_menu = tk.Menu(self._root, tearoff=0)
                self._context_menu.add_command(label="Open Controls", command=self._open_controls_window)
                self._context_menu.add_command(label="Show Metadata", command=self._on_show_metadata)
                self._img_canvas.bind("<Button-3>", self._on_canvas_right_click)
            except Exception:
                pass

        if not self._compact:
            try:
                ctrl_frame.columnconfigure(0, weight=1)
            except Exception:
                pass
        main.rowconfigure(0, weight=1)
        main.rowconfigure(1, weight=0)
        main.columnconfigure(0, weight=3)
        main.columnconfigure(1, weight=1)

        # Bottom: Status bar (FPS)
        status_frame = ttk.Frame(main, relief="sunken")
        status_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(5, 0))
        self._fps_label = ttk.Label(status_frame, text="FPS: 0.00")
        self._fps_label.pack(side="left", padx=5)

        # runtime state
        self._photo = None
        self._pending_frame = None
        self._lock = threading.Lock()
        self._current_pil_image = None
        self._current_pil_image_raw = None
        self._params_changed = False
        self._current_brightness = 0
        self._current_contrast = 0
        self._running = False
        self._frame_times = []
        self._last_metadata = None
        self._meta_window = None
        self._meta_text = None

        # polling
        self._poll_interval_ms = 100
        self._running = True
        self._root.after(self._poll_interval_ms, self._poll)

        try:
            logging.getLogger().info("SfpSarViewer: GUI initialized")
        except Exception:
            pass

    # --- control callbacks -------------------------------------------------
    def _on_brightness_changed(self, value):
        try:
            v = int(float(value))
            with self._lock:
                self._current_brightness = v
                self._params_changed = True
            if self._on_sar_param_changed:
                self._on_sar_param_changed("brightness", None, v)
        except Exception:
            pass

    def _on_contrast_changed(self, value):
        try:
            v = int(float(value))
            with self._lock:
                self._current_contrast = v
                self._params_changed = True
            if self._on_sar_param_changed:
                self._on_sar_param_changed("contrast", None, v)
        except Exception:
            pass

    def _on_autocontrast(self):
        if self._on_sar_param_changed:
            self._on_sar_param_changed("autocontrast", None, True)
        with self._lock:
            self._params_changed = True

    def _on_save_current_image(self):
        """Save the current equalized SAR image to file."""
        try:
            with self._lock:
                raw = self._current_pil_image_raw.copy() if self._current_pil_image_raw is not None else None
            if raw is None:
                logging.warning("SAR: no image to save")
                return
            
            # Apply current brightness/contrast to get the equalized image
            pil = self._apply_brightness_contrast(raw.copy())
            
            # Save with timestamp
            import time
            ts = time.strftime('%Y%m%d_%H%M%S')
            dumps_dir = os.path.join(os.getcwd(), 'dumps')
            os.makedirs(dumps_dir, exist_ok=True)
            filename = f'SAR_equalized_{ts}.png'
            filepath = os.path.join(dumps_dir, filename)
            pil.save(filepath)
            logging.info("SAR: saved equalized image to %s", filepath)
        except Exception:
            logging.exception("SAR: failed to save current image")

    def _on_save_png_toggled(self):
        val = bool(self._save_png_var.get())
        logging.info("SAR Save PNG toggled: %s", val)
        if self._on_sar_param_changed:
            self._on_sar_param_changed("save_png", None, val)

    def _on_canvas_right_click(self, event):
        try:
            if hasattr(self, '_context_menu') and self._context_menu:
                self._context_menu.tk_popup(event.x_root, event.y_root)
        except Exception:
            pass

    def _open_controls_window(self):
        """Open a Toplevel window with SAR controls (used in compact mode)."""
        try:
            win = tk.Toplevel(self._root)
            win.title('SAR Controls')
            frm = ttk.Frame(win, padding=8)
            frm.pack(fill='both', expand=True)

            ttk.Label(frm, text="Brightness").grid(row=0, column=0, sticky="w")
            b_slider = ttk.Scale(frm, from_=-100, to=100, orient="horizontal",
                                 variable=self._brightness_var, command=self._on_brightness_changed)
            b_slider.grid(row=1, column=0, sticky="ew", pady=(0,6))

            ttk.Label(frm, text="Contrast").grid(row=2, column=0, sticky="w")
            c_slider = ttk.Scale(frm, from_=-100, to=100, orient="horizontal",
                                 variable=self._contrast_var, command=self._on_contrast_changed)
            c_slider.grid(row=3, column=0, sticky="ew", pady=(0,6))

            auto_btn = ttk.Button(frm, text="AutoContrast", command=self._on_autocontrast)
            auto_btn.grid(row=4, column=0, sticky="ew", pady=(4,8))

            meta_btn = ttk.Button(frm, text="Show Metadata", command=self._on_show_metadata)
            meta_btn.grid(row=5, column=0, sticky="ew", pady=(4,8))

            save_img_btn = ttk.Button(frm, text="Save Image", command=self._on_save_current_image)
            save_img_btn.grid(row=6, column=0, sticky="ew", pady=(4,8))

            save_cb = ttk.Checkbutton(frm, text="Save .png", variable=self._save_png_var, command=self._on_save_png_toggled)
            save_cb.grid(row=7, column=0, sticky="w", pady=(2,2))

            frm.columnconfigure(0, weight=1)
            win.transient(self._root)
            win.lift()
        except Exception:
            logging.exception("Failed to open SAR controls window")

    def _on_show_metadata(self):
        try:
            if self._meta_window is None or not getattr(self._meta_window, 'winfo_exists', lambda: False)():
                self._meta_window = tk.Toplevel(self._root)
                self._meta_window.title('SAR Metadata')
                self._meta_text = tk.Text(self._meta_window, wrap='none', width=80, height=30, font=('Courier New', 9))
                self._meta_text.pack(fill='both', expand=True)
            # Insert current metadata
            self._meta_text.config(state='normal')
            self._meta_text.delete('1.0', tk.END)
            if self._last_metadata:
                self._meta_text.insert('1.0', self._last_metadata)
            else:
                self._meta_text.insert('1.0', '<No SAR metadata received yet>')
            self._meta_text.config(state='disabled')
            self._meta_window.lift()
        except Exception:
            pass

    def show_metadata(self, metadata_str: str) -> None:
        """Called by the module to update stored metadata and (optionally) update the window."""
        try:
            self._last_metadata = metadata_str
            if self._meta_text is not None and getattr(self._meta_text, 'config', None):
                try:
                    self._meta_text.config(state='normal')
                    self._meta_text.delete('1.0', tk.END)
                    self._meta_text.insert('1.0', metadata_str or '<No metadata>')
                    self._meta_text.config(state='disabled')
                except Exception:
                    pass
        except Exception:
            pass

    # --- frame handling ----------------------------------------------------
    def show_frame(self, frame: Any) -> None:
        try:
            with self._lock:
                self._pending_frame = frame
                self._frame_times.append(time.time())
                cutoff = time.time() - 2.0
                while self._frame_times and self._frame_times[0] < cutoff:
                    self._frame_times.pop(0)
        except Exception:
            pass

    def _apply_brightness_contrast(self, img: 'Image.Image') -> 'Image.Image':
        try:
            if Image is None or ImageEnhance is None:
                return img
            # Brightness: -100..100 -> factor 0.0..2.0 (0 black, 1 original, 2 double)
            with self._lock:
                b = self._current_brightness
                c = self._current_contrast
            bf = max(0.0, 1.0 + (b / 100.0))
            # Contrast: -100..100 -> factor 0.0..2.0
            cf = max(0.0, 1.0 + (c / 100.0))

            result = img
            try:
                if bf != 1.0:
                    result = ImageEnhance.Brightness(result).enhance(bf)
                if cf != 1.0:
                    result = ImageEnhance.Contrast(result).enhance(cf)
            except Exception:
                logging.exception("Error applying brightness/contrast")
            return result
        except Exception:
            return img

    def _build_tk_image(self, frame: Any):
        try:
            if Image is None or ImageTk is None:
                return None

            if hasattr(frame, 'copy'):
                img = frame.copy()
            elif isinstance(frame, (bytes, bytearray)):
                import io
                img = Image.open(io.BytesIO(frame)).convert('L')
            else:
                return None

            # Convert grayscale to RGB for display
            if img.mode == 'L':
                img = img.convert('RGB')

            # Keep an unmodified raw copy so adjustments can be reapplied
            try:
                with self._lock:
                    self._current_pil_image_raw = img.copy()
            except Exception:
                pass

            # Apply brightness/contrast to a copy for display
            try:
                disp_img = self._apply_brightness_contrast(img.copy())
            except Exception:
                disp_img = img

            # Resize to fit the canvas while preserving aspect ratio.
            try:
                w = self._img_canvas.winfo_width()
                h = self._img_canvas.winfo_height()
                if w > 2 and h > 2:
                    max_size = (max(1, w), max(1, h))
                    try:
                        disp_img = disp_img.copy()
                        disp_img.thumbnail(max_size, Image.LANCZOS)
                    except Exception:
                        pass
            except Exception:
                pass

            # Save a copy of current (adjusted) PIL image for quick display
            try:
                with self._lock:
                    self._current_pil_image = disp_img.copy()
            except Exception:
                pass

            try:
                return ImageTk.PhotoImage(disp_img, master=self._root)
            except Exception:
                return ImageTk.PhotoImage(disp_img)
        except Exception:
            logging.exception("SfpSarViewer: failed to build tk image")
            return None

    def _draw_image_on_canvas(self, photo_image: ImageTk.PhotoImage) -> None:
        try:
            # Keep reference to avoid GC
            self._last_photoimage = photo_image
            # Draw into canvas, centered
            if self._canvas_image_id is None:
                self._canvas_image_id = self._img_canvas.create_image(
                    self._img_canvas.winfo_width() // 2,
                    self._img_canvas.winfo_height() // 2,
                    image=self._last_photoimage,
                    anchor='center'
                )
            else:
                self._img_canvas.itemconfigure(self._canvas_image_id, image=self._last_photoimage)
                # Recenter in case canvas size changed
                self._img_canvas.coords(self._canvas_image_id,
                                         self._img_canvas.winfo_width() // 2,
                                         self._img_canvas.winfo_height() // 2)
        except Exception:
            pass

    def _poll(self):
        try:
            frame = None
            params_changed = False
            with self._lock:
                if self._pending_frame is not None:
                    frame = self._pending_frame
                    self._pending_frame = None
                params_changed = self._params_changed
                self._params_changed = False

            if frame is not None:
                tkimg = self._build_tk_image(frame)
                if tkimg is not None:
                    self._photo = tkimg
                    self._draw_image_on_canvas(self._photo)
            elif params_changed:
                # No new frame but params changed: refresh current image
                self._refresh_current_image()

            # update FPS
            now = time.time()
            cutoff = now - 1.0
            fps = len([t for t in self._frame_times if t >= cutoff])
            self._fps_label.config(text=f"FPS: {fps}")
        except Exception:
            pass
        finally:
            try:
                self._root.after(self._poll_interval_ms, self._poll)
            except Exception:
                pass

    def run(self):
        self._running = True
        try:
            if self._owns_root:
                try:
                    self._root.mainloop()
                except KeyboardInterrupt:
                    pass
            else:
                # Embedded mode: block until stop() invoked
                while self._running:
                    time.sleep(0.1)
        finally:
            self._running = False

    def _refresh_current_image(self) -> None:
        """Rebuild and display the current PIL image applying current SAR params."""
        try:
            if Image is None or ImageTk is None:
                return
            with self._lock:
                raw = self._current_pil_image_raw.copy() if self._current_pil_image_raw is not None else None
            if raw is None:
                return
            try:
                pil = self._apply_brightness_contrast(raw.copy())
            except Exception:
                pil = raw
            try:
                w = self._img_canvas.winfo_width()
                h = self._img_canvas.winfo_height()
                if w > 2 and h > 2:
                    pil = pil.copy()
                    pil.thumbnail((max(1, w), max(1, h)), Image.LANCZOS)
            except Exception:
                pass
            try:
                tkimg = ImageTk.PhotoImage(pil, master=self._root)
            except Exception:
                tkimg = ImageTk.PhotoImage(pil)
            with self._lock:
                self._photo = tkimg
            try:
                self._draw_image_on_canvas(self._photo)
                self._img_canvas.update_idletasks()
            except Exception:
                pass
        except Exception:
            logging.exception("SfpSarViewer: _refresh_current_image failed")

    def run_async(self):
        threading.Thread(target=self.run, daemon=True).start()

    def stop(self):
        self._running = False
        try:
            self._root.quit()
            self._root.destroy()
        except Exception:
            pass
