from six.moves import configparser
import copy
import os
import collections
import tensorflow as tf
from collections import OrderedDict


class SettingsContextManager(object):
    def __init__(self, manager, tmp_settings):
        self._manager = manager
        self._tmp_settings = tmp_settings

    def __enter__(self):
        self._manager.push(self._tmp_settings)

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._manager.pop()


class SettingsManager(object):
    def __init__(self, set):
        self._cur_settings = set
        self._settings_stack = []

    def __getattr__(self, name):
        try:
            return self._cur_settings[name]
        except KeyError:
            raise AttributeError("Unknown setting.")

    def push(self, settings):
        self._settings_stack.append(self._cur_settings)
        self._cur_settings = settings

    def pop(self):
        rem = self._cur_settings
        self._cur_settings = self._settings_stack.pop()
        return rem

    def temp_settings(self, tmp_settings):
        return SettingsContextManager(self, tmp_settings)

    def get_settings(self):
        c = copy.deepcopy(self._cur_settings)
        return c


class MutableNamedTuple(OrderedDict):
    """
    A class that doubles as a mutable named tuple, to allow settings to be re-set during
    """

    def __init__(self, *args, **kwargs):
        super(MutableNamedTuple, self).__init__(*args, **kwargs)
        self._settings_stack = []
        self._initialised = True

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        if not hasattr(self, "_initialised"):
            super(MutableNamedTuple, self).__setattr__(name, value)
        else:
            super(MutableNamedTuple, self).__setitem__(name, value)


# a very simple parser
def parse(string):
    if type(string) is not str:
        raise ValueError
    if string in ['true', 'True']:
        return True
    elif string in ['false', 'False']:
        return False
    elif string in ['float64', 'float32', 'float16', 'int64', 'int32', 'int16']:
        return getattr(tf, string)
    elif any([string.count(s) for s in '.eE']):
        try:
            return float(string)
        except:
            return string
    else:
        try:
            return int(string)
        except:
            return string


# make the dictionary into a nested series of named tuples. This is what allows
# accessing by attribute: settings.numerics.jitter
def namedtuplify(mapping):  # thank you https://gist.github.com/hangtwenty/5960435
    if isinstance(mapping, collections.Mapping):
        for key, value in list(mapping.items()):
            mapping[key] = namedtuplify(value)
        try:
            mapping.pop('__name__')
        except:
            pass
        # return collections.namedtuple('settingsa', dict(**mapping))(**mapping)
        return MutableNamedTuple(mapping)
    return parse(mapping)


def read_config_file(path=None):
    c = configparser.ConfigParser()

    if path is None:  # pragma: no cover
        # first look in the current directory,
        # then in the user's home directory,
        # then in the same directory as this file.
        locations = map(os.path.abspath, [os.curdir,
                                          os.path.expanduser('~'),
                                          os.path.dirname(os.path.realpath(__file__))])
        for loc in locations:
            # try both with and without preceeding 'dot' for hidden files (prefer non-hidden)
            if c.read(os.path.join(loc, 'gpflowrc')):
                break
            if c.read(os.path.join(loc, '.gpflowrc')):
                break
    else:
        assert (c.read(path))
    return c


c = read_config_file()
loaded_settings = namedtuplify(c._sections)
settings = SettingsManager(loaded_settings)
