import socket
import threading
import time

class UdpHandler:
    """
    Handles UDP communication in a separate thread.
    
    Attributes:
        rx_ip (str): IP address to bind for receiving.
        rx_port (int): Port to bind for receiving.
        tx_ip (str): Target IP for sending.
        tx_port (int): Target Port for sending.
    """

    def __init__(self, rx_ip="127.0.0.1", rx_port=61553, tx_ip="127.0.0.1", tx_port=51553):
        self.rx_ip = rx_ip
        self.rx_port = rx_port
        self.tx_ip = tx_ip
        self.tx_port = tx_port
        
        self._sock = None
        self._running = False
        self._recv_thread = None
        self._callback = None # Function to call when data arrives

    def register_callback(self, callback_func):
        """
        Registers a function to be called when a packet is received.
        Signature: callback_func(data: bytes, addr: tuple)
        """
        self._callback = callback_func

    def start(self):
        """Starts the receiving thread."""
        if self._running:
            return

        try:
            self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            # Allow reusing the address to avoid "Address already in use" errors during restarts
            self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self._sock.bind((self.rx_ip, self.rx_port))
            self._sock.settimeout(1.0) # Non-blocking with timeout
            
            self._running = True
            self._recv_thread = threading.Thread(target=self._receive_loop, daemon=True)
            self._recv_thread.start()
            print(f"[Core] UDP Listening on {self.rx_ip}:{self.rx_port}")
            
        except Exception as e:
            print(f"[Core] Error starting UDP: {e}")
            self._running = False

    def stop(self):
        """Stops the receiving thread and closes the socket."""
        self._running = False
        if self._recv_thread:
            self._recv_thread.join()
        if self._sock:
            self._sock.close()
        print("[Core] UDP Stopped.")

    def send(self, data):
        """Sends raw bytes to the configured target."""
        if not self._sock:
            return
        try:
            self._sock.sendto(data, (self.tx_ip, self.tx_port))
        except Exception as e:
            print(f"[Core] Send Error: {e}")

    def _receive_loop(self):
        """Internal loop running in a thread."""
        while self._running:
            try:
                data, addr = self._sock.recvfrom(4096)
                if self._callback:
                    self._callback(data, addr)
            except socket.timeout:
                continue # Normal timeout, check _running flag
            except Exception as e:
                if self._running:
                    print(f"[Core] Receive Error: {e}")