import re

from .exc import ConverterError

pat_siprefix = re.compile(r'\b(\d+\.\d*|\.\d+|\d+\.|\d+)(meg|g|t|mil)\b')

pat_temper = re.compile(r"\btemper\b")

# First '=' that is not part of a comparison operator (==, !=, <=, >=),
# i.e. the name/value separator of a parameter assignment. Values can
# contain comparison operators, e.g. lr='(d_sab==0)?1e-15:d_sab'.
pat_assign_eq = re.compile(r"(?<![=!<>])=(?!=)")

prefix_map = {
    "meg": "M", 
    "g": "G", 
    "t": "T"
}

def si_replace_worker(match):
    """
    Converts SI prefixes based on a rexexp match. 
    xmeg -> xM
    xg -> xG
    xt -> xT
    xmil -> (x*25.4e-6)
    """
    num = match.group(1)
    prefix = match.group(2)
    if prefix=="mil":
        return "("+num+"*25.4e-6)"
    else:
        return num+prefix_map[prefix]

def si_replace(expr):
    """
    Converts SI prefixes to VACASK syntax. 
    """
    return pat_siprefix.sub(si_replace_worker, expr)


class ParamsMixin:
    def format_extra_params(self, extras, lws, indent):
        """
        Format extra parameters.

        For now, we do not care about line length. 
        """
        intxt = lws + (" "*indent)

        txt = ""
        first = True
        for name, value in extras.items():
            if not first:
                txt += " "
            first = False
            txt += name+"="+value
        
        return intxt+txt
    
    def format_value(self, valuestr):
        """
        Formats a value. Removes curly braces. Changes SI prefixes. 
        """
        if valuestr[0]=="{":
            valuestr = valuestr[1:-1]
        
        # Handle SI prefixes
        return si_replace(valuestr)

    def format_params(self, params, atcol=0, indent=2):
        """
        Format parameters, indent them, make sure lines are not too long. 

        *lws* is the leading whitespace string. 

        *atcol* is the column number where we start to dump parameters. 

        Does not indent. Uses *indent* for computing when to split a line. 

        Returns a string, a flag indicating string has multiple lines, 
        and a flag indicating that the formatted output has more then one 
        line. 
        """
        # Strip braces from parameter values, replace SI units
        # Compute full size
        newpar = []
        full_size = 0
        for name, value in params:
            value = self.format_value(value)
            newpar.append((name, value))

            full_size += len(name)+1+len(value)+1
        params = newpar

        # Do we need to split the line 
        need_split = atcol+full_size>self.cfg["columns"]

        split = False
        line = ""
        fragment = ""
        first = True
        for name, value in params:
            # Handle SI prefixes
            value = si_replace(value)

            # Do we need to start a new line
            if indent+len(fragment)+len(name)+1+len(value)>self.cfg["columns"]:
                # New line
                if not first:
                    line += "\n"
                    split = True
                else:
                    first = False
                line += fragment
                fragment = ""
            
            # Space as separator
            if len(fragment)>0:
                fragment += " "
            
            # Add parameter
            fragment += name + "=" + value
        
        # Flush last fragment
        if len(fragment)>0:
            if not first:
                line += "\n"
                split = True
            line += fragment
        
        return line, need_split, split

    def indent(self, txt, indent):
        """
        Indents text by *indent* spaces. 
        """
        istr = " "*indent
        return "\n".join([istr+l for l in txt.split("\n")])

    def format_subckt_params(self, params, lws):
        """
        Format subcircuit parameters. 

        One parameter per line. 
        """
        txt = ""
        first = True
        for name, value in params:
            value = si_replace(value)
            if not first:
                txt += "\n"
            first = False
            txt += lws + "parameters "+name+"="+value

        return txt

    def split_params(self, params, handle_m=False, m_chain=None):
        """
        Splits a list of parameter assignments into a list of 
        (names, value) pairs. 

        Treats boolean parameters as <param>=1. 

        If requested, renames m/_mfactor parameter into $mfactor. 

        If m_chain is given, the existing $mfactor is multiplied with m_chain. 
        If $mfactor is not in the list of parameters, it is added. 
        """
        psplit = []
        mfact_index = None
        for ii, p in enumerate(params):
            m = pat_assign_eq.search(p)
            if m is None:
                # Handle booleans
                split = (p, "1")
            else:
                split = (p[:m.start()], p[m.end():])
            
            # Handle mfactor
            if handle_m:
                if split[0]=="m" or split[0]=="_mfactor":
                    split = ( "$mfactor", split[1] )
            
            # Check if this is $mfactor
            if split[0]=="$mfactor":
                mfact_index = ii
            
            psplit.append(split)

        if m_chain is not None:
            if mfact_index is not None:
                # Exists, multiply
                psplit[mfact_index] = ( psplit[mfact_index][0], "("+psplit[mfact_index][1]+")*"+m_chain )
            else:
                # Does not exist, add
                psplit.append(("$mfactor", m_chain))
        
        # Remove duplicates
        seen = {}
        pl = []
        for pn, pv in psplit:
            if pn in seen:
                if pv != seen[pn]:
                    print("Warning: redefinition of parameter '"+pn+"'")
                continue
            pl.append((pn, pv))
            seen[pn] = pv

        return pl

    def remove_params(self, params, to_remove=None):
        """
        Removes all parameters listed in set *to_remove*.
        Can handle unsplit and split parameters.
        """
        if to_remove is None:
            to_remove = set()
        out = []
        for part in params:
            if isinstance(part, list) or isinstance(part, tuple):
                if part[0] in to_remove:
                    continue
            elif part in to_remove:
                    continue
            out.append(part)
        return out
    
    def merge_vectors(self, params, vecnames=None):
        """
        Merges vector parameters into one string.

        Assumes *params* is a list of unsplit parameters.
        """
        if vecnames is None:
            vecnames = set()
        out = []
        ndx = 0
        while ndx < len(params):
            part = params[ndx]
            sp = part.split("=", 1)
            if len(sp)>1 and sp[0] in vecnames:
                # Start merging
                merged = ""
                while ndx<len(params):
                    merged += params[ndx]
                    if params[ndx].strip()[-1]!=",":
                        ndx += 1
                        break
                    ndx += 1
                out.append(merged)
            else:
                out.append(part)
                ndx += 1

        return out

    def process_expressions(self, params):
        """
        Processes expressions, replace 
          temper -> $temp
        """
        pout = []
        for p, e in params:
            e = re.sub(pat_temper, "$temp", e)
            pout.append((p, e))
        
        return pout

    def process_instance_params(self, params, insttype, handle_m=False, in_sub=None):
        """
        Processes instance parameters. 

        Merges vectors, removes unneeded parameters. 

        If requested, renames m parameter. 

        Returns split parameters. 
        """
        # Merge vector parameters
        vecnames = self.cfg["merge_vector_instance_params"].get(insttype, None)
        if vecnames is not None:
            params = self.merge_vectors(params, vecnames)

        # Split parameters, default boolean parameters, handle m
        m_chain, graft = self.cfg["subckt_multiplier"].get(in_sub, (None, False))
        psplit = self.split_params(params, handle_m=handle_m, m_chain=m_chain)

        # Remove unneeded parameters
        vecnames = self.cfg["remove_instance_params"].get(insttype, None)
        if vecnames is not None:
            psplit = self.remove_params(psplit, vecnames)
        
        # Process expressions
        psplit = self.process_expressions(psplit)

        # Parent m name is $mfactor
        # Look for $mfactor in list
        # Multiply by $mfactor or introduce $mfactor


        return psplit
    
    def process_terminals(self, terminals):
        """
        Processes terminals. For now replaces ! with _. 
        """
        return [ t.replace("!", "_") for t in terminals]
