import socket
from configparser import ConfigParser
from pathlib import Path

from PyQt5.QtCore import QFileSystemWatcher, QObject, pyqtSignal

from .dict_merger import merge_dicts


class ConfigReader(QObject):
    updated = pyqtSignal()

    def __init__(self, system_id=None, auto_select=None, *args, **kwargs):
        super().__init__()
        self.system_id_param = system_id
        self.system_id = system_id if system_id is not None else socket.gethostname()
        self.auto_select = auto_select
        self._args, self._kwargs = args, kwargs
        self.configs_dir = Path(".") / "config" / "machine_settings"
        self._watcher = QFileSystemWatcher()
        self._watcher.fileChanged.connect(self._update)
        self.extra_paths = []
        self._update()

    def _update(self, *args, signal=True):
        hostnames = self.read_config_file(self.configs_dir / "hostnames.ini")
        config_paths = [self.configs_dir / "defaults.ini"]
        specific_config = hostnames.get("hostnames", {}).get(self.system_id.lower(), None)
        if specific_config is not None:
            self.machine_id = specific_config
            config_paths.append(self.configs_dir / f"{specific_config}.ini")
        else:
            self.machine_id = self.system_id
            if self.system_id_param is not None:
                config_paths.append(self.configs_dir / f"{self.system_id_param}.ini")
        config_paths += self.extra_paths
        # remove duplicate paths keeping their order (keeps the last)
        config_paths = list(reversed(dict.fromkeys(reversed(config_paths))))
        values = {}
        for config_path in config_paths:
            config = self.read_config_file(config_path)
            values = merge_dicts(values, config)
        self._values = values
        if signal:
            self.updated.emit()
        # replace watched paths
        self._watcher.removePaths(self._watcher.directories() + self._watcher.files())
        self._watcher.addPaths(map(str, config_paths))

    def read_config_file(self, config_path):
        config_path = str(config_path)
        config = ConfigParser(*self._args, **self._kwargs, allow_no_value=True,comment_prefixes="#",inline_comment_prefixes="#")
        read = config.read(config_path)
        if len(read) != 1 or config_path != read[0]:
            raise AssertionError(f"Config file {config_path} could not be read.")
        return dict(config._sections)

    def add_config_paths(self, paths):
        self.extra_paths += paths
        # remove duplicate paths keeping their order (keeps the last)
        paths = list(reversed(dict.fromkeys(reversed(paths))))
        self._update()

    def remove_config_paths(self, paths):
        # remove paths keeping order
        self.extra_paths = list(dict.fromkeys(self.extra_paths).keys() - dict.fromkeys(paths).keys())
        self._update()

    # PROXY SOME _VALUES (DICT) METHODS TROUGH

    def __contains__(self, *args, **kwargs):
        return self._values.__contains__(*args, **kwargs)

    def __eq__(self, *args, **kwargs):
        return self._values.__eq__(*args, **kwargs)

    def __format__(self, *args, **kwargs):
        return self._values.__format__(*args, **kwargs)

    def __getitem__(self, *args, **kwargs):
        return self._values.__getitem__(*args, **kwargs)

    def __iter__(self, *args, **kwargs):
        return self._values.__iter__(*args, **kwargs)

    def __ne__(self, *args, **kwargs):
        return self._values.__ne__(*args, **kwargs)

    def __reduce__(self, *args, **kwargs):
        return self._values.__reduce__(*args, **kwargs)

    def __reduce_ex__(self, *args, **kwargs):
        return self._values.__reduce_ex__(*args, **kwargs)

    def __repr__(self, *args, **kwargs):
        return self._values.__repr__(*args, **kwargs)

    def __reversed__(self, *args, **kwargs):
        return self._values.__reversed__(*args, **kwargs)

    def __setitem__(self, *args, **kwargs):
        return self._values.__setitem__(*args, **kwargs)

    def __str__(self, *args, **kwargs):
        return self._values.__str__(*args, **kwargs)

    def get(self, *args, **kwargs):
        return self._values.get(*args, **kwargs)

    def items(self, *args, **kwargs):
        return self._values.items(*args, **kwargs)

    def keys(self, *args, **kwargs):
        return self._values.keys(*args, **kwargs)

    def values(self, *args, **kwargs):
        return self._values.values(*args, **kwargs)
