import threading
import serial
import time
import pynmea2
import os
from datetime import datetime, timedelta

class GPSReader(threading.Thread):
    """GPS reader thread that handles NMEA data and provides GPS timing with separate logging"""
    
    def __init__(self, serial_port, baud_rate, data_callback, status_callback, sync_callback, log_directory="/home/morepork/Documents/CSEM/logs"):
        super().__init__()
        self.serial_port = "/dev/ttyAMA0"  # Hardware UART for Pi
        self.baud_rate = 4800  # Standard GPS baud rate
        self.data_callback = data_callback
        self.status_callback = status_callback
        self.sync_callback = sync_callback
        self.log_directory = log_directory
        
        # Create log directory if it doesn't exist
        os.makedirs(self.log_directory, exist_ok=True)
        
        # Threading control
        self.running = True
        self.last_data_time = 0
        
        # Sync control
        self.sync_requested = False
        self.sync_lock = threading.Lock()
        self.last_gps_second = None
        
        # GPS logging
        self.gps_log_path = None
        self.gps_log_fp = None
        
        # Current GPS state
        self.current_gps_data = {
            'gps_datetime': None,
            'lat': 'N/A',
            'lon': 'N/A',
            'sats': 'N/A'
        }

    def start_gps_log(self):
        """Start GPS position logging to separate file"""
        timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
        self.gps_log_path = os.path.join(self.log_directory, f"gps_log_{timestamp}.txt")
        try:
            self.gps_log_fp = open(self.gps_log_path, 'w')
            
            # Write comprehensive GPS log header
            self.gps_log_fp.write("=" * 60 + "\n")
            self.gps_log_fp.write("MBARI CSEM TRIGGERBOX - GPS POSITION LOG\n")
            self.gps_log_fp.write("=" * 60 + "\n")
            self.gps_log_fp.write(f"Log created: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC\n")
            self.gps_log_fp.write(f"Serial port: {self.serial_port}\n")
            self.gps_log_fp.write(f"Baud rate: {self.baud_rate}\n")
            self.gps_log_fp.write("\nGPS Configuration:\n")
            self.gps_log_fp.write("  - Position updates logged every second\n")
            self.gps_log_fp.write("  - Format: ISO_DateTime, Latitude, Longitude, Satellites, Fix_Quality\n")
            self.gps_log_fp.write("  - All times in UTC\n")
            self.gps_log_fp.write("\n" + "=" * 60 + "\n")
            self.gps_log_fp.write("GPS POSITION DATA:\n")
            self.gps_log_fp.write("=" * 60 + "\n")
            
            self.gps_log_fp.flush()
            self.status_callback(f"GPS logging started: {self.gps_log_path}")
        except Exception as e:
            self.status_callback(f"Failed to create GPS log: {e}")
            self.gps_log_fp = None

    def stop_gps_log(self):
        """Stop GPS position logging"""
        if self.gps_log_fp:
            try:
                self.gps_log_fp.write(f"\n{datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC - GPS logging stopped\n")
                self.gps_log_fp.close()
                self.status_callback(f"GPS log closed: {self.gps_log_path}")
            except Exception as e:
                self.status_callback(f"Error closing GPS log: {e}")
            finally:
                self.gps_log_fp = None

    def get_gps_log_file(self):
        """Get current GPS log file path"""
        return self.gps_log_path

    def log_gps_event(self, event_type, message=""):
        """Log GPS-specific events to GPS log file"""
        if not self.gps_log_fp:
            return
            
        try:
            timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
            log_line = f"{timestamp} UTC - GPS_{event_type}"
            if message:
                log_line += f": {message}"
            log_line += "\n"
            
            self.gps_log_fp.write(log_line)
            self.gps_log_fp.flush()
        except Exception as e:
            self.status_callback(f"GPS log write error: {e}")

    def request_sync(self):
        """Request synchronization on the next GPS second boundary"""
        with self.sync_lock:
            if not self.sync_requested:  # Only set if not already requested
                self.sync_requested = True
                self.last_gps_second = None  # Reset to ensure fresh boundary detection
                self.status_callback("Waiting for GPS second boundary...")
                self.log_gps_event("SYNC_REQUEST", "Sync requested, waiting for second boundary")

    def run(self):
        """Main GPS reader loop"""
        self.start_gps_log()
        self.log_gps_event("CONNECTION_START", f"Connecting to {self.serial_port} at {self.baud_rate} baud")
        
        try:
            self.status_callback("Connecting to GPS...")
            with serial.Serial(self.serial_port, self.baud_rate, timeout=1) as ser:
                self.status_callback(f"Connected to {self.serial_port} at {self.baud_rate} baud.")
                self.log_gps_event("CONNECTION_SUCCESS", f"Connected to {self.serial_port}")
                
                while self.running:
                    line = ser.readline().decode('ascii', errors='replace').strip()
                    if line:
                        self.last_data_time = time.time()
                        self._process_nmea_line(line)
                    elif time.time() - self.last_data_time > 5:
                        self.status_callback("Connected, but no valid GPS data received.")
                        
        except serial.SerialException as e:
            error_msg = f"Serial Error: {e}"
            self.status_callback(error_msg)
            self.log_gps_event("CONNECTION_ERROR", str(e))
        except Exception as e:
            error_msg = f"GPS Reader Error: {e}"
            self.status_callback(error_msg)
            self.log_gps_event("READER_ERROR", str(e))
        finally:
            self.log_gps_event("CONNECTION_STOP", "GPS reader stopping")
            self.stop_gps_log()

    def _process_nmea_line(self, line):
        """Process a single NMEA sentence"""
        try:
            msg = pynmea2.parse(line)
            
            if isinstance(msg, pynmea2.types.talker.RMC):
                self._process_rmc_message(msg)
            elif isinstance(msg, pynmea2.types.talker.GGA):
                self._process_gga_message(msg)
                
            # Always callback with current data after processing any message
            self.data_callback(self.current_gps_data.copy())
            
        except pynmea2.ParseError:
            pass  # Ignore invalid NMEA sentences

    def _process_rmc_message(self, msg):
        """Process RMC (Recommended Minimum) message"""
        if msg.status == 'A':  # Valid fix
            gps_dt = datetime.combine(msg.datestamp, msg.timestamp)
            self.current_gps_data['gps_datetime'] = gps_dt
            self.current_gps_data['lat'] = f"{msg.latitude:.5f} {msg.lat_dir}" if msg.latitude else 'N/A'
            self.current_gps_data['lon'] = f"{msg.longitude:.5f} {msg.lon_dir}" if msg.longitude else 'N/A'

            # Log GPS position to separate GPS log file
            if self.gps_log_fp:
                try:
                    # Enhanced GPS position logging with more details
                    lat_str = self.current_gps_data['lat'] if self.current_gps_data['lat'] != 'N/A' else 'No Fix'
                    lon_str = self.current_gps_data['lon'] if self.current_gps_data['lon'] != 'N/A' else 'No Fix'
                    sats_str = self.current_gps_data['sats'] if self.current_gps_data['sats'] != 'N/A' else '0'
                    
                    self.gps_log_fp.write(f"{gps_dt.isoformat()}+00:00, {lat_str}, {lon_str}, {sats_str} sats, Valid Fix\n")
                    self.gps_log_fp.flush()
                except Exception as e:
                    self.status_callback(f"GPS log write error: {e}")

            # Check for second boundary and sync if requested
            self._check_sync_boundary(gps_dt)
        else:
            # Log invalid fix status
            if self.gps_log_fp:
                try:
                    timestamp = datetime.utcnow().isoformat()
                    self.gps_log_fp.write(f"{timestamp}+00:00, No Fix, No Fix, 0 sats, Invalid Fix Status: {msg.status}\n")
                    self.gps_log_fp.flush()
                except Exception as e:
                    self.status_callback(f"GPS log write error: {e}")

    def _process_gga_message(self, msg):
        """Process GGA (Global Positioning System Fix Data) message"""
        if msg.num_sats:
            # Update satellite count without logging changes
            self.current_gps_data['sats'] = str(msg.num_sats)

    def _check_sync_boundary(self, gps_dt):
        """Check for GPS second boundary for synchronization"""
        current_second = gps_dt.second
        
        with self.sync_lock:
            if (self.sync_requested and 
                self.last_gps_second is not None and 
                current_second != self.last_gps_second):
                
                # We've crossed a second boundary - sync now!
                # Add 1 second to account for the boundary we just crossed
                sync_time = gps_dt + timedelta(seconds=1)
                sync_time = sync_time.replace(microsecond=0)  # Ensure clean second
                
                self.log_gps_event("SYNC_BOUNDARY", f"Second boundary detected, syncing to {sync_time.isoformat()}")
                self.sync_callback(sync_time)
                self.sync_requested = False
                
        self.last_gps_second = current_second

    def stop(self):
        """Stop the GPS reader thread"""
        self.running = False