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
from log import DataLogger

class GPSApp:
    def __init__(self, root):
        self.root = root
        self.root.title("MBARI CSEM Triggerbox")
        # Account for window decorations - use larger size for new sections
        self.root.geometry("1000x450")
        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
        self.gui_initialized = False  # Track GUI initialization
        
        # Transmit/Pause timing state
        self.transmit_time = 60  # seconds
        self.pause_time = 30     # seconds
        self.cycle_state = 'idle'  # 'idle', 'transmitting', 'pausing'
        self.cycle_timer = None
        
        # Initialize data logger
        self.data_logger = DataLogger()
        
        # Store GPS data for logging
        self.current_gps_data = {
            'lat': 'N/A',
            'lon': 'N/A', 
            'sats': 'N/A'
        }
        
        # 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)
        
        # Mark GUI as fully initialized
        self.gui_initialized = True
        
        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)

        import os
        
        def update_log_sizes(self):
            if not self.gui_initialized:
                return
            log = self.data_logger.get_current_log_file()
            gps = self.data_logger.get_current_gps_log_file()
            if log and os.path.exists(log):
                size_kb = os.path.getsize(log) / 1024
                self.logsize_label.config(text=f"Log: {size_kb:.1f} KB")
            if gps and os.path.exists(gps):
                size_kb = os.path.getsize(gps) / 1024
                self.gpslogsize_label.config(text=f"GPS Log: {size_kb:.1f} KB")

        # Call update_log_sizes from update_countdown_display and after transmission cycle methods

        # Update update_countdown_display():
        def update_countdown_display(self, countdown_text):
            if not self.gui_initialized:
                return
            if self.data_logger.is_logging_active():
                self.data_logger.log_countdown(countdown_text)
                self.update_log_sizes()
            if "00:00" in countdown_text:
                self.countdown_label.config(text="")
                self.tx_status_label.config(text="TRANSMITTING", fg="green")
            else:
                self.countdown_label.config(text=countdown_text)
                self.tx_status_label.config(text="COUNTDOWN", fg="yellow")

        # In stop_transmission(), set status label:
        self.tx_status_label.config(text="NOT TRANSMITTING", fg="red")

        # In __init__, set initial label:
        self.tx_status_label.config(text="NOT TRANSMITTING", fg="red")

        # In _start_pause_cycle:
        self.tx_status_label.config(text="PAUSED", fg="orange")

    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 moved closer
        time_section_frame = tk.Frame(main_frame, bg='black')
        time_section_frame.pack(fill=tk.X, pady=(0, 8))
        
        # Times container
        times_container = tk.Frame(time_section_frame, bg='black')
        times_container.pack()
        
        # Times on the left
        time_frame = tk.Frame(times_container, 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 moved closer to times (reduced padx)
        sync_frame = tk.Frame(times_container, bg='black')
        sync_frame.pack(side=tk.RIGHT, padx=(20, 0))  # Reduced from 40 to 20
        
        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 with three control panels
        tx_frame = tk.Frame(main_frame, bg='black')
        tx_frame.pack(fill=tk.X, pady=(0, 8))
        
        # Container for all three control sections
        control_sections_frame = tk.Frame(tx_frame, bg='black')
        control_sections_frame.pack(pady=(0, 5))
        
        # Frequency setting section
        freq_section = tk.Frame(control_sections_frame, bg='black')
        freq_section.pack(side=tk.LEFT, padx=(0, 20))
        
        # Frequency label and info
        freq_info_line = tk.Frame(freq_section, bg='black')
        freq_info_line.pack()
        
        tk.Label(freq_info_line, text="Frequency (Hz):", font=("Arial", 10), 
                fg="white", bg="black").pack()
        
        self.period_display = tk.Label(freq_info_line, text="Period: 1.000s", 
                                     font=("Arial", 9), fg="orange", bg="black")
        self.period_display.pack(pady=(2, 0))
        
        self.decimal_indicator = tk.Label(freq_info_line, text="Editing: Ones place", 
                                        font=("Arial", 8), fg="lightblue", bg="black")
        self.decimal_indicator.pack()
        
        # Frequency control layout
        freq_control_frame = tk.Frame(freq_section, 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)
        
        # Transmit Time section
        transmit_section = tk.Frame(control_sections_frame, bg='black')
        transmit_section.pack(side=tk.LEFT, padx=(0, 20))
        
        tk.Label(transmit_section, text="Transmit Time", font=("Arial", 10), 
                fg="white", bg="black").pack()
        tk.Label(transmit_section, text="(seconds)", font=("Arial", 8), 
                fg="orange", bg="black").pack()
        
        transmit_control_frame = tk.Frame(transmit_section, bg='black')
        transmit_control_frame.pack(pady=(5, 0))
        
        # Transmit up button
        self.transmit_up_btn = tk.Button(transmit_control_frame, text="▲", 
                                       font=("Arial", 12, "bold"), bg="lightgray", fg="black",
                                       width=3, height=1, command=self.transmit_time_up)
        self.transmit_up_btn.grid(row=0, column=1, padx=2, pady=1)
        
        # Transmit display
        self.transmit_display = tk.Label(transmit_control_frame, text="60", 
                                       font=("Courier", 14, "bold"), fg="lightgreen", bg="black",
                                       width=8, relief="sunken", bd=2)
        self.transmit_display.grid(row=1, column=1, padx=2, pady=2)
        
        # Transmit down button
        self.transmit_down_btn = tk.Button(transmit_control_frame, text="▼", 
                                         font=("Arial", 12, "bold"), bg="lightgray", fg="black",
                                         width=3, height=1, command=self.transmit_time_down)
        self.transmit_down_btn.grid(row=2, column=1, padx=2, pady=1)
        
        # Pause Time section
        pause_section = tk.Frame(control_sections_frame, bg='black')
        pause_section.pack(side=tk.LEFT)
        
        tk.Label(pause_section, text="Pause Time", font=("Arial", 10), 
                fg="white", bg="black").pack()
        tk.Label(pause_section, text="(seconds)", font=("Arial", 8), 
                fg="orange", bg="black").pack()
        
        pause_control_frame = tk.Frame(pause_section, bg='black')
        pause_control_frame.pack(pady=(5, 0))
        
        # Pause up button
        self.pause_up_btn = tk.Button(pause_control_frame, text="▲", 
                                    font=("Arial", 12, "bold"), bg="lightgray", fg="black",
                                    width=3, height=1, command=self.pause_time_up)
        self.pause_up_btn.grid(row=0, column=1, padx=2, pady=1)
        
        # Pause display
        self.pause_display = tk.Label(pause_control_frame, text="30", 
                                    font=("Courier", 14, "bold"), fg="lightcoral", bg="black",
                                    width=8, relief="sunken", bd=2)
        self.pause_display.grid(row=1, column=1, padx=2, pady=2)
        
        # Pause down button
        self.pause_down_btn = tk.Button(pause_control_frame, text="▼", 
                                      font=("Arial", 12, "bold"), bg="lightgray", fg="black",
                                      width=3, height=1, command=self.pause_time_down)
        self.pause_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, padx=(0, 15))
        
        
        # Countdown and status display
        status_display_frame = tk.Frame(tx_frame, bg='black')
        status_display_frame.pack(pady=(2, 2))  # Reduced padding

        # Single line for countdown and status
        self.status_line_frame = tk.Frame(status_display_frame, bg='black')
        self.status_line_frame.pack()

        self.countdown_label = tk.Label(self.status_line_frame, text="", 
                                      font=("Courier", 14, "bold"), fg="yellow", bg="black")
        self.countdown_label.pack(side=tk.LEFT, padx=(0, 10))

        self.tx_status_label = tk.Label(self.status_line_frame, text="NOT TRANSMITTING", 
                                      font=("Courier", 14, "bold"), fg="red", bg="black")
        self.tx_status_label.pack(side=tk.LEFT)
        
        # GPIO Status Indicators with PPS
        gpio_indicators_frame = tk.Frame(status_display_frame, bg='black')
        gpio_indicators_frame.pack(pady=(5, 2))
        
        # PPS indicator (GPIO 17)
        pps_frame = tk.Frame(gpio_indicators_frame, bg='black')
        pps_frame.pack(side=tk.LEFT, padx=(0, 15))
        
        tk.Label(pps_frame, text="PPS", font=("Arial", 8), 
                fg="white", bg="black").pack()
        self.pps_indicator = tk.Label(pps_frame, text="●", font=("Arial", 32), 
                                     fg="gray", bg="black")
        self.pps_indicator.pack()
        
        # 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()

        # --- Recording Indicator (moved) ---
        recording_frame = tk.Frame(gpio_indicators_frame, bg='black')
        recording_frame.pack(side=tk.LEFT, padx=(15, 5))

        tk.Label(recording_frame, text="Rec", font=("Arial", 8), 
                fg="white", bg="black").pack()
        self.recording_indicator = tk.Label(recording_frame, text="●", font=("Arial", 32), 
                                          fg="gray", bg="black")
        self.recording_indicator.pack()

        # --- Log Size Displays ---
        logsize_frame = tk.Frame(gpio_indicators_frame, bg='black')
        logsize_frame.pack(side=tk.LEFT, padx=(10, 0))

        self.logsize_label = tk.Label(logsize_frame, text="Log: -- KB", font=("Arial", 9), 
                                    fg="lightgray", bg="black")
        self.logsize_label.pack(anchor='w')

        self.gpslogsize_label = tk.Label(logsize_frame, text="GPS Log: -- KB", font=("Arial", 9), 
                                       fg="lightgray", bg="black")
        self.gpslogsize_label.pack(anchor='w')

    def start_threads(self):
        # GPS and RTC threads
        #self.gps_reader = GPSReader('/dev/ttyUSB0', 4800, self.update_gps_data, #For USB GPS
        #                          self.update_status, self.handle_sync) #For USB GPS
        self.gps_reader = GPSReader('/dev/ttyAMA0', 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()

        # Start PPS monitoring
        self.gpio_controller.start_pps_monitoring(self.update_pps_indicator)

        # Queue processing
        self.process_rtc_queue()

    # Transmit/Pause time control methods
    def transmit_time_up(self):
        """Increase transmit time"""
        if not self.gui_initialized:
            return
        self.transmit_time = min(3600, self.transmit_time + 1)  # Max 1 hour, increment by 1 seconds
        self.transmit_display.config(text=str(self.transmit_time))
        print(f"[TIMING] Transmit time: {self.transmit_time}s")

    def transmit_time_down(self):
        """Decrease transmit time"""
        if not self.gui_initialized:
            return
        self.transmit_time = max(1, self.transmit_time - 1)  # Min 1 seconds, decrement by 1 seconds
        self.transmit_display.config(text=str(self.transmit_time))
        print(f"[TIMING] Transmit time: {self.transmit_time}s")

    def pause_time_up(self):
        """Increase pause time"""
        if not self.gui_initialized:
            return
        self.pause_time = min(3600, self.pause_time + 1)  # Max 1 hour, increment by 1 seconds
        self.pause_display.config(text=str(self.pause_time))
        print(f"[TIMING] Pause time: {self.pause_time}s")

    def pause_time_down(self):
        """Decrease pause time"""
        if not self.gui_initialized:
            return
        self.pause_time = max(0, self.pause_time - 1)  # Min 0 seconds, decrement by 1 seconds
        self.pause_display.config(text=str(self.pause_time))
        print(f"[TIMING] Pause time: {self.pause_time}s")

    def update_pps_indicator(self, pps_state):
        """Update PPS indicator based on GPIO 17 state"""
        if self.gui_initialized:
            self.root.after(0, lambda: self.pps_indicator.config(fg="yellow" if pps_state else "gray"))

    def start_transmission_cycle(self):
        """Start the transmit/pause cycle"""
        if self.cycle_state != 'idle':
            return
            
        print(f"[CYCLE] Starting transmission cycle: {self.transmit_time}s ON, {self.pause_time}s OFF")
        self.cycle_state = 'transmitting'
        self._start_gpio_transmission()
        
        # Log transmission start
        if self.data_logger.is_logging_active():
            frequency = self.frequency_controller.get_frequency()
            self.data_logger.log_transmission_start(frequency)
        
        # Schedule pause after transmit time
        self.cycle_timer = self.root.after(self.transmit_time * 1000, self._start_pause_cycle)

    def _start_gpio_transmission(self):
        """Start actual GPIO transmission"""
        frequency = self.frequency_controller.get_frequency()
        
        # Set up indicator callbacks
        indicator_callbacks = {
            'onoff': lambda state: self.root.after(0, lambda: self.onoff_indicator.config(fg="lightblue" if state else "gray")) if self.gui_initialized else None,
            'duty': lambda state: self.root.after(0, lambda: self.duty_indicator.config(fg="lightblue" if state else "gray")) if self.gui_initialized else None,
            'polarity': lambda state: self.root.after(0, lambda: self.polarity_indicator.config(fg="lightblue" if state else "gray")) if self.gui_initialized else None
        }
        
        # Start GPIO transmission
        self.gpio_controller.start_transmission(frequency, indicator_callbacks)
        if self.gui_initialized:
            self.tx_status_label.config(text="TRANSMITTING", fg="green")

    def _start_pause_cycle(self):
        """Start pause cycle"""
        if self.cycle_state != 'transmitting':
            return
            
        print(f"[CYCLE] Starting pause cycle: {self.pause_time}s")
        self.cycle_state = 'pausing'
        
        # Log pause start
        if self.data_logger.is_logging_active():
            self.data_logger.log_pause_start(self.pause_time)
        
        # Stop GPIO transmission
        indicator_callbacks = {
            'onoff': lambda state: self.root.after(0, lambda: self.onoff_indicator.config(fg="lightblue" if state else "gray")) if self.gui_initialized else None,
            'duty': lambda state: self.root.after(0, lambda: self.duty_indicator.config(fg="lightblue" if state else "gray")) if self.gui_initialized else None,
            'polarity': lambda state: self.root.after(0, lambda: self.polarity_indicator.config(fg="lightblue" if state else "gray")) if self.gui_initialized else None
        }
        
        self.gpio_controller.stop_transmission(indicator_callbacks)
        if self.gui_initialized:
            self.tx_status_label.config(text="PAUSED", fg="orange")
        
        # Schedule next transmission after pause time
        if self.pause_time > 0:
            self.cycle_timer = self.root.after(self.pause_time * 1000, self._resume_transmission_cycle)
        else:
            # If pause time is 0, immediately resume
            self._resume_transmission_cycle()

    def _resume_transmission_cycle(self):
        """Resume transmission after pause"""
        if self.cycle_state != 'pausing':
            return
            
        print(f"[CYCLE] Resuming transmission cycle")
        
        # Log pause completion
        if self.data_logger.is_logging_active():
            self.data_logger.log_pause_complete()
        
        self.cycle_state = 'transmitting'
        self._start_gpio_transmission()
        
        # Schedule next pause
        self.cycle_timer = self.root.after(self.transmit_time * 1000, self._start_pause_cycle)

    def _stop_transmission_cycle(self):
        """Stop the transmission cycle"""
        was_active = self.cycle_state != 'idle'
        self.cycle_state = 'idle'
        
        # Cancel any pending timer
        if self.cycle_timer:
            self.root.after_cancel(self.cycle_timer)
            self.cycle_timer = None
        
        # Stop GPIO transmission
        indicator_callbacks = {
            'onoff': lambda state: self.root.after(0, lambda: self.onoff_indicator.config(fg="lightblue" if state else "gray")) if self.gui_initialized else None,
            'duty': lambda state: self.root.after(0, lambda: self.duty_indicator.config(fg="lightblue" if state else "gray")) if self.gui_initialized else None,
            'polarity': lambda state: self.root.after(0, lambda: self.polarity_indicator.config(fg="lightblue" if state else "gray")) if self.gui_initialized else None
        }
        
        self.gpio_controller.stop_transmission(indicator_callbacks)
        if self.gui_initialized:
            self.tx_status_label.config(text="")
        
        # Log manual stop if it was active
        if was_active and self.data_logger.is_logging_active():
            self.data_logger.log_manual_stop()
        
        print("[CYCLE] Transmission cycle stopped")

    def update_time_displays(self, cached_times):
        """Update the time display labels with cached synchronized times"""
        if self.gui_initialized:
            # 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)"""
        if not self.gui_initialized:
            return
            
        # 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
        if self.gui_initialized:
            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"""
        # Store GPS data for logging
        self.current_gps_data = {
            'lat': data['lat'],
            'lon': data['lon'],
            'sats': data['sats']
        }
        
        # 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)
        
        if not self.gui_initialized:
            return
            
        # 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.gui_initialized and (not self.sync_controller or 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"""
        if not self.gui_initialized:
            return
        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 or not self.gui_initialized:
            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"""
        if not self.gui_initialized:
            return
        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"""
        if not self.gui_initialized:
            return
        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"""
        if not self.gui_initialized:
            return
        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"""
        if not self.gui_initialized:
            return
        self.frequency_controller.frequency_up()

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

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

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

    def start_countdown(self):
        """Start countdown to next minute based on RTC time"""
        if not self.gui_initialized:
            return
            
        # Start data logging
        cached_times = self.timing_controller.get_cached_times()
        log_success = self.data_logger.start_logging(
            frequency=self.frequency_controller.get_frequency(),
            transmit_time=self.transmit_time,
            pause_time=self.pause_time,
            rpi_time=cached_times.get('rpi'),
            gps_time=cached_times.get('gps'),
            rtc_time=cached_times.get('rtc'),
            lat=self.current_gps_data.get('lat'),
            lon=self.current_gps_data.get('lon'),
            sats=self.current_gps_data.get('sats')
        )
        
        gps_log_path = self.gps_reader.get_gps_log_file()
        self.data_logger.set_gps_log_path(gps_log_path)

        
        if log_success:
            self.recording_indicator.config(fg="red")
            print(f"[LOG] Started logging to: {self.data_logger.get_current_log_file()}")
        
        success = self.timing_controller.start_countdown(
            countdown_callback=self.update_countdown_display,
            transmission_start_callback=self.start_transmission_cycle  # Changed to use cycle
        )
        
        if success:
            self.start_button.config(state="disabled")
            self.stop_button.config(state="normal")

    def update_countdown_display(self, countdown_text):
        """Update countdown display"""
        if not self.gui_initialized:
            return
            
        # Log countdown events
        if self.data_logger.is_logging_active():
            self.data_logger.log_countdown(countdown_text)
        
        # Clear countdown text when it reaches zero
        if "00:00" in countdown_text:
            self.countdown_label.config(text="")
        else:
            self.countdown_label.config(text=countdown_text)

    def start_transmission(self):
        """Legacy method - now redirects to cycle"""
        self.start_transmission_cycle()

    def stop_transmission(self):
        """Stop GPIO transmission"""
        if not self.gui_initialized:
            return
            
        # Stop timing controller countdown
        self.timing_controller.stop_countdown()
        
        # Stop transmission cycle
        self._stop_transmission_cycle()
        
        # Stop data logging
        if self.data_logger.is_logging_active():
            self.data_logger.stop_logging()
            self.recording_indicator.config(fg="gray")
        
        # Update GUI
        self.transmitting = False
        self.start_button.config(state="normal")
        self.stop_button.config(state="disabled")
        self.countdown_label.config(text="")  # Clear countdown when stopping
        
        print("[TRANSMISSION] Stopped")

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

    def on_close(self):
        """Clean up and close application"""
        self.transmitting = False
        self.gui_initialized = False  # Prevent further GUI updates
        
        # Stop data logging
        if hasattr(self, 'data_logger') and self.data_logger.is_logging_active():
            self.data_logger.stop_logging()
        
        # Stop transmission cycle
        self._stop_transmission_cycle()
        
        # 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()