# radar_data_reader/core/structures/generic_structures.py

"""
Contains ctypes and dataclass definitions for various other data blocks
like AESA, TIMER, EXP, PC, DET, and signal-related blocks.
"""

import ctypes
from dataclasses import dataclass, field
from typing import Optional, Union, TYPE_CHECKING
import numpy as np

from .base_structures import CtypesStructureBase, BaseBlock

if TYPE_CHECKING:
    from .ge_header_structures import GeHeader

# ==================================================================================================
# --- TIMER Block Structures ---
# ==================================================================================================


class GrifoFwBlobHeader(CtypesStructureBase):
    _fields_ = [
        ("header_marker", ctypes.c_uint32),
        ("header_marker_bis", ctypes.c_uint32),
        ("size", ctypes.c_uint32),
        ("work_size", ctypes.c_uint32),
        ("sub_marker", ctypes.c_uint32),
        ("counter", ctypes.c_uint32),
        ("timetag", ctypes.c_uint32),
        ("sw_reserved", ctypes.c_uint32),
    ]


class ShiftRegisters(CtypesStructureBase):
    _fields_ = [
        ("B_Filter", ctypes.c_int32),
        ("B_RF", ctypes.c_int32),
        ("PT_DET", ctypes.c_int32),
        ("PC_Win", ctypes.c_int32),
        ("RX_SYNC", ctypes.c_int32),
    ]


class UniquePrtFifo(CtypesStructureBase):
    _fields_ = [("fifo", ctypes.c_uint32 * 8)]


class PrtFifo(CtypesStructureBase):
    _fields_ = [("fifo", ctypes.c_uint32 * 128)]


class DelayWithPeriod(CtypesStructureBase):
    _fields_ = [
        ("delay", UniquePrtFifo),
        ("width", UniquePrtFifo),
        ("period", UniquePrtFifo),
    ]


class DelayWidth(CtypesStructureBase):
    _fields_ = [("delay", UniquePrtFifo), ("width", UniquePrtFifo)]


class TimerRawIf(CtypesStructureBase):
    _fields_ = [
        ("tcr", ctypes.c_uint32),
        ("tpr", ctypes.c_uint32),
        ("tor", ctypes.c_uint32),
        ("stsr", ctypes.c_uint32),
        ("prt_num", ctypes.c_uint16),
        ("diff_prt_num", ctypes.c_uint16),
        ("spares__", ctypes.c_uint32 * 3),
        ("shift", ShiftRegisters),
        ("spare_after_shift", ctypes.c_byte * 12),
        ("aesa_delay", UniquePrtFifo * 2),
        ("spare0__", ctypes.c_byte * 48),
        ("exp_pulse1_delay", UniquePrtFifo * 2),
        ("exp_pulse2_delay", UniquePrtFifo * 2),
        ("pretrigger_det_delay", UniquePrtFifo * 2),
        ("rx_synch", DelayWithPeriod * 2),
        ("cf_synch", DelayWithPeriod * 2),
        ("ew_synch", DelayWithPeriod * 2),
        ("ant_rxn", DelayWidth * 2),
        ("down_blank", DelayWidth * 2),
        ("up_enable", DelayWidth * 2),
        ("blank_out", DelayWidth * 2),
        ("acq_det_window", DelayWidth * 2),
        ("acq_pc_window", DelayWidth * 2),
        ("provision", (DelayWidth * 8) * 2),
        ("spare_pre_prt__", ctypes.c_byte * (0x1000 - 0xBFF - 1)),
        ("prt_fifo_odd", PrtFifo),
        ("spare_pre_prt_even__", ctypes.c_byte * (0x1400 - 0x11FF - 1)),
        ("prt_fifo_even", PrtFifo),
    ]


class GrifoTimerBlob(CtypesStructureBase):
    _fields_ = [("hdr", GrifoFwBlobHeader), ("payload", TimerRawIf)]


@dataclass
class TimerBlock(BaseBlock):
    is_valid: bool
    blob: Optional[GrifoTimerBlob] = None


# ==================================================================================================
# --- AESA Block Structures ---
# ==================================================================================================


class AesaSyntheticReport(CtypesStructureBase):
    _fields_ = [("aesa_fail_mask", ctypes.c_uint), ("comm", ctypes.c_uint)]


@dataclass
class AesaBlock(BaseBlock):
    block_subtype: str
    is_valid: bool
    payload: Optional[Union["AesaSyntheticReport", bytes]] = None
    raw_data_bytes: Optional[bytes] = None


# ==================================================================================================
# --- EXP, PC, DET Block Structures (Minimized for Robustness) ---
# ==================================================================================================


# --- EXP ---
class ExpanderBasicSettings(CtypesStructureBase):
    _fields_ = [
        ("mof", ctypes.c_uint32),
        ("reset", ctypes.c_uint32),
        ("wf_address", ctypes.c_uint32),
        ("frq_start", ctypes.c_uint32),
        ("frq_len", ctypes.c_uint32),
        ("frq_inc", ctypes.c_int32),
        ("amp_peak", ctypes.c_uint32),
    ]


class ExpRawIf(CtypesStructureBase):
    _fields_ = [("ctrl", ctypes.c_uint32)]


@dataclass
class ExpBlock(BaseBlock):
    is_valid: bool
    payload: Optional["ExpRawIf"]
    raw_data_bytes: Optional[bytes] = None


# --- PC ---
class PcBasicSettings(CtypesStructureBase):
    _fields_ = [
        ("mof", ctypes.c_uint32),
        ("lpf_filter_id", ctypes.c_uint32),
        ("lsn_start_56mhz", ctypes.c_uint32),
        ("lsn_len_56mhz", ctypes.c_uint32),
        ("out_ofs", ctypes.c_uint32),
        ("n_samples", ctypes.c_uint32),
    ]


class PcRawIf(CtypesStructureBase):
    _fields_ = [("settings", PcBasicSettings)]


@dataclass
class PcBlock(BaseBlock):
    is_valid: bool
    payload: Optional["PcRawIf"]
    raw_data_bytes: Optional[bytes] = None


# --- DET ---
class DetectorBasicSettings(CtypesStructureBase):
    _fields_ = [
        ("mof", ctypes.c_uint32),
        ("pre_filter_id", ctypes.c_uint32),
        ("post_filter_id", ctypes.c_uint32),
        ("f_base", ctypes.c_int32),
        ("f_delta", ctypes.c_int32),
        ("lsn_start_56mhz", ctypes.c_uint32),
        ("lsn_len_56mhz", ctypes.c_uint32),
        ("prt_num", ctypes.c_uint32),
    ]


class DetectorRawIf(CtypesStructureBase):
    _fields_ = [("settings", DetectorBasicSettings)]


@dataclass
class DetBlock(BaseBlock):
    is_valid: bool
    payload: Optional["DetectorRawIf"]
    raw_data_bytes: Optional[bytes] = None


# ==================================================================================================
# --- Generic and Signal Block Dataclasses ---
# ==================================================================================================


@dataclass
class SignalBlock(BaseBlock):
    signal_type: str
    iq_data: np.ndarray


@dataclass
class GenericBlock(BaseBlock):
    """Represents a generic block for which no specific parser is available."""

    raw_data_bytes: bytes = field(default_factory=bytes, repr=False)


@dataclass
class DspsBlock(BaseBlock):
    """
    Represents a DSPS (DSP Status) block.

    This block itself does not contain a full GeHeader, but it is associated
    with the last DspHeaderIn block received. The 'ge_header' attribute is
    populated from that last header to provide context.
    """

    is_valid: bool = False
    payload: Optional[bytes] = field(default=None, repr=False)
    # This will be populated by the parser from the last seen header
    ge_header: Optional["GeHeader"] = field(default=None, repr=False)
