# awg file -> (what, we, put, into, make_send_and_load_awg_file)
# This module parses an awg file using THREE sub-parser. This code could
# probably be streamlined somewhat.
import struct
from typing import Any, Dict, List, Tuple, Union

import numpy as np

AWG_FILE_FORMAT = {
    'MAGIC': 'h',
    'VERSION': 'h',
    'SAMPLING_RATE': 'd',    # d
    'REPETITION_RATE': 'd',    # # NAME?
    'HOLD_REPETITION_RATE': 'h',    # True | False
    'CLOCK_SOURCE': 'h',    # Internal | External
    'REFERENCE_SOURCE': 'h',    # Internal | External
    'EXTERNAL_REFERENCE_TYPE': 'h',    # Fixed | Variable
    'REFERENCE_CLOCK_FREQUENCY_SELECTION': 'h',  # 10 MHz | 20 MHz | 100 MHz
    'REFERENCE_MULTIPLIER_RATE': 'h',    #
    'DIVIDER_RATE': 'h',   # 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256
    'TRIGGER_SOURCE': 'h',    # Internal | External
    'INTERNAL_TRIGGER_RATE': 'd',    #
    'TRIGGER_INPUT_IMPEDANCE': 'h',    # 50 ohm | 1 kohm
    'TRIGGER_INPUT_SLOPE': 'h',    # Positive | Negative
    'TRIGGER_INPUT_POLARITY': 'h',    # Positive | Negative
    'TRIGGER_INPUT_THRESHOLD': 'd',    #
    'EVENT_INPUT_IMPEDANCE': 'h',    # 50 ohm | 1 kohm
    'EVENT_INPUT_POLARITY': 'h',    # Positive | Negative
    'EVENT_INPUT_THRESHOLD': 'd',
    'JUMP_TIMING': 'h',    # Sync | Async
    'INTERLEAVE': 'h',    # On |  This setting is stronger than coupling
    'ZEROING': 'h',    # On | Off
    'COUPLING': 'h',    # The Off | Pair | All setting is weaker than .
    'RUN_MODE': 'h',    # Continuous | Triggered | Gated | Sequence
    'WAIT_VALUE': 'h',    # First | Last
    'RUN_STATE': 'h',    # On | Off
    'INTERLEAVE_ADJ_PHASE': 'd',
    'INTERLEAVE_ADJ_AMPLITUDE': 'd',
    'EVENT_JUMP_MODE': 'h',  # Event jump | Dynamic jump
    'TABLE_JUMP_STROBE': 'h',  # On
    'TABLE_JUMP_DEFINITION': 'ignore',  # Array of tablejump
    # ---------------------
    # Channel 1 settings
    # ---------------------
    'OUTPUT_WAVEFORM_NAME_1': 's',  # if in continuous mode
    'DAC_RESOLUTION_1': 'h',  # 8 | 10
    'CHANNEL_STATE_1': 'h',  # On | Off
    'ANALOG_DIRECT_OUTPUT_1': 'h',  # On | Off
    'ANALOG_FILTER_1': 'h',  # Enum type.
    'ANALOG_METHOD_1': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'ANALOG_AMPLITUDE_1': 'd',
    # When the Input Method is High/Low, it is skipped.
    'ANALOG_OFFSET_1': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'ANALOG_HIGH_1': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'ANALOG_LOW_1': 'd',
    'MARKER1_SKEW_1': 'd',
    'MARKER1_METHOD_1': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'MARKER1_AMPLITUDE_1': 'd',
    # When the Input Method is High/Low, it is skipped.
    'MARKER1_OFFSET_1': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER1_HIGH_1': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER1_LOW_1': 'd',
    'MARKER2_SKEW_1': 'd',
    'MARKER2_METHOD_1': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'MARKER2_AMPLITUDE_1': 'd',
    # When the Input Method is High/Low, it is skipped.
    'MARKER2_OFFSET_1': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER2_HIGH_1': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER2_LOW_1': 'd',
    'DIGITAL_METHOD_1': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'DIGITAL_AMPLITUDE_1': 'd',
    # When the Input Method is High/Low, it is skipped.
    'DIGITAL_OFFSET_1': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'DIGITAL_HIGH_1': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'DIGITAL_LOW_1': 'd',
    'EXTERNAL_ADD_1': 'h',  # AWG5000 only
    'PHASE_DELAY_INPUT_METHOD_1':   'h',  # Phase/DelayInme/DelayInints
    'PHASE_1': 'd',  # When the Input Method is not Phase, it is skipped.
    # When the Input Method is not DelayInTime, it is skipped.
    'DELAY_IN_TIME_1': 'd',
    # When the Input Method is not DelayInPoint, it is skipped.
    'DELAY_IN_POINTS_1': 'd',
    'CHANNEL_SKEW_1': 'd',
    'DC_OUTPUT_LEVEL_1': 'd',  # V
    # ---------------------
    # ---------------------
    'OUTPUT_WAVEFORM_NAME_2': 's',
    'DAC_RESOLUTION_2': 'h',  # 8 | 10
    'CHANNEL_STATE_2': 'h',  # On | Off
    'ANALOG_DIRECT_OUTPUT_2': 'h',  # On | Off
    'ANALOG_FILTER_2': 'h',  # Enum type.
    'ANALOG_METHOD_2': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'ANALOG_AMPLITUDE_2': 'd',
    # When the Input Method is High/Low, it is skipped.
    'ANALOG_OFFSET_2': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'ANALOG_HIGH_2': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'ANALOG_LOW_2': 'd',
    'MARKER1_SKEW_2': 'd',
    'MARKER1_METHOD_2': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'MARKER1_AMPLITUDE_2': 'd',
    # When the Input Method is High/Low, it is skipped.
    'MARKER1_OFFSET_2': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER1_HIGH_2': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER1_LOW_2': 'd',
    'MARKER2_SKEW_2': 'd',
    'MARKER2_METHOD_2': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'MARKER2_AMPLITUDE_2': 'd',
    # When the Input Method is High/Low, it is skipped.
    'MARKER2_OFFSET_2': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER2_HIGH_2': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER2_LOW_2': 'd',
    'DIGITAL_METHOD_2': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'DIGITAL_AMPLITUDE_2': 'd',
    # When the Input Method is High/Low, it is skipped.
    'DIGITAL_OFFSET_2': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'DIGITAL_HIGH_2': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'DIGITAL_LOW_2': 'd',
    'EXTERNAL_ADD_2': 'h',  # AWG5000 only
    'PHASE_DELAY_INPUT_METHOD_2':   'h',  # Phase/DelayInme/DelayInints
    'PHASE_2': 'd',  # When the Input Method is not Phase, it is skipped.
    # When the Input Method is not DelayInTime, it is skipped.
    'DELAY_IN_TIME_2': 'd',
    # When the Input Method is not DelayInPoint, it is skipped.
    'DELAY_IN_POINTS_2': 'd',
    'CHANNEL_SKEW_2': 'd',
    'DC_OUTPUT_LEVEL_2': 'd',  # V
    # ---------------------
    # ---------------------
    'OUTPUT_WAVEFORM_NAME_3': 's',
    'DAC_RESOLUTION_3': 'h',  # 8 | 10
    'CHANNEL_STATE_3': 'h',  # On | Off
    'ANALOG_DIRECT_OUTPUT_3': 'h',  # On | Off
    'ANALOG_FILTER_3': 'h',  # Enum type.
    'ANALOG_METHOD_3': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'ANALOG_AMPLITUDE_3': 'd',
    # When the Input Method is High/Low, it is skipped.
    'ANALOG_OFFSET_3': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'ANALOG_HIGH_3': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'ANALOG_LOW_3': 'd',
    'MARKER1_SKEW_3': 'd',
    'MARKER1_METHOD_3': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'MARKER1_AMPLITUDE_3': 'd',
    # When the Input Method is High/Low, it is skipped.
    'MARKER1_OFFSET_3': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER1_HIGH_3': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER1_LOW_3': 'd',
    'MARKER2_SKEW_3': 'd',
    'MARKER2_METHOD_3': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'MARKER2_AMPLITUDE_3': 'd',
    # When the Input Method is High/Low, it is skipped.
    'MARKER2_OFFSET_3': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER2_HIGH_3': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER2_LOW_3': 'd',
    'DIGITAL_METHOD_3': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'DIGITAL_AMPLITUDE_3': 'd',
    # When the Input Method is High/Low, it is skipped.
    'DIGITAL_OFFSET_3': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'DIGITAL_HIGH_3': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'DIGITAL_LOW_3': 'd',
    'EXTERNAL_ADD_3': 'h',  # AWG5000 only
    'PHASE_DELAY_INPUT_METHOD_3':   'h',  # Phase/DelayInme/DelayInints
    'PHASE_3': 'd',  # When the Input Method is not Phase, it is skipped.
    # When the Input Method is not DelayInTime, it is skipped.
    'DELAY_IN_TIME_3': 'd',
    # When the Input Method is not DelayInPoint, it is skipped.
    'DELAY_IN_POINTS_3': 'd',
    'CHANNEL_SKEW_3': 'd',
    'DC_OUTPUT_LEVEL_3': 'd',  # V
    # ---------------------
    # ---------------------
    'OUTPUT_WAVEFORM_NAME_4': 's',
    'DAC_RESOLUTION_4': 'h',  # 8 | 10
    'CHANNEL_STATE_4': 'h',  # On | Off
    'ANALOG_DIRECT_OUTPUT_4': 'h',  # On | Off
    'ANALOG_FILTER_4': 'h',  # Enum type.
    'ANALOG_METHOD_4': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'ANALOG_AMPLITUDE_4': 'd',
    # When the Input Method is High/Low, it is skipped.
    'ANALOG_OFFSET_4': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'ANALOG_HIGH_4': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'ANALOG_LOW_4': 'd',
    'MARKER1_SKEW_4': 'd',
    'MARKER1_METHOD_4': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'MARKER1_AMPLITUDE_4': 'd',
    # When the Input Method is High/Low, it is skipped.
    'MARKER1_OFFSET_4': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER1_HIGH_4': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER1_LOW_4': 'd',
    'MARKER2_SKEW_4': 'd',
    'MARKER2_METHOD_4': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'MARKER2_AMPLITUDE_4': 'd',
    # When the Input Method is High/Low, it is skipped.
    'MARKER2_OFFSET_4': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER2_HIGH_4': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'MARKER2_LOW_4': 'd',
    'DIGITAL_METHOD_4': 'h',  # Amplitude/Offset, High/Low
    # When the Input Method is High/Low, it is skipped.
    'DIGITAL_AMPLITUDE_4': 'd',
    # When the Input Method is High/Low, it is skipped.
    'DIGITAL_OFFSET_4': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'DIGITAL_HIGH_4': 'd',
    # When the Input Method is Amplitude/Offset, it is skipped.
    'DIGITAL_LOW_4': 'd',
    'EXTERNAL_ADD_4': 'h',  # AWG5000 only
    'PHASE_DELAY_INPUT_METHOD_4':   'h',  # Phase/DelayInme/DelayInints
    'PHASE_4': 'd',  # When the Input Method is not Phase, it is skipped.
    # When the Input Method is not DelayInTime, it is skipped.
    'DELAY_IN_TIME_4': 'd',
    # When the Input Method is not DelayInPoint, it is skipped.
    'DELAY_IN_POINTS_4': 'd',
    'CHANNEL_SKEW_4': 'd',
    'DC_OUTPUT_LEVEL_4': 'd',  # V
    }

# Note: this dictionary is dynamically updated by _parser1
AWG_FILE_FORMAT_WAV = {
    'WAVEFORM_NAME': 's',
    'WAVEFORM_TYPE': 'h',
    'WAVEFORM_LENGTH': 'l',
    'WAVEFORM_TIMESTAMP': '8H',
    'WAVEFORM_DATA_4': None
    }

AWG_FILE_FORMAT_SEQ = {
    'SEQUENCE_WAIT': 'h',  # On | Off
    'SEQUENCE_LOOP': 'l',  # 0=infinite
    'SEQUENCE_JUMP': 'h',  # OFF:0, INDEX: element #, NEXT: -1
    'SEQUENCE_GOTO': 'h',  # 0 if GOTO is OFF, else an element number
    'SEQUENCE_WAVEFORM': 's',
    'SEQUENCE_IS': '2h',  # Yes | No
    'SEQUENCE_SUBSEQ': 's'
    }

AWG_TRANSLATER = {
    'HOLD_REPETITION_RATE': {0: 'False', 1: 'True'},
    'CLOCK_SOURCE': {1: 'Internal', 2: 'External'},
    'REFERENCE_SOURCE': {1: 'Internal', 2: 'External'},
    'EXTERNAL_REFERENCE_TYPE': {1: 'Fixed', 2: 'Variable'},
    'TRIGGER_SOURCE': {1: 'Internal', 2: 'External'},
    'TRIGGER_INPUT_IMPEDANCE': {1: '50 Ohm', 2: '1 kOhm'},
    'TRIGGER_INPUT_SLOPE': {1: 'Positive', 2: 'Negative'},
    'TRIGGER_INPUT_POLARITY': {1: 'Positive', 2: 'Negative'},
    'EVENT_INPUT_IMPEDANCE': {1: '50 Ohm', 2: '1 kOhm'},
    'EVENT_INPUT_SLOPE': {1: 'Positive', 2: 'Negative'},
    'EVENT_INPUT_POLARITY': {1: 'Positive', 2: 'Negative'},
    'JUMP_TIMING': {1: 'Sync', 2: 'Async'},
    'RUN_MODE': {1: 'Continuous', 2: 'Triggered', 3: 'Gated', 4: 'Sequence'},
    'WAIT_VALUE': {1: 'First', 2: 'Last'}
    }

_parser3_output = Tuple[
    List[List[Dict[Any, Any]]],
    List[List[Dict[Any, Any]]],
    List[List[Dict[Any, Any]]],
    List[Union[str, int]],
    List[Union[str, int]],
    List[Union[str, int]],
    List[Union[str, int]],
    List[int]
]


def _unpacker(
        binaryarray: np.ndarray,
        dacbitdepth: int = 14
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    Unpacks an awg-file integer wave into a waveform and two markers
    in the same way as the AWG does. This can be useful for checking
    how the signals are going to be interpreted by the instrument.

    Args:
        binaryarray: A numpy array containing the
            packed waveform and markers.
        dacbitdepth: Specifies the bit depth for the digitisation
            of the waveform. Allowed values: 14, 8. Default: 14.

    Returns:
        The waveform scaled to have values from -1 to 1, marker 1, marker 2.
    """

    wflength = len(binaryarray)
    wf = np.zeros(wflength)
    m1 = np.zeros(wflength)
    m2 = np.zeros(wflength)

    for ii, bitnum in enumerate(binaryarray):
        bitstring = bin(bitnum)[2:].zfill(16)
        m2[ii] = int(bitstring[0])
        m1[ii] = int(bitstring[1])
        wf[ii] = (int(bitstring[2:], base=2)-2**13)/2**13
        # print(bitstring, int(bitstring[2:], base=2))

    return wf, m1, m2


def _unwrap(bites: bytes, fmt: str) -> Union[str, int, Tuple[Any, ...]]:
    """
    Helper function for interpreting the bytes from the awg file.

    Args:
        bites: a bytes object
        fmt: the format string (either 's', 'h' or 'd')

    """
    value: Union[str, int, Tuple[Any, ...]]
    if fmt == 's':
        value = bites[:-1].decode('ascii')
    elif fmt == 'ignore':
        value = 'Not read'
    else:
        value = struct.unpack('<'+fmt, bites)
        if len(value) == 1:
            value = value[0]

    return value


def _getendingnumber(string: str) -> Tuple[int, str]:
    """
    Helper function to extract the last number of a string

    Args:
        string: A .awg field name, like SEQUENCE_JUMP_23

    Returns:
        The number and the shortened string,
        e.g. 'SEQUENCE_JUMP_23' -> (23, 'SEQUENCE_JUMP_')
    """

    num = ''

    for char in string[::-1]:
        if char.isdigit():
            num += char
        else:
            break

    return int(num[::-1]), string[:-len(num)]


awgfilepath = ('/Users/william/AuxiliaryQCoDeS/AWGhelpers/awgfiles/' +
               'customawgfile.awg')

awgfilepath2 = ('/Users/william/AuxiliaryQCoDeS/AWGhelpers/awgfiles/' +
                'machinemadefortest.awg')


def _parser1(
        awgfilepath: str
) -> Tuple[Dict[str, Union[str, int, Tuple[Any, ...]]], List[List[Any]], List[List[Any]]]:
    """
    Helper function doing the heavy lifting of reading and understanding the
    binary .awg file format.

    Args:
        awgfilepath: The absolute path of the awg file to read

    Returns:
        Tuple of instrument settings (a dict), waveforms (list of lists), sequencer settings (list of lists)
    """

    instdict = {}
    waveformlist: List[List[Any]] = [[], []]
    sequencelist: List[List[Any]] = [[], []]
    wfmlen: int

    with open(awgfilepath, 'rb') as fid:

        while True:
            chunk = fid.read(8)
            if not chunk:
                break

            (namelen, valuelen) = struct.unpack('<II', chunk)

            rawname = fid.read(namelen)
            rawvalue = fid.read(valuelen)

            name = rawname[:-1].decode('ascii')  # remove NULL termination char

            if name.startswith('WAVEFORM'):

                namestop = name[name.find('_')+1:].find('_')+name.find('_')
                lookupname = name[:namestop+1]

                if 'DATA' in name:
                    fmtstr = f'{wfmlen}H'
                    AWG_FILE_FORMAT_WAV['WAVEFORM_DATA'] = fmtstr

                file_format = AWG_FILE_FORMAT_WAV[lookupname]
                assert file_format is not None
                value = _unwrap(rawvalue, file_format)
                (number, barename) = _getendingnumber(name)
                fieldname = barename + f"{number-20}"
                waveformlist[0].append(fieldname)
                waveformlist[1].append(value)

                if 'LENGTH' in name:
                    assert isinstance(value, int)
                    wfmlen = value

                continue

            if name.startswith('SEQUENCE'):

                namestop = name[name.find('_')+1:].find('_')+name.find('_')
                lookupname = name[:namestop+1]
                value = _unwrap(rawvalue, AWG_FILE_FORMAT_SEQ[lookupname])
                sequencelist[0].append(name)
                sequencelist[1].append(value)

                continue

            else:
                value = _unwrap(rawvalue, AWG_FILE_FORMAT[name])

            if name in AWG_TRANSLATER:
                assert isinstance(value, int)
                value = AWG_TRANSLATER[name][value]

            instdict.update({name: value})

    return instdict, waveformlist, sequencelist


def _parser2(waveformlist: List[List[Any]]) -> Dict[str, Dict[str, np.ndarray]]:
    """
    Cast the waveformlist from _parser1 into a dict used by _parser3.

    Args:
        waveformlist: A list of lists of waveforms from ``_parser1``

    Returns:
        dict: A dictionary with keys waveform name and values for marker1,
            marker2, and the waveform as np.arrays
    """

    outdict = {}

    for (fieldname, fieldvalue) in zip(waveformlist[0], waveformlist[1]):
        if 'NAME' in fieldname:
            name = fieldvalue
        if 'DATA' in fieldname:
            value = _unpacker(fieldvalue)
            outdict.update({name: {'m1': value[1], 'm2': value[2],
                                   'wfm': value[0]}})

    return outdict


def _parser3(
        sequencelist: List[List[Any]],
        wfmdict: Dict[Any, Any]
             ) -> _parser3_output:
    """
    The final parser! OMG+1
    """

    sequencedict: Dict[str, List[Any]] = {
        'SEQUENCE_WAIT': [],
        'SEQUENCE_LOOP': [],
        'SEQUENCE_JUMP': [],
        'SEQUENCE_GOTO': [],
        'SEQUENCE_WAVEFORM_NAME_CH_1': [],
        'SEQUENCE_WAVEFORM_NAME_CH_2': [],
        'SEQUENCE_WAVEFORM_NAME_CH_3': [],
        'SEQUENCE_WAVEFORM_NAME_CH_4': []
    }

    for fieldname, fieldvalue in zip(sequencelist[0], sequencelist[1]):

        seqnum, name = _getendingnumber(fieldname)

        if 'WAVEFORM' not in name:
            sequencedict[name[:-1]].append(fieldvalue)
        else:
            sequencedict[name[:-1]].append(wfmdict[fieldvalue])

    # clean the dict
    keys = list(sequencedict.keys())
    for key in keys:
        if sequencedict[key] == []:
            sequencedict.pop(key)

    # waveforms
    wfms = []
    m1s = []
    m2s = []
    channels = []
    for key in [key for key in sequencedict if 'WAVE' in key]:
        channels.append(_getendingnumber(key)[0])
        wfms_temp = []
        m1s_temp = []
        m2s_temp = []
        for wfmdict in sequencedict[key]:
            wfms_temp.append(wfmdict['wfm'])
            m1s_temp.append(wfmdict['m1'])
            m2s_temp.append(wfmdict['m2'])

        wfms.append(wfms_temp)
        m1s.append(m1s_temp)
        m2s.append(m2s_temp)

    nreps = sequencedict['SEQUENCE_LOOP']
    waits = sequencedict['SEQUENCE_WAIT']
    gotos = sequencedict['SEQUENCE_GOTO']
    jumps = sequencedict['SEQUENCE_JUMP']

    return wfms, m1s, m2s, nreps, waits, gotos, jumps, channels


def parse_awg_file(
        awgfilepath: str
) -> Tuple[_parser3_output, Dict[str, Union[str, int, Tuple[Any, ...]]]]:
    """
    Parser for a binary .awg file. Returns a tuple matching the call signature
    of make_send_and_load_awg_file and a dictionary with instrument settings

    NOTE: Build-in waveforms are not stored in .awg files. Blame tektronix.

    Args:
        awgfilepath: The absolute path to the awg file

    Returns:
        A tuple and a dict, where the tuple is
        (wfms, m1s, m2s, nreps, trigs, gotos, jumps, channels)
        and the dict contains all instrument settings from the file
    """

    instdict, waveformlist, sequencelist = _parser1(awgfilepath)
    wfmdict = _parser2(waveformlist)
    callsigtuple = _parser3(sequencelist, wfmdict)

    return callsigtuple, instdict
