from dataclasses import dataclass, field
from typing import List, Optional

def decode_hamming_8_4(byte_val):
    # Extract data bits: bits 1, 3, 5, 7
    return ((byte_val >> 1) & 1) | \
           (((byte_val >> 3) & 1) << 1) | \
           (((byte_val >> 5) & 1) << 2) | \
           (((byte_val >> 7) & 1) << 3)

@dataclass
class Packet:
    """
    Represents a single Teletext packet (Row).
    In T42, we have 42 bytes:
    Byte 0-1: Magazine and Row Address (Hamming 8/4 encoding)
    Byte 2-41: Data bytes (40 bytes)
    """
    original_data: bytes
    magazine: int = field(init=False)
    row: int = field(init=False)
    data: bytearray = field(init=False)

    def __post_init__(self):
        if len(self.original_data) != 42:
            raise ValueError(f"Packet must be 42 bytes, got {len(self.original_data)}")
        
        # Parse Magazine and Row from the first 2 bytes (Hamming 8/4)
        # MRAG (Magazine + Row Address Group)
        # Byte 0: P1 D1 P2 D2 P3 D3 P4 D4
        # Byte 1: P1 D1 P2 D2 P3 D3 P4 D4
        
        b1 = self.original_data[0]
        b2 = self.original_data[1]
        
        # De-interleave Hamming bits to get M (3 bits) and R (5 bits)
        d1 = decode_hamming_8_4(b1)
        d2 = decode_hamming_8_4(b2)
        
        # Magazine is 3 bits (Logic is specific: Mag 8 is encoded as 0)
        # Row is 5 bits.
        
        # According to Spec (ETSI EN 300 706):
        # b1 encoded: M1 M2 M3 R1
        # b2 encoded: R2 R3 R4 R5
        
        self.magazine = (d1 & 0b0111) 
        if self.magazine == 0:
            self.magazine = 8
            
        row_low_bit = (d1 >> 3) & 1
        row_high_bits = d2
        self.row = (row_high_bits << 1) | row_low_bit
        
        self.data = bytearray(self.original_data[2:])

    @property
    def is_header(self):
        return self.row == 0

@dataclass
class Page:
    """
    Represents a Teletext Page (e.g., 100).
    Can have multiple subpages.
    """
    magazine: int
    page_number: int  # 00-99 (Hex storage: 0x00-0xFF)
    sub_code: int = 0 # 13-bit subcode (0000 to 3F7F hex)
    
    # Control bits C4-C14
    control_bits: int = 0 
    
    language: int = 0 # National Option (0-7, from C12-C14)
    packets: List[Packet] = field(default_factory=list)
    
    @property
    def full_page_number(self):
        # Format as Hex to support A-F pages
        return f"{self.magazine}{self.page_number:02X}"

    def get_control_bit(self, n: int) -> bool:
        """ Returns value of control bit Cn (4-14) """
        if 4 <= n <= 14:
            return bool((self.control_bits >> (n - 4)) & 1)
        return False

    def set_control_bit(self, n: int, value: bool):
        """ Sets value of control bit Cn (4-14) """
        if 4 <= n <= 14:
            if value:
                self.control_bits |= (1 << (n - 4))
            else:
                self.control_bits &= ~(1 << (n - 4))

    def calculate_crc(self) -> int:
        """
        Calculates the CRC-16 checksum for the page.
        According to ETSI EN 300 706 (Section 9.6.1 & Figure 13):
        - G(x) = x^16 + x^12 + x^9 + x^7 + 1 (Poly 0x1281)
        - Initial value: 0.
        - Processed bits b8 to b1 (MSB first for stored bytes).
        - Total 1024 bytes (32 packets * 32 bytes).
        - Packet X/0: Bytes 14 to 37 (24 bytes) + 8 spaces.
        - Packets X/1 to X/25: Bytes 14 to 45 (32 bytes).
        - Packets X/26 to X/31: 32 spaces each.
        """
        crc = 0
        poly = 0x1281
        
        # Helper to update CRC with a byte (MSB first)
        def update_crc(c, val):
            v = (val << 8) & 0xFFFF
            for _ in range(8):
                if (c ^ v) & 0x8000:
                    c = (c << 1) ^ poly
                else:
                    c = c << 1
                v <<= 1
                c &= 0xFFFF
            return c

        # Organize packets by row
        rows = {p.row: p for p in self.packets}
            
        for r in range(32): # Process 32 slots (0-31)
            start, end = 0, 0
            padding = 0
            
            if r == 0:
                # Row 0: Bytes 14-37 (24 bytes)
                start, end = 8, 32 # data[8..31]
                padding = 8 
            elif 1 <= r <= 25:
                # Rows 1-25: Bytes 14-45 (32 bytes)
                start, end = 8, 40 # data[8..39]
                padding = 0
            else:
                # Rows 26-31: 32 spaces each
                padding = 32
                
            # Process packet data if available
            if r in rows and start < end:
                p_data = rows[r].data
                for i in range(start, end):
                    byte_val = (p_data[i] & 0x7F) if i < len(p_data) else 0x20
                    crc = update_crc(crc, byte_val)
            elif start < end:
                # Missing packet but slot exists
                for _ in range(start, end):
                    crc = update_crc(crc, 0x20)
            
            # Add padding for this slot
            for _ in range(padding):
                crc = update_crc(crc, 0x20)
                    
        return crc

    def get_stored_crc(self) -> Optional[int]:
        """
        Attempts to retrieve the stored CRC from Packet 27/0 if present.
        Returns None if not found.
        """
        # Look for Packet 27
        for p in self.packets:
            if p.row == 27:
                # Check Designation Code (Byte 0)
                try:
                    if len(p.data) >= 40:
                        b0 = p.data[0]
                        # Decode Hamming 8/4
                        designation = decode_hamming_8_4(b0)
                        
                        # Packets X/27/0 to X/27/3 exist, but only X/27/0 has the CRC.
                        # We also check if b0 is raw 0 as a fallback for some captures.
                        if designation == 0 or b0 == 0:
                            # Packet 27/0
                            # Checksum is in bytes 38 and 39 (TBytes 44 and 45).
                            hi = p.data[38]
                            lo = p.data[39]
                            
                            crc = (hi << 8) | lo
                            return crc
                except:
                    pass
        return None

@dataclass
class TeletextService:
    """
    Container for all pages.
    """
    pages: List[Page] = field(default_factory=list)
    # We also keep a flat list of all packets to preserve order on save
    all_packets: List[Packet] = field(default_factory=list)

