from typing import Iterable, Mapping, Set, Tuple, Union  # noqa

from uqbar.containers import UniqueTreeContainer
from uqbar.graphs.Attributes import Attributes
from uqbar.graphs.Edge import Edge  # noqa
from uqbar.graphs.RecordField import RecordField
from uqbar.graphs.RecordGroup import RecordGroup
from uqbar.graphs.Table import Table


class Node(UniqueTreeContainer):
    """
    A Graphviz node.

    ::

        >>> import uqbar.graphs
        >>> node = uqbar.graphs.Node()
        >>> print(format(node, 'graphviz'))
        node_0;

    """

    ### CLASS VARIABLES ###

    __documentation_section__ = "Core Classes"

    ### INITIALIZER ###

    def __init__(
        self,
        children: Iterable[Union[RecordField, RecordGroup]] = None,
        *,
        attributes: Union[Mapping[str, object], Attributes] = None,
        name: str = None,
    ) -> None:
        UniqueTreeContainer.__init__(self, name=name, children=children)
        self._attributes = Attributes("node", **(attributes or {}))
        self._edges: Set[Edge] = set()

    ### SPECIAL METHODS ###

    def __format__(self, format_spec=None) -> str:
        # TODO: make the format specification options machine-readable
        if format_spec == "graphviz":
            return self.__format_graphviz__()
        return str(self)

    def __format_graphviz__(self) -> str:
        node_definition = Attributes._format_value(self._get_canonical_name())
        result = [node_definition]
        attributes = self.attributes.copy()
        if len(self):
            if isinstance(self[0], Table):
                label = "<\n{}>".format(format(self[0], "graphviz"))
            else:
                label = " | ".join(format(_, "graphviz") for _ in self)
            attributes["label"] = label
        if len(attributes):
            attributes = format(attributes, "graphviz").split("\n")
            result[0] = "{} {}".format(result[0], attributes[0])
            result.extend(attributes[1:])
        else:
            result[-1] += ";"
        return "\n".join(result)

    ### PRIVATE METHODS ###

    def _get_canonical_name(self) -> str:
        prefix = "node"
        if self.name is not None:
            root = self.root
            if root:
                instances = root[self.name]
                if instances is not self:
                    return "{}_{}".format(self.name, instances.index(self))
            return self.name
        elif self.graph_order:
            suffix = "_".join(str(x) for x in self.graph_order)
        else:
            suffix = "0"
        return "{}_{}".format(prefix, suffix)

    ### PUBLIC METHODS ###

    def attach(
        self,
        node,
        is_directed=True,
        head_port_position=None,
        tail_port_position=None,
        **attributes,
    ) -> Edge:
        import uqbar.graphs

        edge = uqbar.graphs.Edge(
            attributes=attributes,
            head_port_position=head_port_position,
            is_directed=is_directed,
            tail_port_position=tail_port_position,
        )
        edge.attach(self, node)
        return edge

    ### PRIVATE PROPERTIES ###

    @property
    def _node_class(self) -> Tuple[type, ...]:
        import uqbar.graphs

        return (uqbar.graphs.RecordField, uqbar.graphs.RecordGroup, uqbar.graphs.Table)

    ### PUBLIC PROPERTIES ###

    @property
    def attributes(self) -> Attributes:
        return self._attributes

    @property
    def edges(self) -> Set[Edge]:
        return set(self._edges)
