"""Together with :mod:`~configupdater.block` this module forms the basis of
the class hierarchy in **ConfigUpdater**.

The :class:`Container` is the parent class of everything that can contain configuration
blocks, e.g. a section or the entire file itself.
"""
import sys
from abc import ABC
from copy import deepcopy
from textwrap import indent
from typing import TYPE_CHECKING, Generic, Optional, TypeVar

if sys.version_info[:2] >= (3, 9):  # pragma: no cover
    from collections.abc import Iterator

    List = list
else:  # pragma: no cover
    from typing import Iterator, List

if TYPE_CHECKING:
    from .block import Block  # noqa

T = TypeVar("T", bound="Block")
C = TypeVar("C", bound="Container")


class Container(ABC, Generic[T]):
    """Abstract Mixin Class describing a container that holds blocks of type ``T``"""

    def __init__(self):
        self._structure: List[T] = []

    def _repr_blocks(self) -> str:
        blocks = "\n".join(repr(block) for block in self._structure)
        blocks = indent(blocks, " " * 4)
        return f"[\n{blocks.rstrip()}\n]" if blocks.strip() else "[]"

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {self._repr_blocks()}>"

    def __deepcopy__(self: C, memo: dict) -> C:
        clone = self._instantiate_copy()
        memo[id(self)] = clone
        return clone._copy_structure(self._structure, memo)

    def _copy_structure(self: C, structure: List[T], memo: dict) -> C:
        """``__deepcopy__`` auxiliary method also useful with multi-inheritance"""
        self._structure = [b.attach(self) for b in deepcopy(structure, memo)]
        return self

    def _instantiate_copy(self: C) -> C:
        """Auxiliary method that allows subclasses calling ``__deepcopy__``"""
        return self.__class__()  # allow overwrite for different init args

    @property
    def structure(self) -> List[T]:
        return self._structure

    @property
    def first_block(self) -> Optional[T]:
        if self._structure:
            return self._structure[0]
        else:
            return None

    @property
    def last_block(self) -> Optional[T]:
        if self._structure:
            return self._structure[-1]
        else:
            return None

    def _remove_block(self: C, idx: int) -> C:
        """Remove block at index idx within container

        Use `.container_idx` of a block to get the index.
        Not meant for users, rather use block.detach() instead!
        """
        del self._structure[idx]
        return self

    def iter_blocks(self) -> Iterator[T]:
        """Iterate over all blocks inside container."""
        return iter(self._structure)

    def __len__(self) -> int:
        """Number of blocks in container"""
        return len(self._structure)

    def append(self: C, block: T) -> C:
        self._structure.append(block)
        return self
