import minimalmodbus

class MA01_AACX2240(minimalmodbus.Instrument):
    """Instrument class for MA01/MA02-AACX2240 IO module.

    Args:
        port (str): Port name
        slave_address (int, optional): Modbus slave address. Defaults to 1.
    """

    # Register addresses for different functions
    REGISTERS = {
        'do1': 0,      # DO1 control
        'do2': 1,      # DO2 control
        'do3': 2,      # DO3 control
        'do4': 3,      # DO4 control
        'do_powerup': 100,    # DO power-up state (0x0064)
        'di_value': 0,        # Digital input values
        'di_count': 2527,     # DI count value (0x09DF)
        'ai_value': 100,      # AI engineering value (0x0064)
        'ai_calibration_high': 400,  # AI high point calibration (0x0190)
        'ai_calibration_low': 600,   # AI low point calibration (0x0258)
        'ai_filter': 1200,    # AI filter parameters (0x04B0)
        'ai_range': 1202      # AI sampling range (0x04B2)
    }

    # DO control values
    DO_ON = 65280   # FF 00 in decimal
    DO_OFF = 0      # 00 00 in decimal

    def __init__(self, port: str, slave_address: int = 1):
        """Initialize with default settings and read initial states."""
        super().__init__(port, slave_address)

        # Default serial settings
        self.serial.baudrate = 19200
        self.serial.stopbits = 1
        self.serial.bytesize = 8
        self.serial.parity = 'N'
        self.serial.timeout = 0.5
        self.mode = minimalmodbus.MODE_RTU
        self.close_port_after_each_call = True

        # Initialize state tracking
        self._do_states = {1: False, 2: False, 3: False, 4: False}


        # Read initial states
        try:
            self._read_all_states()
        except Exception as e:
            print(f"Warning: Could not read initial states: {e}")

    def _read_all_states(self):
        """Read and store all current states."""
        # Read DO states
        for channel in range(1, 5):
            self._do_states[channel] = self.read_do(channel)

    @classmethod
    def with_custom_settings(cls, port: str, slave_address: int = 1,
                           baudrate: int = 9600, stopbits: int = 1,
                           bytesize: int = 8, parity: str = 'N',
                           timeout: float = 0.5, mode: str = 'rtu',
                           close_port: bool = True) -> 'MA01_AACX2240':
        """Initialize with custom settings.

        Returns:
            MADevice: Configured device instance
        """
        instance = cls(port, slave_address)
        instance.serial.baudrate = baudrate
        instance.serial.stopbits = stopbits
        instance.serial.bytesize = bytesize
        instance.serial.parity = parity
        instance.serial.timeout = timeout
        instance.mode = minimalmodbus.MODE_RTU if mode.lower() == 'rtu' else minimalmodbus.MODE_ASCII
        instance.close_port_after_each_call = close_port
        return instance

    def read_di(self, channel: int) -> bool:
        """Read digital input state.

        Args:
            channel (int): DI channel number (1-2)

        Returns:
            bool: True if input is high, False if low
        """
        if not 1 <= channel <= 2:
            raise ValueError("Channel must be between 1 and 2")

        value = self.read_register(self.REGISTERS['di_value'])
        return bool(value & (1 << (channel - 1)))

    def read_di_count(self, channel: int) -> int:
        """Read digital input counter value.

        Args:
            channel (int): DI channel number (1-2)

        Returns:
            int: Counter value
        """
        if not 1 <= channel <= 2:
            raise ValueError("Channel must be between 1 and 2")

        return self.read_register(self.REGISTERS['di_count'] + channel - 1)

    def read_ai(self, channel: int) -> float:
        """Read analog input value.

        Args:
            channel (int): AI channel number (1-2)

        Returns:
            float: Current in mA
        """
        if not 1 <= channel <= 2:
            raise ValueError("Channel must be between 1 and 2")

        value = self.read_register(self.REGISTERS['ai_value'] + channel - 1)
        return value / 1000  # Convert to mA

    def set_do(self, channel: int, state: bool) -> None:
        """Set digital output state using Modbus function code 05.

        Args:
            channel (int): DO channel number (1-4)
            state (bool): True for on, False for off
        """
        if not 1 <= channel <= 4:
            raise ValueError("Channel must be between 1 and 4")

        # Use write_bit with function code 05
        # FF 00 hex for ON, 00 00 hex for OFF according to manual section 7.3.5
        register = self.REGISTERS[f'do{channel}']

        self.write_bit(register, state)

        # Update state tracking
        self._do_states[channel] = state

    def read_do(self, channel: int) -> bool:
        """Read digital output state.

        Args:
            channel (int): DO channel number (1-4)

        Returns:
            bool: True if output is on, False if off
        """
        if not 1 <= channel <= 4:
            raise ValueError("Channel must be between 1 and 4")

        register = self.REGISTERS[f'do{channel}']
        value = self.read_bit(register,functioncode=1)
        return value

    def switch_do(self,channel: int) -> bool:
        """Read digital output state.

        Args:
            channel (int)
        """
        if not 1 <= channel <= 4:
            raise ValueError("Channel must be between 1 and 4")
        state = not self.read_do(channel)
        self.set_do(channel, state)
        self._do_states[channel] = state



    def configure_ai(self, filter_param: int = 6, sampling_range: int = 0) -> None:
        """Configure analog input parameters.

        Args:
            filter_param (int): Filter parameter (1-16)
            sampling_range (int): 0 for 0-20mA, 1 for 4-20mA
        """
        if not 1 <= filter_param <= 16:
            raise ValueError("Filter parameter must be between 1 and 16")
        if sampling_range not in [0, 1]:
            raise ValueError("Sampling range must be 0 (0-20mA) or 1 (4-20mA)")

        self.write_register(self.REGISTERS['ai_filter'], filter_param)
        self.write_register(self.REGISTERS['ai_range'], sampling_range)

    def calibrate_ai(self, channel: int, high_point: int = None, low_point: int = None) -> None:
        """Calibrate analog input channel.

        Args:
            channel (int): AI channel number (1-2)
            high_point (int, optional): High point calibration value
            low_point (int, optional): Low point calibration value
        """
        if not 1 <= channel <= 2:
            raise ValueError("Channel must be between 1 and 2")

        if high_point is not None:
            self.write_register(self.REGISTERS['ai_calibration_high'] + channel - 1, high_point)
        if low_point is not None:
            self.write_register(self.REGISTERS['ai_calibration_low'] + channel - 1, low_point)

    def __del__(self):
        """Destructor - ensures all outputs are turned off when object is destroyed."""
        try:
            for channel in range(1, 5):
                self.set_do(channel, False)
        except Exception as e:
            print(f"Warning: Could not clear outputs during cleanup: {e}")

    @property
    def state(self) -> dict:
        """Get the current state of all I/O.

        Returns:
            dict: Current state of all inputs and outputs
        """
        return {
            'digital_outputs': dict(self._do_states)
        }