import tkinter as tk
from tkinter import ttk
import queue

from rtc import DS3231
from gps_reader import GPSReader
from rtc_reader import RTCReader
from gpio_controller import GPIOController
from timing_controller import TimingController
from sync_controller import SyncController
from frequency_controller import FrequencyController

class GPSApp:
    def __init__(self, root):
        self.root = root
        self.root.title("MBARI CSEM Triggerbox")
        # Account for window decorations - use slightly smaller size
        self.root.geometry("800x450")
        self.root.resizable(False, False)  # Prevent resizing to avoid GUI bugs
        self.root.configure(bg='black')

        # Initialize hardware interfaces
        self.rtc = DS3231()
        self.rtc_queue = queue.Queue()
        
        # GUI state
        self.transmitting = False
        
        # Initialize controllers (but don't trigger updates yet)
        self._init_controllers()
        
        self.setup_gui()
        
        # Now initialize frequency display after GUI is set up
        self.frequency_controller.update_callback = self.update_frequency_display
        self.frequency_controller.set_frequency(1.0)
        
        self.start_threads()

        # Bind window events to prevent GUI issues
        self.root.bind('<Map>', self.on_window_map)
        self.root.bind('<Configure>', self.on_window_configure)
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)

    def _init_controllers(self):
        """Initialize all controller objects"""
        # GPIO Controller
        self.gpio_controller = GPIOController(
            status_callback=lambda msg: print(f"[GPIO] {msg}")
        )
        
        # Timing Controller
        self.timing_controller = TimingController(
            rtc=self.rtc,
            update_callback=self.update_time_displays
        )
        
        # Frequency Controller (don't trigger update callback during init)
        self.frequency_controller = FrequencyController(
            initial_frequency=1.0,
            update_callback=None  # Will be set after GUI is created
        )
        
        # Sync Controller (will be initialized after GPS reader is created)
        self.sync_controller = None

    def setup_gui(self):
        # Main container with reduced padding
        main_frame = tk.Frame(self.root, bg='black')
        main_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=5)
        
        # Status at top with GPS info on same line
        self.status_frame = tk.Frame(main_frame, bg='black')
        self.status_frame.pack(fill=tk.X, pady=(0, 5))
        
        # Create a single line for status and GPS info
        status_info_frame = tk.Frame(self.status_frame, bg='black')
        status_info_frame.pack()
        
        self.status_label = tk.Label(status_info_frame, text="Initializing...", 
                                   font=("Courier", 9), fg="gray", bg="black")
        self.status_label.pack(side=tk.LEFT)
        
        # GPS info on same line
        self.lat_label = tk.Label(status_info_frame, text="Lat: ---", 
                                font=("Courier", 9), fg="lightgreen", bg="black")
        self.lat_label.pack(side=tk.LEFT, padx=(15, 5))

        self.lon_label = tk.Label(status_info_frame, text="Long: ---", 
                                font=("Courier", 9), fg="lightgreen", bg="black")
        self.lon_label.pack(side=tk.LEFT, padx=(0, 5))

        self.sats_label = tk.Label(status_info_frame, text="Sats: --", 
                                 font=("Courier", 9), fg="orange", bg="black")
        self.sats_label.pack(side=tk.LEFT, padx=(0, 5))

        # Time display section with sync button on the right
        time_section_frame = tk.Frame(main_frame, bg='black')
        time_section_frame.pack(fill=tk.X, pady=(0, 8))
        
        # Times on the left
        time_frame = tk.Frame(time_section_frame, bg='black')
        time_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        self.rpi_time_label = tk.Label(time_frame, text="RPI Time (UTC): --", 
                                     font=("Courier", 12, "bold"), fg="white", bg="black", anchor="center")
        self.rpi_time_label.pack(pady=1, fill=tk.X)

        self.gps_time_label = tk.Label(time_frame, text="GPS Time (UTC): --", 
                                     font=("Courier", 12, "bold"), fg="cyan", bg="black", anchor="center")
        self.gps_time_label.pack(pady=1, fill=tk.X)

        self.rtc_time_label = tk.Label(time_frame, text="RTC Time (UTC): --", 
                                     font=("Courier", 12, "bold"), fg="yellow", bg="black", anchor="center")
        self.rtc_time_label.pack(pady=1, fill=tk.X)

        # Sync section on the right
        sync_frame = tk.Frame(time_section_frame, bg='black')
        sync_frame.pack(side=tk.RIGHT, padx=(10, 0))
        
        self.sync_status_label = tk.Label(sync_frame, text="", 
                                        font=("Courier", 8), fg="lightblue", bg="black")
        self.sync_status_label.pack()

        self.sync_button = tk.Button(sync_frame, text="Sync GPS\nto RTC", 
                                   command=self.sync_rtc_time, font=("Arial", 9),
                                   bg="lightblue", fg="black", width=8, height=3)
        self.sync_button.pack(pady=3)

        # Transmission control section
        tx_frame = tk.Frame(main_frame, bg='black')
        tx_frame.pack(fill=tk.X, pady=(0, 8))
        
        # Frequency setting with compact layout
        freq_frame = tk.Frame(tx_frame, bg='black')
        freq_frame.pack(pady=(0, 5))
        
        # First line: Frequency label, period, and editing indicator
        freq_info_line = tk.Frame(freq_frame, bg='black')
        freq_info_line.pack()
        
        tk.Label(freq_info_line, text="Frequency (Hz):", font=("Arial", 10), 
                fg="white", bg="black").pack(side=tk.LEFT)
        
        self.period_display = tk.Label(freq_info_line, text="Period: 1.000s", 
                                     font=("Arial", 9), fg="orange", bg="black")
        self.period_display.pack(side=tk.LEFT, padx=(15, 15))
        
        self.decimal_indicator = tk.Label(freq_info_line, text="Editing: Ones place", 
                                        font=("Arial", 8), fg="lightblue", bg="black")
        self.decimal_indicator.pack(side=tk.LEFT)
        
        # Frequency control layout (moved up)
        freq_control_frame = tk.Frame(freq_frame, bg='black')
        freq_control_frame.pack(pady=(5, 0))
        
        # Up button
        self.freq_up_btn = tk.Button(freq_control_frame, text="▲", 
                                   font=("Arial", 12, "bold"), bg="lightgray", fg="black",
                                   width=3, height=1, command=self.freq_up)
        self.freq_up_btn.grid(row=0, column=1, padx=2, pady=1)
        
        # Left and Right buttons with frequency display
        freq_middle_frame = tk.Frame(freq_control_frame, bg='black')
        freq_middle_frame.grid(row=1, column=0, columnspan=3, pady=2)
        
        self.freq_left_btn = tk.Button(freq_middle_frame, text="◄", 
                                     font=("Arial", 12, "bold"), bg="lightgray", fg="black",
                                     width=3, height=1, command=self.freq_left)
        self.freq_left_btn.pack(side=tk.LEFT, padx=2)
        
        self.freq_display = tk.Label(freq_middle_frame, text="1.0", 
                                   font=("Courier", 14, "bold"), fg="yellow", bg="black",
                                   width=8, relief="sunken", bd=2)
        self.freq_display.pack(side=tk.LEFT, padx=5)
        
        self.freq_right_btn = tk.Button(freq_middle_frame, text="►", 
                                      font=("Arial", 12, "bold"), bg="lightgray", fg="black",
                                      width=3, height=1, command=self.freq_right)
        self.freq_right_btn.pack(side=tk.LEFT, padx=2)
        
        # Down button
        self.freq_down_btn = tk.Button(freq_control_frame, text="▼", 
                                     font=("Arial", 12, "bold"), bg="lightgray", fg="black",
                                     width=3, height=1, command=self.freq_down)
        self.freq_down_btn.grid(row=2, column=1, padx=2, pady=1)
        
        # Control buttons
        button_frame = tk.Frame(tx_frame, bg='black')
        button_frame.pack()
        
        self.start_button = tk.Button(button_frame, text="START", command=self.start_countdown,
                                    font=("Arial", 12, "bold"), bg="green", fg="white", 
                                    width=6, height=1)
        self.start_button.pack(side=tk.LEFT, padx=(0, 8))
        
        self.stop_button = tk.Button(button_frame, text="STOP", command=self.stop_transmission,
                                   font=("Arial", 12, "bold"), bg="red", fg="white", 
                                   width=6, height=1, state="disabled")
        self.stop_button.pack(side=tk.LEFT)
        
        # Countdown and status display
        status_display_frame = tk.Frame(tx_frame, bg='black')
        status_display_frame.pack(pady=(8, 0))
        
        self.countdown_label = tk.Label(status_display_frame, text="", 
                                      font=("Courier", 16, "bold"), fg="yellow", bg="black")
        self.countdown_label.pack(pady=(2, 2))
        
        self.tx_status_label = tk.Label(status_display_frame, text="", 
                                      font=("Courier", 14, "bold"), fg="green", bg="black")
        self.tx_status_label.pack(pady=(2, 2))
        
        # GPIO Status Indicators
        gpio_indicators_frame = tk.Frame(status_display_frame, bg='black')
        gpio_indicators_frame.pack(pady=(5, 2))
        
        # ON/OFF indicator (GPIO 13)
        onoff_frame = tk.Frame(gpio_indicators_frame, bg='black')
        onoff_frame.pack(side=tk.LEFT, padx=(0, 15))
        
        tk.Label(onoff_frame, text="ON/OFF", font=("Arial", 8), 
                fg="white", bg="black").pack()
        self.onoff_indicator = tk.Label(onoff_frame, text="●", font=("Arial", 32), 
                                       fg="gray", bg="black")
        self.onoff_indicator.pack()
        
        # Duty indicator (GPIO 6)
        duty_frame = tk.Frame(gpio_indicators_frame, bg='black')
        duty_frame.pack(side=tk.LEFT, padx=(0, 15))
        
        tk.Label(duty_frame, text="Duty", font=("Arial", 8), 
                fg="white", bg="black").pack()
        self.duty_indicator = tk.Label(duty_frame, text="●", font=("Arial", 32), 
                                      fg="gray", bg="black")
        self.duty_indicator.pack()
        
        # Polarity indicator (GPIO 26)
        polarity_frame = tk.Frame(gpio_indicators_frame, bg='black')
        polarity_frame.pack(side=tk.LEFT)
        
        tk.Label(polarity_frame, text="Polarity", font=("Arial", 8), 
                fg="white", bg="black").pack()
        self.polarity_indicator = tk.Label(polarity_frame, text="●", font=("Arial", 32), 
                                          fg="gray", bg="black")
        self.polarity_indicator.pack()

    def start_threads(self):
        # GPS and RTC threads
        self.gps_reader = GPSReader('/dev/ttyUSB0', 4800, self.update_gps_data, 
                                  self.update_status, self.handle_sync)
        self.gps_reader.start()

        self.rtc_reader = RTCReader(self.rtc, self.queue_rtc_time)
        self.rtc_reader.start()

        # Initialize sync controller now that GPS reader exists
        self.sync_controller = SyncController(
            rtc=self.rtc,
            gps_reader=self.gps_reader,
            status_callback=self.update_sync_status
        )

        # Start timing controller
        self.timing_controller.start_synchronized_timing()

        # Queue processing
        self.process_rtc_queue()

    def update_time_displays(self, cached_times):
        """Update the time display labels with cached synchronized times"""
        # Schedule GUI update on main thread
        self.root.after(0, lambda: self._update_time_labels(cached_times))

    def _update_time_labels(self, cached_times):
        """Update time labels (called on main thread)"""
        # Update RPI time
        if cached_times['rpi']:
            rpi_time_str = cached_times['rpi'].strftime("%Y-%m-%d %H:%M:%S")
            self.rpi_time_label.config(text=f"RPI Time (UTC): {rpi_time_str}")
        
        # Update GPS time
        if cached_times['gps']:
            gps_time_str = cached_times['gps'].strftime("%Y-%m-%d %H:%M:%S")
            self.gps_time_label.config(text=f"GPS Time (UTC): {gps_time_str}")
        else:
            self.gps_time_label.config(text="GPS Time (UTC): --")
        
        # Update RTC time
        if cached_times['rtc']:
            rtc_time_str = cached_times['rtc'].strftime("%Y-%m-%d %H:%M:%S")
            self.rtc_time_label.config(text=f"RTC Time (UTC): {rtc_time_str}", fg="yellow")
        else:
            self.rtc_time_label.config(text="RTC Time (UTC): Read Error", fg="red")

    def queue_rtc_time(self, rtc_time, error=None):
        # RTC time is now handled in the synchronized display update
        # This callback is kept for compatibility but doesn't update display directly
        if error:
            print(f"[RTC ERROR] {error}")

    def process_rtc_queue(self):
        try:
            while not self.rtc_queue.empty():
                rtc_time, error = self.rtc_queue.get_nowait()
                # Queue processing kept for compatibility but display update handled elsewhere
        except queue.Empty:
            pass
        self.root.after(100, self.process_rtc_queue)

    def handle_sync(self, sync_time):
        """Handle the actual RTC synchronization"""
        if self.sync_controller:
            self.sync_controller.handle_sync(sync_time)

    def update_gps_data(self, data):
        """Update GPS data from GPS reader"""
        # Update timing controller
        self.timing_controller.update_gps_time(data['gps_datetime'])
        
        # Update sync controller
        try:
            gps_sats = int(data['sats']) if data['sats'] != 'N/A' else 0
        except (ValueError, TypeError):
            gps_sats = 0
        
        if self.sync_controller:
            self.sync_controller.update_gps_data(data['gps_datetime'], gps_sats)
        
        # Update compact GPS display
        lat_str = data['lat'] if data['lat'] != 'N/A' else '---'
        lon_str = data['lon'] if data['lon'] != 'N/A' else '---'
        sats_str = data['sats'] if data['sats'] != 'N/A' else '--'
        
        self.lat_label.config(text=f"Lat: {lat_str}")
        self.lon_label.config(text=f"Long: {lon_str}")
        self.sats_label.config(text=f"Sats: {sats_str}")
        
        # Update status if not syncing
        if self.sync_controller and not self.sync_controller.is_sync_in_progress():
            self.status_label.config(text="GPS OK", fg="green")

    def update_status(self, msg):
        """Update status message"""
        if self.sync_controller and not self.sync_controller.is_sync_in_progress() or "Waiting for GPS second boundary" in msg:
            self.status_label.config(text=msg, fg="orange")
        print(f"[STATUS] {msg}")

    def update_sync_status(self, msg, color=None):
        """Update sync-related status"""
        color_map = {"red": "red", "green": "green", "orange": "orange"}
        fg_color = color_map.get(color, "gray")
        self.status_label.config(text=msg, fg=fg_color)

    def sync_rtc_time(self):
        """Initiate GPS to RTC synchronization"""
        if not self.sync_controller:
            return
            
        success = self.sync_controller.sync_rtc_time(
            success_callback=self.sync_success,
            error_callback=self.sync_error
        )
        
        if success:
            self.sync_button.config(state="disabled")
            self.sync_status_label.config(text="Sync in progress...", fg="orange")

    def sync_success(self, sync_time):
        """Called after successful sync"""
        sync_str = sync_time.strftime("%Y-%m-%d %H:%M:%S")
        self.sync_status_label.config(text=f"Last sync: {sync_str}", fg="lightblue")
        self.sync_button.config(state="normal")

    def sync_error(self, error):
        """Called after sync error"""
        self.sync_status_label.config(text=f"Sync error: {str(error)}", fg="red")
        self.sync_button.config(state="normal")

    def update_frequency_display(self, frequency, period, decimal_place_name):
        """Update frequency display elements"""
        self.freq_display.config(text=f"{frequency:.3f}")
        self.period_display.config(text=f"Period: {period:.3f}s")
        self.decimal_indicator.config(text=f"Editing: {decimal_place_name} place")

    def freq_up(self):
        """Increase frequency at current decimal place"""
        self.frequency_controller.frequency_up()

    def freq_down(self):
        """Decrease frequency at current decimal place"""
        self.frequency_controller.frequency_down()

    def freq_left(self):
        """Move decimal place to the left (higher significance)"""
        self.frequency_controller.decimal_place_left()

    def freq_right(self):
        """Move decimal place to the right (lower significance)"""
        self.frequency_controller.decimal_place_right()

    def start_countdown(self):
        """Start countdown to next minute based on RTC time"""
        success = self.timing_controller.start_countdown(
            countdown_callback=self.update_countdown_display,
            transmission_start_callback=self.start_transmission
        )
        
        if success:
            self.start_button.config(state="disabled")
            self.stop_button.config(state="normal")

    def update_countdown_display(self, countdown_text):
        """Update countdown display"""
        self.countdown_label.config(text=countdown_text)

    def start_transmission(self):
        """Start GPIO transmission at specified frequency"""
        try:
            frequency = self.frequency_controller.get_frequency()
            
            self.transmitting = True
            self.tx_status_label.config(text="TRANSMITTING", fg="green")
            self.countdown_label.config(text="")
            
            # Set up indicator callbacks
            indicator_callbacks = {
                'onoff': lambda state: self.onoff_indicator.config(fg="lightblue" if state else "gray"),
                'duty': lambda state: self.root.after(0, lambda: self.duty_indicator.config(fg="lightblue" if state else "gray")),
                'polarity': lambda state: self.root.after(0, lambda: self.polarity_indicator.config(fg="lightblue" if state else "gray"))
            }
            
            # Start GPIO transmission
            self.gpio_controller.start_transmission(frequency, indicator_callbacks)
            
            print(f"[TRANSMISSION] Started at {frequency} Hz")
            
        except ValueError as e:
            self.status_label.config(text=f"Invalid frequency: {e}", fg="red")
            self.stop_transmission()

    def stop_transmission(self):
        """Stop GPIO transmission"""
        # Stop timing controller countdown
        self.timing_controller.stop_countdown()
        
        # Stop GPIO transmission
        indicator_callbacks = {
            'onoff': lambda state: self.onoff_indicator.config(fg="lightblue" if state else "gray"),
            'duty': lambda state: self.duty_indicator.config(fg="lightblue" if state else "gray"),
            'polarity': lambda state: self.polarity_indicator.config(fg="lightblue" if state else "gray")
        }
        
        self.gpio_controller.stop_transmission(indicator_callbacks)
        
        # Update GUI
        self.transmitting = False
        self.tx_status_label.config(text="")
        self.countdown_label.config(text="")
        self.start_button.config(state="normal")
        self.stop_button.config(state="disabled")
        
        print("[TRANSMISSION] Stopped")

    def on_window_map(self, event):
        """Handle window mapping events"""
        if event.widget == self.root:
            self.root.update_idletasks()
    
    def on_window_configure(self, event):
        """Handle window configuration changes"""
        if event.widget == self.root:
            # Force update to prevent GUI corruption
            self.root.update_idletasks()

    def on_close(self):
        """Clean up and close application"""
        self.transmitting = False
        
        # Stop controllers
        if hasattr(self, 'timing_controller'):
            self.timing_controller.stop_synchronized_timing()
        
        if hasattr(self, 'gpio_controller'):
            self.gpio_controller.cleanup()
        
        # Stop threads
        if hasattr(self, 'gps_reader'):
            self.gps_reader.stop()
        if hasattr(self, 'rtc_reader'):
            self.rtc_reader.stop()
            
        self.root.destroy()