"""Implements functionality unique to the Lake Shore Model 224 temperature monitor"""

from enum import IntEnum
import serial
from .generic_instrument import GenericInstrument, InstrumentException, RegisterBase
from .temperature_controllers import StandardEventRegister


class Model224AlarmParameters:
    """Class used to disable or configure an alarm in conjunction with the set/get_alarm_parameters() method"""

    def __init__(self, high_value, low_value, deadband, latch_enable, audible=None, visible=None):
        """Constructor for Model224AlarmParameters class

            Args:
                high_value (float):
                    Sets the value the source is checked against to activate the
                    high alarm
                low_value (float):
                    Sets the value the source is checked against to activate low alarm.
                deadband (float):
                    Sets the value that the source must change outside of an alarm
                    condition to deactivate an unlatched alarm.
                latch_enable (bool):
                    Specifies a latched alarm (False = off, True = on)
                audible (bool):
                    Specifies if the internal speaker will beep when an alarm condition
                    occurs (False = off, True = on)
                    Optional parameter.
                visible (bool):
                    Specifies if the Alarm LED on the instrument front panel will blink
                    when an alarm condition occurs (False = off, True = on)
                    Optional parameter.
        """
        self.high_value = high_value
        self.low_value = low_value
        self.deadband = deadband
        self.latch_enable = latch_enable
        self.audible = audible
        self.visible = visible


class Model224InputSensorType(IntEnum):
    """Enumeration for the type of sensor being used for a given input."""
    INPUT_DISABLED = 0
    DIODE = 1
    PLATINUM_RTD = 2
    NTC_RTD = 3


class Model224InputSensorUnits(IntEnum):
    """Enumeration for the preferred units of an input sensor."""
    KELVIN = 1
    CELSIUS = 2
    SENSOR = 3


class Model224DiodeSensorRange(IntEnum):
    """Enumeration for the voltage range of a diode sensor."""
    RANGE_2_POINT_5_VOLTS = 0
    RANGE_10_VOLTS = 1


class Model224PlatinumRTDSensorResistanceRange(IntEnum):
    """Enumeration of the resistance range of a platinum RTD input sensor."""
    TEN_OHMS = 0
    THIRTY_OHMS = 1
    ONE_HUNDRED_OHMS = 2
    THREE_HUNDRED_OHMS = 3
    ONE_KILOHM = 4
    THREE_KILOHMS = 5
    TEN_KILOHMS = 6


class Model224NTCRTDSensorResistanceRange(IntEnum):
    """Enumeration of the resistance range of a NTC RTD input sensor."""
    TEN_OHMS = 0
    THIRTY_OHMS = 1
    ONE_HUNDRED_OHMS = 2
    THREE_HUNDRED_OHMS = 3
    ONE_KILOHM = 4
    THREE_KILOHMS = 5
    TEN_KILOHMS = 6
    THIRTY_KILOHMS = 7
    ONE_HUNDRED_KILOHMS = 8


class Model224InputSensorSettings:
    """Class representing the parameters of a sensor in one of the instrument's inputs."""

    def __init__(self,
                 sensor_type,
                 preferred_units,
                 sensor_range=None,
                 autorange_enabled=False,
                 compensation=False):
        """Constructor for the Model224InputSensorSettings class.

            Args:
                sensor_type (Model224InputSensorType or int):
                    * Specifies what type of sensor is being used at the input.
                preferred_units (Model224InputSensorUnits or int):
                    * Specifies the preferred units used for sensor readings and alarm setpoints when displayed.
                sensor_range (IntEnum):
                    * Specifies the range of the sensor.
                    * Optional if auto range is enabled
                autorange_enabled (bool):
                    * Defines if autorange is enabled.
                    * Not applicable for diode sensors
                    * Defaults to false
                compensation (bool):
                    * Defines if thermal input compensation is on or off.
                    * Not applicable for diode sensors
                    * Defaults to false
        """
        self.sensor_type = sensor_type
        self.sensor_range = sensor_range
        self.preferred_units = preferred_units
        self.autorange_enabled = autorange_enabled
        self.compensation = compensation


class Model224InterfaceMode(IntEnum):
    """Enumeration for the mode of the remote interface"""
    LOCAL = 0
    REMOTE = 1
    REMOTE_LOCAL_LOCK = 2


class Model224RemoteInterface(IntEnum):
    """Enumeration for the remote interface being used to communicate with the instrument."""
    USB = 0
    ETHERNET = 1
    IEEE_488 = 2


class Model224DisplayFieldUnits(IntEnum):
    """Enumerated type defining how units are enumerated for settings and using Display Fields."""
    KELVIN = 1
    CELSIUS = 2
    SENSOR = 3
    MINIMUM_DATA = 4
    MAXIMUM_DATA = 5


class Model224InputChannel(IntEnum):
    """Enumerated type defining which input channels correspond to ints for setting and using Display Fields."""
    NO_INPUT = 0
    INPUT_A = 1
    INPUT_B = 2
    INPUT_C = 3
    INPUT_D1 = 4
    INPUT_D2 = 5
    INPUT_D3 = 6
    INPUT_D4 = 7
    INPUT_D5 = 8
    INPUT_C2 = 9
    INPUT_C3 = 10
    INPUT_C4 = 11
    INPUT_C5 = 12


class Model224DisplayMode(IntEnum):
    """Enumeration defining what input or information is shown on the front panel display."""
    INPUT_A = 0
    INPUT_B = 1
    INPUT_C = 2
    INPUT_D1 = 3
    CUSTOM = 4
    ALL_INPUTS = 5
    INPUT_D2 = 6
    INPUT_D3 = 7
    INPUT_D4 = 8
    INPUT_D5 = 9
    INPUT_C2 = 10
    INPUT_C3 = 11
    INPUT_C4 = 12
    INPUT_C5 = 13


class Model224NumberOfFields(IntEnum):
    """Enumerated type specifying the number of display fields to configure in the Custom display mode."""
    LARGE_4 = 0
    LARGE_8 = 1
    LARGE_4_SMALL_8 = 2
    SMALL_16 = 3


class Model224RelayControlAlarm(IntEnum):
    """Enumeration of the setting determining which alarm(s) cause a relay to activate in alarm mode."""
    LOW_ALARM = 0
    HIGH_ALARM = 1
    BOTH_ALARMS = 2


class Model224RelayControlMode(IntEnum):
    """Enumeration of the configured mode of a relay."""
    RELAY_OFF = 0
    RELAY_ON = 1
    ALARMS = 2


class Model224CurveFormat(IntEnum):
    """Enumerations specify formats for temperature sensor curves"""
    MILLIVOLT_PER_KELVIN = 1
    VOLTS_PER_KELVIN = 2
    OHMS_PER_KELVIN = 3
    LOG_OHMS_PER_KELVIN = 4


class Model224CurveTemperatureCoefficients(IntEnum):
    """Enumerations specify positive/negative temperature sensor curve coefficients"""
    NEGATIVE = 1
    POSITIVE = 2


class Model224DiodeExcitationCurrent(IntEnum):
    """Enum type representing the different excitation currents available for a diode sensor."""
    TEN_MICRO_AMPS = 0
    ONE_MILLI_AMP = 1


class Model224SoftCalSensorTypes(IntEnum):
    """Enum type representing the standard curves used to generate a SoftCal curve. The 3 standard curves each
    represent a different type of sensor that can be calibrated with a SoftCal curve."""
    DT_400 = 1
    PT_100 = 6
    PT_1000 = 7


class Model224CurveHeader:
    """A class that configures the user curve header and corresponding parameters"""

    def __init__(self, curve_name, serial_number, curve_data_format, temperature_limit, coefficient):
        """Constructor for Model224CurveHeader class

            Args:
                curve_name (str):
                    Specifies curve name (limit of 15 characters)

                serial_number (str):
                    Specifies curve serial number (limit of 10 characters)

                curve_data_format (Model224CurveFormat):
                    Specifies the curve data format

                temperature_limit (float):
                    * Specifies the curve temperature limit in Kelvin

                coefficient (Model224CurveTemperatureCoefficients):
                    * Specifies the curve temperature coefficient

        """

        self.curve_name = curve_name
        self.serial_number = serial_number
        self.curve_data_format = curve_data_format
        self.temperature_limit = temperature_limit
        self.coefficient = coefficient


Model224StandardEventRegister = StandardEventRegister


class Model224ServiceRequestRegister(RegisterBase):
    """Class object representing the Service Request Enable register."""
    bit_names = [
        "",
        "",
        "",
        "",
        "message_available",
        "event_summary",
        ""
        "operation_summary"
    ]

    def __init__(self,
                 message_available,
                 event_summary,
                 operation_summary):
        self.message_available = message_available
        self.event_summary = event_summary
        self.operation_summary = operation_summary


class Model224StatusByteRegister(RegisterBase):
    """Class object representing the status byte register."""
    bit_names = [
        "",
        "",
        "",
        "",
        "message_available",
        "event_summary",
        "master_summary_status"
        "operation_summary"
    ]

    def __init__(self,
                 message_available,
                 event_summary,
                 master_summary_status,
                 operation_summary):
        self.message_available = message_available
        self.event_summary = event_summary
        self.master_summary_status = master_summary_status
        self.operation_summary = operation_summary


class Model224ReadingStatusRegister(RegisterBase):
    """Class object representing the reading status of an input. While not a literal register, the return of an int
    representation of multiple booleans makes it conveninet to represent this functionality as a register."""
    bit_names = [
        "invalid_reading",
        "",
        "",
        "",
        "temperature_under_range",
        "temperature_over_range",
        "sensor_units_zero",
        "sensor_units_over_range"
    ]

    def __init__(self,
                 invalid_reading,
                 temperature_under_range,
                 temperature_over_range,
                 sensor_units_zero,
                 sensor_units_over_range):
        self.invalid_reading = invalid_reading
        self.temperature_under_range = temperature_under_range
        self.temperature_over_range = temperature_over_range
        self.sensor_units_zero = sensor_units_zero
        self.sensor_units_over_range = sensor_units_over_range


class Model224(GenericInstrument):
    """A class object representing the Lake Shore Model 224 temperature monitor"""

    vid_pid = [(0x1FB9, 0x0204)]

    def __init__(self,
                 serial_number=None,
                 com_port=None,
                 baud_rate=57600,
                 data_bits=7,
                 stop_bits=1,
                 parity=serial.PARITY_ODD,
                 flow_control=False,
                 handshaking=False,
                 timeout=2.0,
                 ip_address=None,
                 tcp_port=7777,
                 **kwargs):

        # Call the parent init, then fill in values specific to the 224
        GenericInstrument.__init__(self, serial_number, com_port, baud_rate, data_bits, stop_bits, parity, flow_control,
                                   handshaking, timeout, ip_address, tcp_port, **kwargs)

    @staticmethod
    def _error_check(error_code):
        event_register = Model224StandardEventRegister.from_integer(error_code)
        if event_register.query_error:
            raise InstrumentException('Query Error')
        if event_register.command_error:
            raise InstrumentException('Command Error: Invalid Command or Query')
        if event_register.execution_error:
            raise InstrumentException('Execution Error: Instrument not able to execute command or query.')

    def command(self, *commands, check_errors=True):
        """Send a SCPI command or multiple commands to the instrument

            Args:
                commands (str):
                    * A serial command

            Kwargs:
                check_errors (bool):
                    * Chooses whether to check for and raise errors after sending a command. True by default.

        """

        # Group all commands and queries a single string with SCPI delimiters.
        command_string = ";:".join(commands)

        if check_errors:
            self.query(command_string)
        else:
            command_string += ";*OPC?"
            self.query(command_string, check_errors=False)

    def query(self, *queries, check_errors=True):
        """Send a query to the instrument and return the response

            Args:
                queries (str):
                    * A serial query ending in a question mark

            Return:
               * The instrument query response as a string.

        """

        # Group all commands and queries a single string with SCPI delimiters.
        query_string = ";:".join(queries)

        # Append the query with an additional error buffer query.
        if check_errors:
            query_string += ";*ESR?"

        response = GenericInstrument.query(self, query_string)

        if check_errors:
            response_list = response.split(';')
            error_code = response_list.pop()
            self._error_check(error_code)
            response = ';'.join(response_list)

        return response

    def get_standard_event_enable_mask(self):
        """Returns the names of the standard event enable register bits and their values.
        These values determine which bits propagate to the standard event register"""

        response = self.query("*ESE?")
        status_register = Model224StandardEventRegister.from_integer(response)
        return status_register

    def set_standard_event_enable_mask(self, register_mask):
        """Configures values of the standard event enable register bits.
        These values determine which bits propagate to the standard event register

            Args:
                register_mask (Model224StandardEventRegister):
                    An StandardEventRegister class object with all bits set to a value

        """

        integer_representation = register_mask.to_integer()
        self.command("*ESE " + str(integer_representation))

    def clear_interface_command(self):
        """Clears the bits in the Status Byte Register, Standard Event Status Register, and Operation Event Register,
        and terminates all pending operations. Clears the interface, but not the controller."""

        self.command("*CLS")

    def reset_instrument(self):
        """Sets controller parameters to power-up settings"""

        self.command("*RST")

    def set_service_request(self, register_mask):
        """Manually enable/disable the mask of the corresponding status flag bit in the status
        byte register

            Args:
                register_mask (Model224ServiceRequestRegister):
                    A Model224ServiceRequestRegister class object with all bits configured
        """

        integer_representation = register_mask.to_integer()
        self.command("*SRE " + str(integer_representation))

    def get_service_request(self):
        """Returns the status byte register bits and their values as a class instance"""

        response = self.query("*SRE?")
        status_register = Model224ServiceRequestRegister.from_integer(response)
        return status_register

    def get_status_byte(self):
        """Returns the status flag bits as a class instance without resetting the register"""

        response = self.query("*STB?")
        status_flag = Model224StatusByteRegister.from_integer(response)
        return status_flag

    def get_self_test(self):
        """Instrument self test result completed at power up

            Return:
                test_errors (bool):
                    * True = errors found
                    * False = no errors found
        """

        test_errors = bool(int(self.query("*TST?")))
        return test_errors

    def set_wait_to_continue(self):
        """Causes the IEEE-488 interface to hold off until all pending operations have been completed.
        This has the same function as the set_operation_complete() method, except that it does not set the
        Operation Complete event bit in the Event Status Register"""

        self.command("*WAI")

    def set_to_factory_defaults(self):
        """Sets all the settings and configurations to their factory default values."""
        self.command("DFLT 99")

    def get_reading_status(self, input_channel):
        """Returns the reading status of any input status flags that may be set.

            Args:
                input_channel (str):
                    * The input to check for reading status flags.
                    * Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

            Returns:
                (dict):
                    {invalid_reading: bool, temperature_under_range: bool, temperature_over_range: bool,
                    sensor_units_zero: bool, sensor_units_over_range: bool}
        """
        flag_code = int(self.query(f"RDGST? {input_channel}"))
        reading_status = Model224ReadingStatusRegister.from_integer(flag_code)
        return reading_status

    def get_kelvin_reading(self, input_channel):
        """Returns the temperature value in kelvin of either channel

            Args:
                input_channel:
                    * Selects the channel to retrieve measurement.
                    * Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

            Returns:
                (float):
                    The reading of the sensor in kelvin


        """

        return float(self.query(f"KRDG? {input_channel}"))

    def get_sensor_reading(self, input_channel):
        """Returns the sensor reading in the sensor's units.

            Args:
                input_channel:
                    * Selects the channel to retrieve measurement.
                    * Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

            Returns:
                reading (float):
                    * The raw sensor reading in the units of the connected sensor

        """

        return float(self.query(f"SRDG? {input_channel}"))

    def get_celsius_reading(self, input_channel):
        """Returns the given input's temperature reading in degrees Celsius.

            Args:
                input_channel (str)
                    Selects input to retrieve measurement from.

            Returns:
                (float):
                    Temperature readings in degrees Celsius
        """
        return float(self.query(f"CRDG? {input_channel}"))

    def get_all_inputs_celsius_reading(self):
        """Returns the temperature reading in degrees Celsius of all the inputs.

            Returns:
                (dict):
                    {input_a_reading: float, input_b_reading: float, input_c1_reading: float, input_c2_reading: float,
                    input_c3_reading: float, input_c4_reading: float, input_c5_reading: float, input_d1_reading: float,
                    input_d2_reading: float, input_d3_reading: float, input_d4_reading: float, input_d5_reading: float}
        """
        reading = self.query("CRDG? 0")
        separated_readings = reading.split(",")
        return {'input_a_reading': float(separated_readings[0]),
                'input_b_reading': float(separated_readings[1]),
                'input_c1_reading': float(separated_readings[2]),
                'input_c2_reading': float(separated_readings[3]),
                'input_c3_reading': float(separated_readings[4]),
                'input_c4_reading': float(separated_readings[5]),
                'input_c5_reading': float(separated_readings[6]),
                'input_d1_reading': float(separated_readings[7]),
                'input_d2_reading': float(separated_readings[8]),
                'input_d3_reading': float(separated_readings[9]),
                'input_d4_reading': float(separated_readings[10]),
                'input_d5_reading': float(separated_readings[11])}

    def set_input_diode_excitation_current(self, input_channel, diode_current):
        """Sets the excitation current of a diode sensor. Input must be configured for a diode sensor for command
        to work. Current defaults to 10uA.

            Args:
                input_channel (str):
                    * The input to configure the diode excitation current for.

                diode_current (Model224DiodeExcitationCurrent):
                    * The excitation current for the diode sensor.

        """
        self.command(f"DIOCUR {input_channel},{diode_current}")

    def get_input_diode_excitation_current(self, input_channel):
        """Returns the diode excitation current for the given diode sensor.

            Args:
                input_channel (str):
                    The diode sensor input to query the current of.

            Returns:
                diode_current (Model224DiodeExcitationCurrent):
                    A member of the Model224DiodeExcitationCurrent enum class.

        """
        diode_current_int = int(self.query(f"DIOCUR? {input_channel}"))
        return Model224DiodeExcitationCurrent(diode_current_int)

    def set_sensor_name(self, channel, sensor_name):
        """Sets a given name to a sensor on the specified channel

            Args:
                channel (str):
                    * Specifies which the sensor to name is on.
                    * Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

                sensor_name(str):
                    * Name user wants to give to the sensor on the specified channel

        """

        self.command(f"INNAME {channel},\"{sensor_name}\"")

    def get_sensor_name(self, channel):
        """Returns the name of the sensor on the specified channel

            Args:
                channel (str):
                    * Specifies which input sensor to retrieve name of.
                    * Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

            Returns:
                name (str)
                    * Name associated with the sensor

        """

        return self.query(f"INNAME? {channel}")

    def set_display_contrast(self, contrast_level):
        """Sets the contrast level for the front panel display

            Args:
                contrast_level (int):
                    * Display contrast for the front panel LCD screen
                    * Options are:
                        * 1 - 32

        """

        self.command(f"BRIGT {contrast_level}")

    def get_display_contrast(self):
        """Returns the contrast level of front panel display

            Return:
                (int):
                    * Contrast level of the front panel LCD screen

        """

        return int(self.query("BRIGT?"))

    def set_ieee_488(self, address):
        """Specifies the IEEE address

            Args:
                address (int):
                    * 1-30 (0 and 31 reserved)
        """
        self.command(f"IEEE {address}")

    def get_ieee_488(self):
        """Returns the IEEE address set

            Return:
                address (int):
                    * 1-30 (0 and 31 reserved)

        """
        return int(self.query("IEEE?"))

    def set_led_state(self, state):
        """Sets the front panel LEDs to on or off.

            Args:
                state (bool)
                    * Sets the LEDs to functional or nonfunctional. Options are:
                    * False for off or True for on

        """
        self.command(f"LEDS {str(int(state))}")

    def get_led_state(self):
        """Returns whether or not front panel LEDs are enabled.

            Returns:
                state (bool)
                    * Specifies whether front panel LEDs are functional. Returns:
                    * False if disabled, True enabled.

        """
        return bool(int(self.query("LEDS?")))

    def set_keypad_lock(self, state, code):
        """Locks or unlocks front panel keypad (except for alarms and disabling heaters).

            Args:
                state (bool)
                    * Sets the keypad to locked or unlocked. Options are:
                    * False for unlocked or True for locked

                code (int)
                    * Specifies 3 digit lock-out code. Options are:
                    * 000 - 999

        """
        self.command(f"LOCK {str(int(state))},{str(code)}")

    def get_keypad_lock(self):
        """Returns the state of the keypad lock and the lock-out code.

            Return:
                (dict):
                    * [state: bool, code: int]

        """
        output_string = self.query("LOCK?")
        separated_response = output_string.split(",")
        return {'state': bool(int(separated_response[0])),
                'code': int(separated_response[1])}

    def get_min_max_data(self, input_channel):
        """Returns the minimum and maximum data from an input

            Args:
                input_channel (str):
                    Specifies which input to query
            Return:
                min_max_data (dict):
                    * [minimum: float, maximum: float]

        """
        min_max_data = self.query("MDAT? " + str(input_channel)).split(",")
        min_max_dictionary = {"minimum": float(min_max_data[0]),
                              "maximum": float(min_max_data[1])}
        return min_max_dictionary

    def reset_min_max_data(self):
        """Resets the minimum and maximum input data"""
        self.command("MNMXRST")

    def set_input_curve(self, input_channel, curve_number):
        """Specifies the curve an input uses for temperature conversion

            Args:
                input_channel (str):
                    Specifies which input to configure

                curve_number (int):
                    * 0 = none, 1-20 = standard curves, 21-59 = user curves

        """
        self.command(f"INCRV {str(input_channel)},{str(curve_number)}")
        # Check that the user mapped an input to a curve (not just set the input to no curve)
        if curve_number != 0:
            # Query the curve mapped to input_channel, if the query returns zero,
            # an invalid curve was selected for the specified input
            set_curve = self.get_input_curve(input_channel)
            if set_curve == 0:
                raise InstrumentException("The specified curve type does not match the configured input type")

    def get_input_curve(self, input_channel):
        """Returns the curve number being used for a given input

            Args:
                input_channel (str):
                    Specifies which input to query

            Return:
                curve_number (int):
                    * 0-59

        """
        return int(self.query(f"INCRV? {str(input_channel)}"))

    def set_website_login(self, username, password):
        """Sets the username and password to connect instrument to website.

            Args:
                username (str)
                    * Username to set for login.
                    * Must be less than or equal to 15 characters.
                    * Method automatically puts quotation marks around string, so they are not needed in the
                      string literal passed into the method.

                password (str)
                    * password to set for login.
                    * Must be less than or equal to 15 characters.
                    * Method automatically puts quotation marks around string, so they are not needed in the
                      string literal passed into the method.

        """
        self.command("WEBLOG \"" + username + "\",\"" + password + "\"")

    def get_website_login(self):
        """Returns the set username and password for web login for the instrument.

            Returns:
                (dict):
                    {username: str, password: str}
        """
        username_password = self.query("WEBLOG?")
        separated_string = username_password.split(",")
        # Remove padded whitespace and quotations in the returned username and password
        username = separated_string[0].strip(' "')
        password = separated_string[1].strip(' "')
        return {"username": username,
                "password": password}

    def set_alarm_parameters(self, input_channel, alarm_enable, alarm_settings=None):
        """Configures the alarm parameters for an input

            Args:
                input_channel (str):
                    Specifies which input to configure
                alarm_enable (bool):
                    Specifies whether to turn on the alarm for the input, or turn the alarm off.
                alarm_settings (Model224AlarmParameters):
                    See Model224AlarmParameters class. Optional if alarm_enable is set to False

        """

        if alarm_enable:
            if alarm_settings.audible is None:
                audible = 0
            else:
                audible = int(alarm_settings.audible)
            if alarm_settings.visible is None:
                visible = 0
            else:
                visible = int(alarm_settings.visible)
            self.command(f"ALARM {input_channel.upper()},{int(alarm_enable)},{alarm_settings.high_value}," +
                        f"{alarm_settings.low_value},{alarm_settings.deadband},{int(alarm_settings.latch_enable)}," +
                        f"{audible},{visible}")
        else:
            self.command("ALARM " + str(input_channel.upper()) + "," + str(int(alarm_enable)) + ",0,0,0,0,0,0")

    def get_alarm_parameters(self, input_channel):
        """Returns the present state of all alarm parameters

            Args:
                input_channel (str):
                    Specifies which input to configure

            Return:
                (dict):
                    {alarm_enable: bool, alarm_settings: Model224AlarmParameters}

        """
        parameters = self.query("ALARM? " + str(input_channel)).split(",")
        alarm_enable = bool(int(parameters[0]))
        alarm_settings = Model224AlarmParameters(float(parameters[1]), float(parameters[2]),
                                                 float(parameters[3]), bool(int((parameters[4]))),
                                                 audible=bool(int(parameters[5])), visible=bool(int(parameters[6])))
        return {'alarm_enable': alarm_enable,
                'alarm_settings': alarm_settings}

    def get_alarm_status(self, input_channel):
        """Returns the high state and low state of the alarm for the specified channel

            Args:
                input_channel (str)
                    * Specifies which input channel to read from.
                    * Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

            Returns:
                (dict):
                    {high_state: bool, low_state: bool}
                        * high_state (bool)
                            * True if high state is on, False if high state is off

                        * low_state (bool)
                            * True if low state is on, False if low state is off

        """
        response = self.query("ALARMST? " + str(input_channel))
        separated_response = response.split(",")
        return {"high_state": bool(int(separated_response[0])),
                "low_state": bool(int(separated_response[1]))}

    def reset_alarm_status(self):
        """Clears the high and low status of all alarms."""
        self.command("ALMRST")

    def set_curve_header(self, curve_number, curve_header):
        """Configures the user curve header

            Args:
                curve_number (int):
                    * Specifies which curve to configure.
                    * Options are:
                        * 21 - 59

                curve_header (Model224CurveHeader):
                    * A Model224CurveHeader class object containing the desired curve information

        """
        command_string = (f"CRVHDR {curve_number},{curve_header.curve_name},{curve_header.serial_number}," +
                        f"{curve_header.curve_data_format},{curve_header.temperature_limit},{curve_header.coefficient}")

        self.command(command_string)

    def get_curve_header(self, curve):
        """Returns parameters set on a particular user curve header

            Args:
                curve (int):
                    * Specifies a curve to retrieve
                    * Options are:
                        * 21 - 59

            Returns:
                header (Model224CurveHeader):
                    * A Model224CurveHeader class object containing the desired curve information

        """
        response = self.query(f"CRVHDR? {curve}")
        curve_header = response.split(",")
        header = Model224CurveHeader(str(curve_header[0]),
                                     str(curve_header[1]),
                                     Model224CurveFormat(int(curve_header[2])),
                                     float(curve_header[3]),
                                     Model224CurveTemperatureCoefficients(int(curve_header[4])))

        return header

    def set_curve_data_point(self, curve, index, sensor_units, temperature):
        """Configures a user curve point

            Args:
                curve (int or str):
                    * Specifies which curve to configure

                index (int):
                    * Specifies the points index in the curve

                sensor_units (float):
                    * Specifies sensor units for this point to 6 digits

                temperature (float):
                    * Specifies the corresponding temperature in Kelvin for this point to 6 digits

        """
        self.command(f"CRVPT {curve},{index},{sensor_units},{temperature}")

    def get_curve_data_point(self, curve, index):
        """Returns a standard or user curve data point

            Args:
                curve (int):
                    * Specifies which curve to query

                index (int):
                    * Specifies the points index in the curve

            Return:
                curve_point (tuple)
                    * (sensor_units: float, temp_value: float))

        """
        curve_point = self.query(f"CRVPT? {curve},{index}").split(",")
        return float(curve_point[0]), float(curve_point[1])

    def delete_curve(self, curve):
        """Deletes the user curve

            Args:
                curve (int):
                    * Specifies a user curve to delete

        """
        self.command(f"CRVDEL {curve}")

    def generate_and_apply_soft_cal_curve(self, source_curve, curve_number, serial_number, calibration_point_1,
                                          calibration_point_2=(0, 0), calibration_point_3=(0, 0)):
        """Creates a SoftCal curve from 1-3 temperature/sensor points and a standard curve. Inputs generated curve
        into the given curve number.

            Args:
                source_curve (Model224SoftCalSensorTypes):
                    * The standard curve to use to generate the SoftCal curve from along with calibration points.

                curve_number (int):
                    * The curve number to save the generated curve to.
                    * Options are:
                        * 21 - 59

                serial_number (str):
                    * Serial number of the user curve.
                    * Maximum of 10 characters

                calibration_point_1 (tuple):
                    * Tuple of two floats in the form (temperature_value, sensor_value)

                calibration_point_2 (tuple):
                    * Tuple of two floats in the form (temperature_value, sensor_value)
                    * Optional parameter

                calibration_point_3 (tuple):
                    * Tuple of two floats in the form (temperature_value, sensor_value)
                    * Optional parameter

        """
        command_string = (f"SCAL {source_curve},{curve_number},{serial_number}," +
                            f"{calibration_point_1[0]},{calibration_point_1[1]}," +
                            f"{calibration_point_2[0]},{calibration_point_2[1]}," +
                            f"{calibration_point_3[0]},{calibration_point_3[1]}")
        self.command(command_string)

    def get_curve(self, curve):
        """Returns a list of all the data points in a particular curve

            Args:
                curve (int):
                    * Specifies which curve to set

            Return:
                data_points (list):
                    * A list containing every point in the curve represented as a tuple
                        * (sensor_units: float, temp_value: float)

        """

        data_points = []
        true_point_index = 0
        for i in range(0, 200):
            point = self.get_curve_data_point(curve, i + 1)
            data_points.append(point)
            if point[0] != 0 or point[1] != 0:
                true_point_index = i

        # Remove all extraneous points
        data_points = data_points[:true_point_index + 1]

        return data_points

    def set_curve(self, curve, data_points):
        """Method to define a user curve using a list of data points

            Args:
                curve (int):
                    * Specifies which curve to set

                data_points (list):
                    * A list containing every point in the curve represented as a tuple
                        * (sensor_units: float, temp_value: float)

        """

        self.delete_curve(curve)

        for index, point in data_points:
            self.set_curve_data_point(curve, index + 1, point[0], point[1])

    def get_relay_status(self, relay_channel):
        """Returns whether the specified relay is On or Off.

            Args:
                relay_channel (int)
                    * The relay channel to query.
                    * Options are:
                        * 1 or 2

            Returns:
                (bool):
                    * True if relay is on, False if relay is off.
        """
        return bool(int(self.query(f"RELAYST? {str(relay_channel)}")))

    def set_filter(self, input_channel, filter_enabled, number_of_points=8, filter_reset_threshold=10):
        """Enables or disables a filter for the readings of the specified input channel. Filter is a running
        average that smooths input readings exponentially.

            Args:
                input_channel (str):
                    * The input to set or disable a filter for.
                    * Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

                filter_enabled (bool):
                    * Enables or disables a filter for the input channel.
                    * True for enabled, False for disabled.

                number_of_points (int):
                    * Specifies the number of points used for the filter.
                    * Inputting a larger number of points will slow down the instrument's response to changes in
                        temperature.
                    * Options are:
                        * 2 - 64
                    * Optional if disabling the filter function.

                filter_reset_threshold (int):
                    * Specifies the limit for restarting the filter, represented by a percent of the full scale reading.
                        If raw reading differs from filtered value by more than this threshold, filter averaging resets.
                    * Options are:
                        * 1% - 10%
                    * Optional if disabling the filter function.

        """
        self.command("FILTER " + str(input_channel) + "," + str(int(filter_enabled)) + "," + str(number_of_points) +
                     "," + str(filter_reset_threshold))

    def get_filter(self, input_channel):
        """Retrieves information about the filter set on the specified input channel.

            Args:
                input_channel (str):
                    * The input to query for filter information.
                    * Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

            Returns:
                (dict):
                    {filter_enabled: bool, number_of_points: int, filter_reset_threshold: int}

        """
        filter_information = self.query(f"FILTER? {str(input_channel)}")
        separated_information = filter_information.split(",")
        return {'filter_enabled': bool(int(separated_information[0])),
                'number_of_points': int(separated_information[1]),
                'filter_reset_threshold': int(separated_information[2])}

    def configure_input(self, input_channel, settings):
        """Configures a sensor for measurement input readings.

            Args:
                input_channel (str):
                    The input to configure the input for. Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

                settings (Model224InputSensorSettings):
                    Object of the Model224InputSensorSettings containing information for sensor setup.

        """
        command_string = (f"INTYPE {input_channel},{settings.sensor_type},{int(settings.autorange_enabled)}," +
                        f"{settings.sensor_range},{int(settings.compensation)},{settings.preferred_units}")
        self.command(command_string)

    def disable_input(self, input_channel):
        """Disables the selected input channel.

            Args:
                input_channel (str):
                    The input to disable. Options are:
                        * A
                        * B
                        * C (1 - 5)
                        * D (1 - 5)

        """
        # Fill all parameters with 0 to disable
        self.command(f"INTYPE {str(input_channel)},0,0,0,0,0")

    def get_input_configuration(self, input_channel):
        """Returns the configuration settings of the sensor at the specified input channel.

            Args:
                input_channel (str)
                    The input to query. Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

            Returns:
                (Model224InputSensorSettings):
                    Object of type Model224InputSensorSettings containing information about the sensor at the given
                    input_channel

        """
        settings_string = self.query(f"INTYPE? {str(input_channel)}")
        separated_settings = settings_string.split(",")
        # Convert sensor_range depending on sensor type
        sensor_type = int(separated_settings[0])
        if sensor_type == 1:
            sensor_range = Model224DiodeSensorRange(int(separated_settings[2]))
        elif sensor_type == 2:
            sensor_range = Model224PlatinumRTDSensorResistanceRange(int(separated_settings[2]))
        elif sensor_type == 3:
            sensor_range = Model224NTCRTDSensorResistanceRange(int(separated_settings[2]))
        else:
            # Sensor is disabled
            sensor_range = None
        # Create object
        return Model224InputSensorSettings(Model224InputSensorType(sensor_type),
                                           Model224InputSensorUnits(int(separated_settings[4])),
                                           sensor_range,
                                           bool(int(separated_settings[1])),
                                           bool(int(separated_settings[3])))

    def select_remote_interface(self, remote_interface):
        """Selects the remote interface to use for communications.

            Args:
                remote_interface (Model224RemoteInterface):
                    Object of enum type Model224RemoteInterface, representing the type of interface used for
                    communications

        """
        self.command(f"INTSEL {remote_interface}")

    def get_remote_interface(self):
        """Returns the remote interface being used for communications.

            Returns:
                (Model224RemoteInterface):
                    Object of enum type Model224RemoteInterface representing the interface being used for
                    communications

        """
        interface_number = int(self.query("INTSEL?"))
        return Model224RemoteInterface(interface_number)

    def select_interface_mode(self, interface_mode):
        """Selects the mode for the remote interface being used.

            Args:
                interface_mode (Model224InterfaceMode):
                    Object of enum type Model224InterfaceMode representing the desired communication mode.

        """
        self.command(f"MODE {interface_mode}")

    def get_interface_mode(self):
        """Returns the mode of the remote interface.

            Returns:
                (Model224InterfaceMode):
                    Object of enum type Model224InterfaceMode representing the communication mode.

        """
        mode_number = int(self.query("MODE?"))
        return Model224InterfaceMode(mode_number)

    def set_display_field_settings(self, field, input_channel, display_units):
        """Configures a display field in custom display mode.

            Args:
                field (int):
                    * Specifies which display field to configure.
                    * Options are:
                        * 1 - 8

                input_channel (Model224InputChannel)
                    * Defines which input to display.

                display_units (Model224DisplayFieldUnits)
                    * Defines which units to display reading in.

        """
        self.command(f"DISPFLD {field},{input_channel},{display_units}")

    def get_display_field_settings(self, field):
        """Returns the settings of a single display field in custom display mode.

            Args:
                field (int):
                    * Specifies the display field to query.
                    * Options are:
                        * 1 - 8

            Returns:
                (dict):
                    {input_channel: Model224InputChannel, display_units: Model224DisplayFieldUnits}

        """
        display_field_settings = self.query("DISPFLD? " + str(field))
        separated_settings = display_field_settings.split(",")
        return {'input_channel': Model224InputChannel(int(separated_settings[0])),
                'display_units': Model224DisplayFieldUnits(int(separated_settings[1]))}

    def configure_display(self, display_mode, number_of_fields=0):
        """Configures the display of the instrument.

            Args:
                display_mode (Model224DisplayMode):
                    * Defines what mode to set the display in.
                    * Mode either defines which input to display, or sets up a custom display using display fields.

                number_of_fields (Model224NumberOfFields):
                    * Defines the number of display locations to display.
                    * Only valid if mode is set to CUSTOM

        """
        self.command(f"DISPLAY {display_mode},{number_of_fields}")

    def get_display_configuration(self):
        """Returns the mode of the display. If display mode is Custom, this method also returns the number of
        display fields in the custom display.

            Returns:
                (dict):
                    {display_mode: Model224DisplayMode, number_of_fields: Model224NumberOfFields}

        """
        display_settings_string = self.query("DISPLAY?")
        separated_settings = display_settings_string.split(",")
        display_mode = int(separated_settings[0])
        if display_mode != 4:
            return_dictionary = {'display_mode': Model224DisplayMode(display_mode),
                                 'number_of_fields': None}
        else:
            number_of_fields = int(separated_settings[1])
            return_dictionary = {'display_mode': Model224DisplayMode(display_mode),
                                 'number_of_fields': Model224NumberOfFields(number_of_fields)}
        return return_dictionary

    def turn_relay_on(self, relay_number):
        """Turns the specified relay on.

            Args:
                relay_number (int):
                    * The relay to turn on.
                    * Options are:
                        * 1 or 2

        """
        self.command(f"RELAY {relay_number},1,0,0")

    def turn_relay_off(self, relay_number):
        """Turns the specified relay off.

            Args:
                relay_number (int):
                    * The relay to turn off.
                    * Options are:
                        * 1 or 2

        """
        self.command(f"RELAY {relay_number},0,0,0")

    def set_relay_alarms(self, relay_number, activating_input_channel, alarm_relay_trigger_type):
        """Sets a relay to turn on and off automatically based on the state of the alarm of the specified input channel.

            Args:
                relay_number (int):
                    * The relay to configure.
                    * Options are:
                        * 1 or 2

                activating_input_channel (str):
                    * Specifies which input alarm activates the relay when the relay is in alarm mode
                    * Only applies if ALARM mode is chosen.
                    * Options are:
                        * A
                        * B
                        * C(1 - 5)
                        * D(1 - 5)

                alarm_relay_trigger_type (Model224RelayControlAlarm):
                    * Specifies the type of alarm that triggers the relay
                    * Only applies if ALARM mode is chosen.

        """
        self.command(f"RELAY {relay_number},2,{activating_input_channel},{alarm_relay_trigger_type}")

    def get_relay_alarm_control_parameters(self, relay_number):
        """Returns the relay alarm configuration for either of the two configurable relays. Relay must be
        configured for alarm mode to retrieve parameters.

            Args:
                relay_number (int)
                    * Specifies which relay to query
                    * Options are:
                        * 1 or 2

            Return:
                (dict):
                    {activating_input_channel: str, alarm_relay_trigger_type: Model224RelayControlAlarm}

        """

        relay_config = self.query(f"RELAY? {relay_number}").split(",")
        activating_input_channel = relay_config[1]
        alarm_relay_trigger_type = Model224RelayControlAlarm(int(relay_config[2]))
        return {'activating_input_channel': activating_input_channel,
                'alarm_relay_trigger_type': alarm_relay_trigger_type}

    def get_relay_control_mode(self, relay_number):
        """Returns the configured mode of the specified relay.

            Args:
                relay_number (int):
                    * Specifies which relay to query
                    * Options are:
                        * 1 or 2

            Returns:
                (Model224RelayControlMode):
                    * The configured mode of the relay, represented as an object of the enum type
                        Model224RelayControlMode

        """
        relay_settings = self.query("RELAY? " + str(relay_number))
        split_relay_settings = relay_settings.split(",")
        return Model224RelayControlMode(int(split_relay_settings[0]))

    def _get_identity(self):
        return self.query('*IDN?', check_errors=False).split(',')


__all__ = ['Model224', 'Model224AlarmParameters', 'Model224CurveFormat', 'Model224CurveHeader',
           'Model224CurveTemperatureCoefficients', 'Model224DiodeExcitationCurrent', 'Model224DisplayMode',
           'Model224DiodeSensorRange', 'Model224InputChannel', 'Model224InterfaceMode', 'Model224DisplayFieldUnits',
           'Model224StandardEventRegister', 'Model224InputSensorSettings', 'Model224InputSensorType', 'Model224InputSensorUnits',
           'Model224NTCRTDSensorResistanceRange', 'Model224NumberOfFields', 'Model224PlatinumRTDSensorResistanceRange',
           'Model224ReadingStatusRegister', 'Model224RelayControlAlarm', 'Model224RelayControlMode', 'Model224RemoteInterface',
           'Model224ServiceRequestRegister', 'Model224SoftCalSensorTypes', 'Model224StatusByteRegister']
