import itertools
import logging
from pathlib import Path

from .error import VBANCMDError

try:
    import tomllib
except ModuleNotFoundError:
    import tomli as tomllib

from .kinds import request_kind_map as kindmap

logger = logging.getLogger(__name__)


class TOMLStrBuilder:
    """builds a config profile, as a string, for the toml parser"""

    def __init__(self, kind):
        self.kind = kind
        self.higher = itertools.chain(
            [f'strip-{i}' for i in range(kind.num_strip)],
            [f'bus-{i}' for i in range(kind.num_bus)],
        )

    def init_config(self, profile=None):
        self.virt_strip_params = (
            [
                'mute = false',
                'mono = false',
                'solo = false',
                'gain = 0.0',
            ]
            + [f'A{i} = false' for i in range(1, self.kind.phys_out + 1)]
            + [f'B{i} = false' for i in range(1, self.kind.virt_out + 1)]
        )
        self.phys_strip_params = self.virt_strip_params + [
            'comp.knob = 0.0',
            'gate.knob = 0.0',
            'denoiser.knob = 0.0',
            'eq.on = false',
        ]
        self.bus_float = ['gain = 0.0']
        self.bus_params = [
            'mono = false',
            'eq.on = false',
            'mute = false',
            'gain = 0.0',
        ]

        if profile == 'reset':
            self.reset_config()

    def reset_config(self):
        self.phys_strip_params = list(
            map(lambda x: x.replace('B1 = false', 'B1 = true'), self.phys_strip_params)
        )
        self.virt_strip_params = list(
            map(lambda x: x.replace('A1 = false', 'A1 = true'), self.virt_strip_params)
        )

    def build(self, profile='reset'):
        self.init_config(profile)
        toml_str = str()
        for eachclass in self.higher:
            toml_str += f'[{eachclass}]\n'
            toml_str = self.join(eachclass, toml_str)
        return toml_str

    def join(self, eachclass, toml_str):
        kls, index = eachclass.split('-')
        match kls:
            case 'strip':
                toml_str += ('\n').join(
                    self.phys_strip_params
                    if int(index) < self.kind.phys_in
                    else self.virt_strip_params
                )
            case 'bus':
                toml_str += ('\n').join(self.bus_params)
            case _:
                pass
        return toml_str + '\n'


class TOMLDataExtractor:
    def __init__(self, file):
        with open(file, 'rb') as f:
            self._data = tomllib.load(f)

    @property
    def data(self):
        return self._data

    @data.setter
    def data(self, value):
        self._data = value


def dataextraction_factory(file):
    """
    factory function for parser

    this opens the possibility for other parsers to be added
    """
    if file.suffix == '.toml':
        extractor = TOMLDataExtractor
    else:
        raise ValueError('Cannot extract data from {}'.format(file))
    return extractor(file)


class SingletonType(type):
    """ensure only a single instance of Loader object"""

    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonType, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class Loader(metaclass=SingletonType):
    """
    invokes the parser

    checks if config already in memory

    loads data into memory if not found
    """

    def __init__(self, kind):
        self._kind = kind
        self.logger = logger.getChild(self.__class__.__name__)
        self._configs = dict()
        self.defaults(kind)
        self.parser = None

    def defaults(self, kind):
        self.builder = TOMLStrBuilder(kind)
        toml_str = self.builder.build()
        self.register('reset', tomllib.loads(toml_str))

    def parse(self, identifier, data):
        if identifier in self._configs:
            self.logger.info(
                f'config file with name {identifier} already in memory, skipping..'
            )
            return
        try:
            self.parser = dataextraction_factory(data)
        except tomllib.TOMLDecodeError as e:
            ERR_MSG = (str(e), f'When attempting to load {identifier}.toml')
            self.logger.error(f'{type(e).__name__}: {" ".join(ERR_MSG)}')
            return
        return True

    def register(self, identifier, data=None):
        self._configs[identifier] = data if data else self.parser.data
        self.logger.info(f'config {self.name}/{identifier} loaded into memory')

    def deregister(self):
        self._configs.clear()
        self.defaults(self._kind)

    @property
    def configs(self):
        return self._configs

    @property
    def name(self):
        return self._kind.name


def loader(kind):
    """
    traverses defined paths for config files

    directs the loader

    returns configs loaded into memory
    """
    logger_loader = logger.getChild('loader')
    loader = Loader(kind)

    for path in (
        Path.cwd() / 'configs' / kind.name,
        Path.home() / '.config' / 'vban-cmd' / kind.name,
        Path.home() / 'Documents' / 'Voicemeeter' / 'configs' / kind.name,
    ):
        if path.is_dir():
            logger_loader.info(f'Checking [{path}] for TOML config files:')
            for file in path.glob('*.toml'):
                identifier = file.with_suffix('').stem
                if loader.parse(identifier, file):
                    loader.register(identifier)
    return loader.configs


def request_config(kind_id: str):
    """
    config entry point.

    Returns all configs loaded into memory for a kind
    """
    try:
        configs = loader(kindmap(kind_id))
    except KeyError:
        raise VBANCMDError(f'Unknown Voicemeeter kind {kind_id}')
    return configs
