# -*- coding: utf-8 -*-
"""
Created on Fri Aug  8 15:31:34 2014

@author: cokelaer
"""
import os
from easydev import DynamicConfigParser, underline
import copy
import shutil

import appdirs

__all__ = ["defaultParams", "BioServicesConfig"]


#TODO Move some contents to easydev.config_tools

# first item if the value
# second item if a type or TUPLE of types possible
# third item is documentation
defaultParams = {
    'user.email': ["unknown", (str), "email addresss that may be used in some utilities (e.g. EUtils)"],
    'general.timeout': [30, (int,float), ""],
    'general.max_retries': [3, int, ''],
    'general.async_concurrent': [50, int, ''],
    'general.async_threshold': [10, int, 'when to switch to asynchronous requests'],
    'cache.tag_suffix': ["_bioservices_database",str, 'suffix to append for cache databases'],
    'cache.on': [False, bool, 'CACHING on/off'],
    'cache.fast': [True, bool, "FAST_SAVE option"],
    'chemspider.token': [None, (str, type(None)), 'token see http://www.chemspider.com'],
}


class ConfigReadOnly(object):
    """A generic Config file handler

    Uses appdirs from ypi to handle the XDG protocol

    Read the configuration in the XDG directory. If not found, the
    config and cache directories are created. Then, reads the configuration
    file. If not found, nothing happens. A dictionary should be provided
    to initialise the default parameters. This dictionary is updated
    wit the content of the user config file if any. Reset the parameters
    to the default values is possible at any time. Re-read the user
    config file is possible at any time (meth:`read_`)

    """
    def __init__(self, name=None, default_params={}):
        """name is going to be the generic name of the config folder

        e.g., /home/user/.config/<name>/<name>.cfg

        """
        if name is None:
            raise Exception("Name parameter must be provided")
        else:
            # use input parameters
            self.name = name
            self._default_params = copy.deepcopy(default_params)
            self.params = copy.deepcopy(default_params)

            # useful tool to handle XDG config file, path and parameters
            self.appdirs = appdirs.AppDirs(self.name)

            # useful tool to handle the config ini file
            self.config_parser = DynamicConfigParser()

            # Now, create the missing directories if needed
            self.init() # and read the user config file updating params if needed

    def read_user_config_file_and_update_params(self):
        """Read the configuration file and update parameters

        Read the configuration file (file with extension cfg and name of your
        app). Section and option found will be used to update the :attr:`params`.

        If a set of section/option is not correct (not in the :attr:`params`) then
        it is ignored.

        The :attr:`params` is a dictionary with keys being labelled as <section>.<option>
        For instance, the key "cache.on" should be written in the configuration file as::

            [cache]
            on = True

        where True is the expected value.


        """
        try:
            self.config_parser.read(self.user_config_file_path)
        except IOError:

            msg = "Welcome to %s" % self.name.capitalize()
            print(underline(msg))
            print("It looks like you do not have a configuration file.")
            print("We are creating one with default values in %s ." % self.user_config_file_path)
            print("Done")
            self.create_default_config_file()

        # now, update the params attribute if needed
        for section in self.config_parser.sections():
            dic = self.config_parser.section2dict(section)
            for key in dic.keys():
                value = dic[key]
                newkey = section + '.' + key
                if newkey in self.params.keys():
                    # the type should be self.params[newkey][1]
                    cast = self.params[newkey][1]
                    # somehow
                    if isinstance(value, cast) is True:
                        self.params[newkey][0] = value
                    else:
                        print("Warning:: found an incorrect type while parsing {} file. In section '{}', the option '{}' should be a {}. Found value {}. Trying a cast...".format(self.user_config_file_path, section, key, cast, value))
                        self.params[newkey][0] = cast(value)
                else:
                    print("Warning:: found invalid option or section in %s (ignored):" % self.user_config_file_path)
                    print("   %s %s" % (section, option))

    def _get_home(self):
        # This function should be robust
        # First, let us try with expanduser
        try:
            homedir = os.path.expanduser("~")
        except ImportError:
            # This may happen.
            pass
        else:
            if os.path.isdir(homedir):
                return homedir
        # Then, with getenv
        for this in ('HOME', 'USERPROFILE', 'TMP'):
            # getenv is same as os.environ.get
            homedir = os.environ.get(this)
            if homedir is not None and os.path.isdir(homedir):
                return homedir
        return None
    home = property(_get_home)

    def _mkdirs(self, newdir, mode=0o777):
        """from matplotlib mkdirs

        make directory *newdir* recursively, and set *mode*.  Equivalent to ::

        > mkdir -p NEWDIR
        > chmod MODE NEWDIR
        """
        try:
            if not os.path.exists(newdir):
                parts = os.path.split(newdir)
                for i in range(1, len(parts) + 1):
                    thispart = os.path.join(*parts[:i])
                    if not os.path.exists(thispart):
                        os.makedirs(thispart, mode)

        except OSError as err:
            # Reraise the error unless it's about an already existing directory
            if err.errno != errno.EEXIST or not os.path.isdir(newdir):
                raise

    def _get_and_create(self, sdir):
        if not os.path.exists(sdir):
            print("Creating directory %s " % sdir)
            try:
                self._mkdirs(sdir)
            except Exception:
                print("Could not create the path %s " % sdir)
                return None
        return sdir

    def _get_config_dir(self):
        sdir = self.appdirs.user_config_dir
        return self._get_and_create(sdir)
    user_config_dir = property(_get_config_dir,
            doc="return directory of this configuration file")

    def _get_cache_dir(self):
        sdir = self.appdirs.user_cache_dir
        return self._get_and_create(sdir)
    user_cache_dir = property(_get_cache_dir,
            doc="return directory of the cache")

    def _get_config_file_path(self):
        return self.user_config_dir + os.sep +self.config_file
    user_config_file_path = property(_get_config_file_path,
            doc="return configuration filename (with fullpath)")

    def _get_config_file(self):
        return self.name + ".cfg"
    config_file = property(_get_config_file,
            doc="config filename (without path)")

    def init(self):
        """Reads the user_config_file and update params.
        Creates the directories for config and cache if they do not exsits

        """
        # Let us create the directories by simply getting these 2 attributes:
        try:
            _ = self.user_config_dir
        except:
            print("Could not retrieve or create the config file and/or directory in %s" % self.name)
        try:
            _ = self.user_cache_dir
        except:
            print("Could not retrieve or create the cache file and/or directory in %s" % self.name)
        self.read_user_config_file_and_update_params()

    def create_default_config_file(self, force=False):

        # if the file already exists, we should save the file into
        # a backup file
        if os.path.exists(self.user_config_file_path):
            # we need to copy the file into a backup file
            filename = self.user_config_file_path + '.bk'
            if os.path.exists(filename) and force is False:
                print("""Trying to save the current config file {} into a backup file {}\n but it exists already. Please remove the backup file first or set the 'force' parameter to True""".format(self.user_config_file_path, filename))
                return
            else:
                shutil.copy(self.user_config_file_path, filename)

        # Now, we can rewrite the configuration file
        sections = sorted(set([x.split(".")[0] for x in self._default_params.keys()]))
        if 'general' in sections:
            sections = ["general"] + [x for x in sections if x!="general"]

        fh = open(self.user_config_file_path, "w") # open and delete content
        for section in sections:
            fh.write("[" + section +"]\n")
            options = [x.split(".")[1] for x in self._default_params.keys() if x.startswith(section+".")]
            for option in options:
                key = section + '.' + option
                value = self._default_params[key]
                try:
                    fh.write("# {}\n{} = {}\n".format(value[2], 
                        option, value[0]))
                except:
                    print('Could not write this value/option. skipped')
                    print(value, option)

            fh.write("\n")
        fh.close()

    def reload_default_params(self):
        self.params = copy.deepcopy(self._default_params)


class BioServicesConfig(ConfigReadOnly):
    def __init__(self):
        super(BioServicesConfig, self).__init__(name="bioservices",
                default_params=defaultParams)

    # some aliases
    def _get_caching(self):
        return self.params['cache.on'][0]
    def _set_caching(self, value):
        self.params['cache.on'][0] = value
    CACHING = property(_get_caching)

    def _get_fast_save(self):
        return self.params['cache.fast'][0]
    FAST_SAVE = property(_get_fast_save)

    def _get_async_concurrent(self):
        return self.params['general.async_concurrent'][0]
    CONCURRENT = property(_get_async_concurrent)

    def _get_async_threshold(self):
        return self.params['general.async_threshold'][0]
    ASYNC_THRESHOLD = property(_get_async_threshold)

    def _get_timeout(self):
        return self.params['general.timeout'][0]
    def _set_timeout(self, timeout):
        self.params['general.timeout'][0] = timeout
    TIMEOUT = property(_get_timeout, _set_timeout)

    def _get_max_retries(self):
        return self.params['general.max_retries'][0]
    def _set_max_retries(self, max_retries):
        self.params['general.max_retries'][0] = max_retries
    MAX_RETRIES = property(_get_max_retries, _set_max_retries)