import re
from pathlib import Path
from typing import List, Tuple, Union, Mapping, Callable, Any

from ee import EeVal, EeException
from ee.part import PartDb, load_db, save_db
from ee.xml import bomFile, uris

__all__ = ["normalize_facts"]


# TODO: this should be moved to a generic normalizer
def handle_tolerance(tolerance: bomFile.Fact, capacitance: bomFile.Fact) -> List[bomFile.Fact]:
    cap_value = float(EeVal.parse(capacitance.valueProp))

    s = tolerance.valueProp
    print("tolerance: {}".format(s))
    is_plus_minus = s.startswith("±")
    s = s[1:] if is_plus_minus else s

    is_percent = s.endswith("%")

    is_farad = s.endswith("F")

    print("tolerance={}, is_plus_minus={}, is_farad={}, is_percent={}".format(
        tolerance.valueProp, is_plus_minus, is_farad, is_percent))
    print("capacitance={}".format(EeVal.parse(capacitance.valueProp)))

    low_pct = low_value = None
    high_pct = high_value = None
    facts = []  # type: List[Tuple[str, str, Union[str, EeVal]]]

    if is_farad:
        s = s[0:-1] if is_farad else s

        try:
            farad = EeVal.parse(s)
        except EeException:
            farad = None

    elif is_percent:
        s = s[0:-1]
        pct_value = int(s)

        if is_plus_minus:
            low_pct = high_pct = str(pct_value)
            pct = float(pct_value) / 100
            low_value = EeVal(value=cap_value * (1 - pct), exp=1, unit="F")
            high_value = EeVal(value=cap_value * (1 + pct), exp=1, unit="F")
    else:
        raise EeException("bad combination")

    if low_pct:
        facts.append(("tolerance-percent-lower", "Tolerance, lower (%)", low_pct))
    if high_pct:
        facts.append(("tolerance-percent-upper", "Tolerance, upper (%)", high_pct))
    if low_value:
        facts.append(("tolerance-lower", "Tolerance, lower", low_value))
    if high_value:
        facts.append(("tolerance-upper", "Tolerance, upper", high_value))

    if low_pct and low_pct == high_pct:
        facts.append(("tolerance-percent", "Tolerance (±%)", low_pct))

    return [bomFile.Fact(key=uris.make_fact_key(key), label=label, value=str(value)) for key, label, value in facts]


def re_parser(pattern, kis: List[Tuple[str, int]]):
    r = re.compile(pattern)

    def parse(value):
        m = r.match(value)

        if not m:
            return

        return [(k, m.group(i)) for k, i in kis]

    return parse


def no_parser(key):
    def parse(value):
        return key, value

    return parse


def ee_val_parser(key):
    def parse(value):
        return key, EeVal.parse(value)

    return parse


_operating_temperature_re = re.compile("-([0-9]+)°C ~ ([0-9])+°C")
_height_seated_re = re.compile("([0-9]+\\.[0-9]+)\" \\(([0-9]+\\.[0-9]+)mm\\)")
_land_size_re = re.compile("([0-9]+\\.[0-9]+)\" L x ([0-9]+\\.[0-9]+)\" W "
                           "\\(([0-9]+\\.[0-9]+)mm x ([0-9]+\\.[0-9]+)mm\\)")  # 0.260" L x 0.260" W (6.60mm x 6.60mm)
parsers: Mapping[int, Callable[[str], Any]] = {
    3: no_parser("tolerance"),
    1471: ee_val_parser("voltage-input-min"),  # Voltage - Input (Min)
    573: ee_val_parser("voltage-input-max"),  # Voltage - Input (Max)
    1429: ee_val_parser("voltage-output-max"),  # Voltage - Output (Max)
    252: re_parser(_operating_temperature_re, [("temperature-min", 1), ("temperature-max", 2)]),
    1686: re_parser(_operating_temperature_re, [("temperature-junction-min", 1), ("temperature-junction-max", 2)]),

    1500: re_parser(_height_seated_re, [("part-height", 2)]),
    884: re_parser(_land_size_re, [("land-size-x", 3), ("land-size-y", 4)]),

    2049: ee_val_parser("capacitance"),
    2079: ee_val_parser("voltage-rating"),
}


def normalize_facts(in_dir: Path, out_dir: Path):
    print("in: {}, out: {}".format(in_dir, out_dir))

    in_db = load_db(in_dir)
    out_parts = PartDb()

    for part in in_db.iterparts():  # type: bomFile.Part
        fact_list: bomFile.FactList = part.factsProp
        if fact_list is None:
            continue

        in_facts: List[bomFile.Fact] = fact_list.factProp
        out_facts = []

        for f in in_facts:
            key = uris.parse_digikey_fact_key(f.keyProp)

            value = f.valueProp

            if value == "-":
                continue

            parser = parsers.get(key)

            if parser is not None:
                res = parser(value)

                if res is not None:
                    if isinstance(res, list):
                        results = res
                    else:
                        results = [res]
                    facts = [bomFile.Fact(key=uris.make_fact_key(key), value=value) for key, value in results]
                    out_facts.extend(facts)

        if len(out_facts) == 0:
            continue

        part.factsProp.factProp = out_facts
        out_parts.add_entry(part, True)

    print("Saving {} work parts".format(out_parts.size()))
    save_db(out_dir, out_parts)