import re
from typing import List, Set

from functools import total_ordering

from ee import EeException


class Position(object):
    def __init__(self, x, y):
        self._x = x
        self._y = y

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y


class ComponentField(object):
    names = ["Reference", "Value", "Footprint", "Datasheet"]

    def __init__(self, index, name, value, position):
        value = value.strip() if value and len(value.strip()) > 0 else None

        self._index = index
        self._name = name if index >= len(ComponentField.names) else ComponentField.names[index]
        self._value = value
        self._position = position

    @property
    def index(self):
        return self._index

    @property
    def name(self):
        return self._name

    @property
    def value(self):
        return self._value

    @property
    def position(self):
        return self._position

    @property
    def is_custom(self):
        return self._index >= len(ComponentField.names)


@total_ordering
class Component(object):
    def __init__(self, position, timestamp, library, name, unit, ref, fields):
        self._position = position
        self._timestamp = timestamp
        self._library = library
        self._name = name
        self._unit = unit
        self._ref = ref
        self._fields = fields  # type List[ComponentField]

        r = re.compile("([^0-9]+)(.+)")
        try:
            parts = r.split(self._ref)
            self._ref_type = parts[1]
            self._ref_num = int(parts[2])
        except ValueError:
            self._ref_type = None
            self._ref_num = None

    def __eq__(self, o: object) -> bool:
        other = o  # type: Component
        return isinstance(o, Component) and self.ref == other.ref

    def __lt__(self, o: object) -> bool:
        if not isinstance(o, Component):
            return True

        other = o  # type Component

        if self.has_ref_num and other.has_ref_num:
            return (self.ref_type, self.ref_num) < (other.ref_type, other.ref_num)
        else:
            # raise Exception("fail, ref={}".format(self.ref))
            return self.ref < other.ref

    def __hash__(self) -> int:
        return self._ref.__hash__()

    @property
    def timestamp(self):
        return self._timestamp

    @property
    def unit(self):
        return self._unit

    @property
    def ref(self):
        return self._ref

    @property
    def has_ref_num(self):
        return self._ref_num is not None

    @property
    def ref_type(self) -> str:
        if self._ref_type is not None:
            return self._ref_type
        raise EeException("This component does not have a ref type")

    @property
    def ref_num(self) -> int:
        if self._ref_num is not None:
            return self._ref_num
        raise EeException("This component does not have a ref num")

    @property
    def is_pwr(self) -> bool:
        return self.ref_type == "#PWR"

    @property
    def is_flg(self) -> bool:
        return self.ref_type == "#FLG"

    @property
    def value(self) -> str:
        return self._fields[1].value

    @property
    def footprint(self) -> str:
        return self._fields[2].value

    @property
    def fields(self) -> List[ComponentField]:
        return list(self._fields)

    @property
    def named_fields(self):
        return [f for f in self._fields if f.name]

    def get_field(self, name) -> ComponentField:
        for f in self.fields:
            if name.lower() == f.name.lower():
                return f


class Sheet(object):
    def __init__(self, name, path):
        self._name = name
        self._path = path

    @property
    def path(self):
        return self._path

    @property
    def name(self):
        return self._name


class Library(object):
    def __init__(self, name):
        self.name = name


class Schematic(object):
    def __init__(self, path):
        self.path = path
        self._libraries = set()
        self._sheets = []
        self._components = []

    @property
    def libraries(self):
        return frozenset(self._libraries)

    def add_library(self, library):
        self._libraries.add(Library(library))

    @property
    def sheets(self):
        return frozenset(self._sheets)

    def add_sheet(self, sheet):
        self._sheets.append(sheet)

    @property
    def components(self) -> Set[Component]:
        return frozenset(self._components)

    def add_component(self, component):
        self._components.append(component)

    def get_component(self, ref, unit=1):
        c = self.find_component(ref, unit)

        if c:
            return c

        raise KeyError("No such component: {}".format(ref))

    def find_component(self, ref, unit=1):
        for c in self.components:
            if c.ref == ref and unit == unit:
                return c


class Schematics(object):
    def __init__(self, schematics):
        self._schematics = schematics

    @property
    def schematics(self) -> List[Schematic]:
        return list(self._schematics)

    @property
    def components(self) -> List[Component]:
        cs = []
        for s in self._schematics:
            for c in s.components:
                cs.append(c)
        return cs

    def get_component(self, ref, unit=1):
        for s in self._schematics:
            c = s.find_component(ref, unit)
            if c:
                return c

        raise KeyError("No such component: {}".format(ref))

    @property
    def libraries(self) -> List[Library]:
        ls = []
        for s in self._schematics:
            for l in s.libraries:
                ls.append(l)
        return ls