from collections import defaultdict
from typing import Tuple, List, Dict, Type

from . import page_attributes
from ..pages import Page
from ..objects import Options, DatabaseObject, Source


class MultiPageOptions:
    def __init__(
            self,
            max_displayed_options: int = 10,
            option_digits: int = 3,
            derived_from: DatabaseObject = None
    ) -> None:
        self.max_displayed_options = max_displayed_options
        self.option_digits: int = option_digits

        self._length = 0
        self._current_option_dict: Dict[Type[Page], Options] = defaultdict(lambda: Options())
        
        self._derive_from = derived_from

    def __getitem__(self, key: Type[Page]):
        return self._current_option_dict[key]

    def __setitem__(self, key: Type[Page], value: Options):
        self._current_option_dict[key] = value
        
        self._length = 0
        for key in self._current_option_dict:
            self._length += 1

    def __len__(self) -> int:
        return self._length

    def get_page_str(self, page: Page) -> str:
        page_name_fill = "-"
        max_page_len = 21
        
        return f"({page_attributes.PAGE_NAME_MAP[page]}) ------------------------{type(page).__name__:{page_name_fill}<{max_page_len}}------------"

    def string_from_all_pages(self) -> str:
        if self._length == 1:
            for key in self._current_option_dict:
                return self.string_from_single_page(key)
        
        lines: List[str] = []

        j = 0
        for page, options in self._current_option_dict.items():
            lines.append(self.get_page_str(page))

            i = -1

            option_obj: DatabaseObject
            for i, option_obj in enumerate(options):
                if i >= self.max_displayed_options:
                    lines.append("...")
                    break

                lines.append(f"{j + i:0{self.option_digits}} {option_obj.option_string}")

            j += i + 1

        return "\n".join(lines)

    def choose_from_all_pages(self, index: int) -> Tuple[DatabaseObject, Type[Page]]:
        if self._length == 1:
            for key in self._current_option_dict:
                return self.choose_from_single_page(key, index), key
        
        sum_of_length = 0
        for page, options in self._current_option_dict.items():
            option_len = min((len(options), self.max_displayed_options))

            index_of_list = index - sum_of_length
            
            if index_of_list < option_len:
                return options[index_of_list], page

            sum_of_length += option_len

        raise IndexError("index is out of range")

    def string_from_single_page(self, page: Type[Page]) -> str:
        lines: List[str] = [self.get_page_str(page)]

        option_obj: DatabaseObject
        for i, option_obj in enumerate(self._current_option_dict[page]):
            lines.append(f"{i:0{self.option_digits}} {option_obj.option_string}")

        return "\n".join(lines)

    def choose_from_single_page(self, page: Type[Page], index: int) -> DatabaseObject:
        return self._current_option_dict[page][index]

    def __repr__(self) -> str:
        return self.string_from_all_pages()
    