from typing import Union, Tuple, Dict, Iterable, List
from datetime import datetime
import logging
import os

from ..exception.config import SettingNotFound, SettingValueError
from ..path_manager import LOCATIONS
from .base_classes import Description, Attribute, Section, EmptyLine, COMMENT_PREFIX
from .audio import AUDIO_SECTION
from .logging import LOGGING_SECTION
from .connection import CONNECTION_SECTION
from .misc import MISC_SECTION
from .paths import PATHS_SECTION


LOGGER = logging.getLogger("config")


class Config:
    def __init__(self):
        self.config_elements: Tuple[Union[Description, Attribute, Section], ...] = (
            Description("IMPORTANT: If you modify this file, the changes for the actual setting, will be kept as is.\n"
                        "The changes you make to the comments, will be discarded, next time you run music-kraken. "
                        "Have fun!"),
            Description(f"Latest reset: {datetime.now()}"),
            Description("Those are all Settings for the audio codec.\n"
                        "If you, for some reason wanna fill your drive real quickly, I mean enjoy HIFI music,\n"
                        "feel free to tinker with the Bitrate or smth. :)"),
            AUDIO_SECTION,
            Description("Modify how Music-Kraken connects to the internet:"),
            CONNECTION_SECTION,
            Description("Modify all your paths, except your config file..."),
            PATHS_SECTION,
            Description("For all your Logging needs.\n"
                        "If you found a bug, and wan't to report it, please set the Logging level to 0,\n"
                        "reproduce the bug, and attach the logfile in the bugreport. ^w^"),
            LOGGING_SECTION,
            Description("If there are stupid settings, they are here."),
            MISC_SECTION,
            Description("🏳️‍⚧️🏳️‍⚧️ Protect trans youth. 🏳️‍⚧️🏳️‍⚧️\n"),
        )

        self._length = 0
        self._section_list: List[Section] = []
        self._name_section_map: Dict[str, Section] = dict()

        for element in self.config_elements:
            if not isinstance(element, Section):
                continue

            self._section_list.append(element)
            for name in element.name_attribute_map:
                if name in self._name_section_map:
                    raise ValueError(f"Two sections have the same name: "
                                     f"{name}: "
                                     f"{element.__class__.__name__} {self._name_section_map[name].__class__.__name__}")

                self._name_section_map[name] = element
                self._length += 1

    def set_name_to_value(self, name: str, value: str):
        """
        :raises SettingValueError, SettingNotFound:
        :param name:
        :param value:
        :return:
        """
        if name not in self._name_section_map:
            raise SettingNotFound(setting_name=name)

        LOGGER.debug(f"setting: {name} value: {value}")

        self._name_section_map[name].modify_setting(setting_name=name, new_value=value)

    def __len__(self):
        return self._length

    @property
    def config_string(self) -> str:
        return "\n\n".join(str(element) for element in self.config_elements)

    def _parse_conf_line(self, line: str, index: int):
        """
        :raises SettingValueError, SettingNotFound:
        :param line:
        :param index:
        :return:
        """
        line = line.strip()
        if line.startswith(COMMENT_PREFIX):
            return

        if line == "":
            return

        if "=" not in line:
            """
            TODO
            No value error but custom conf error
            """
            raise ValueError(f"Couldn't find the '=' in line {index}.")

        line_segments = line.split("=")
        name = line_segments[0]
        value = "=".join(line_segments[1:])

        self.set_name_to_value(name, value)

    def read_from_config_file(self, path: os.PathLike):
        with open(path, "r", encoding=LOCATIONS.FILE_ENCODING) as conf_file:
            for section in self._section_list:
                section.reset_list_attribute()

            for i, line in enumerate(conf_file):
                self._parse_conf_line(line, i+1)

    def write_to_config_file(self, path: os.PathLike):
        with open(path, "w", encoding=LOCATIONS.FILE_ENCODING) as conf_file:
            conf_file.write(self.config_string)

    def __iter__(self) -> Iterable[Attribute]:
        for section in self._section_list:
            for name, attribute in section.name_attribute_map.items():
                yield attribute


config = Config()


def read():
    if not LOCATIONS.CONFIG_FILE.is_file():
        write()
    config.read_from_config_file(LOCATIONS.CONFIG_FILE)


def write():
    config.write_to_config_file(LOCATIONS.CONFIG_FILE)
