import re
from typing import Tuple


def readasc(filename: str) -> Tuple[dict, dict]:
    """
    Reads Siemens ASC ascii-formatted textfile and returns a dictionary
    structure.
    E.g. a[0].b[2][3].c = "string"
    parses into:
      asc['a'][0]['b'][2][3]['c'] = "string"

    Parameters
    ----------
    filename : str
        Filename of the ASC file.

    Returns
    -------
    asc : dict
        Dictionary of ASC part of file.
    extra : dict
        Dictionary of other fields after "ASCCONV END"
    """
    asc, extra = {}, {}

    # Read asc file and convert it into a dictionary structure
    with open(filename, 'r') as fp:
        end_of_asc = False

        for next_line in fp:
            next_line = next_line.strip()

            if next_line == '### ASCCONV END ###':  # find end of mrProt in the asc file
                end_of_asc = True

            if next_line == '' or next_line[0] == '#':
                continue

            # regex wizardry: Matches lines like 'a[0].b[2][3].c = "string" # comment'
            # Note this assumes correct formatting, e.g. does not check whether
            # brackets match.
            match = re.match(
                r'^\s*([a-zA-Z0-9\[\]\._]+)\s*\=\s*(("[^"]*"|\'[^\']\')|(\d+)|([0-9\.e\-]+))\s*((#|\/\/)(.*))?$',
                next_line,
            )

            if match:
                field_name = match[1]

                # Keep track of where to put the value: base[assign_to] = value
                if end_of_asc:
                    base = extra
                else:
                    base = asc

                assign_to = None

                # Iterate over every segment of the field name
                parts = field_name.split('.')
                for p in parts:
                    # Update base so final assignment is like: base[assign_to][p] = value
                    if assign_to is not None and assign_to not in base:
                        base[assign_to] = {}
                    if assign_to is not None:
                        base = base[assign_to]

                    # Iterate over brackets
                    start = p.find('[')
                    if start != -1:
                        name = p[:start]
                        assign_to = name

                        while start != -1:
                            stop = p.find(']', start)
                            index = int(p[start + 1 : stop])

                            # Update base so final assignment is like: base[assign_to][p][index] = value
                            if assign_to not in base:
                                base[assign_to] = {}
                            base = base[assign_to]
                            assign_to = index

                            start = p.find('[', stop)
                    else:
                        assign_to = p

                # Depending on which regex section matched we can infer the value type
                if match[3]:
                    base[assign_to] = match[3][1:-1]
                elif match[4]:
                    base[assign_to] = int(match[4])
                elif match[5]:
                    base[assign_to] = float(match[5])
                else:
                    raise RuntimeError('This should not be reached')
            elif next_line.find('=') != -1:
                raise RuntimeError(f'Bug: ASC line with an assignment was not parsed correctly: {next_line}')

    return asc, extra
