"""Implements functionality unique to the M81 Measure Modules."""

from datetime import datetime
from warnings import warn
from lakeshore.xip_instrument import RegisterBase
from lakeshore.ssm_base_module import SSMSystemModuleQuestionableRegister, BaseModule


class SSMSystemMeasureModuleOperationRegister(RegisterBase):
    """Class object representing the operation status register of a measure module"""

    bit_names = [
        "overload",
        "settling",
        "unlocked"
    ]

    def __init__(
            self,
            overload,
            settling,
            unlocked):
        self.overload = overload
        self.settling = settling
        self.unlocked = unlocked

# pylint: disable=R0904
class MeasureModule(BaseModule):
    """Class for interaction with a specific measure channel of the M81 instrument"""

    def get_name(self):
        """Returns the user-settable name of the module"""

        return self.device.query(f'SENSe{self.module_number}:NAME?').strip('\"')

    def set_name(self, new_name):
        """Set the name of the module"""

        self.device.command(f'SENSe{self.module_number}:NAME "{new_name}"')

    def get_notes(self):
        """Returns the user-settable notes of the module"""

        return self.device.query(f'SENSe{self.module_number}:NOTes?').strip('\"')

    def set_notes(self, new_note):
        """Set the notes of the module"""

        self.device.command(f'SENSe{self.module_number}:NOTes "{new_note}"')

    def get_model(self):
        """Returns the model of the module (i.e. VM-10)"""

        return self.device.query(f'SENSe{self.module_number}:MODel?').strip('\"')

    def get_serial(self):
        """Returns the serial number of the module (i.e. LSA1234)"""

        return self.device.query(f'SENSe{self.module_number}:SERial?').strip('\"')

    def get_hw_version(self):
        """Returns the hardware version of the module"""

        return int(self.device.query(f'SENSe{self.module_number}:HWVersion?'))

    def get_self_cal_status(self):
        """Returns the status of the last self calibration of the module"""

        return self.device.query(f'SENSe{self.module_number}:SCALibration:STATus?')

    def run_self_cal(self):
        """Run a self calibration for the module"""

        self.device.command(f'SENSe{self.module_number}:SCALibration:RUN')

    def reset_self_cal(self):
        """Restore factory self calibration for the module"""

        self.device.command(f'SENSe{self.module_number}:SCALibration:RESet')

    def get_averaging_time(self):
        """Returns the averaging time of the module in Power Line Cycles. Not relevant in lock-in mode."""

        return float(self.device.query(f'SENSe{self.module_number}:NPLCycles?'))

    def set_averaging_time(self, nplc):
        """Sets the averaging time of the module. Not relevant in lock-in mode.

            Args:
                nplc (float):
                    The new number of power line cycles to average
        """

        self.device.command(f'SENSe{self.module_number}:NPLCycles {float(nplc)}')

    def get_mode(self):
        """Returns the measurement mode of the module. 'DC', 'AC', or 'LIA'."""

        return self.device.query(f'SENSe{self.module_number}:MODE?')

    def set_mode(self, mode):
        """Sets the measurement mode of the module

            Args:
                mode (str):
                    The new measurement mode ('DC', 'AC', or 'LIA')
        """

        self.device.command(f'SENSe{self.module_number}:MODE {mode}')

    def get_coupling(self):
        """Return input coupling of the module. 'AC' or 'DC'."""

        return self.device.query(f'SENSe{self.module_number}:COUPling?')

    def set_coupling(self, coupling):
        """Sets the input coupling of the module

            Args:
                coupling (str):
                    The new input coupling ('AC' or 'DC')
        """

        self.device.command(f'SENSe{self.module_number}:COUPling {coupling}')

    def use_ac_coupling(self):
        """Sets the input coupling of the module to 'AC'"""

        self.set_coupling('AC')

    def use_dc_coupling(self):
        """Sets the input coupling of the module to 'DC'"""

        self.set_coupling('DC')

    def get_input_configuration(self):
        """Returns the input configuration of the module. 'AB', 'A', or 'GROUND'."""
        return self.device.query(f'SENSe{self.module_number}:CONFiguration?')

    def set_input_configuration(self, input_configuration):
        """Sets the input configuration of the module

            Args:
                input_configuration (str):
                    The new input configuration ('AB', 'A', or 'GROUND')
        """

        self.device.command(f'SENSe{self.module_number}:CONFiguration {input_configuration}')

    def enable_bias_voltage(self):
        """Enables the bias voltage applied to the amplifier"""

        self.device.command(f'SENSe{self.module_number}:BIAS:STATe 1')

    def disable_bias_voltage(self):
        """Disables the bias voltage applied to the amplifier"""

        self.device.command(f'SENSe{self.module_number}:BIAS:STATe 0')

    def get_bias_voltage_enabled(self):
        """Return whether the bias voltage is enabled"""

        return bool(int(self.device.query(f'SENSe{self.module_number}:BIAS:STATe?')))

    def get_bias_voltage(self):
        """Return the bias voltage applied on the amplifier input in Volts"""

        return float(self.device.query(f'SENSe{self.module_number}:BIAS:VOLTage:DC?'))

    def set_bias_voltage(self, bias_voltage):
        """Sets the bias voltage applied on the amplifier input

            Args:
                bias_voltage (float):
                    The new bias voltage in Volts
        """

        self.device.command(f'SENSe{self.module_number}:BIAS:VOLTage:DC {str(bias_voltage)}')

    def get_filter_state(self):
        """Returns whether the hardware filter is engaged"""

        return bool(int(self.device.query(f'SENSe{self.module_number}:FILTer:STATe?')))

    def get_lowpass_corner_frequency(self):
        """Returns the low pass filter cuttoff frequency. 'NONE', 'F10', 'F30', 'F100', 'F300', 'F1000', 'F3000', or 'F10000'."""

        return self.device.query(f'SENSe{self.module_number}:FILTer:LPASs:FREQuency?')

    def get_lowpass_rolloff(self):
        """Returns the low pass filter roll-off. 'R6' or 'R12'."""

        return self.device.query(f'SENSe{self.module_number}:FILTer:LPASs:ATTenuation?')

    def get_highpass_corner_frequency(self):
        """Returns the high pass filter cuttoff frequency. 'NONE', 'F10', 'F30', 'F100', 'F300', 'F1000', or 'F3000'."""

        return self.device.query(f'SENSe{self.module_number}:FILTer:HPASs:FREQuency?')

    def get_highpass_rolloff(self):
        """Returns the high pass filter roll-off. 'R6' or 'R12'."""

        return self.device.query(f'SENSe{self.module_number}:FILTer:HPASs:ATTenuation?')

    def get_gain_allocation_strategy(self):
        """Returns the gain allocation strategy used for the hardware filter. 'NOISE', or 'RESERVE'."""

        return self.device.query(f'SENSe{self.module_number}:FILTer:OPTimization?')

    def set_gain_allocation_strategy(self, optimization_type):
        """Sets the gain allocation strategy used for the hardware filter

            Args:
                optimization_type (str):
                    The new optimization type ('NOISE', or 'RESERVE')
        """

        self.device.command(f'SENSe{self.module_number}:FILTer:OPTimization {optimization_type}')

    def configure_input_lowpass_filter(self, corner_frequency, rolloff='R12'):
        """Configure the input low pass filter

            Args:
                corner_frequency (str):
                    The low pass corner frequency ('NONE', 'F10', 'F30', 'F100', 'F300', 'F1000', 'F3000', or 'F10000'). F10 = 10 Hz, etc.

                rolloff (str):
                    The low pass roll-off ('R6' or 'R12'). R6 = 6 dB/Octave, R12 = 12 dB/Octave.
        """

        self.device.command(f'SENSe{self.module_number}:FILTer:LPASs:FREQuency {corner_frequency}')
        self.device.command(f'SENSe{self.module_number}:FILTer:LPASs:ATTenuation {rolloff}')
        self.device.command(f'SENSe{self.module_number}:FILTer:STATe 1')

    def configure_input_highpass_filter(self, corner_frequency, rolloff='R12'):
        """Configure the input high pass filter

            Args:
                corner_frequency (str):
                    The high pass corner frequency ('NONE', 'F10', 'F30', 'F100', 'F300', 'F1000', or 'F3000'). F10 = 10 Hz, etc.

                rolloff (str):
                    The high pass roll-off ('R6' or 'R12'). R6 = 6 dB/Octave, R12 = 12 dB/Octave.
        """

        self.device.command(f'SENSe{self.module_number}:FILTer:HPASs:FREQuency {corner_frequency}')
        self.device.command(f'SENSe{self.module_number}:FILTer:HPASs:ATTenuation {rolloff}')
        self.device.command(f'SENSe{self.module_number}:FILTer:STATe 1')

    def disable_input_filters(self):
        """Disables the hardware filters"""

        self.device.command(f'SENSe{self.module_number}:FILTer:STATe 0')

    def get_current_range(self):
        """Returns the current range in Amps"""

        return float(self.device.query(f'SENSe{self.module_number}:CURRent:RANGe?'))

    def get_i_range(self):
        """
        Returns the current range in Amps

        .. deprecated:: 1.5.4
           Use get_current_range instead
        """

        warn('The get_i_range method is deprecated. Use get_current_range instead.', DeprecationWarning)
        return self.get_current_range()

    def get_current_autorange_status(self):
        """Returns whether autoranging is enabled for the module"""

        return bool(int(self.device.query(f'SENSe{self.module_number}:CURRent:RANGe:AUTO?')))

    def get_i_autorange_status(self):
        """
        Returns whether autoranging is enabled for the module

        .. deprecated:: 1.5.4
           Use get_current_autorange_status instead
        """

        warn('The get_i_autorange_status method is deprecated. Use get_current_autorange_status instead.', DeprecationWarning)
        return self.get_current_autorange_status()

    def configure_current_range(self, autorange, max_level=None):
        """Configure current ranging for the module

            Args:
                autorange (bool):
                    True to enable real time range decisions by the module. False for manual ranging.

                max_level (float):
                    The largest current that needs to be measured by the module in Amps.
        """

        if autorange:
            if max_level is not None:
                raise ValueError('If autorange is selected, a manual range cannot be specified.')

            self.device.command(f'SENSe{self.module_number}:CURRent:RANGe:AUTO 1')
        else:
            if max_level is not None:
                self.device.command(f'SENSe{self.module_number}:CURRent:RANGe {str(max_level)}')

    def configure_i_range(self, autorange, max_level=None):
        """
        Configure current ranging for the module

        .. deprecated:: 1.5.4
           Use configure_current_range instead

            Args:
                autorange (bool):
                    True to enable real time range decisions by the module. False for manual ranging.

                max_level (float):
                    The largest current that needs to be measured by the module in Amps.
        """

        warn('The configure_i_range method is deprecated. Use configure_current_range instead.', DeprecationWarning)
        self.configure_current_range(autorange, max_level)

    def get_voltage_range(self):
        """Returns the voltage range in Volts"""

        return float(self.device.query(f'SENSe{self.module_number}:VOLTage:RANGe?'))

    def get_voltage_autorange_status(self):
        """Returns whether autoranging is enabled for the module"""

        return bool(int(self.device.query(f'SENSe{self.module_number}:VOLTage:RANGe:AUTO?')))

    def configure_voltage_range(self, autorange, max_level):
        """Configure voltage ranging for the module

            Args:
                autorange (bool):
                    True to enable real time range decisions by the module. False for manual ranging.

                max_level (float):
                    The largest voltage that needs to be measured by the module in Volts.
        """

        if autorange:
            if max_level is not None:
                raise ValueError('If autorange is selected, a manual range cannot be specified.')

            self.device.command(f'SENSe{self.module_number}:VOLTage:RANGe:AUTO 1')
        else:
            if max_level is not None:
                self.device.command(f'SENSe{self.module_number}:VOLTage:RANGe {str(max_level)}')

    def get_reference_source(self):
        """Returns the lock-in reference source. 'S1', 'S2', 'S3', 'RIN'."""

        return self.device.query(f'SENSe{self.module_number}:LIA:RSOurce?')

    def set_reference_source(self, reference_source):
        """Sets the lock-in reference source

            Args:
                reference_source (str):
                    The new reference source ('S1', 'S2', 'S3', 'RIN')
        """

        self.device.command(f'SENSe{self.module_number}:LIA:RSOurce {reference_source}')

    def get_reference_harmonic(self):
        """Returns the lock-in reference harmonic"""

        return int(self.device.query(f'SENSe{self.module_number}:LIA:DHARmonic?'))

    def set_reference_harmonic(self, harmonic):
        """Sets the lock-in reference harmonic

            Args:
                harmonic (int):
                    The new reference harmonic. 1 is the fundamental frequency, 2 is twice the fundamental frequency, etc.
        """

        self.device.command(f'SENSe{self.module_number}:LIA:DHARmonic {str(harmonic)}')

    def get_reference_phase_shift(self):
        """Returns the lock-in reference phase shift in degrees"""

        return float(self.device.query(f'SENSe{self.module_number}:LIA:DPHase?'))

    def set_reference_phase_shift(self, phase_shift):
        """Sets the lock-in reference phase shift

            Args:
                phase_shift (float):
                    The new reference phase shift in degrees
        """
        self.device.command(f'SENSe{self.module_number}:LIA:DPHase {str(phase_shift)}')

    def auto_phase(self):
        """Executes a one time adjustment of the reference phase shift such that the present phase measurement is zero."""

        self.device.command(f'SENSe{self.module_number}:LIA:DPHase:AUTO')

    def get_lock_in_time_constant(self):
        """Returns the lock-in time constant in seconds"""

        return float(self.device.query(f'SENSe{self.module_number}:LIA:TIMEconstant?'))

    def set_lock_in_time_constant(self, time_constant):
        """Sets the lock-in time constant

            Args:
                time_constant (float):
                    The new time constant in seconds
        """
        self.device.command(f'SENSe{self.module_number}:LIA:TIMEconstant {str(time_constant)}')

    def get_lock_in_settle_time(self, settle_percent=0.01):
        """Returns the lock-in settle time in seconds

            Args:
                settle_percent (float)
                    The desired percent signal has settled to in percent
                    A value of `0.1` is interpreted as 0.1 %
        """
        return float(self.device.query(f'SENSe{self.module_number}:LIA:STIMe? {str(settle_percent)}'))

    def get_lock_in_equivalent_noise_bandwidth(self):
        """Returns the equivalent noise bandwidth (ENBW) in Hz"""
        return float(self.device.query(f'SENSe{self.module_number}:LIA:ENBW?'))

    def get_lock_in_rolloff(self):
        """Returns the lock-in PSD output filter roll-off for the present module. 'R6', 'R12', 'R18' or 'R24'."""

        return self.device.query(f'SENSe{self.module_number}:LIA:ROLLoff?')

    def set_lock_in_rolloff(self, rolloff):
        """Sets the lock-in PSD output filter roll-off

            Args:
                rolloff (str):
                    The new PSD output filter roll-off ('R6', 'R12', 'R18' or 'R24')
        """

        self.device.command(f'SENSe{self.module_number}:LIA:ROLLoff {rolloff}')

    def get_lock_in_iir_state(self):
        """Returns the state of the lock-in PSD output IIR filter"""

        return bool(int(self.device.query(f'SENSe{self.module_number}:LIA:IIR:STATe?')))

    def set_lock_in_iir_state(self, state):
        """Sets the state of the lock-in PSD output IIR filter

            Args:
                state (bool):
                    The new state of the PSD output IIR filter
        """

        self.device.command(f'SENSe{self.module_number}:LIA:IIR:STATe {str(int(state))}')

    def enable_lock_in_iir(self):
        """Sets the state of the lock-in PSD output IIR filter to True."""

        self.set_lock_in_iir_state(True)

    def disable_lock_in_iir(self):
        """Sets the state of the lock-in PSD output IIR filter to False."""

        self.set_lock_in_iir_state(False)

    def get_lock_in_fir_state(self):
        """Returns the state of the lock-in PSD output FIR filter"""

        return bool(int(self.device.query(f'SENSe{self.module_number}:LIA:FIR:STATe?')))

    def set_lock_in_fir_state(self, state):
        """Sets the state of the lock-in PSD output FIR filter

            Args:
                state (bool):
                    The new state of the PSD output FIR filter
        """

        self.device.command(f'SENSe{self.module_number}:LIA:FIR:STATe {str(int(state))}')

    def enable_lock_in_fir(self):
        """Sets the state of the lock-in PSD output FIR filter to True."""

        self.set_lock_in_fir_state(True)

    def disable_lock_in_fir(self):
        """Sets the state of the lock-in PSD output FIR filter to False."""

        self.set_lock_in_fir_state(False)

    def get_lock_in_fir_cycles(self):
        """Returns the number of FIR cycles"""

        return int(self.device.query(f'SENSe{self.module_number}:LIA:FIR:CYCLes?'))

    def set_lock_in_fir_cycles(self, cycles):
        """Sets the number of FIR cycles

            Args:
                cycles (int):
                    The desired number of FIR cycles, between 1 and 100
        """

        self.device.command(f'SENSe{self.module_number}:LIA:FIR:CYCLes {str(int(cycles))}')

    def setup_dc_measurement(self, nplc=1):
        """Setup the module for DC measurement

            Args:
                nplc (float):
                    The new number of power line cycles to average
        """

        self.set_mode('DC')
        self.set_averaging_time(nplc)

    def setup_ac_measurement(self, nplc=1):
        """Setup the module for DC measurement

            Args:
                nplc (float):
                    The new number of power line cycles to average
        """

        self.set_mode('AC')
        self.set_averaging_time(nplc)

    def setup_lock_in_measurement(self,
                                  reference_source,
                                  time_constant,
                                  rolloff='R24',
                                  reference_phase_shift=0.0,
                                  reference_harmonic=1,
                                  use_fir=True):
        """Setup the module for lock-in measurement

            Args:
                reference_source (str):
                    Lock-in reference source ('S1', 'S2', 'S3', 'RIN')

                time_constant (float):
                    Time constant in seconds

                rolloff (str):
                    Lock-in PSD output filter roll-off ('R6', 'R12', 'R18' or 'R12')

                reference_phase_shift (float):
                    Lock-in reference phase shift in degrees

                reference_harmonic (int):
                    Lock-in reference harmonic. 1 is the fundamental frequency, 2 is twice the fundamental frequency, etc.

                use_fir (bool):
                    Enable or disable the PSD output FIR filter
        """

        self.set_mode('LIA')
        self.set_reference_source(reference_source)
        self.set_lock_in_time_constant(time_constant)
        self.set_lock_in_rolloff(rolloff)
        self.set_reference_phase_shift(reference_phase_shift)
        self.set_reference_harmonic(reference_harmonic)
        self.set_lock_in_fir_state(use_fir)

    def zero_relative_baseline(self):
        """Sets the present measurement as the baseline value for calculating relative readings"""

        self.device.command(f'SENSe{self.module_number}:RELative:ZERO')

    def set_relative_baseline(self, baseline):
        """Sets the relative baseline"""

        self.device.command(f'SENSe{self.module_number}:RELative:BASEline {str(baseline)}')

    def get_relative_baseline(self):
        """Returns the relative baseline"""

        return float(self.device.query(f'SENSe{self.module_number}:RELative:BASEline?'))

    def get_multiple(self, *data_sources):
        """This function is deprecated. Use fetch_multiple() instead."""

        return self.fetch_multiple(*data_sources)

    def get_dc(self):
        """Returns the DC measurement in module units"""

        return float(self.device.query(f'READ:SENSe{self.module_number}:DC?'))

    def get_dc_relative(self):
        """Returns the relative DC measurement in module units"""

        return float(self.device.query(f'READ:SENSe{self.module_number}:DC:RELative?'))

    def get_dc_minimum(self):
        """Returns the minimum DC indication in module units"""

        return float(self.device.query(f'STATistic:MINimum:SENSe{self.module_number}:DC?'))

    def get_dc_maximum(self):
        """Returns the maximum DC indication in module units"""

        return float(self.device.query(f'STATistic:MAXimum:SENSe{self.module_number}:DC?'))

    def get_rms(self):
        """Returns the RMS measurement in module units"""

        return float(self.device.query(f'READ:SENSe{self.module_number}:RMS?'))

    def get_rms_relative(self):
        """Returns the relative RMS measurement in module units"""

        return float(self.device.query(f'READ:SENSe{self.module_number}:RMS:RELative?'))

    def get_rms_minimum(self):
        """Returns the minimum RMS indication in module units"""

        return float(self.device.query(f'STATistic:MINimum:SENSe{self.module_number}:RMS?'))

    def get_rms_maximum(self):
        """Returns the maximum RMS indication in module units"""

        return float(self.device.query(f'STATistic:MAXimum:SENSe{self.module_number}:RMS?'))

    def get_peak_to_peak(self):
        """Returns the peak to peak measurement in module units"""

        return float(self.device.query(f'READ:SENSe{self.module_number}:PTPeak?'))

    def get_peak_to_peak_minimum(self):
        """Returns the minimum peak to peak indication in module units"""

        return float(self.device.query(f'STATistic:MINimum:SENSe{self.module_number}:PTPeak?'))

    def get_peak_to_peak_maximum(self):
        """Returns the maximum peak to peak indication in module units"""

        return float(self.device.query(f'STATistic:MAXimum:SENSe{self.module_number}:PTPeak?'))

    def get_positive_peak(self):
        """Returns the positive peak measurement in module units"""

        return float(self.device.query(f'READ:SENSe{self.module_number}:PPEak?'))

    def get_positive_peak_minimum(self):
        """Returns the minimum positive peak indication in module units"""

        return float(self.device.query(f'STATistic:MINimum:SENSe{self.module_number}:PPEak?'))

    def get_positive_peak_maximum(self):
        """Returns the maximum positive peak indication in module units"""

        return float(self.device.query(f'STATistic:MAXimum:SENSe{self.module_number}:PPEak?'))

    def get_negative_peak(self):
        """Returns the negative peak measurement in module units"""

        return float(self.device.query(f'READ:SENSe{self.module_number}:NPEak?'))

    def get_negative_peak_minimum(self):
        """Returns the minimum negative peak indication in module units"""

        return float(self.device.query(f'STATistic:MINimum:SENSe{self.module_number}:NPEak?'))

    def get_negative_peak_maximum(self):
        """Returns the maximum negative peak indication in module units"""

        return float(self.device.query(f'STATistic:MAXimum:SENSe{self.module_number}:NPEak?'))

    def get_lock_in_x(self):
        """Returns the present X measurement from the lock-in"""

        return float(self.device.query(f'FETCh:SENSe{self.module_number}:LIA:X?'))

    def get_lock_in_x_minimum(self):
        """Returns the minimum X indication from the lock in"""

        return float(self.device.query(f'STATistic:MINimum:SENSe{self.module_number}:LIA:X?'))

    def get_lock_in_x_maximum(self):
        """Returns the maximum X indication from the lock in"""

        return float(self.device.query(f'STATistic:MAXimum:SENSe{self.module_number}:LIA:X?'))

    def get_lock_in_y(self):
        """Returns the present Y measurement from the lock-in"""

        return float(self.device.query(f'FETCh:SENSe{self.module_number}:LIA:Y?'))

    def get_lock_in_y_minimum(self):
        """Returns the minimum Y indication from the lock in"""

        return float(self.device.query(f'STATistic:MINimum:SENSe{self.module_number}:LIA:Y?'))

    def get_lock_in_y_maximum(self):
        """Returns the maximum Y indication from the lock in"""

        return float(self.device.query(f'STATistic:MAXimum:SENSe{self.module_number}:LIA:Y?'))

    def get_lock_in_r(self):
        """Returns the present magnitude measurement from the lock-in"""

        return float(self.device.query(f'FETCh:SENSe{self.module_number}:LIA:R?'))

    def get_lock_in_r_minimum(self):
        """Returns the minimum magnitude indication from the lock in"""

        return float(self.device.query(f'STATistic:MINimum:SENSe{self.module_number}:LIA:R?'))

    def get_lock_in_r_maximum(self):
        """Returns the maximum magnitude indication from the lock in"""

        return float(self.device.query(f'STATistic:MAXimum:SENSe{self.module_number}:LIA:R?'))

    def get_lock_in_theta(self):
        """Returns the present angle measurement from the lock-in"""

        return float(self.device.query(f'FETCh:SENSe{self.module_number}:LIA:THETa?'))

    def get_lock_in_theta_minimum(self):
        """Returns the minimum angle indication from the lock in"""

        return float(self.device.query(f'STATistic:MINimum:SENSe{self.module_number}:LIA:THETa?'))

    def get_lock_in_theta_maximum(self):
        """Returns the maximum angle indication from the lock in"""

        return float(self.device.query(f'STATistic:MAXimum:SENSe{self.module_number}:LIA:THETa?'))

    def get_lock_in_frequency(self):
        """Returns the present detected frequency from the Phase Locked Loop (PLL)"""

        return float(self.device.query(f'FETCh:SENSe{self.module_number}:LIA:FREQuency?'))

    def get_pll_lock_status(self):
        """Returns the present lock status of the PLL. True if locked, False if unlocked."""

        return bool(int(self.device.query(f'FETCh:SENSe{self.module_number}:LIA:LOCK?')))

    def get_present_questionable_status(self):
        """Returns the names of the questionable status register bits and their values"""

        response = self.device.query(f'STATus:QUEStionable:SENSe{self.module_number}:CONDition?', check_errors=False)
        status_register = SSMSystemModuleQuestionableRegister.from_integer(response)

        return status_register

    def get_questionable_events(self):
        """Returns the names of questionable event status register bits that are currently high.
        The event register is latching and values are reset when queried."""

        response = self.device.query(f'STATus:QUEStionable:SENSe{self.module_number}:EVENt?', check_errors=False)
        status_register = SSMSystemModuleQuestionableRegister.from_integer(response)

        return status_register

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

        response = self.device.query(f'STATus:QUEStionable:SENSe{self.module_number}:ENABle?', check_errors=False)
        status_register = SSMSystemModuleQuestionableRegister.from_integer(response)

        return status_register

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

            Args:
                register_mask ([Instrument]QuestionableRegister):
                    An instrument specific QuestionableRegister class object with all bits configured true or false.
        """

        integer_representation = register_mask.to_integer()
        self.device.command(f'STATus:QUEStionable:SENSe{self.module_number}:ENABle {integer_representation}', check_errors=False)

    def get_present_operation_status(self):
        """Returns the names of the operation status register bits and their values"""

        response = self.device.query(f'STATus:OPERation:SENSe{self.module_number}:CONDition?', check_errors=False)
        status_register = SSMSystemMeasureModuleOperationRegister.from_integer(response)

        return status_register

    def get_operation_events(self):
        """Returns the names of operation event status register bits that are currently high.
        The event register is latching and values are reset when queried."""

        response = self.device.query(f'STATus:OPERation:SENSe{self.module_number}:EVENt?', check_errors=False)
        status_register = SSMSystemMeasureModuleOperationRegister.from_integer(response)

        return status_register

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

        response = self.device.query(f'STATus:OPERation:SENSe{self.module_number}:ENABle?', check_errors=False)
        status_register = SSMSystemMeasureModuleOperationRegister.from_integer(response)

        return status_register

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

            Args:
                register_mask ([Instrument]OperationRegister):
                    An instrument specific OperationRegister class object with all bits configured true or false.
        """

        integer_representation = register_mask.to_integer()
        self.device.command(f'STATus:OPERation:SENSe{self.module_number}:ENABle {integer_representation}', check_errors=False)

    def get_identify_state(self):
        """Returns the identification state for the given pod."""
        response = bool(int(self.device.query(f'SENSe{self.module_number}:IDENtify?', check_errors=False)))
        return response

    def set_identify_state(self, state):
        """Sets the identification state for the given pod.

            Args:
                state (bool):
                    The desired state for the LED, 1 for identify, 0 for normal state
        """
        self.device.command(f'SENSe{self.module_number}:IDENtify {int(state)}', check_errors=False)

    def get_dark_mode_state(self):
        """Returns the dark mode state for the given pod"""
        response = self.device.query(f'SENSe{self.module_number}:DMODe?', cherk_errors=False)
        return response

    def set_dark_mode_state(self, state):
        """Configures the dark mode state for the given pod.

            Args:
                state (bool):
                    The desired operation for the LED, 1 for normal mode, 0 for dark mode
        """
        self.device.command(f'SENSe{self.module_number}:DMODe {state}', check_errors=False)

    def get_frequency_range_threshold(self):
        """Returns the frequency range threshold for the module. Frequency range threshold normalized to the -3 db
           point. For example, a value of 0.1 means 10 % of the -3 db point.
        """

        return float(self.device.query(f'SENSe{self.module_number}:FRTHreshold?'))

    def set_frequency_range_threshold(self, threshold):
        """Sets the frequency range threshold for the specified module.
        When the modules range is set to Auto, a range such that the frequency of the signal does not exceed the given
        percentage of the bandwidth of the range will be chosen.

        Args:
            threshold (float):
                Frequency range threshold normalized to the -3 db point with a valid range of 0.0 to 1.0.
                For example, a value of 0.1 means 10 % of the -3 db point.
        """

        self.device.command(f'SENSe{self.module_number}:FRTHreshold {float(threshold)}')

    def get_digital_high_pass_filter_state(self):
        """Returns the state of the digital high pass filter for lock-in mode."""
        return bool(int(self.device.query(f'SENSe{self.module_number}:DIGital:FILTer:HPASs:STATe?')))

    def set_digital_high_pass_filter_state(self, state):
        """Sets the state of the digital high pass filter for lock-in mode.

            Args:
                state (bool):
                    The desired state fot he digital lock-in high pass filter. 1 for enabled, 0 for disabled.
        """
        self.device.command(f'SENSe{self.module_number}:DIGital:FILTer:HPASs:STATE {int(state)}')

    def get_resistance(self):
        """Returns the present resistance measurement in Ohms.  A valid source must be configured."""

        return float(self.device.query(f'CALCulate:SENSe{self.module_number}:RESistance?'))

    def set_resistance_source(self, source):
        """Configures the resistance feature to use a specified source module to calculate resistance.

            Args:
                source (str):
                    The channel used for calculating resistance ('S1', 'S2', or 'S3').
        """

        self.device.command(f'CALCulate:SENSe{self.module_number}:RESistance:SOURce {source}')

    def get_resistance_source(self):
        """Returns the present source module being used to calculate resistance."""

        return self.device.query(f'CALCulate:SENSe{self.module_number}:RESistance:SOURce?')

    def reset_settings(self):
        """Resets the settings for the specified module to their power on defaults.
        """

        self.device.command(f'SENSe{self.module_number}:PRESet')

    def unload(self):
        """Unloads the specified module.
        """

        self.device.command(f'SENSe{self.module_number}:UNLoad')

    def get_load_state(self):
        """Returns the loaded state for the specified module.
        """

        response = bool(int(self.device.query(f'SENSe{self.module_number}:LOAD?')))
        return response

    def fetch_multiple(self, *data_sources):
        """Gets a list of the latest values corresponding to the input data sources for this module, and returns them
        as soon as possible.

            Args:
                data_sources (SSMSystemDataSourceMnemonic or str): Variable length list of data sources.

            Returns:
                Tuple of values corresponding to the given data sources for this module
        """

        elements = [(data_source, self.module_number) for data_source in data_sources]
        return self.device.fetch_multiple(*elements)

    def read_multiple(self, *data_sources):
        """Initiates measurement of new values corresponding to the input data sources for this module, and returns them
        after the measurement is complete.

            Args:
                data_sources (SSMSystemReadDataSourceMnemonic or str): Variable length list of data sources.

            Returns:
                Tuple of values corresponding to the given data sources for this module
        """

        elements = [(data_source, self.module_number) for data_source in data_sources]
        return self.device.read_multiple(*elements)

    def get_self_cal_datetime(self):
        """Returns the self calibration date and time for the specified module.
        """

        response = self.device.query(f'SENSe{self.module_number}:SCALibration:DATE?').split(',')
        return datetime(int(response[0]), int(response[1]), int(response[2]), int(response[3]),int(response[4]), int(response[5]))

    def get_self_cal_temperature(self):
        """Returns the self calibration temperature for the specified module.
        """

        return float(self.device.query(f'SENSe{self.module_number}:SCALibration:TEMP?'))
        