# Sketch - A Python-based interactive drawing program
# Copyright (C) 1998, 1999, 2000, 2001, 2002 by Bernhard Herzog
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 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
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library 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

import sys, os

import re, imp
from string import join, split

from skexceptions import SketchError
from warn import warn_tb, warn, USER, INTERNAL, pdebug
import config

from Sketch import _, dgettext, gettext, bindtextdomain, message_dir, \
     Subscribe, config, const


# All plugins are loaded as modules in the _plugin_package_name package
# to keep the namespaces clean
_plugin_package_name = "Sketch.Plugins"


def create_packages(package):
    """
    Return the package's module and create it and it's parents if necessary
    """
    names = split(package, '.')
    for i in range(1, len(names) + 1):
        name = join(names[:i], '.')
        if not sys.modules.has_key(name):
            module = imp.new_module(name)
            module.__path__ = []
            sys.modules[name] = module
    return sys.modules[name]

class ConfigInfo:

    module = None
    package = _plugin_package_name

    def __init__(self, module_name, dir, version = '1.0.0', unload = None,
                 load_immediately = 0, standard_messages = 0):
        self.module_name = module_name
        self.dir = dir
        self.unload = unload
        self.load_immediately = load_immediately
        self.version = version
        self.plugin_list.append(self)
        self.standard_messages = standard_messages
        if not self.standard_messages:
            bindtextdomain(self.module_name, message_dir)

        if self.load_immediately:
            self.load_module()

    def load_module(self):
        if self.module is not None:
            return self.module
        try:
            file, filename, desc = imp.find_module(self.module_name,
                                                   [self.dir])
        except:
            warn_tb(INTERNAL, 'Cannot find plugin module %s', self.module_name)
            return None
        try:
            try:
                create_packages(self.package)
                module_name = self.package + '.' + self.module_name
                self.module = imp.load_module(module_name, file, filename,
                                              desc)
            except:
                warn_tb(USER, _("Cannot load plugin module %s"),
                        self.module_name)
                raise
        finally:
            if file is not None:
                file.close()
        self.module._ = self.nls_function()
        return self.module

    def UnloadPlugin(self):
        if self.unload:
            module_name = self.package + '.' + self.module_name
            try:
                module = sys.modules[module_name]
            except KeyError:
                # might happen if the module wasn't even loaded
                return
            self.module = None
            if type(self.unload) == type(""):
                getattr(module, self.unload)(module)
            else:
                name = module.__name__
                module.__dict__.clear()
                del sys.modules[name]

    def nls_function(self):
        if self.standard_messages:
            _ = gettext
        else:
            domain = self.module_name
            def _(text, domain = domain):
                #print domain, text
                result = dgettext(domain, text)
                #print '->', result
                return result
        return _

    def gettext(self, text):
        if self.standard_messages:
            return gettext(text)
        else:
            return dgettext(self.module_name, text)


NativeFormat = 'SK-1'

import_plugins = []

class ImportInfo(ConfigInfo):

    plugin_list = import_plugins

    def __init__(self, module_name, dir, rx_magic, class_name, format_name,
                 tk_file_type = (), version = '1.0.0', unload = None,
                 load_immediately = 0, standard_messages = 0):
        ConfigInfo.__init__(self, module_name, dir, version = version,
                            unload = unload,
                            load_immediately = load_immediately,
                            standard_messages = standard_messages)
        self.rx_magic = re.compile(rx_magic)
        self.class_name = class_name
        self.format_name = format_name
        self.tk_file_type = tk_file_type
        self.translate()

    def translate(self):
        name, ext = self.tk_file_type
        name = self.gettext(name)
        self.tk_file_type = name, ext

    def __call__(self, *args, **kw):
        try:
            module = self.load_module()
            if module is not None:
                return apply(getattr(module, self.class_name), args, kw)
        except:
            warn_tb(INTERNAL, 'When importing plugin %s', self.module_name)
            raise SketchError('Cannot load filter %(name)s.%(message)s'
                              % {'name':self.module_name,
                                 'message':self.class_name})

    def UnloadPlugin(self):
        if config.preferences.unload_import_filters:
            ConfigInfo.UnloadPlugin(self)

export_plugins = []
export_formats = {}

class ExportInfo(ConfigInfo):

    plugin_list = export_plugins

    def __init__(self, module_name, dir, format_name, tk_file_type = (),
                 extensions = (), version = '1.0.0', unload = None,
                 load_immediately = 0, standard_messages = 0):
        ConfigInfo.__init__(self, module_name, dir, version = version,
                            unload = unload,
                            load_immediately = load_immediately,
                            standard_messages = standard_messages)
        self.format_name = format_name
        self.tk_file_type = tk_file_type
        if type(extensions) != type(()):
            extensions = (extensions,)
        self.extensions = extensions
        export_formats[format_name] = self

    def translate(self):
        name, ext = self.tk_file_type
        name = self.gettext(name)
        self.tk_file_type = name, ext

    def __call__(self, document, filename, file = None, options = None):
        if options is None:
            options = {}
        try:
            module = self.load_module()
        except:
            warn_tb(INTERNAL, 'When importing plugin %s', self.module_name)
            raise SketchError(_("Cannot load filter %(name)s")
                              % {'name':self.module_name})
        if file is None:
            file = open(filename, 'w')
            close = 1
        else:
            close = 0
        if module is not None:
            module.save(document, file, filename, options)
        if close:
            file.close()
        if self.format_name == NativeFormat:
            document.ClearEdited()
        self.UnloadPlugin()

    def UnloadPlugin(self):
        if config.preferences.unload_import_filters:
            ConfigInfo.UnloadPlugin(self)

def find_export_plugin(name):
    return export_formats.get(name)

def guess_export_plugin(extension):
    for plugin in export_plugins:
        if extension in plugin.extensions:
            return plugin.format_name
    return ''

object_plugins = {}
compound_plugins = []
class PluginCompoundInfo(ConfigInfo):

    plugin_list = compound_plugins

    def __init__(self, module_name, dir, class_name, menu_text, factory = '',
                 version = '1.0.0', parameters = (), uses_selection = 0,
                 custom_dialog = '', load_immediately = 0,
                 standard_messages = 0):
        ConfigInfo.__init__(self, module_name, dir, version = version,
                            load_immediately = load_immediately,
                            standard_messages = standard_messages)
        self.class_name = class_name
        self.factory = factory
        self.menu_text = menu_text
        self.parameters = parameters
        self.uses_selection = uses_selection
        self.custom_dialog = custom_dialog
        object_plugins[class_name] = self
        self.translate()

    def translate(self):
        gettext = self.gettext
        self.menu_text = gettext(self.menu_text)
        parameters = []
        for parameter in self.parameters:
            parameters.append(parameter[:-1] + (gettext(parameter[-1]),))
        self.parameters = parameters

    def load_module_attr(self, attr):
        try:
            module = self.load_module()
            if module is not None:
                return getattr(module, attr)
        except:
            warn_tb(INTERNAL, 'When importing plugin %s', self.module_name)
            raise SketchError(_("Cannot load plugin %(name)s.%(attr)s")
                              % {'name':self.module_name, 'attr':attr})

    def Constructor(self):
        return self.load_module_attr(self.class_name)

    def CallFactory(self, *args, **kw):
        if self.factory:
            attr = self.factory
        else:
            attr = self.class_name
        return apply(self.load_module_attr(attr), args, kw)

    __call__ = CallFactory

    def HasParameters(self):
        return len(self.parameters)

    def UsesSelection(self):
        return self.uses_selection

    def HasCustomDialog(self):
        return self.custom_dialog

    def CreateCustomDialog(self, root, mainwindow, document):
        dialog = self.load_module_attr(self.custom_dialog)
        return dialog(root, mainwindow, document)

def find_object_plugin(name):
    return object_plugins.get(name)



config_types = {'Import': ImportInfo,
                'Export': ExportInfo,
                'PluginCompound': PluginCompoundInfo
                }


def extract_cfg(file):
    rx_start = re.compile('^###Sketch[ \t]+Config')
    rx_end = re.compile('^###End')
    file = open(file, 'r')
    cfg = []
    for line in file.readlines():
        if rx_end.match(line):
            break
        elif cfg or rx_start.match(line):
            if line[0] == '#':
                line = line[1:]
            if line[-2] == '\\':
                line = line[:-2]
            cfg.append(line)
    return join(cfg, '')



def _search_dir(dir, recurse, package = 'Sketch.Plugins'):
    try:
        files = os.listdir(dir)
    except os.error, value:
        warn(USER, _("Cannot list directory %(filename)s\n%(message)s"),
             filename = dir, message = value[1])
        return
    for file in files:
        filename = os.path.join(dir, file)
        if os.path.isdir(filename):
            if file == "Lib":
                # A Lib directory on the plugin path. It's assumed to
                # hold library files for some plugin. Append it to the
                # current package's .Lib package's __path__ so that it's
                # modules can be imported by prefixing their names with
                # Lib.
                lib_pkg = create_packages(package + '.Lib')
                lib_pkg.__path__.append(filename)
            elif recurse:
                # an ordinary directory and we should recurse into it to
                # find more modules, so do that.
                _search_dir(filename, recurse - 1, package + '.' + file)
        elif filename[-3:] == '.py':
            try:
                module_name = os.path.splitext(os.path.basename(filename))[0]
                vars = {'_':_}	# hack
                cfg = extract_cfg(filename)
                exec cfg in config_types, vars
                infoclass = vars.get('type')
                if infoclass is None:
                    warn(USER, _("No plugin-type information in %(filename)s"),
                         filename = filename)
                else:
                    del vars['type']
                    del vars['_']
                    info = apply(infoclass, (module_name, dir), vars)
                    info.package = package
            except:
                warn_tb(INTERNAL, 'In config file %s', filename)
                warn(USER, _("can't read configuration information from "
                             "%(filename)s"),
                     filename =	 filename)

def load_plugin_configuration(): #path):
    if __debug__:
        import time
        start = time.clock()
    path = config.plugin_path
    for dir in path:
        # XXX unix specific
        if len(dir) >= 2 and dir[-1] == '/':
            if dir[-2] == '/':
                recurse = -1
            else:
                recurse = 1
        else:
            recurse = 0
        _search_dir(dir, recurse)
    if __debug__:
        pdebug('timing', 'time to scan cfg files: %g', time.clock()-start)
    # rearrange import plugins to ensure that native format is first
    for loader in import_plugins:
        if loader.format_name == NativeFormat:
            import_plugins.remove(loader)
            import_plugins.insert(0, loader)


Subscribe(const.INITIALIZE, load_plugin_configuration)
