"""
Lineage object definition
"""


from __future__ import annotations
from typing import Optional
from collections import UserList
from .Node import Node, DummyNode, _BaseNode, MergedNode
from .utils import linne


class Lineage(UserList):
    """
    Taxomic Lineage

    Defines a linear and ordered succession of Nodes.
    A Lineage is created by providing a single Node that
    will be used as a base to retrieve higher Nodes.
    Ranks are ascending by default.

    Parameters
    ----------
    base_node: taxidTools._Basenode
        An instance of a `taxidTools._BaseNode`subclass from which the ancestry
        should be retrieved
    ascending: bool, optional
        Should the Lineage by sorted by ascending ranks?

    Notes
    -----
    A Lineage does not have to be continuous. Nodes can have parents that
    are not included in the Lineage, as long as Nodes in a Lineage form a
    linear path.

    Lineage methods will never modify the Node objects it contains

    Examples
    --------
    >>> root = Node(1, "root", "root")
    >>> child1 = Node(2, "child1", "child_rank", root)
    >>> child2 = Node(3, "child2", "sub_child_rank", child1)
    >>> Lineage(child2)
    Lineage([Node(3), Node(2), Node(1)])

    Lineage elements are the Node objects themselves

    >>> Lineage(child2)[-1]
    Node object:
            Taxid: 1
            Name: root
            Rank: root
            Parent: None

    A Lineage can also be descending
    >>> Lineage(child2, ascending = False)
    Lineage([Node(1), Node(2), Node(3)])
    """

    def __init__(self, base_node: _BaseNode, ascending: Optional[bool] = True) -> None:
        if not isinstance(base_node, (_BaseNode,MergedNode)):
            raise ValueError(
                "Lineage should be instanciated with a Node or list of Nodes")

        self._baseNode = base_node

        vec = [base_node]

        while vec[-1].parent:
            vec.append(vec[-1].parent)

        self.data = vec

        if not ascending:
            self.reverse()

    def filter(self, ranks: Optional[list[str]] = linne()) -> None:
        """
        Filter a Lineage to a set of specified ranks.

        Modifies a Lineage in-place.
        Lineage order will not be conserved and dummy nodes will
        be added as placeholders for missing ranks.

        Parameters
        ----------
        ranks: list, optional
            List of ranks to filter. It is assumed to be sorted
            in the same order as Lineage.

        Notes
        -----
        The Nodes are not modified by this method!
        That means that Node.parent will
        still point to the original parent Node,
        even if it was masked in the Lineage.

        Examples
        --------
        >>> root = Node(1, "root", "root")
        >>> child1 = Node(2, "child1", "child_rank", root)
        >>> child2 = Node(3, "child2", "sub_child_rank", child1)
        >>> lin = Lineage(child2)
        >>> lin.filter(["sub_child_rank", "norank", "child_rank"])
        >>> lin
        Lineage([Node(3), 'dummy', Node(2)])

        Order is not conserved!

        >>> lin = Lineage(child2)
        >>> lin.filter(["root", "sub_child_rank"])
        Lineage([Node(1), Node(3)])
        """
        nodedict = {node.rank: node for node in self if node.rank in ranks}

        new = []
        for rank in ranks:
            try:
                new.append(nodedict[rank])
            except KeyError:
                new.append(DummyNode(rank=rank))

        self.data = new

    def __repr__(self) -> str:
        return f"Lineage({[node for node in self]})"
