import socket
import threading
import binascii
import logging

# Debug flag - set to True for detailed packet logging
DEBUG_PACKETS = False

logger = logging.getLogger(__name__)

def hex_dump(data: bytes, prefix: str = "") -> str:
    """Create a hex dump of bytes for debugging."""
    hex_str = binascii.hexlify(data).decode('ascii')
    formatted = ' '.join(hex_str[i:i+4] for i in range(0, len(hex_str), 4))
    return f"{prefix}[{len(data)} bytes] {formatted}"

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

    def __init__(self, rx_ip="127.0.0.1", rx_port=61553):
        self.rx_ip = rx_ip
        self.rx_port = rx_port
        
        self._sock = None
        self._running = False
        self._recv_thread = None
        self._callback = None 

    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)
            self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self._sock.bind((self.rx_ip, self.rx_port))
            self._sock.settimeout(1.0)
            
            self._running = True
            self._recv_thread = threading.Thread(target=self._receive_loop, daemon=True)
            self._recv_thread.start()
            logger.info(f"UDP Listening on {self.rx_ip}:{self.rx_port}")
            
        except Exception as e:
            logger.error(f"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()
        logger.info("UDP Stopped")

    def send(self, data, ip, port):
        """Sends raw bytes to the specified target."""
        if not self._sock:
            return
        try:
            self._sock.sendto(data, (ip, port))
        except Exception as e:
            logger.error(f"Send Error: {e}")

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