"""Helper functions and classes for dlist operations"""

from copy import deepcopy


class chars:
    """Special characters for tables
    http://jrgraphix.net/r/Unicode/2500-257F
    """
    he = '\u2550' 
    vl = '\u2502'  # vertical line │
    Vl = '\u2503'
    va = '\u257f'  # vertical arrow
    vt = '\u252f'  # vertical t  ┯  (hl down)
    hl = '\u2500'  # horizontal line ─
    Hl = '\u2501'
    tr = '\u251c'  # t right ├
    Tr = '\u2523'
    lr = '\u2514'  # l right └
    lR = '\u2570'  # round  ╰
    Lr = '\u2517'
    ra = '\u257c'  # simil arrow
    # junctions for table borders
    cr = '\u253c'  # cross ┼  (hl + vl)
    tu = '\u2534'  # t up  ┴  (hl + vl up)
    td = '\u252c'  # t down ┬ (hl + vl down)
    tl = '\u2524'  # t left ┤ (vl + hl left)
    # corners
    ctl = '\u250c'  # corner top-left ┌
    ctr = '\u2510'  # corner top-right ┐
    cbl = '\u2514'  # corner bot-left └
    cbr = '\u2518'  # corner bot-right ┘


def flattenDict(d, charSep='__', dic=True):
    """Flattens a nested dictionary structure
    
    Parameters:
        d (dict): Dictionary to flatten
        charSep (str): Separator for nested keys (default: '__')
        dic (bool): If True, marks dict values with '__dict__'
    
    Example::
    
        >>> dd = {'k1':'v1',
        >>>       'k2':{'a':'va','b':'vb','c':{'qq':'pi2'}},
        >>>       'k3':{'A':'vA'}
        >>>      }
        >>> flattenDict(dd)
        >>> Out: {'k1': 'v1', 
                  'k2': '__dict__', 'k2__a': 'va', 'k2__b': 'vb', 
                  'k2__c': '__dict__', 'k2__c__qq': 'pi2', 
                  'k3': '__dict__', 'k3__A': 'vA'}
    """
    try: 
        n = len(d.keys())
    except:
        return d
        
    out = {}
    for key in d:
        try: 
            # is dict
            n = len(d[key].keys())
            dd = flattenDict(d[key], charSep=charSep, dic=dic)
            if dic:
                out[key] = '__dict__'
            for key2 in dd:
                out[f'{key}{charSep}{key2}'] = dd[key2]
        except:
            # is val
            out[key] = d[key]
    return out


def structDict(d, charSep='__'):
    """UnFlatten a dict nested structure
    
    Reverses the operation of flattenDict
    
    Parameters:
        d (dict): Flattened dictionary
        charSep (str): Separator character used in keys
    
    Returns:
        dict: Structured dictionary
    """
    # all keys
    try:
        ks = list(d.keys())
    except:
        return d
    ks = [k for k in ks if (d[k] != '__dict__')]

    # rooted keys without struct
    k0 = list(filter(lambda x: charSep not in x, ks))
    out = {k: d[k] for k in k0}
    
    # keys with struct
    k1 = [k for k in ks if k not in k0]
    
    # ..roots
    k10 = map(lambda x: x.split(charSep)[0], k1)
    k10 = list(set(list(k10)))

    for kr in k10:
        # branches
        dd = {}
        for k in k1:
            if k.startswith(kr + charSep):
                kb = charSep.join(k.split(charSep)[1:])
                dd[kb] = d[k]
        out[kr] = structDict(dd, charSep=charSep)

    return out


def merge_dicts(d1, d2, over=True, charSep='__'):
    """Merge two dictionaries, considering structure
    
    Parameters:
        d1 (dict): dictionary_1
        d2 (dict): dictionary_2
        over (bool): if True, accepts override changes in declared keys of d1
        charSep (str): Separator character for nested keys
    
    Note:
        The operation is not symmetric, but these sentences are equivalent:
        merge_dicts(d1, d2, over=True) <--> merge_dicts(d2, d1, over=False)
    """
    # Flatten structure
    D1 = flattenDict(d1, charSep=charSep)
    D2 = flattenDict(d2, charSep=charSep)

    # d1 | d2: right side wins on conflicts
    if over:
        out = D1 | D2       # d2 overrides d1
    else:
        out = D2 | D1       # d1 preserved, only new keys from d2

    return structDict(out, charSep=charSep)


def getDitem(dict_obj, skey, charSep='__'):
    """Call a structured key in dict
    
    Parameters:
        dict_obj (dict): Dictionary to query
        skey (str): Structured key with separator
        charSep (str): Separator character
    
    Returns:
        Value at key or {} if doesn't exist
    """
    if skey == '':
        return dict_obj

    out = deepcopy(dict_obj)
    for k in skey.split(charSep):
        try:
            out = out[k]
        except:
            out = {}

    return out


def cleanStr(x):
    """Default string cleaner - coerce to str"""
    if isinstance(x, list):
        return ', '.join(str(i) for i in x)
    if isinstance(x, dict):
        return ', '.join(f'{k}: {v}' for k, v in x.items())
    return str(x) if not isinstance(x, str) else x


def LATEXcleanStr(x):
    """Clean string for LaTeX output"""
    x = cleanStr(x)
    return x.replace('_', r'\_').replace('&', r'\&').replace('%', r'\%').replace(' ', '~')
