import queue
import os
from datetime import datetime

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

class ApplicationController:
    """Main application controller that coordinates all subsystems with separate logging"""
    
    def __init__(self, gui_callbacks=None, log_directory="/home/morepork/Documents/CSEM/logs"):
        self.gui_callbacks = gui_callbacks or {}
        self.log_directory = log_directory
        
        # Initialize hardware interfaces
        self.rtc = DS3231()
        self.rtc_queue = queue.Queue()
        
        # Application state
        self.initialized = False
        
        # Current GPS data for logging
        self.current_gps_data = {
            'lat': 'N/A',
            'lon': 'N/A', 
            'sats': 'N/A'
        }
        
        # Initialize all controllers
        self._init_controllers()
        
        # Thread references
        self.gps_reader = None
        self.rtc_reader = None
        
    def _init_controllers(self):
        """Initialize all controller objects"""
        # Data logger for MAIN application events
        self.data_logger = DataLogger(self.log_directory)
        
        # GPIO Controller
        self.gpio_controller = GPIOController(
            status_callback=self._gpio_status_callback
        )
        
        # Timing Controller
        self.timing_controller = TimingController(
            rtc=self.rtc,
            update_callback=self._time_update_callback
        )
        
        # Frequency Controller
        self.frequency_controller = FrequencyController(
            initial_frequency=1.0,
            update_callback=self._frequency_update_callback
        )
        
        # Transmission Controller
        self.transmission_controller = TransmissionController(
            gpio_controller=self.gpio_controller,
            data_logger=self.data_logger,
            status_callback=self._transmission_status_callback
        )
        
        # Sync Controller (will be initialized after GPS reader is created)
        self.sync_controller = None
    
    def initialize(self):
        """Initialize the application and start threads"""
        if self.initialized:
            return True
            
        try:
            # Start threads
            self._start_threads()
            
            # Start timing controller
            self.timing_controller.start_synchronized_timing()
            
            # Start PPS monitoring
            self.gpio_controller.start_pps_monitoring(self._pps_update_callback)
            
            self.initialized = True
            self._status_callback("Application initialized successfully")
            
            # Log system initialization
            if self.data_logger:
                self.data_logger.log_system_event("INIT_SUCCESS", "Application initialized successfully")
            
            return True
            
        except Exception as e:
            error_msg = f"Failed to initialize: {e}"
            self._status_callback(error_msg)
            
            # Try to log initialization failure
            if self.data_logger:
                try:
                    self.data_logger.log_system_event("INIT_FAILURE", str(e))
                except:
                    pass  # Don't fail completely if logging fails
            
            return False
    
    def _start_threads(self):
        """Start GPS and RTC reader threads"""
        # GPS Reader with separate logging
        self.gps_reader = GPSReader(
            '/dev/ttyAMA0', 4800, 
            self._gps_data_callback, 
            self._status_callback, 
            self._sync_callback,
            log_directory=self.log_directory  # Pass log directory to GPS reader
        )
        self.gps_reader.start()
        self.data_logger.set_gps_reader(self.gps_reader) #Read data logger for final coordinates
        
        # RTC Reader
        self.rtc_reader = RTCReader(self.rtc, self._rtc_queue_callback)
        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._sync_status_callback
        )
    
    def register_gui_callbacks(self, callbacks):
        """Register GUI callback functions"""
        self.gui_callbacks.update(callbacks)
        
        # Update controller callbacks
        self.frequency_controller.update_callback = self._frequency_update_callback
        self.transmission_controller.register_gui_callbacks(self.gui_callbacks)
    
    def start_countdown_and_transmission(self):
        """Start countdown and transmission sequence"""
        if not self.initialized:
            return False
        
        # Start MAIN application 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.transmission_controller.transmit_time,
            pause_time=self.transmission_controller.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')
        )
        
        if log_success:
            # Set GPS log path from GPS reader (GPS logs are separate)
            gps_log_path = self.gps_reader.get_gps_log_file()
            self.data_logger.set_gps_log_path(gps_log_path)
            
            # Update GUI recording indicator
            if 'recording_indicator' in self.gui_callbacks:
                self.gui_callbacks['recording_indicator'](True)
            
            main_log_file = self.data_logger.get_current_log_file()
            self._status_callback(f"Started logging - Main: {os.path.basename(main_log_file)}, GPS: {os.path.basename(gps_log_path) if gps_log_path else 'Separate'}")
        
        # Update transmission controller with current settings
        self.transmission_controller.set_frequency(self.frequency_controller.get_frequency())
        
        # Start countdown
        success = self.timing_controller.start_countdown(
            countdown_callback=self._countdown_callback,
            transmission_start_callback=self.transmission_controller.start_transmission_cycle
        )
        
        if success and self.data_logger.is_logging_active():
            self.data_logger.log_event("COUNTDOWN_START", f"Countdown started for {self.frequency_controller.get_frequency():.3f} Hz transmission")
        
        return success
    
    def stop_transmission(self):
        """Stop transmission and logging"""
        if not self.initialized:
            return
        
        # Log stop event before stopping
        if self.data_logger.is_logging_active():
            self.data_logger.log_event("TRANSMISSION_STOP_REQUESTED", "User requested transmission stop")
        
        # Stop timing controller countdown
        self.timing_controller.stop_countdown()
        
        # Stop transmission cycle
        self.transmission_controller.stop_transmission_cycle()
        
        # Stop MAIN data logging (GPS logging continues independently)
        if self.data_logger.is_logging_active():
            self.data_logger.stop_logging()
            if 'recording_indicator' in self.gui_callbacks:
                self.gui_callbacks['recording_indicator'](False)
    
    def sync_rtc_time(self, success_callback=None, error_callback=None):
        """Initiate GPS to RTC synchronization"""
        if not self.sync_controller:
            return False
        
        # Log sync attempt
        if self.data_logger.is_logging_active():
            self.data_logger.log_sync_event("ATTEMPT", "GPS to RTC sync requested")
            
        return self.sync_controller.sync_rtc_time(
            success_callback=success_callback,
            error_callback=error_callback
        )
    
    def update_transmit_time(self, transmit_time):
        """Update transmit time setting"""
        old_time = self.transmission_controller.transmit_time
        self.transmission_controller.transmit_time = transmit_time
        
        # Log parameter change
        if self.data_logger.is_logging_active() and old_time != transmit_time:
            self.data_logger.log_event("PARAM_CHANGE", f"Transmit time changed from {old_time}s to {transmit_time}s")
    
    def update_pause_time(self, pause_time):
        """Update pause time setting"""
        old_time = self.transmission_controller.pause_time
        self.transmission_controller.pause_time = pause_time
        
        # Log parameter change
        if self.data_logger.is_logging_active() and old_time != pause_time:
            self.data_logger.log_event("PARAM_CHANGE", f"Pause time changed from {old_time}s to {pause_time}s")
    
    def get_frequency_controller(self):
        """Get frequency controller for direct access"""
        return self.frequency_controller
    
    def get_transmission_state(self):
        """Get current transmission state"""
        if not self.initialized:
            return 'not_initialized'
        return self.transmission_controller.get_cycle_state()
    
    def is_sync_in_progress(self):
        """Check if sync is in progress"""
        return self.sync_controller and self.sync_controller.is_sync_in_progress()
    
    def get_log_files(self):
        """Get current log file paths"""
        return {
            'main_log': self.data_logger.get_current_log_file(),
            'gps_log': self.data_logger.get_current_gps_log_file()
        }
    
    def get_log_sizes(self):
        """Get current log file sizes"""
        sizes = {'main_log_kb': 0, 'gps_log_kb': 0}
        
        main_log = self.data_logger.get_current_log_file()
        if main_log and os.path.exists(main_log):
            sizes['main_log_kb'] = os.path.getsize(main_log) / 1024
        
        gps_log = self.data_logger.get_current_gps_log_file()  
        if gps_log and os.path.exists(gps_log):
            sizes['gps_log_kb'] = os.path.getsize(gps_log) / 1024
            
        return sizes
    
    def cleanup(self):
        """Clean up all resources"""
        if not self.initialized:
            return
            
        # Log cleanup start
        if self.data_logger.is_logging_active():
            self.data_logger.log_system_event("CLEANUP_START", "Application cleanup initiated")
        
        self.initialized = False
        
        # Stop transmission
        self.stop_transmission()
        
        # Stop controllers
        self.timing_controller.stop_synchronized_timing()
        self.gpio_controller.cleanup()
        self.transmission_controller.cleanup()
        
        # Stop MAIN data logging (GPS reader handles its own logging)
        if self.data_logger.is_logging_active():
            self.data_logger.log_system_event("CLEANUP_COMPLETE", "Application cleanup completed")
            self.data_logger.stop_logging()
        
        # Stop threads
        if self.gps_reader:
            self.gps_reader.stop()
        if self.rtc_reader:
            self.rtc_reader.stop()
    
    # Callback methods that bridge to GUI
    def _status_callback(self, msg):
        """Status update callback"""
        if 'status_update' in self.gui_callbacks:
            self.gui_callbacks['status_update'](msg, None)
        print(f"[APP] {msg}")
    
    def _gpio_status_callback(self, msg):
        """GPIO status callback"""
        # Log GPIO events to main log
        if self.data_logger.is_logging_active() and ("initialized" in msg.lower() or "error" in msg.lower()):
            self.data_logger.log_gpio_event("STATUS", msg)
        print(f"[GPIO] {msg}")
    
    def _transmission_status_callback(self, msg):
        """Transmission status callback"""
        # Log transmission events to main log
        if self.data_logger.is_logging_active():
            self.data_logger.log_event("TRANSMISSION_STATUS", msg)
        print(f"[TX] {msg}")
    
    def _time_update_callback(self, cached_times):
        """Time update callback"""
        if 'time_update' in self.gui_callbacks:
            self.gui_callbacks['time_update'](cached_times)
    
    def _frequency_update_callback(self, frequency, period, decimal_place_name):
        """Frequency update callback"""
        # Log frequency changes to main log
        if hasattr(self, '_last_logged_frequency'):
            if self.data_logger.is_logging_active() and abs(frequency - self._last_logged_frequency) > 0.001:
                self.data_logger.log_frequency_change(self._last_logged_frequency, frequency)
        self._last_logged_frequency = frequency
        
        if 'frequency_update' in self.gui_callbacks:
            self.gui_callbacks['frequency_update'](frequency, period, decimal_place_name)
    
    def _countdown_callback(self, countdown_text):
        """Countdown update callback"""
        # Log countdown events to MAIN log
        if self.data_logger.is_logging_active():
            self.data_logger.log_countdown(countdown_text)
        
        if 'countdown_update' in self.gui_callbacks:
            self.gui_callbacks['countdown_update'](countdown_text)
    
    def _pps_update_callback(self, pps_state):
        """PPS indicator update callback"""
        if 'pps_indicator' in self.gui_callbacks:
            self.gui_callbacks['pps_indicator'](pps_state)
    
    def _gps_data_callback(self, data):
        """GPS data update callback"""
        # Store GPS data for logging (positions are logged separately by GPS reader)
        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)
        
        # Update GUI
        if 'gps_update' in self.gui_callbacks:
            self.gui_callbacks['gps_update'](data)
        
        # Update status if not syncing
        if not self.is_sync_in_progress():
            if 'status_update' in self.gui_callbacks:
                self.gui_callbacks['status_update']("GPS OK", "green")
    
    def _sync_callback(self, sync_time):
        """Handle GPS sync callback"""
        if self.sync_controller:
            # Log sync attempt to main log
            if self.data_logger.is_logging_active():
                self.data_logger.log_sync_event("BOUNDARY_DETECTED", f"GPS second boundary detected at {sync_time.isoformat()}")
            
            self.sync_controller.handle_sync(sync_time)
    
    def _sync_status_callback(self, msg, color=None):
        """Sync status update callback"""
        # Log sync status to main log
        if self.data_logger.is_logging_active():
            if "success" in msg.lower():
                self.data_logger.log_sync_event("SUCCESS", msg)
            elif "error" in msg.lower() or "fail" in msg.lower():
                self.data_logger.log_sync_event("ERROR", msg)
            else:
                self.data_logger.log_sync_event("STATUS", msg)
        
        if 'sync_status_update' in self.gui_callbacks:
            self.gui_callbacks['sync_status_update'](msg, color)
    
    def _rtc_queue_callback(self, rtc_time, error=None):
        """RTC queue callback"""
        if error:
            error_msg = f"RTC ERROR: {error}"
            print(f"[RTC ERROR] {error}")
            # Log RTC errors to main log
            if self.data_logger.is_logging_active():
                self.data_logger.log_system_event("RTC_ERROR", str(error))
        # RTC time is now handled in the synchronized display update