"""This module provides base class of [Node](mkapi.core.node.Node) and
[Module](mkapi.core.module.Module)."""
from dataclasses import dataclass, field
from typing import Any, Iterator, List, Union

from mkapi.core.base import Base, Type
from mkapi.core.docstring import Docstring, get_docstring
from mkapi.core.object import (get_origin, get_qualname,
                               get_sourcefile_and_lineno,
                               split_prefix_and_name)
from mkapi.core.signature import Signature, get_signature

"a.b.c".rpartition(".")


@dataclass
class Object(Base):
    """Object class represents an object.

    Args:
        name: Object name.
        prefix: Object prefix.
        qualname: Qualified name.
        kind: Object kind such as 'class', 'function', *etc.*
        signature: Signature if object is module or callable.

    Attributes:
        id: ID attribute of HTML.
        type: Type for missing Returns and Yields sections.
    """

    prefix: str = ""
    qualname: str = ""
    kind: str = ""
    signature: Signature = field(default_factory=Signature)
    module: str = field(init=False)
    markdown: str = field(init=False)
    id: str = field(init=False)
    type: Type = field(default_factory=Type, init=False)

    def __post_init__(self):
        from mkapi.core import linker

        self.id = self.name
        if self.prefix:
            self.id = ".".join([self.prefix, self.name])
        if not self.qualname:
            self.module = self.id
        else:
            self.module = self.id[: -len(self.qualname) - 1]
        if not self.markdown:
            name = linker.link(self.name, self.id)
            if self.prefix:
                prefix = linker.link(self.prefix, self.prefix)
                self.markdown = ".".join([prefix, name])
            else:
                self.markdown = name

    def __repr__(self):
        class_name = self.__class__.__name__
        id = self.id
        return f"{class_name}({id!r})"

    def __iter__(self) -> Iterator[Base]:
        yield from self.type
        yield self


@dataclass
class Tree:
    """Tree class. This class is the base class of [Node](mkapi.core.node.Node)
    and [Module](mkapi.core.module.Module).

    Args:
        obj: Object.

    Attributes:
        sourcefile: Source file path.
        lineno: Line number.
        object: Object instance.
        docstring: Docstring instance.
        parent: Parent instance.
        members: Member instances.
    """

    obj: Any = field()
    sourcefile: str = field(init=False)
    lineno: int = field(init=False)
    object: Object = field(init=False)
    docstring: Docstring = field(init=False)
    parent: Any = field(default=None, init=False)
    members: List[Any] = field(init=False)

    def __post_init__(self):
        obj = get_origin(self.obj)
        self.sourcefile, self.lineno = get_sourcefile_and_lineno(obj)
        prefix, name = split_prefix_and_name(obj)
        qualname = get_qualname(obj)
        kind = self.get_kind()
        if kind == "enum":
            # Don't show any signature for Enums
            signature = Signature(object)
        else:
            signature = get_signature(obj)
        self.object = Object(
            prefix=prefix, name=name, qualname=qualname, kind=kind, signature=signature,
        )
        self.docstring = get_docstring(obj)
        self.obj = obj
        if kind == "enum":
            # Do not include members from base classes or we'll just get builtin functions
            self.members = []
        else:
            self.members = self.get_members()
        for member in self.members:
            member.parent = self
        self.members = sorted(self.members)

    def __repr__(self):
        class_name = self.__class__.__name__
        id = self.object.id
        sections = len(self.docstring.sections)
        numbers = len(self.members)
        return f"{class_name}({id!r}, num_sections={sections}, num_members={numbers})"

    def __getitem__(self, index: Union[int, str, List[str]]):
        """Returns a member {class} instance.

        If `index` is str, a member Tree instance whose name is equal to `index`
        is returned.

        Raises:
            IndexError: If no member found.
        """
        if isinstance(index, list):
            node = self
            for name in index:
                node = node[name]
            return node
        if isinstance(index, int):
            return self.members[index]
        if isinstance(index, str) and "." in index:
            names = index.split(".")
            return self[names]
        for member in self.members:
            if member.object.name == index:
                return member
        raise IndexError

    def __len__(self):
        return len(self.members)


    def __lt__(self, other):
        return self.object.id < other.object.id

    def __contains__(self, name):
        for member in self.members:
            if member.object.name == name:
                return True
        return False

    def get_kind(self) -> str:
        """Returns kind of self."""
        raise NotImplementedError

    def get_members(self) -> List["Tree"]:
        """Returns a list of members."""
        raise NotImplementedError

    def get_markdown(self) -> str:
        """Returns a Markdown source for docstring of self."""
        raise NotImplementedError

    def walk(self) -> Iterator["Tree"]:
        """Yields all members."""
        yield self
        for member in self.members:
            yield from member.walk()
