# app.py
"""
This is the main module of the Grifo E Control Panel application.
It orchestrates the interaction between the UI, image processing, and display modules.
"""

import threading
import time
import tkinter as tk
import cv2
import numpy as np
import screeninfo
import queue
import os
import logging

import config  # Import the config module
from ui import ControlPanel, StatusBar, create_main_window  # Import the UI module
from image_processing import load_image, apply_brightness_contrast, apply_color_palette, normalize_image, resize_image  # Import the image processing module
from display import DisplayManager  # Import the display module
from utils import put_queue # Import the utils module

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

class App:
    """
    The main application class for the Grifo E Control Panel.
    """
    def __init__(self, root):
        """
        Initializes the App.

        Args:
            root (tk.Tk): The main Tkinter window.
        """
        self.root = root
        self.root.title("Grifo E Control Panel")  # Set application title
        self.root.minsize(config.TKINTER_MIN_WIDTH, config.TKINTER_MIN_HEIGHT)  # Set minimum size

        # --- Queues for SAR Images and Mouse Coordinates ---
        self.sar_queue = queue.Queue(maxsize=5)
        self.mouse_queue = queue.Queue(maxsize=20)

        # --- Get Screen Information ---
        screen = screeninfo.get_monitors()[0]  # Use the primary screen
        screen_width = screen.width
        screen_height = screen.height

        # --- Window Placement (Initial values) ---
        self.tkinter_x = 10
        self.tkinter_y = config.MFD_HEIGHT + 20  # Place Tkinter window below MFD
        self.root.geometry(f"+{self.tkinter_x}+{self.tkinter_y}")

        self.mfd_x = self.tkinter_x
        self.mfd_y = 10

        self.sar_x = self.tkinter_x + config.TKINTER_MIN_WIDTH + 20  # Place SAR window to the right of Tkinter
        self.sar_y = 10

        # --- Initial SAR Contrast and Brightness ---
        self.sar_contrast = config.DEFAULT_SAR_CONTRAST
        self.sar_brightness = config.DEFAULT_SAR_BRIGHTNESS
        self.sar_palette = config.DEFAULT_SAR_PALETTE

        # --- Initial SAR Size ---
        self.sar_display_width = config.INITIAL_SAR_WIDTH
        self.sar_display_height = config.INITIAL_SAR_HEIGHT

        # --- Translation Offset (for simulation) ---
        self.mfd_x_offset = 0
        self.sar_x_offset = 0

        # --- FPS Counter ---
        self.mfd_frame_count = 0
        self.mfd_start_time = time.time()
        self.sar_update_time = time.time()  # Time of last SAR update
        self.sar_frame_count = 0
        self.mfd_fps = 0.0  # Initial MFD FPS
        self.sar_fps = 0.0  # Initial SAR FPS
        self.last_mouse_update = time.time()

        # --- Initial Image Data ---
        self.mfd_image_data = load_image(config.MFD_IMAGE_PATH, expected_dtype=np.uint8)
        self.sar_image_data = load_image(config.SAR_IMAGE_PATH, expected_dtype=config.SAR_DATA_TYPE)
        self.current_sar = normalize_image(self.sar_image_data)  # Current SAR for display
        self.adjusted_sar = self.current_sar.copy()  # Create a copy for contrast/brightness adjustments

        # --- Window Initialization Flags ---
        self.mfd_window_initialized = False
        self.sar_window_initialized = False
        self.sar_mouse_callback_set = False  # Flag to track if the callback has been set

        # --- Test Image Buffers ---
        self.test_mfd_image = np.zeros((config.MFD_HEIGHT, config.MFD_WIDTH), dtype=np.uint8)
        self.test_sar_image = np.zeros((config.SAR_HEIGHT, config.SAR_WIDTH), dtype=config.SAR_DATA_TYPE)

        # --- LUT Initialization ---
        self.brightness_contrast_lut = None  # Initialize LUT
        self.update_brightness_contrast_lut()  # Initial LUT calculation

        # --- UI Components ---
        self.statusbar = StatusBar(self.root)
        self.control_panel = ControlPanel(self.root, self)

        # --- Initialize resized_sar ---
        self.resized_sar = np.zeros((config.INITIAL_SAR_HEIGHT, config.INITIAL_SAR_WIDTH), dtype=np.uint8) # Initialize with a placeholder

        # --- Display Manager ---
        self.display_manager = DisplayManager(self, self.sar_queue, self.mouse_queue, self.sar_x, self.sar_y, self.mfd_x, self.mfd_y)

        # --- Image Loading Thread ---
        self.image_loading_thread = threading.Thread(target=self.load_images_and_generate_test_data, daemon=True)
        self.image_loading_thread.start()

        # --- Start Image Display ---
        self.update_mfd()
        self.update_sar()
        self.process_sar_queue()
        self.process_mouse_queue()

    def load_images_and_generate_test_data(self):
        """Loads images and generates test data in a separate thread."""
        self.set_status("Loading images...")
        self.mfd_image_data = load_image(config.MFD_IMAGE_PATH, expected_dtype=np.uint8)
        self.sar_image_data = load_image(config.SAR_IMAGE_PATH, expected_dtype=config.SAR_DATA_TYPE)
        self.generate_test_images()  # Generate initial test images
        self.set_initial_sar_image() # Set the initial SAR image
        self.update_sar() # Update the SAR display after loading
        self.set_status("Ready")  # Back to ready status

    def set_initial_sar_image(self):
        """Sets the initial SAR image for display."""
        self.current_sar = normalize_image(self.sar_image_data)  # Current SAR for display
        self.adjusted_sar = self.current_sar.copy()  # Create a copy for contrast/brightness adjustments
        self.resized_sar = resize_image(self.adjusted_sar, self.sar_display_width, self.sar_display_height)

        if self.resized_sar is not None and self.resized_sar.size > 0:
             print(f"Initial SAR image size: {self.resized_sar.shape}")
        else:
            print("Error: Resized SAR image has invalid dimensions.")
            self.resized_sar = np.zeros((config.INITIAL_SAR_HEIGHT, config.INITIAL_SAR_WIDTH), dtype=np.uint8) # Provide a placeholder image

    def generate_test_images(self):
        """Generates random test images for MFD and SAR."""
        self.test_mfd_image = np.random.randint(0, 256, size=(config.MFD_HEIGHT, config.MFD_WIDTH), dtype=np.uint8)
        self.test_sar_image = np.random.randint(0, 65536, size=(config.SAR_HEIGHT, config.SAR_WIDTH), dtype=config.SAR_DATA_TYPE).astype(
            config.SAR_DATA_TYPE)
        self.resized_sar = resize_image(normalize_image(self.test_sar_image), self.sar_display_width, self.sar_display_height)

        if self.resized_sar is not None and self.resized_sar.size > 0:
            print(f"Test SAR image size: {self.resized_sar.shape}")
        else:
            print("Error: Resized test SAR image has invalid dimensions.")
            self.resized_sar = np.zeros((config.INITIAL_SAR_HEIGHT, config.INITIAL_SAR_WIDTH), dtype=np.uint8) # Provide a placeholder image

    def update_mfd(self):
        """Updates the MFD image with translation and calculates FPS, limiting to MFD_FPS."""
        try:
            start_time = time.time()
            if self.control_panel.test_image_var.get() == 1:
                # Use test image and simulate translation
                self.mfd_x_offset = (self.mfd_x_offset + 1) % config.MFD_WIDTH
                translated_image = np.roll(self.test_mfd_image, -self.mfd_x_offset, axis=1)
                delay = max(1, int(1000 / config.MFD_FPS))  # Target delay for MFD_FPS
            else:
                # Use loaded image
                translated_image = self.mfd_image_data
                delay = 1  # Update as fast as possible when not in test mode

            self.display_manager.show_mfd_image(translated_image)

            # Calculate FPS
            self.mfd_frame_count += 1
            elapsed_time = time.time() - self.mfd_start_time
            if elapsed_time >= 1.0:  # Update FPS every second
                self.mfd_fps = self.mfd_frame_count / elapsed_time
                self.mfd_start_time = time.time()
                self.mfd_frame_count = 0
                self.update_status()  # Update status bar

            self.root.after(delay, self.update_mfd)  # Schedule the next update

        except Exception as e:
            print(f"Error updating MFD: {e}")
            logging.exception(f"Error updating MFD: {e}")

    def update_sar(self):
        """Updates the SAR image with translation."""
        try:
            if self.control_panel.test_image_var.get() == 1:
                # Use test image and simulate translation
                self.sar_x_offset = (self.sar_x_offset + 1) % self.sar_display_width
                translated_image = np.roll(self.resized_sar, -self.sar_x_offset, axis=1)  # shift columns
            else:
                # Use loaded image
                translated_image = self.resized_sar

            self.display_manager.show_sar_image(translated_image)

            # Calculate SAR FPS
            self.sar_frame_count += 1
            elapsed_time = time.time() - self.sar_update_time
            if elapsed_time >= 1.0:
                self.sar_fps = self.sar_frame_count / elapsed_time
                self.sar_update_time = time.time()
                self.sar_frame_count = 0
                self.update_status()  # Update status bar

            self.root.after(5000, self.update_sar)

        except Exception as e:
            print(f"Error updating SAR: {e}")
            logging.exception(f"Error updating SAR: {e}")

    def update_sar_size(self, event=None):
        """Updates the SAR image size based on the combobox selection."""
        selected_size = self.control_panel.sar_size_combo.get()
        if selected_size == "1:1":
            self.sar_display_width = config.SAR_WIDTH
            self.sar_display_height = config.SAR_HEIGHT
        elif selected_size == "1:2":
            self.sar_display_width = config.INITIAL_SAR_WIDTH
            self.sar_display_height = config.INITIAL_SAR_HEIGHT
        elif selected_size == "1:3":
            self.sar_display_width = config.SAR_WIDTH // 3
            self.sar_display_height = config.SAR_HEIGHT // 3
        elif selected_size == "1:5":
            self.sar_display_width = config.SAR_WIDTH // 5
            self.sar_display_height = config.SAR_HEIGHT // 5
        elif selected_size == "1:10":
            self.sar_display_width = config.SAR_WIDTH // 10
            self.sar_display_height = config.SAR_HEIGHT // 10

        if self.control_panel.test_image_var.get() == 1:
            self.generate_test_images() #Regenerate the test images
        else:
            self.set_initial_sar_image()

        # Resize the adjusted image
        self.resized_sar = resize_image(self.adjusted_sar, self.sar_display_width, self.sar_display_height)
        print(f"New SAR size: {self.sar_display_width}x{self.sar_display_height}")
        self.update_sar()  # Update display

        # Update SAR window position in case the size changed
        self.sar_x = self.tkinter_x + config.TKINTER_MIN_WIDTH + 20  # Place SAR window to the right of Tkinter
        self.sar_y = 10

    def update_contrast(self, value):
        """Updates the SAR image contrast based on the slider value."""
        try:
            self.sar_contrast = float(value)
            self.update_brightness_contrast_lut()
            self.update_sar_display()
        except ValueError:
            pass

    def update_brightness(self, value):
        """Updates the SAR image brightness based on the slider value."""
        try:
            self.sar_brightness = int(float(value))
            self.update_brightness_contrast_lut()
            self.update_sar_display()
        except ValueError:
            pass

    def update_sar_palette(self, event=None):
        """Updates the SAR image palette based on the combobox selection."""
        self.sar_palette = self.control_panel.palette_combo.get()
        self.update_sar_display()

    def update_brightness_contrast_lut(self):
        """Calculates the Look-Up Table (LUT) for brightness and contrast adjustment."""
        self.brightness_contrast_lut = np.array([
            np.clip(i * self.sar_contrast + self.sar_brightness, 0, 255).astype(np.uint8) for i in range(256)
        ])

    def update_sar_display(self):
        """Applies contrast, brightness, and palette adjustments to the SAR image and triggers SAR update."""

        # Apply contrast and brightness using LUT
        if self.control_panel.test_image_var.get() == 1:
            adjusted_image = apply_brightness_contrast(normalize_image(self.test_sar_image), self.sar_brightness, self.sar_contrast)
        else:
            adjusted_image = apply_brightness_contrast(self.current_sar, self.sar_brightness, self.sar_contrast)

        # Apply color Palette
        if self.sar_palette != "GRAY":
            adjusted_image = apply_color_palette(adjusted_image, self.sar_palette)

        self.adjusted_sar = adjusted_image.copy()  # Store the adjusted image
        # Resize the adjusted image
        self.resized_sar = resize_image(self.adjusted_sar, self.sar_display_width, self.sar_display_height)
        # Force image display update
        put_queue(self.sar_queue, self.resized_sar)

    def set_sar_info(self, message):
        """Updates the SAR info label in Tkinter."""
        self.control_panel.sar_info_label.config(text=message)

    def update_status(self, ):
        """Updates the status bar with MFD and SAR FPS (and seconds per image for SAR)."""
        status_text = f"Ready | MFD FPS: {self.mfd_fps:.2f} | SAR FPS: {self.sar_fps:.2f}"

        # Add seconds per image for SAR
        if self.sar_fps > 0:
            seconds_per_image = 1 / self.sar_fps
            status_text += f" ({seconds_per_image:.2f} s/img)"
        else:
            status_text += " (N/A s/img)"

        self.root.after(0, self.statusbar.set_status_text, status_text)

    def set_status(self, message):
        """Updates the status bar."""
        self.root.after(0, self.statusbar.set_status_text, message)

    def set_new_sar_image(self, image):
        """Function to set a new Sar image"""
        self.sar_image_data = image
        self.current_sar = normalize_image(self.sar_image_data)
        self.adjusted_sar = self.current_sar.copy()  # Update the adjusted image too
        self.resized_sar = resize_image(self.adjusted_sar, self.sar_display_width, self.sar_display_height)
        # Force update to image
        put_queue(self.sar_queue, self.resized_sar)

    def update_mouse_latlon_label(self, latitude, longitude):
        """Updates the Tkinter label with the mouse latitude and longitude."""
        self.control_panel.set_mouse_coordinates(latitude, longitude)

    def process_sar_queue(self):
        """This function get the new Sar image from queue and displays"""
        self.display_manager.process_sar_queue()
        self.root.after(1, self.process_sar_queue)  # Loop in the main Thread

    def process_mouse_queue(self):
         """Processes the mouse coordinate queue and updates the Tkinter label."""
         self.display_manager.process_mouse_queue()
         self.root.after(1, self.process_mouse_queue)

    def update_image_mode(self):
        """Updates the image mode based on the checkbox value and reload images if needed."""
        if self.control_panel.test_image_var.get() == 1:
            self.generate_test_images()  # Generate test images when test mode is enabled
        else:
            self.set_initial_sar_image() # Reset the SAR image
        self.update_mfd()
        self.update_sar()

if __name__ == "__main__":
    root = create_main_window("Grifo E Control Panel", config.TKINTER_MIN_WIDTH, config.TKINTER_MIN_HEIGHT, 10, config.MFD_HEIGHT + 20)
    app = App(root)
    root.mainloop()
    cv2.destroyAllWindows()