# Copyright (c) 2004 DoCoMo Euro-Labs GmbH (Munich, Germany).
# Copyright (c) 2001-2004 LOGILAB S.A. (Paris, FRANCE).
#
# http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""narval interpreter's configuration handling

:version: $Revision:$   
:author: Logilab

:copyright:
  2001-2004 LOGILAB S.A. (Paris, FRANCE)
  
  2004 DoCoMo Euro-Labs GmbH (Munich, Germany)

:contact:
  http://www.logilab.fr/ -- mailto:contact@logilab.fr
  
  http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
"""

__revision__ = "$Id: config.py 20 2004-04-15 14:43:51Z syt $"
__docformat__ = "restructuredtext en"

import sys
import shutil
import os
from os.path import normpath, join, split, expanduser, isfile, exists, dirname
from ConfigParser import SafeConfigParser

from narval.__pkginfo__ import version

def normjoin(*path_elements):
    """join path elements and normalize the resulting path before returning it
    """
    return normpath(join(*path_elements))


class ConfigurationError(Exception):
    """exception class for configuration errors"""


# expected subdirectories in a narval home
HOME_SUBDIRS = ('data', #'doc', FIXME: removed doc due to a packaging pb
                #'extensions',
                #'elements', 'interfaces', 'protocol_handlers',
                #'modules',
                'recipes',
                #'dtd'
                )

def create_home():
    """create the narval home by copying the skeleton"""
    home = get_home()
    try:
        os.listdir(home)
    except OSError:
        os.mkdir(home)
    share = get_share()
    for directory in HOME_SUBDIRS:
        dest = normjoin(home, directory)
        try:
            os.listdir(dest)
        except OSError:
            if directory == 'data':
                shutil.copytree(normjoin(share, directory), dest)
            else: # recipe
                os.mkdir(dest)

def check_home():
    """validate the narval home directory"""
    home = get_home()
    if not exists(home):
        raise ConfigurationError('''Narval home %s doesn\'t exist.
Create it using the --create-home option of the narval interpreter''' % home)
    for directory in HOME_SUBDIRS:
        dest = normjoin(home, directory)
        try:
            os.listdir(dest)
        except OSError:
            raise ConfigurationError('''\
Narval home %s seems to be incomplete (missing %s subdirectory).
Fix it using the --create-home option of the narval interpreter''' % (
                home, directory))

## def narval_path():
##     """return the narval actions/recipes path (i.e. a list of directories
##     to search for actions,elements, interfaces, protocol handlers
##     (i.e. python modules) and recipes (xml files)
##     """
##     path = os.environ.get('NARVAL_PATH')
##     if not path:
##         path = []
##     else:
##         path = path.split(':')
##     if not config.get_home() in path:
##         path.append(config.get_home())
##     if not config.get_share() in path:
##         path.append(config.get_share())
##     # FIXME: devel mode !
##     return [directory for directory in path if exists(directory)]


class _Configuration:
    """Holds configuration information for the application"""
    def __init__(self):
        self._home = None
        self._home_from_command_line = False
        self._share = None
        self._share_from_command_line = False
        self._user_home = None
##         self._path = None

##     def get_path(self):
##         return self._path
    
    def get_home(self):
        return self._home

    def get_share(self):
        return self._share

    def get_user_home(self):
        return self._user_home

    def _set_home(self, home, from_command_line=False):
        self.__set_conf_attr('_home', home, '_home_from_command_line', from_command_line)
        
    def _set_share(self, share, from_command_line=False):
        self.__set_conf_attr('_share', share, '_share_from_command_line', from_command_line)

    def _set_user_home(self, user_home):
        self.__set_conf_attr('_user_home', user_home)

##     def _set_path(self, path):
##         if self._path:
##             for directory in self._path:
##                 try:
##                     sys.path.remove(directory)
##                 except:
##                     continue
##         reversed_path = path[:]
##         reversed_path.reverse()
##         for directory in reversed_path:
##             sys.path.insert(0, directory)
##         self._path = path
        
    def __set_conf_attr(self, attr, value,
                        protection_attr=None, from_command_line=None):
        if protection_attr is None or from_command_line or not getattr(self, protection_attr):
            if value == 'None':
                if getattr(self, attr) is not None:
                    return
                value = None
            if protection_attr is not None:
                setattr(self, protection_attr, from_command_line)
            setattr(self, attr, value)
        
class Configurator:
    """Creates a configuration object"""
    
    def __init__(self):
        self.__conf = _Configuration()

    def build_config(self):
        self._parse_environ()
        if sys.platform == "win32":
            self._parse_registry()
        else:
            self._parse_file()
        devel_share = join(dirname(__file__), 'share')
        if exists(devel_share):
            self.__conf._set_share(devel_share)
            
    def set_home_from_command_line(self, home):
        self.__conf._set_home(home, True)

    def set_share_from_command_line(self, share):
        self.__conf._set_share(share, True)

    def get_configuration(self):
        return self.__conf

    def _parse_file(self, filename=None):
        if filename is None:
            filename = self._guess_candidate_config_files()
        parser = SafeConfigParser()
        parser.read(filename)
        self.__conf._set_home(parser.get('Directories', 'HOME'))
        self.__conf._set_share(parser.get('Directories', 'SHARE'))

    def _parse_environ(self):
        self.__conf._set_share(os.getenv('NARVAL_SHARE'))
        user_home = os.getenv('HOME') or os.getenv('USERPROFILE')
        self.__conf._set_user_home(user_home)
        narval_home = os.getenv('NARVAL_HOME',
                                join(self.__conf.get_user_home(), '.narval'))
        self.__conf._set_home(narval_home)
##         self.__conf._set_share(narval_path())
        
    def _parse_registry(self):
        if sys.platform == 'win32':
            from  _winreg import OpenKey, QueryInfoKey, EnumValue, \
                 HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE
            d = {}
            key_name = r"Software\Logilab\Narval\%s\config" % version
            _register_values(HKEY_LOCAL_MACHINE, key_name, d)
            _register_values(HKEY_CURRENT_USER, key_name, d)
            # user home
            key_name = r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
            try:
                key = OpenKey(HKEY_CURRENT_USER, key_name)
                for i in range(QueryInfoKey(key)[1]):
                    (name, value, _) = EnumValue(key, i)
                    if name == 'AppData':
                        self.__conf.set_user_home(join(value.encode('mbcs'),
                                                       'narval_data'))
                        break
            except:
                log(LOG_INFO,
                    'problem accessing registry HKCU\\%s to get NARVAL_HOME. '
                    'Using defaults.',
                    key_name)

            if not d.has_key('home'):
                d['home'] = d['user_home']
            self.__conf._set_home(d['home'])
            self.__conf._set_share(d['share'])
        
    def _guess_candidate_config_files(self):
        candidates = ['/etc/narval/narval.conf']
        head, tail = split(__file__)
        devel_conf = normjoin(head, 'conf', 'narval.conf')
        # FIXME: windows ?
        while head and head != '/':
            if tail == 'lib':
                candidates.append(join(head, 'etc', 'narval.conf'))
                break
            head, tail = split(head)

        candidates.append(devel_conf)
        localconf = expanduser('~/.narval.conf')
        candidates.append(localconf)
        candidates = [cand for cand in candidates if isfile(cand)]
        if not candidates:
            self._build_default_conf(localconf)
            candidates = [localconf]
        return candidates

    def _build_default_conf(self, filename):
        f = open(filename, 'w')
        f.write('[Directories]\n')
        f.write('HOME: None\n')
        f.write('SHARE: None\n')
        f.close()

def _register_values(section, key_name, conf_dict):
    """register values in conf_dict in the windows registry

    :param section: root section of the registry

    :type key_name: str
    :param key_name: name of the key in the section

    :type conf_dict: dict
    :param conf_dict: dictionary handling values to register
    """
    if sys.platform == "win32":
        from  _winreg import OpenKey, QueryInfoKey, EnumValue
        try:
            key = OpenKey(section, key_name)
            for i in range(QueryInfoKey(key)[1]):
                (name, value, _) = EnumValue(key, i)
                conf_dict[name.lower()] = value.encode('mbcs')
            key.Close()
        except:
            log(LOG_INFO, 'problem accessing registry HKLM\\%s', key_name)


DEFAULT_CONFIGURATOR = Configurator()
DEFAULT_CONFIGURATOR.build_config()

def get_home():
    """return the narval home"""
    home = DEFAULT_CONFIGURATOR.get_configuration().get_home()
    if home not in sys.path:
        sys.path.insert(0, home)
    return home

def get_user_home():
    """return the user's home"""
    return DEFAULT_CONFIGURATOR.get_configuration().get_user_home()

def get_share():
    """return the narval shared directory"""
    return DEFAULT_CONFIGURATOR.get_configuration().get_share()

