import ctypes
from enum import Enum

class Field:
    """
    Base class for 1553 Data Word fields descriptors.
    Handles the logic to extract specific bits from a 16-bit word
    following MIL-STD-1553 convention (Bit 0 = MSB, Bit 15 = LSB).
    """
    def __init__(self, word_index, start_bit, width=1):
        """
        Args:
            word_index (int): Index of the 16-bit word in the message (0-based).
            start_bit (int): Starting bit index as per ICD (0=MSB, 15=LSB).
            width (int): Number of bits occupied by the field.
        """
        self.word_index = word_index
        self.start_bit = start_bit
        self.width = width
        
        # Calculate shift required for Little Endian architecture
        # to match 1553 Big Endian bit numbering (0=MSB).
        # Example: Bit 0 (MSB) with width 1 -> Shift 15
        self._shift = 16 - (start_bit + width)
        self._mask = (1 << width) - 1

    def _get_raw_value(self, instance):
        """Extracts the raw unsigned integer from the message data."""
        # instance.data is expected to be a ctypes array or list of ints
        if self.word_index >= len(instance.data):
            return 0 # Fail safe for incomplete messages
            
        word_val = instance.data[self.word_index]
        return (word_val >> self._shift) & self._mask

    def _set_raw_value(self, instance, value):
        """Writes the raw unsigned integer back into the message data."""
        if self.word_index >= len(instance.data):
            return # Fail safe
            
        current_word = instance.data[self.word_index]
        
        # Clear the bits
        clear_mask = ~(self._mask << self._shift)
        current_word &= clear_mask
        
        # Set the new bits (masked to width)
        val_to_write = (int(value) & self._mask) << self._shift
        current_word |= val_to_write
        
        instance.data[self.word_index] = current_word


class BitField(Field):
    """
    Represents a generic numeric field (Unsigned).
    Used for flags, counters, or raw values without scaling.
    """
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return self._get_raw_value(instance)

    def __set__(self, instance, value):
        self._set_raw_value(instance, value)


class EnumField(Field):
    """
    Represents a field mapped to a Python Enum.
    Automatically converts between the raw integer and the Enum member.
    """
    def __init__(self, word_index, start_bit, enum_cls, width=None):
        """
        Args:
            enum_cls (Enum): The Enum class to map to.
            width (int, optional): Auto-calculated from enum length if not provided.
        """
        # Calculate width based on max enum value if not provided, 
        # but usually ICD defines width explicitly.
        if width is None:
            # Minimal width to fit the max value
            max_val = max([e.value for e in enum_cls])
            width = max_val.bit_length()
            
        super().__init__(word_index, start_bit, width)
        self.enum_cls = enum_cls

    def __get__(self, instance, owner):
        if instance is None:
            return self
        raw = self._get_raw_value(instance)
        try:
            return self.enum_cls(raw)
        except ValueError:
            # Return raw value if not found in Enum (e.g. reserved values)
            return raw

    def __set__(self, instance, value):
        if isinstance(value, self.enum_cls):
            val_to_set = value.value
        else:
            val_to_set = int(value)
        self._set_raw_value(instance, val_to_set)


class ScaledField(Field):
    """
    Represents a numeric field with a scale factor (LSB).
    Can be Signed (2's complement) or Unsigned.
    
    Value = Raw * LSB
    """
    def __init__(self, word_index, start_bit, width, lsb_value, signed=False):
        super().__init__(word_index, start_bit, width)
        self.lsb_value = lsb_value
        self.signed = signed

    def __get__(self, instance, owner):
        if instance is None:
            return self
            
        raw = self._get_raw_value(instance)
        
        # Handle 2's complement for signed fields
        if self.signed:
            # Check sign bit (highest bit of the field)
            sign_mask = 1 << (self.width - 1)
            if raw & sign_mask:
                # Calculate negative value
                raw = raw - (1 << self.width)
                
        return float(raw * self.lsb_value)

    def __set__(self, instance, value):
        # Convert physical value to raw steps
        raw = int(round(value / self.lsb_value))
        
        if self.signed:
            # Handle negative input
            if raw < 0:
                raw = (1 << self.width) + raw
                
        self._set_raw_value(instance, raw)


class ASCIIField(Field):
    """
    Represents 2 characters packed into a 16-bit word.
    MSB (bits 0-7) = Character 1
    LSB (bits 8-15) = Character 2
    """
    def __init__(self, word_index):
        # Takes the whole word
        super().__init__(word_index, 0, 16)

    def __get__(self, instance, owner):
        if instance is None:
            return self
        
        raw = self._get_raw_value(instance)
        # In 1553:
        # High Byte (0xFF00) is first char
        # Low Byte (0x00FF) is second char
        char1 = (raw >> 8) & 0xFF
        char2 = raw & 0xFF
        
        # Return string, handling non-printable bytes safely
        s = ""
        s += chr(char1) if 32 <= char1 <= 126 else '?'
        s += chr(char2) if 32 <= char2 <= 126 else '?'
        return s

    def __set__(self, instance, value):
        if not isinstance(value, str) or len(value) != 2:
            raise ValueError("ASCIIField requires a string of exactly 2 characters")
        
        char1 = ord(value[0])
        char2 = ord(value[1])
        
        raw = (char1 << 8) | char2
        self._set_raw_value(instance, raw)