# Copyright (c) 2004-2005 DoCoMo Euro-Labs GmbH (Munich, Germany).
# Copyright (c) 2001-2005 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
"""Library of functions to load /reload / save elements, actions, recipes and
memory, used by the interpreter and some clients


:version: $Revision:$ 
:author: Logilab  

:copyright:
  2001-2005 LOGILAB S.A. (Paris, FRANCE)
  
  2004-2005 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: bibal.py 20 2004-04-15 14:43:51Z syt $"
__docformat__ = "restructuredtext en"

import sys
import os
from os.path import normpath, join, dirname, exists

from logilab.common.modutils import load_module_from_name

from narval import config
from narval.public import AL_NS
from narval.narvalrc import NarvalRC

def _init_elements(elements, group, path, callback, name=None):
    """init freshly loaded action / recipe elements

    :type elements: list
    :param elements: list of elements to initialize

    :type group: str
    :param group: the group of the action / recipe
     
    :type load_date: str
    :param load_date: the date where the element has been loaded, as a string

    :type callback: function or method
    :param callback:
      callback to call to register each element, will be called with the
      element as argument

    :type name: str or None
    :param name:
      optional name of a particular element that should be searched in the
      given elements

    :rtype: narval.public.ALElement or None
    :return: the element matching <name> if <name> is specified and found
    """
    target = None
    for elmt in elements:
        elmt.group = group
        elmt.from_file = path
        elmt.load_date = str(os.stat(path)[8])
        callback(elmt)
        if name is not None and elmt.name == name:
            target = elmt
    return target
    


# Load elements, actions, recipes and memory ###################################

BASEDIR = dirname(__file__)

def _python_load(registry, package_name, filter_module_func):
    """load definitions from python modules in a package
    
    :type registry: narval.reader.Registry
    :param registry: the interpreter's registry
    
    :type package_name: str
    :param registry: the name of the package to load
    
    :type filter_module_func: callable
    :param registry: a function to filter modules to load
    """
    modpath = normpath(join(BASEDIR, package_name))
    log(LOG_NOTICE, 'loading %s' % package_name.replace('_', ' '))
    for fname in os.listdir(modpath):
        if not fname.endswith('.py') or fname == '__init__.py' or \
           not filter_module_func(fname[:-3]):
            continue
        try:
            mod = load_module_from_name('narval.%s.%s' % (package_name, fname[:-3]))
            try:
                mod.register(registry)
            except AttributeError:
                registry.autoregister(mod.__name__)
        except:
            log(LOG_ERR, '[%s BROKEN]' % fname[:-3])
            import traceback
            traceback.print_exc()
            log_traceback(LOG_DEBUG, sys.exc_info())
        else:
            log(LOG_NOTICE, '[%s]' % fname[:-3])
    
def load_interfaces(registry):
    """load elements definitions from narval home directory (i.e. in the
    "elements" subdirectory)
    
    :type registry: narval.reader.Registry
    :param registry: the interpreter's registry
    """
    _python_load(registry, 'interfaces', NarvalRC().load_interfaces_module)

def load_elements(registry):
    """load elements definitions from narval home directory (i.e. in the
    "elements" subdirectory)
    
    :type registry: narval.reader.Registry
    :param registry: the interpreter's registry
    """
    _python_load(registry, 'elements', NarvalRC().load_elements_module)

def load_protocol_handlers(engine):
    """load protocol handlers from narval home directory (i.e. in the
    'protocol_handlers' subdirectory)
    
    :type registry: `narval.engine.Narval`
    :param registry: the narval interpreter
    """
    _python_load(engine, 'protocol_handlers',
                 NarvalRC().load_protocol_handler_module)

def load_modules(registry, callback):
    """load all modules registered in narval home (i.e. in the "modules"
    subdirectory)

    :type registry: narval.reader.Registry
    :param registry: the interpreter's registry

    :type callback: function or method
    :param callback:
      callback to call to register each action in modules, will be called with
      the action element as argument
    """
    #modpath = normpath(join(BASEDIR, package_name))
    #modpath = normpath(join(config.get_home(), 'modules'))
    #m_file, m_filename, m_desc = find_module('modules')
    package = load_module_from_name('narval.actions')
    nrc = NarvalRC()
    log(LOG_NOTICE, 'loading modules')
    for fname in os.listdir(package.__path__[0]):
        if not fname.endswith('.py') or fname == '__init__.py' or \
           not nrc.load_actions_module(fname[:-3]):
            continue
        modname = fname[:-3]
        try:
            mod = load_module_from_name('narval.actions.%s' % modname)
            _init_elements(registry.from_string(mod.MOD_XML, 1), modname,
                           mod.__file__.replace('.pyc', '.py'), callback)
        except:
            log(LOG_ERR, '[%s BROKEN]' % modname)
            log_traceback(LOG_ERR, sys.exc_info())
        else:
            log(LOG_NOTICE, '[%s]' % modname)

def reload_module(registry, callback, group, name, from_file):
    """reload action <name> from module <group>. Actually, all others actions
    in the module will also be reloaded
    
    :type registry: `narval.reader.Registry`
    :param registry: the interpreter's registry

    :type callback: function or method
    :param callback:
      callback to call to register each action in modules, will be called with
      the action element as argument

    :type group: str
    :param group: the name of the module to reload

    :type name: str
    :param name: the name of the action to reload

    :rtype: `narval.action.ActionElement`
    :return: the action which has been asked to be reloaded explicitly
    """
    log(LOG_NOTICE, 'reloading module %s from %s', (group, from_file))
    mod = load_module_from_name('narval.actions.%s' % group)
    reload(mod)
    return _init_elements(registry.from_string(mod.MOD_XML, 1), group,
                          from_file, callback, name)

def recipes_path():
    """return a list of paths where recipes should be looked for"""
    devel_dir = join(dirname(__file__), 'share', 'recipes')
    if exists(devel_dir):
        result = (devel_dir,)
    else:
        result = (normpath(join(config.get_share(), 'recipes')),)
    home_dir = normpath(join(config.get_home(), 'recipes'))
    if home_dir not in result:
        result = (home_dir,) + result
    return result
    
def load_recipes(registry, callback):
    """load all recipes registered in narval home (i.e. in the "recipes"
    subdirectory)

    :type registry: `narval.reader.Registry`
    :param registry: the interpreter's registry

    :type callback: function or method
    :param callback:
      callback to call to register each recipe in cookbooks, will be called
      with the action element as argument
    """
    nrc = NarvalRC()
    # FIXME: handle overriden recipes !
    for recipepath in recipes_path():
        log(LOG_NOTICE, 'loading cookbooks from %s', recipepath)
        for fname in os.listdir(recipepath):
            if not fname.endswith('.xml') or not nrc.load_cookbook(fname[:-4]):
                continue
            try:
                path = join(recipepath, fname)
                _init_elements(registry.from_stream(open(path), 1), fname[:-4],
                               path, callback)
            except:
                log_traceback(LOG_ERR, sys.exc_info())
            else:
                log(LOG_NOTICE, '[%s]' % fname[:-4])

def reload_recipe(registry, callback, group, name, from_file):
    """reload recipe <name> from cookbook <group>. Actually, all others recipes
    in the cookbook will also be reloaded
    
    :type registry: `narval.reader.Registry`
    :param registry: the interpreter's registry

    :type callback: function or method
    :param callback:
      callback to call to register each recipe in cookbooks, will be called
      with the recipe element as argument

    :type group: str
    :param group: the name of the cookbook to reload

    :type name: str
    :param name: the name of the recipe to reload

    :rtype: `narval.recipe.RecipeElement`
    :return: the recipe which has been asked to be reloaded explicitly
    """
    log(LOG_NOTICE, 'reloading cookbook %s from %s', (group, from_file))
    return _init_elements(registry.from_stream(open(from_file), 1), group,
                          from_file, callback, name)

def save_recipes(memory, encoding='UTF-8'):
    """save recipes in memory

    :type memory: `narval.memory.Memory`
    :param memory: the narval's main memory

    :type encoding: str
    :param encoding: encoding to use in the cookbook files, default to UTF-8

    :rtype: list
    :return: the list of errors which have occured during save
    """
    recipepath = normpath(join(config.get_home(), 'recipes'))
    files = {}
    errors = []
    for element in memory.get_recipes():
        elements = files.setdefault(element.group, [])
        elements.append(element)
    for name, elements in files.items() :
        log(LOG_INFO, 'Saving cookbook %s', name)
        filename = join(recipepath, '%s.xml' % name)
        try:
            stream = open(filename, 'w')
        except IOError, ex:
            log(LOG_ERR, 'could not open file %s: %s', (filename, ex))
            errors.append(filename)
        else:
            write = stream.write
            write("<?xml version='1.0' encoding='%s'?>\n" % encoding)
            write("<al:cookbook xmlns:al='%s'>\n" % AL_NS)
            for elmt in elements:
                write(elmt.as_xml(encoding=encoding))
            write("</al:cookbook>")
            stream.close()
    return errors

def load_memory(registry, callback, filepath=None):
    """load the initial narval's main memory  (i.e. in the "data/memory.xml"
    file, unless another path is specified)

    :type registry: narval.reader.Registry
    :param registry: the interpreter's registry

    :type callback: function or method
    :param callback:
      callback to call to register each element in the memory file, will be
      called with the element as argument

    :type filepath: str or None
    :param filepath:
      optional path of the memory file, will load $NARVAL_HOME/data/memory.xml
      if not specified
    """
    log(LOG_NOTICE, 'loading memory')
    try:
        memorypath = filepath or normpath(join(config.get_home(), 'data',
                                               'memory.xml'))
        for elmt in registry.from_stream(open(memorypath), 1):
            callback(elmt)
    except:
        log_traceback(LOG_ERR, sys.exc_info())
