import click
import json.decoder
import jsonpickle
import os.path
import proscli
import proscli.utils
# from typing import List


class ConfigNotFoundException(Exception):
    def __init__(self, message, *args, **kwargs):
        super(ConfigNotFoundException, self).__init__(args, kwargs)
        self.message = message


class Config(object):
    def __init__(self, file, error_on_decode=False, ctx=None):
        proscli.utils.debug('Opening {} ({})'.format(file, self.__class__.__name__), ctx=ctx)
        self.save_file = file   # type: str
        self.__ignored = ['save_file', '_Config__ignored']  # type: list(str)
        if file:
            if os.path.isfile(file):
                with open(file, 'r') as f:
                    try:
                        self.__dict__.update(jsonpickle.decode(f.read()).__dict__)
                    except (json.decoder.JSONDecodeError, AttributeError):
                        if error_on_decode:
                            raise
                        else:
                            pass
            elif os.path.isdir(file):
                raise ValueError('{} must be a file, not a directory'.format(file))
            else:
                try:
                    self.save()
                except Exception:
                    pass

    def __getstate__(self):
        state = self.__dict__.copy()
        if '_Config__ignored' in self.__dict__:
            for key in [k for k in self.__ignored if k in state]:
                del state[key]
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)

    def delete(self):
        if os.path.isfile(self.save_file):
            os.remove(self.save_file)

    def save(self, file=None):
        if file is None:
            file = self.save_file
        if isinstance(click.get_current_context().obj, proscli.utils.State) and click.get_current_context().obj.debug:
            proscli.utils.debug('Pretty Formatting {} File'.format(self.__class__.__name__))
            jsonpickle.set_encoder_options('json', sort_keys=True, indent=4)
        else:
            jsonpickle.set_encoder_options('json', sort_keys=True)
        if os.path.dirname(file):
            os.makedirs(os.path.dirname(file), exist_ok=True)
        with open(file, 'w') as f:
            f.write(jsonpickle.encode(self))

    @property
    def directory(self) -> str:
        return os.path.dirname(os.path.abspath(self.save_file))


class ProjectConfig(Config):
    def __init__(self, path: str='.', create: bool=False, raise_on_error: bool=True):
        file = ProjectConfig.find_project(path or '.')
        if file is None and create:
            file = os.path.join(path, 'project.pros')
        elif file is None and raise_on_error:
            raise ConfigNotFoundException('A project config was not found for {}'.format(path))

        self.kernel = None  # type: str
        self.libraries = {}  # type: List[str]
        self.output = 'bin/output.bin'  # type: str
        super(ProjectConfig, self).__init__(file, error_on_decode=raise_on_error)

    @staticmethod
    def find_project(path):
        path = os.path.abspath(path)
        if os.path.isfile(path):
            return path
        elif os.path.isdir(path):
            for n in range(10):
                if path is not None and os.path.isdir(path):
                    files = [f for f in os.listdir(path)
                             if os.path.isfile(os.path.join(path, f)) and f.lower() == 'project.pros']
                    if len(files) == 1:  # found a project.pros file!
                        return os.path.join(path, files[0])
                    path = os.path.dirname(path)
                else:
                    return None
        return None