import tkinter as tk
from tkinter import ttk
from datetime import datetime, timedelta
import threading
import queue
import time

try:
    import gpiod
    GPIO_AVAILABLE = True
except ImportError:
    GPIO_AVAILABLE = False
    print("Warning: gpiod not available, GPIO functions disabled")

from rtc import DS3231
from gps_reader import GPSReader
from rtc_reader import RTCReader

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')

        self.rtc = DS3231()
        self.gps_time_obj = None
        self.gps_sats = 0  # Track number of satellites
        self.rtc_queue = queue.Queue()
        self.sync_in_progress = False
        
        # Transmission control
        self.transmitting = False
        self.countdown_active = False
        self.countdown_seconds = 0
        self.transmission_thread = None
        self.frequency_output_thread = None
        self.gpio26_thread = None
        
        # GPIO pins
        self.tx_status_pin = 13  # GPIO 13 (Pin 33) - HIGH when transmitting
        self.frequency_pin = 6   # GPIO 6 (Pin 31) - toggles at 2x frequency
        self.gpio26_pin = 26     # GPIO 26 - toggles at selected frequency
        self.gpio_state = False
        self.gpio26_state = False
        
        # Frequency control variables
        self.frequency_value = 1.0
        self.decimal_place = 0  # 0=ones, 1=tenths, 2=hundredths, etc.
        
        # Initialize GPIO if available
        self.gpio_chip = None
        self.tx_status_line = None
        self.frequency_line = None
        self.gpio26_line = None
        self.gpio_available = GPIO_AVAILABLE
        
        if self.gpio_available:
            try:
                self.gpio_chip = gpiod.Chip('gpiochip4')  # Pi 5 uses gpiochip4
                
                # Initialize TX status pin (GPIO 13)
                self.tx_status_line = self.gpio_chip.get_line(self.tx_status_pin)
                self.tx_status_line.request(consumer="tx_status", type=gpiod.LINE_REQ_DIR_OUT)
                self.tx_status_line.set_value(0)  # Start LOW
                
                # Initialize frequency output pin (GPIO 6)
                self.frequency_line = self.gpio_chip.get_line(self.frequency_pin)
                self.frequency_line.request(consumer="frequency_output", type=gpiod.LINE_REQ_DIR_OUT)
                self.frequency_line.set_value(0)  # Start LOW
                
                # Initialize GPIO 26 pin
                self.gpio26_line = self.gpio_chip.get_line(self.gpio26_pin)
                self.gpio26_line.request(consumer="gpio26_output", type=gpiod.LINE_REQ_DIR_OUT)
                self.gpio26_line.set_value(0)  # Start LOW
                
                print(f"[GPIO] Initialized pins - TX Status: {self.tx_status_pin}, Frequency: {self.frequency_pin}, GPIO26: {self.gpio26_pin}")
            except Exception as e:
                print(f"[GPIO] Failed to initialize: {e}")
                self.gpio_available = False

        # Synchronized timing control
        self.last_display_second = -1
        self.display_lock = threading.Lock()
        
        # Cache for time values to ensure synchronization
        self.cached_times = {
            'rpi': None,
            'gps': None,
            'rtc': None,
            'timestamp': 0
        }
        
        # High precision timing for synchronization
        self.sync_timer_thread = None
        self.sync_timer_running = False
        
        self.setup_gui()
        self.start_threads()
        self.start_synchronized_timing()
        
        # Initialize frequency display
        self.update_frequency_display()

        # 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 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()

        # Queue processing
        self.process_rtc_queue()

    def start_synchronized_timing(self):
        """Start synchronized timing system with precise second boundaries"""
        self.sync_timer_running = True
        self.sync_timer_thread = threading.Thread(target=self.precision_sync_loop, daemon=True)
        self.sync_timer_thread.start()
        # Remove the separate countdown timer - it's now handled in the sync loop

    def precision_sync_loop(self):
        """High precision timing loop that synchronizes all time readings"""
        import time as time_module
        
        while self.sync_timer_running:
            # Wait for the next second boundary with high precision
            now = datetime.utcnow()
            
            # Calculate microseconds until next second
            microseconds_to_next_second = 1000000 - now.microsecond
            sleep_time = microseconds_to_next_second / 1000000.0
            
            # Sleep until just before the second boundary
            if sleep_time > 0.01:  # Only sleep if we have more than 10ms
                time_module.sleep(sleep_time - 0.005)  # Wake up 5ms early
            
            # Busy wait for the exact second boundary
            while True:
                precise_now = datetime.utcnow()
                if precise_now.microsecond < 10000:  # Within 10ms of second boundary
                    break
                time_module.sleep(0.001)  # 1ms sleep in busy wait
            
            # NOW capture all times at the exact same moment
            self.capture_synchronized_times(precise_now)
            
            # Small sleep to avoid immediate re-trigger
            time_module.sleep(0.1)

    def capture_synchronized_times(self, reference_time):
        """Capture all time sources at the exact same moment"""
        current_second = reference_time.second
        
        # Only update when the second actually changes
        if current_second != self.last_display_second:
            with self.display_lock:
                self.last_display_second = current_second
                
                # Capture all times at this exact moment
                rpi_time = reference_time
                
                # For GPS time, if we have a recent GPS time, project it forward to this exact second
                gps_time = None
                if self.gps_time_obj:
                    try:
                        # Ensure both times are timezone-naive for comparison
                        ref_time_naive = reference_time.replace(tzinfo=None) if reference_time.tzinfo else reference_time
                        gps_time_naive = self.gps_time_obj.replace(tzinfo=None) if self.gps_time_obj.tzinfo else self.gps_time_obj
                        
                        # Calculate how old the GPS time is
                        time_diff = (ref_time_naive - gps_time_naive).total_seconds()
                        if abs(time_diff) < 5:  # Only use if GPS time is less than 5 seconds old
                            # Project GPS time forward to match current second boundary
                            seconds_to_add = round(time_diff)
                            gps_time = gps_time_naive + timedelta(seconds=seconds_to_add)
                        else:
                            gps_time = gps_time_naive  # Use as-is if too old to project
                    except (TypeError, AttributeError) as e:
                        print(f"[GPS SYNC ERROR] {e}")
                        gps_time = self.gps_time_obj  # Fall back to original GPS time
                
                # Get fresh RTC time - this is the critical sync point
                rtc_time = None
                try:
                    rtc_time = self.rtc.read_time()
                except Exception as e:
                    print(f"[RTC ERROR] {e}")
                
                # Update cache with synchronized times
                self.cached_times = {
                    'rpi': rpi_time,
                    'gps': gps_time,
                    'rtc': rtc_time,
                    'timestamp': time.time()
                }
                
                # Schedule GUI update on main thread
                self.root.after(0, self.update_time_displays)
                
                # Update countdown synchronized to RTC time
                if self.countdown_active and rtc_time:
                    self.root.after(0, lambda: self.update_countdown_sync(rtc_time))

    def update_time_displays(self):
        """Update the time display labels with cached synchronized times"""
        cached = self.cached_times
        
        # Update RPI time
        if cached['rpi']:
            rpi_time_str = cached['rpi'].strftime("%Y-%m-%d %H:%M:%S")
            # Add microseconds for debugging sync accuracy
            rpi_debug = cached['rpi'].strftime("%Y-%m-%d %H:%M:%S.%f")[:23]  # Include milliseconds
            self.rpi_time_label.config(text=f"RPI Time (UTC): {rpi_time_str}")
        
        # Update GPS time
        if cached['gps']:
            gps_time_str = cached['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['rtc']:
            rtc_time_str = cached['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")
        
        # Debug output to console for sync verification
        if cached['rpi'] and cached['rtc']:
            rpi_total_seconds = cached['rpi'].hour * 3600 + cached['rpi'].minute * 60 + cached['rpi'].second
            rtc_total_seconds = cached['rtc'].hour * 3600 + cached['rtc'].minute * 60 + cached['rtc'].second
            time_diff = rpi_total_seconds - rtc_total_seconds
            
            # Handle day boundary crossings
            if time_diff > 43200:  # More than 12 hours difference
                time_diff -= 86400
            elif time_diff < -43200:  # Less than -12 hours difference  
                time_diff += 86400
                
            if abs(time_diff) > 1:
                print(f"[SYNC DEBUG] Time difference: RPI-RTC = {time_diff} seconds")
        
        # Debug GPS sync
        if cached['rpi'] and cached['gps']:
            rpi_total_seconds = cached['rpi'].hour * 3600 + cached['rpi'].minute * 60 + cached['rpi'].second
            gps_total_seconds = cached['gps'].hour * 3600 + cached['gps'].minute * 60 + cached['gps'].second
            gps_diff = rpi_total_seconds - gps_total_seconds
            
            # Handle day boundary crossings
            if gps_diff > 43200:
                gps_diff -= 86400
            elif gps_diff < -43200:
                gps_diff += 86400
                
            if abs(gps_diff) > 1:
                print(f"[SYNC DEBUG] GPS difference: RPI-GPS = {gps_diff} seconds")

    def update_countdown_sync(self, rtc_time):
        """Update countdown display synchronized to RTC time"""
        if self.countdown_active:
            # Simple countdown: seconds remaining until next minute (00 seconds)
            remaining_seconds = 60 - rtc_time.second
            
            # Handle the case where we're exactly at 00 seconds
            if rtc_time.second == 0:
                remaining_seconds = 0
            
            if remaining_seconds > 0:
                mins, secs = divmod(remaining_seconds, 60)
                countdown_text = f"Countdown: {mins:02d}:{secs:02d}"
                self.countdown_label.config(text=countdown_text)
            else:
                # Time to start transmission (we've hit the minute boundary)
                self.countdown_label.config(text="Countdown: 00:00")
                self.countdown_active = False
                self.start_transmission()

    def update_countdown(self):
        """Legacy countdown method - now unused but kept for compatibility"""
        pass  # Countdown is now handled in the synchronized timing loop

    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"""
        try:
            self.rtc.write_time(sync_time)
            self.root.after(0, lambda: self.sync_success(sync_time))
            print(f"[SYNC] Successfully wrote {sync_time} to RTC at second boundary")
        except Exception as e:
            self.root.after(0, lambda: self.sync_error(e))
            print(f"[SYNC ERROR] {e}")

    def sync_success(self, sync_time):
        """Called in main thread after successful sync"""
        sync_str = sync_time.strftime("%Y-%m-%d %H:%M:%S")
        self.status_label.config(text="RTC synced successfully!", fg="green")
        self.sync_status_label.config(text=f"Last sync: {sync_str}", fg="lightblue")
        self.sync_button.config(state="normal")
        self.sync_in_progress = False

    def sync_error(self, error):
        """Called in main thread after sync error"""
        self.status_label.config(text="RTC sync failed!", fg="red")
        self.sync_status_label.config(text=f"Sync error: {str(error)}", fg="red")
        self.sync_button.config(state="normal")
        self.sync_in_progress = False

    def update_gps_data(self, data):
        with self.display_lock:
            self.gps_time_obj = data['gps_datetime']
        
        # 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 satellite count for sync validation
        try:
            self.gps_sats = int(data['sats']) if data['sats'] != 'N/A' else 0
        except (ValueError, TypeError):
            self.gps_sats = 0
        
        if not self.sync_in_progress:
            self.status_label.config(text="GPS OK", fg="green")

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

    def sync_rtc_time(self):
        if not self.gps_time_obj:
            self.status_label.config(text="No GPS time available.", fg="red")
            return
            
        if self.gps_sats < 4:
            self.status_label.config(text="Not enough satellites (need 4+)", fg="red")
            return
            
        if self.sync_in_progress:
            self.status_label.config(text="Sync already in progress...", fg="orange")
            return
            
        # Start sync process
        self.sync_in_progress = True
        self.sync_button.config(state="disabled")
        self.status_label.config(text="Waiting for GPS second boundary...", fg="orange")
        self.sync_status_label.config(text="Sync in progress...", fg="orange")
        self.gps_reader.request_sync()

    def start_countdown(self):
        """Start countdown to next minute based on RTC time"""
        try:
            rtc_time = self.rtc.read_time()
            
            # Simple countdown: count down the remaining seconds in the current minute
            remaining_seconds = 60 - rtc_time.second
            
            # Handle edge case where we're exactly at 00 seconds
            if rtc_time.second == 0:
                remaining_seconds = 60  # Full minute countdown
            
            self.countdown_active = True
            
            self.start_button.config(state="disabled")
            self.stop_button.config(state="normal")
            
            print(f"[COUNTDOWN] Starting {remaining_seconds} second countdown to next minute boundary")
            
        except Exception as e:
            self.status_label.config(text=f"Error reading RTC for countdown: {e}", fg="red")

    def start_transmission(self):
        """Start GPIO transmission at specified frequency"""
        try:
            frequency = self.frequency_value
            if frequency <= 0:
                raise ValueError("Frequency must be positive")
                
            self.transmitting = True
            self.tx_status_label.config(text="TRANSMITTING", fg="green")
            self.countdown_label.config(text="")
            
            # Set TX status pin HIGH
            if self.gpio_available and self.tx_status_line:
                try:
                    self.tx_status_line.set_value(1)
                    self.onoff_indicator.config(fg="lightblue")  # Turn indicator ON
                    print(f"[GPIO] TX Status pin {self.tx_status_pin} set HIGH")
                except Exception as e:
                    print(f"[GPIO ERROR] Failed to set TX status HIGH: {e}")
            
            # Start frequency output thread (toggles at 2x frequency)
            self.frequency_output_thread = threading.Thread(
                target=self.frequency_output_loop, 
                args=(frequency * 2,),  # Double the frequency for toggling
                daemon=True
            )
            self.frequency_output_thread.start()
            
            # Start GPIO 26 thread (toggles at selected frequency)
            self.gpio26_thread = threading.Thread(
                target=self.gpio26_loop, 
                args=(frequency,),  # Normal frequency for GPIO 26
                daemon=True
            )
            self.gpio26_thread.start()
            
            print(f"[TRANSMISSION] Started - Status pin HIGH, Frequency pin toggling at {frequency * 2} Hz, GPIO26 toggling at {frequency} Hz")
            
        except ValueError as e:
            self.status_label.config(text=f"Invalid frequency: {e}", fg="red")
            self.stop_transmission()

    def gpio26_loop(self, frequency):
        """GPIO 26 output loop running in separate thread"""
        if not self.gpio_available:
            print("[GPIO] GPIO not available, simulating GPIO 26 output")
            
        half_period = 0.5 / frequency  # Half period for toggle timing
        
        while self.transmitting:
            if self.gpio_available and self.gpio26_line:
                try:
                    self.gpio26_state = not self.gpio26_state
                    self.gpio26_line.set_value(1 if self.gpio26_state else 0)
                    # Update polarity indicator on main thread
                    self.root.after(0, lambda state=self.gpio26_state: 
                                   self.polarity_indicator.config(fg="lightblue" if state else "gray"))
                except Exception as e:
                    print(f"[GPIO ERROR] GPIO 26 output: {e}")
                    break
            else:
                # Simulate for testing without GPIO
                self.gpio26_state = not self.gpio26_state
                # Update polarity indicator for simulation
                self.root.after(0, lambda state=self.gpio26_state: 
                               self.polarity_indicator.config(fg="lightblue" if state else "gray"))
                print(f"[GPIO SIM] GPIO26 pin {self.gpio26_pin}: {'HIGH' if self.gpio26_state else 'LOW'}")
            
            time.sleep(half_period)
    def frequency_output_loop(self, toggle_frequency):
        """Frequency output loop running in separate thread"""
        if not self.gpio_available:
            print("[GPIO] GPIO not available, simulating frequency output")
            
        half_period = 0.5 / toggle_frequency  # Half period for toggle timing
        
        while self.transmitting:
            if self.gpio_available and self.frequency_line:
                try:
                    self.gpio_state = not self.gpio_state
                    self.frequency_line.set_value(1 if self.gpio_state else 0)
                    # Update duty indicator on main thread
                    self.root.after(0, lambda state=self.gpio_state: 
                                   self.duty_indicator.config(fg="lightblue" if state else "gray"))
                except Exception as e:
                    print(f"[GPIO ERROR] Frequency output: {e}")
                    break
            else:
                # Simulate for testing without GPIO
                self.gpio_state = not self.gpio_state
                # Update duty indicator for simulation
                self.root.after(0, lambda state=self.gpio_state: 
                               self.duty_indicator.config(fg="lightblue" if state else "gray"))
                print(f"[GPIO SIM] Frequency pin {self.frequency_pin}: {'HIGH' if self.gpio_state else 'LOW'}")
            
            time.sleep(half_period)

    def stop_transmission(self):
        """Stop GPIO transmission"""
        # Stop the transmission
        self.transmitting = False
        self.countdown_active = False
        
        # Wait for frequency output thread to stop
        if self.frequency_output_thread and self.frequency_output_thread.is_alive():
            self.frequency_output_thread.join(timeout=0.5)
        
        # Wait for GPIO 26 thread to stop
        if self.gpio26_thread and self.gpio26_thread.is_alive():
            self.gpio26_thread.join(timeout=0.5)
        
        # Set all GPIO pins LOW
        if self.gpio_available:
            if self.tx_status_line:
                try:
                    self.tx_status_line.set_value(0)  # TX status LOW
                    self.onoff_indicator.config(fg="gray")  # Turn indicator OFF
                    print(f"[GPIO] TX Status pin {self.tx_status_pin} set LOW")
                except Exception as e:
                    print(f"[GPIO ERROR] Failed to set TX status LOW: {e}")
            
            if self.frequency_line:
                try:
                    self.frequency_line.set_value(0)  # Frequency output LOW
                    self.gpio_state = False
                    self.duty_indicator.config(fg="gray")  # Turn indicator OFF
                    print(f"[GPIO] Frequency pin {self.frequency_pin} set LOW")
                except Exception as e:
                    print(f"[GPIO ERROR] Failed to set frequency output LOW: {e}")
            
            if self.gpio26_line:
                try:
                    self.gpio26_line.set_value(0)  # GPIO 26 LOW
                    self.gpio26_state = False
                    self.polarity_indicator.config(fg="gray")  # Turn indicator OFF
                    print(f"[GPIO] GPIO26 pin {self.gpio26_pin} set LOW")
                except Exception as e:
                    print(f"[GPIO ERROR] Failed to set GPIO26 LOW: {e}")
        else:
            # Update indicators even when GPIO not available
            self.onoff_indicator.config(fg="gray")
            self.duty_indicator.config(fg="gray")
            self.polarity_indicator.config(fg="gray")
        
        # Update GUI
        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 - All GPIO pins set LOW")

    def update_frequency_display(self):
        """Update the frequency display and decimal place indicator"""
        self.freq_display.config(text=f"{self.frequency_value:.3f}")
        
        # Calculate and display period (shortened format)
        period = 1.0 / self.frequency_value
        self.period_display.config(text=f"Period: {period:.3f}s")
        
        places = ["Ones", "Tenths", "Hundredths", "Thousandths"]
        place_name = places[min(self.decimal_place, len(places)-1)]
        self.decimal_indicator.config(text=f"Editing: {place_name} place")

    def freq_up(self):
        """Increase frequency at current decimal place"""
        increment = 10 ** (-self.decimal_place)
        self.frequency_value = round(self.frequency_value + increment, 3)
        self.frequency_value = min(self.frequency_value, 999.999)  # Max limit
        self.update_frequency_display()

    def freq_down(self):
        """Decrease frequency at current decimal place"""
        increment = 10 ** (-self.decimal_place)
        self.frequency_value = round(self.frequency_value - increment, 3)
        self.frequency_value = max(self.frequency_value, 0.001)  # Min limit
        self.update_frequency_display()

    def freq_left(self):
        """Move decimal place to the left (higher significance)"""
        self.decimal_place = max(0, self.decimal_place - 1)
        self.update_frequency_display()

    def freq_right(self):
        """Move decimal place to the right (lower significance)"""
        self.decimal_place = min(3, self.decimal_place + 1)  # Max 3 decimal places
        self.update_frequency_display()

    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):
        self.transmitting = False
        self.countdown_active = False
        self.sync_timer_running = False
        
        # Wait for sync timer thread to stop
        if self.sync_timer_thread and self.sync_timer_thread.is_alive():
            self.sync_timer_thread.join(timeout=1.0)
        
        # Clean up GPIO pins
        if self.gpio_available:
            if self.tx_status_line:
                try:
                    self.tx_status_line.set_value(0)  # Ensure LOW before cleanup
                    self.tx_status_line.release()
                except Exception as e:
                    print(f"[GPIO] TX status cleanup error: {e}")
            
            if self.frequency_line:
                try:
                    self.frequency_line.set_value(0)  # Ensure LOW before cleanup
                    self.frequency_line.release()
                except Exception as e:
                    print(f"[GPIO] Frequency output cleanup error: {e}")
            
            if self.gpio26_line:
                try:
                    self.gpio26_line.set_value(0)  # Ensure LOW before cleanup
                    self.gpio26_line.release()
                except Exception as e:
                    print(f"[GPIO] GPIO26 cleanup error: {e}")
        
        if self.gpio_chip:
            try:
                self.gpio_chip.close()
            except Exception as e:
                print(f"[GPIO] Chip close error: {e}")
            
        self.gps_reader.stop()
        self.rtc_reader.stop()
        self.root.destroy()