# -*- coding: utf-8 -*-
import types
import numpy

class StateBase(object):
    def __init__(self):
        pass
    def to_xml(self, indent = ""):
        return indent + "<!-- " + repr(self) + " -->"

class StateSimple(StateBase):
    def __init__(self, time, content=None):
        super(StateSimple, self).__init__()
        if time < 0:
            raise AssertionError("time for state is negative!")
        self.time = time
        self.content = content

    def to_xml(self, indent = ""):
        s = indent + '<state time="%s"' % repr(self.time)
        if self.content is None:
            return s + '/>\n'
        s += '>\n'
        s += indent + '  ' + str(self.content) + '\n'
        s += indent + '</state>\n'
        return s
    def __repr__(self):
        return 'StateSimple(%s, %s)' % (self.time, repr(self.content))

class StateList(StateBase):
    def __init__(self):
        super(StateList, self).__init__()
        self.list = []
    def to_xml(self, indent = "  "):
        s = ""
        for k in self.list:
            if hasattr(k, "to_xml"):
                s += k.to_xml(indent)
            else:
                s += indent + str(k)
        return s
    def append(self, val):
        self.list.append(val)


class StateLoop(StateList):
    """Represents a loop in the state tree"""
    def __init__(self, repeat):
        super(StateLoop, self).__init__()
        self.repeat = repeat
    def to_xml(self, indent = ""):
        s = indent + ('<sequent repeat="%d">\n' % self.repeat)
        s += super(StateLoop, self).to_xml(indent + "  ")
        s += indent + '</sequent>\n'
        return s
    def __repr__(self):
        return 'StateLoop(repeat=%d, %s)' \
            % (self.repeat, repr(self.list))


#############################################################
#                                                           #
# Class: Experiment                                         #
#                                                           #
# Purpose: Represents one full experiment (one program on   #
#          the pulse-card; one file)                        #
#                                                           #
#############################################################
from . import dac

class Experiment:
    """
    Class holding the complete state tree for a single experiment. This state tree represents one
    program on the pulse card. It is written in a single file and picked up by the backend.

    :param Gating gating: gate length in s
    :param list rf_sources: list of rf sources ttl channels
    :param list rf_gates: list of rf gates ttl channels  len(rf_gates) == len(rf_sources)

    """
    job_id = 0

    def __init__(self, gating=None, rf_sources=[], rf_gates=[]):
        self.job_id = Experiment.job_id
        Experiment.job_id += 1

        self.state_list = StateList()
        self.list_stack = []
        self.description = { }
        self.gating = gating
        assert type(rf_sources) == type(list()), "rf_sources needs to be a list with the channels"
        assert type(rf_gates) == type(list()), "rf_gates needs to be a list with the channels"
        assert len(rf_gates) == len(rf_sources), "rf_sources and rf_gates must have equal number of entries"
        self.rf_sources = rf_sources
        self.rf_gates = rf_gates
        
        #for tracking the experiment length:
        #because loops are possible we need to track the length for each loop level
        self.total_time=[0.0]
        
        #and we need to know the number of iterations of the loops to multiply the state.
        self.loop_iterations=[1]

    # Commands -------------------------------------------------------------------------------------

    def ttl_pulse(self, length, channel = None, value = None):
        """
        Creates a state with length **length** and switches requested bits of the pulse programmer to HIGH.

        This command generates a TTL pulse with the specified duration on a specific channel or multiple channels.

        Parameters:
        -----------
        length : float
            Pulse length in seconds.
        channel : int, optional
            Selects a single channel (No. 1 - 24). If provided, the value is calculated as 2^channel.
        value : int, optional
            Lines to set (integer). For example, value=3 selects channels 0 and 1 (2**0 + 2**1).
            If both channel and value are None, the pulse is set to 0.

        Examples:
        ---------
        >>> e.ttl_pulse(length=5e-6, channel=1)
        Creates a 5 microsecond pulse on channel 1.

        >>> e.ttl_pulse(length=3e-6, value=3)
        Creates a 3 microsecond pulse on channels 0 and 1.

        >>> e.ttl_pulse(length=1e-6, value=0xffffff)
        Creates a 1 microsecond pulse on all channels.

        Notes:
        ------
        Hexadecimal representation (number starts with *0x*) is convenient as the numbers are shorter.
        To set all channels, value would be in decimal 16777215 and in hexadecimal 0xffffff.
        One letter in hexadecimal represents four bits.

        See Also:
        ---------
        ttls : Same as ttl_pulse, but no channel keyword.
        """
        the_value=0
        if value is not None:
            the_value=int(value)
        elif channel is not None:
            the_value=1<<channel
        self.state_list.append(StateSimple(length, \
            '<ttlout value="0x%06x"/>' % the_value))
        
        self.total_time[-1] += length

    ## Same as ttl_pulse, but no *channel* keyword
    def ttls(self, length = None, value = None):
        """
        Same as ttl_pulse, but no *channel* keyword

        :param float length:
        :param int value: lines to set (integer)
        :return:
        """
        the_value=int(value)
        s_content = '<ttlout value="0x%06x"/>' % the_value
        if length is not None:
            self.state_list.append(StateSimple(length, s_content))
            self.total_time[-1] += length
        else:
            self.state_list.append(s_content)
            

    def rf_pulse(self, length=None, phase=0, source=0):
        """
        Make an rf pulse, including gating and phase switching. Only possible if Experiment is configured.

        :param float length: pulse length
        :param float phase: pulse phase
        :param int source: source id
        """
        if not self.gating:
            raise SyntaxError("Can not use rf_pulse without configuration: Experiment(gating=None, rf_sources=[], rf_gates=[]")
        self.set_phase(phase, ttls=self.rf_gates[source])
        self.ttls(length=self.gating-0.5e-6,value=self.rf_gates[source])
        self.ttls(length=length, value=self.rf_gates[source]+self.rf_sources[source])

    ## Beginning of a new state
    def state_start(self, time):
        """
        Starts a state in the pulse programs with duration *time*.
        This must be closed with :func:`state_end`.

        Typically only used in very special cases.
        """
        self.state_list.append('<state time="%s">\n' % repr(time))
        self.total_time[-1] += time

    ## End of *state_start*
    def state_end(self):
        """
        Closes a previous :func:`state_start`
        """
        self.state_list.append('</state>\n')

    def wait(self, time, ttls=None, gating=False):
        """
        Waits for a specified amount of time without performing any actions.

        This command inserts a delay in the pulse sequence, allowing time for relaxation,
        or synchronization with other events.

        Parameters:
        -----------
        time : float
            Time to wait in seconds.
            The minimum time is 90 ns, and the maximum is essentially unlimited (years).
            The backend driver circumvents the limit imposed by the pulse programmer by adding loops.

        ttls : int, optional
            Additional TTL lines to set (integer) during the wait period.
            Allows simultaneous control of TTL lines while waiting.
            For example, ttls=3 activates channels 0 and 1 (2^0 + 2^1).

        gating : bool, optional
            If True, reduces the wait time by the gating time (typically 2 µs).
            This is useful for waiting in front of an rf_pulse to account for gating delays.
            Default is False.

        Returns:
        --------
        None

        Examples:
        ---------
        >>> e.wait(time=2e-3)
        Waits for 2 milliseconds.

        >>> e.wait(time=1e-6, ttls=3)
        Waits for 1 microsecond while activating TTL channels 0 and 1.

        >>> e.wait(time=5e-6, gating=True)
        Waits for 5 microseconds, reduced by the gating time for rf_pulse synchronization.

        """
        if gating:
            time -= self.gating
        if ttls is not None:
            s_content = '<ttlout value="0x%06x"/>' % ttls
            self.state_list.append(StateSimple(time,s_content))
        else:
            self.state_list.append(StateSimple(time))
            
        self.total_time[-1] += time


    def record(self, samples, frequency, timelength=None, sensitivity = None, ttls=None, channels = 3, offset = None, impedance = None):
        """
        Records data with a given number of samples, sampling frequency, and sensitivity.
        This command starts data acquisition from the ADC (Analog-to-Digital Converter).

        Parameters:
        -----------
        samples : int
            Number of samples to record. This determines the number of data points in the resulting signal.

        frequency : float
            Sampling frequency in Hz. This determines how often samples are taken from the analog signal.
            The maximum sampling frequency is 20 MHz.

        timelength : float, optional
            Length of this state in seconds. If not specified (None), it is calculated automatically as samples/frequency.
            This parameter allows overriding the automatic calculation for special timing requirements.

        sensitivity : float or list, optional
            Sensitivity in U_MAX/V. Specifies the input voltage range for the ADC.
            Accepted values are 0.2, 0.5, 1, 2, 5, and 10 V.
            Can be a single value (applied to all channels) or a list of values (one per channel).

        ttls : int, optional
            Additional TTL lines to set (integer) during recording. Allows simultaneous control of TTL lines.
            For example, ttls=3 activates channels 0 and 1 (2^0 + 2^1).

        channels : int, optional
            Channels to activate. Default is 3 (channels 0 and 1).
            Accepted values are 1 (channel 0), 3 (channels 0 and 1), 5 (channels 0, 1, and 2), and 15 (all 4 channels).

        offset : int or list, optional
            Voltage offset for the ADC input. Can be a single integer value (applied to all channels)
            or a list of values (one per channel).
            Normally not used.

        impedance : float or list, optional
            Input impedance for the ADC. Can be a single number (applied to all channels)
            or a list of values (one per channel).
            Normally set in the backend config and not changeable.

        Returns:
        --------
        None

        Examples:
        ---------
        >>> e.record(samples=1024, frequency=2e6, sensitivity=2)
        Records a signal with 1024 data points and 2 MHz sampling frequency.
        The sensitivity will be ±2 V, providing a resolution of 0.2 mV with a 14-bit ADC card.

        >>> e.record(samples=4096, frequency=10e6, sensitivity=[1, 2], channels=3)
        Records a signal with 4096 data points and 10 MHz sampling frequency.
        Channel 0 uses 1 V sensitivity, and channel 1 uses 2 V sensitivity.

        >>> e.record(samples=2048, frequency=5e6, ttls=3)
        Records a signal with 2048 data points and 5 MHz sampling frequency.
        Simultaneously activates TTL channels 0 and 1.

        Notes:
        ------
        - Multiple record statements can be in a single scan (gated sampling) or in a loop.
        - The onboard memory can hold 8M samples shared by all channels (depends on the board).
        - The sensitivity setting affects the resolution of the ADC.
        - The actual timing may vary slightly due to hardware limitationslike pre- and post-trigger delays.

        """
        attributes='s="%d" f="%d"'%(samples,frequency)#%g
        if channels != 1 and channels != 3 and channels != 5 and channels != 15:
            raise ValueError("Channel definition is illegal")
        attributes += ' channels="%i"'%(channels)

        nchannels = 0
        if channels == 1:
            nchannels = 1
        elif channels == 3 or channels == 5:
            nchannels = 2
        elif channels == 15:
            nchannels = 4
        if sensitivity is not None:
            # float values are allowed and applied to all channels
            if isinstance(sensitivity, float) or isinstance(sensitivity, int):
                for i in range(nchannels):
                    attributes +=' sensitivity%i="%f"'%(i, float(sensitivity))
            else:
                for i in range(nchannels):
                    attributes +=' sensitivity%i="%f"'%(i, sensitivity[i])
        if offset is not None:
            # int values are allowed and applied to all channels
            if isinstance(offset, int):
                for i in range(nchannels):
                    attributes +=' offset%i="%f"'%(i, offset)
            else:
                for i in range(nchannels):
                    attributes +=' offset%i="%f"'%(i, offset[i])
        if impedance is not None:
            # float values are allowed and applied to all channels
            if isinstance(impedance, float):
                for i in range(nchannels):
                    attributes += ' impedance%i="%i"'%(i, impedance)
            else:
                for i in range(nchannels):
                    attributes += ' impedance%i="%i"'%(i, impedance[i])

        s_content = '<analogin %s/>' % attributes
        if ttls is not None:
            s_content+='<ttlout value="0x%06x"/>' % ttls
        if timelength is None:
            timelength = samples / float(frequency)#*1.01
        self.state_list.append(StateSimple(timelength, s_content))
        self.total_time[-1] += timelength

    ## Create a loop on the pulse programmer. Loop contents can not change inside the loop.
    # @params iterations Number of loop iterations
    def loop_start(self, iterations):
        """
        creates a loop of given number of iterations and has to be closed by loop_end().
        Commands inside the loop can not change, i.e., the parameters are the same for each loop run.
        This loop is created on the pulse programmer, thus saving commands.
        Close the loop with :func:`loop_end`.

        :param int iterations: number of iterations to loop
        """
        l = StateLoop(iterations)
        self.state_list.append(l)
        # (These two lines could probably be guarded by a mutex)
        self.list_stack.append(self.state_list)
        self.state_list = l
        
        self.total_time.append(0.0)
        self.loop_iterations.append(iterations)

    ## End loop state
    def loop_end(self):
        """
        Closes a l:func:`loop_start` statement.
        """
        # (This line could probably be guarded by a mutex)
        self.state_list = self.list_stack.pop(-1)
        
        looptime=self.total_time.pop(-1)
        loopiterations=self.loop_iterations.pop(-1)
        self.total_time[-1] += looptime*loopiterations


    def set_frequency(self, frequency, phase, ttls=0):
        """
        Sets the frequency and phase of the frequency source and optionally further channels.
        The state length is 2 µs. Stabilisation time depends on RF source.

        :param float frequency: frequency to set in Hz
        :param float phase: phase to set
        :param int ttls: default=0, additional ttl lines to set (integer)
        """
        s_content = '<analogout id="0" f="%f" phase="%f"/>' % (frequency, phase)
        if ttls != 0:
            s_content += '<ttlout value="0x%06x"/>' % ttls
        self.state_list.append(StateSimple(2e-6, s_content))
        
        self.total_time[-1] += 2e-6

    def set_pfg(self, dac_value=None, length=None, shape=('rec', 0), trigger=4, is_seq=False):
        """
        This sets the value for the PFG, it also sets it back to dac_value=0 automatically.
        If you don't wish to do so (i.e. line shapes)  set is_seq=1
        If you want to set a trigger, set trigger (default=4, i.e. channel 2)
        If you want shaped gradients: shape=(ashape, resolution), ashape can be rec, sin2, sin

        The default DAC ID is hardcoded (id=1)!

        :param int dac_value: DAC value to set
        :param float length: Duration of the state, minimum length is 42*90ns=3.78us (default)
        :param tuple shape: Tuple of (shape, resolution/seconds), shape can be one of: rec (default), sin2, sin
        :param bool is_seq: If set to *True*, do *NOT* set DAC to zero at the end of this state
        :param int trigger: default=4, lines to set (integer)
        """
        try:
            form, resolution = shape
        except:
            raise SyntaxError("shape argument needs to be a tuple, i.e. ('shape',resolution), shape can be sin, sin2, rec")

        if length == None:
            # mimimum length
            length=42*9e-8
        if resolution >= length:
            raise ValueError("Resolution %.3e of shaped gradients can not be longer than total length %.3e"%(resolution, length))
        if resolution < 42*9e-8:
            raise ValueError("Resulution %.3e can not be smaller than %.3e"%(resolution, 42*9e-8))

        t_steps = numpy.arange(0,length,resolution)

        if form == 'rec': # shape==None --> rectangular gradients
            s_content = '<ttlout value="%s"/><analogout id="1" dac_value="%i"/>' % (trigger, dac_value)
            self.state_list.append(StateSimple(length, s_content))
            self.total_time[-1] += length

            if not is_seq:
                s_content = '<analogout id="1" dac_value="0"/>'
                self.state_list.append(StateSimple(42*9e-8, s_content))
                
                self.total_time[-1] += 42*9e-8

        elif form == 'sin2':
            # sin**2 shape
            for t in t_steps:
                dac = int (dac_value*numpy.sin(numpy.pi/length*t)**2)
                s_content = '<ttlout value="%s"/><analogout id="1" dac_value="%i"/>' % (trigger, dac)
                self.state_list.append(StateSimple(resolution, s_content))
                
            # set it back to zero
            s_content = '<ttlout value="%s"/><analogout id="1" dac_value="0"/>' % (trigger)
            self.state_list.append(StateSimple(resolution, s_content))
            
            self.total_time[-1] += resolution*(len(t_steps)+1)

        elif form == 'sin':
            # sin shape
            for t in t_steps:
                dac = int (dac_value*numpy.sin(numpy.pi/length*t))
                s_content = '<ttlout value="%s"/><analogout id="1" dac_value="%i"/>' % (trigger, dac)
                self.state_list.append(StateSimple(resolution, s_content))
            # set it back to zero
            s_content = '<ttlout value="%s"/><analogout id="1" dac_value="0"/>' % (trigger)
            self.state_list.append(StateSimple(resolution, s_content))
            
            self.total_time[-1] += resolution*(len(t_steps)+1)
        else: # don't know what to do
            raise SyntaxError("form is unknown: %s"%form)

    def set_dac(self, dac_value, dac_id=1, length=None, is_seq=False, ttls=0):
        """
        Sets the value for the DAC (Digital-to-Analog Converter) and optionally additional TTL lines.
        This command is used to control analog output signals, such as pulsed field gradients.

        Parameters:
        -----------
        dac_value : int
            DAC value, between -2**19-1 (-524287) and +2**19 (524288).
            This value determines the analog output voltage.

        dac_id : int, optional
            Specifies which DAC to control. Default is 1.
            This allows control of multiple DAC channels if available.

        length : float, optional
            Length of this state in seconds. Default is None, which sets the length to 42*90ns=3.78µs.
            If is_seq is False, the total length is doubled due to the automatic reset to zero.

        is_seq : bool, optional
            If True, the DAC value is not reset to zero after the pulse. Default is False.
            Use is_seq=True for creating line shapes or sequential pulses.

        ttls : int, optional
            Lines to set (integer) for TTL output. Default is 0.
            Allows simultaneous control of TTL lines along with the DAC.

        Returns:
        --------
        None

        Examples:
        ---------
        >>> e.set_dac(dac_value=15040, dac_id=1, length=1e-3)
        Sets DAC channel 1 to value 15040 for 1 millisecond and then resets to zero.

        >>> e.set_dac(dac_value=10000, is_seq=True)
        Sets DAC to value 10000 for 3.78µs without resetting to zero.

        >>> e.set_dac(dac_value=20000, ttls=3)
        Sets DAC to value 20000 and activates TTL channel 1 (2^1 + 2^0 = 3).

        Notes:
        ------
        - The minimum state length is 3.78 µs when is_seq=True.
        - When is_seq=False, the DAC is automatically reset to zero, adding another 3.78 µs to the total length.
        - The actual length may be longer than specified if additional time is required for DAC settling.

        """
        if length==None:
            length=42*9e-8
        s_content = '<analogout id="%d" dac_value="%i"/><ttlout value="0x%06x"/>' \
            % (dac_id, dac_value, ttls)
        self.state_list.append(StateSimple(length, s_content))
        
        self.total_time[-1] += length
        
        if not is_seq:
            s_content = '<analogout id="%d" dac_value="0"/><ttlout value="0x%06x"/>' \
                % (dac_id, ttls)
            self.state_list.append(StateSimple(42*9e-8, s_content))
            
            self.total_time[-1] += 42*9e-8

    def set_phase(self, phase, ttls=0):
        """
        Sets the phase of the RF source to the specified value.

        This command changes the phase of the frequency source. The phase is given in degrees and is
        relative to the phase at the beginning of the experiment.

        Parameters:
        -----------
        phase : float
            Phase to set in degrees. This value determines the phase of the RF signal.

        ttls : int, optional
            Lines to set (integer) for TTL output. Default is 0.
            Allows simultaneous control of TTL lines along with setting the phase.

        Returns:
        --------
        None

        Examples:
        ---------
        >>> e.set_phase(phase=90)
        Sets the receiver phase to 90 degrees.

        >>> e.set_phase(phase=180, ttls=3)
        Sets the phase to 180 degrees and activates TTL channels 0 and 1.

        >>> e.set_phase(phase=45)
        Sets the phase to 45 degrees.

        Notes:
        ------
        - The state length for this command is 0.5 µs.
        - The stabilization time for the phase change is RF source dependent. A typical value for PTS310 is 2 µs.
        - The phase is relative to the initial phase set at start of the experiment.
        """
        s_content = '<analogout phase="%f" />' % (phase)
        if ttls!=0:
            s_content += '<ttlout value="%d"/>' % ttls
        self.state_list.append(StateSimple(0.5e-6, s_content))
        
        self.total_time[-1] += 0.5e-6


    def set_description(self, key, value):
        """
        Sets a description key-value pair in the experiment description dictionary.

        This command creates an entry with the specified key and value in the description dictionary.
        In case of data being stored in a HDF5 file, this dictionary is stored as well, allowing
        parameter passing between the experiment script and the result script.

        Parameters:
        -----------
        key : str
            The key identifier for the description entry. This will be used to retrieve the value later.

        value : any
            The value to be associated with the key. Can be of any type (string, number, list, etc.).

        Returns:
        --------
        None

        Examples:
        ---------
        >>> e.set_description(key="tau", value=2e-3)
        Sets the description entry with key "tau" to the value 0.002 seconds.

        >>> e.set_description(key="temperature", value=298.15)
        Sets the description entry with key "temperature" to the value 298.15 Kelvin.

        >>> e.set_description(key="pulse_sequence", value="CPMG")
        Sets the description entry with key "pulse_sequence" to the string "CPMG".

        Notes:
        ------
        If the key already exists in the description dictionary, a warning message will be printed
        indicating that the existing value will be overwritten.
        """
        if key in list(self.description.keys()):
            print('Warning: Overwriting existing description "%s" = "%s" with "%s"' % (key, self.description[key], value))
        self.description[key] = value

    def set_pts_local(self):
        """
        Sets the PTS310/PTS500 frequency source to local mode.
        State length is 2 µs.
        """
        self.state_list.append(StateSimple(1e-6, '<ttlout value="0xf000"/>'))
        self.state_list.append(StateSimple(1e-6, '<ttlout value="0x8000"/>'))
        
        self.total_time[-1] += 2e-6

    # / Commands -----------------------------------------------------------------------------------


    # Public Methods -------------------------------------------------------------------------------

    def get_job_id(self) -> int:
        """
        Returns the job-id of the current experiment

        :returns: job_id
        """
        return self.job_id


    def write_xml_string(self):
        """
        Returns the current program as a string

        :returns: XML string
        """

        # Standart XML-Kopf
        xml_string = '<?xml version="1.0" encoding="ISO-8859-1"?>\n'

        # Experiment-Start-Tag einfügen
        xml_string += '<experiment no="%d">\n' % self.job_id

        # Descriptions einfügen
        if len(self.description)==0:
            xml_string += '  <description/>\n'
        else:
            xml_string += '  <description>\n'
            for key,value in self.description.items():
                type_string="repr"
                if value is None:
                    type_string="None"
                    value=""
                elif type(value) is float or isinstance(value, numpy.floating):
                    type_string="Float"
                    value=repr(value)
                elif type(value) is int or isinstance(value, numpy.integer):
                    type_string="Int"
                    value=repr(value)
                elif type(value) is int:
                    type_string="Long"
                    value=repr(value)
                elif type(value) is complex or isinstance(value, numpy.complexfloating):
                    type_string="Complex"
                    value=repr(value)
                elif type(value) is bool or isinstance(value, numpy.bool_):
                    type_string="Boolean"
                    value=repr(value)
                elif type(value) in (str,):
                    type_string="String"
                else:
                   value=repr(value)
                xml_string += '    <item key="%s" type="%s">%s</item>\n'%(key, type_string ,value)
            xml_string += "  </description>\n"

        # Experiment-Inhalt einfügen
        xml_string += self.state_list.to_xml(indent = "  ")

        # Experiment-End-Tag
        xml_string += '</experiment>\n'

        return xml_string

    def write_quit_job(self):
        """
        Returns a xml quit-job
        :returns: XML quit-job
        """
        return '<?xml version="1.0" encoding="ISO-8859-1"?>\n<quit/>'
    
    def get_length(self):
        timelength = 0.0
        #calculate the correct timelength also for unclosed loops, if there is no unclosed loop
        #the timelength of this experiment is self.total_time[-1].
        for i in range(len(self.total_time)):
            timelength += self.total_time[i]
            timelength *= self.loop_iterations[i]
        return timelength

class Quit(Experiment):
    def write_xml_string(self):
        return '<?xml version="1.0" encoding="ISO-8859-1"?>\n<quit no="%d"/>'%self.job_id


# /Public Methods ------------------------------------------------------------------------------

def self_test():
    e = Experiment()
    e.set_description("key", "value")
    e.set_frequency(85e6, 90, ttls=16)
    e.wait(1e-6)
    #e.rf_pulse(1, 1e-6/3)        # val = 1
    e.ttl_pulse(1e-6/3, 1)       # val = 2
    e.ttl_pulse(1e-6/3, None, 7) # val = 7
    if True:
        e.loop_start(30)
        e.set_pfg(dac_value=1024, length=5e-6, is_seq = True, shape=('rec', 42*9e-8))
        e.loop_start(400)
        e.set_phase(270, ttls = 32)
        e.loop_end()
        e.ttl_pulse(5e-6, channel = 6)
        e.loop_end()
    else:
        l = StateLoop(3)
        l.append(StateSimple(5e-6, '<ttlout value="1"/>'))
        e.state_list.append(l)
    e.set_dac(12345, dac_id=2, is_seq = True, ttls=16)
    e.record(1024, 20e6)
    try:
        e.wait(-1)
    except AssertionError:
        pass
    else:
        raise AssertionError("An exception should happen")
    e.set_pts_local()
    print(e.write_xml_string())
    
    print(e.get_length())

if __name__ == '__main__':
    self_test()
