# GNU Enterprise Common Library - Python language adapter plugin
#
# Copyright 2000-2007 Free Software Foundation
#
# This file is part of GNU Enterprise.
#
# GNU Enterprise is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2, or (at your option) any later version.
#
# GNU Enterprise 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with program; see the file COPYING. If not,
# write to the Free Software Foundation, Inc., 59 Temple Place
# - Suite 330, Boston, MA 02111-1307, USA.
#
# $Id: python.py 9222 2007-01-08 13:02:49Z johannes $
#
# We use * and ** magic on purpose
# pylint: disable-msg=W0142

"""
Language adapter plugin for Python.
"""

from gnue.common.apps import errors
from gnue.common.logic import language
from gnue.common.logic.adapters import Base

__all__ = ['LanguageAdapter', 'ExecutionContext', 'Function']


# =============================================================================
# Implementation of a language adapter for python
# =============================================================================

class LanguageAdapter(Base.LanguageAdapter):
    """
    Implementation of a language engine for python
    """

    # -------------------------------------------------------------------------
    # Create a new execution context
    # -------------------------------------------------------------------------

    def createNewContext(self):
        """
        Create a python execution context
        """
        return ExecutionContext("unknown_executioncontext", {}, {}, {})


# =============================================================================
# Python Execution Context
# =============================================================================

class ExecutionContext(Base.ExecutionContext):
    """
    This class implements an execution context for Python code.
    """

    # -------------------------------------------------------------------------
    # Constructor
    # -------------------------------------------------------------------------

    def __init__(self, name, local_namespace, global_namespace,
            builtin_namespace):

        Base.ExecutionContext.__init__(self, name, local_namespace,
                global_namespace, builtin_namespace)

        # TODO: Change this to self.__name with 0.8
        self.shortname = name
        self.__local_namespace = local_namespace
        self.__global_namespace = global_namespace
        self.__builtin_namespace = builtin_namespace


    # -------------------------------------------------------------------------
    # Create a function
    # -------------------------------------------------------------------------

    def _build_function_(self, name, parameters, code):

        # Strip trailing whitespace from code
        text = code.rstrip()

        # Refuse to run code with tab characters
        tab_pos = text.find ('\t')
        if tab_pos > -1:
            raise errors.ApplicationError, u_(
                    "Sourcecode contains tab character at position %d") \
                                % tab_pos

        # Get the indentation level of the first line
        indent = 0
        for line in text.splitlines():
            if len (line.strip()) and line.lstrip()[0] != "#":
                indent = len(line) - len(line.lstrip())
                break

        # The whole code is enclosed into a pseudo function definition. This
        # way, the code can contain "return" statements.

        # Start with the function header
        revised_code  = "# -*- coding: utf-8 -*-\n"
        revised_code += "def %s(%s):\n" % (name,
                ", ".join(['__namespace'] + parameters))

        # Unpack the namepsace dictionary within the function, so the local
        # namespace appears to be local *inside* the function
        revised_code += "    __add = None\n"
        revised_code += "    for __add in __namespace.keys():\n"
        revised_code += "        exec '%s = __namespace[\"%s\"]' % "
        revised_code += "(__add, __add) in globals(), locals()\n"
        revised_code += "    del __add, __namespace\n\n"

        # Add the actual code
        for line in text.splitlines():
            revised_code += "    %s\n" % line[indent:]

        # End the function with a "pass" statement in case the function body is
        # empty
        revised_code += "    pass\n\n"

        # Before calling the function, add the builtins
        revised_code += "import __builtin__\n"
        revised_code += "for (__key, __value) in __builtins.items():\n"
        revised_code += "    __builtin__.__dict__[__key] = __value\n"

        # And finally run the function and save the result
        revised_code += "__result = %s(__namespace, **__parameters)\n" % name

        try:
            compiled_code = compile(revised_code.encode('utf-8'),
                    '<%s>' % self.shortname.encode('utf-8'), 'exec')
        except:
            (group, name, message, detail) = errors.getException(1)
            if group == 'system':
                group = 'application'
            raise language.CompileError, (group, name, message, detail)

        # Make sure namespaces are clean
        # TODO: This can be moved to Base.ExecutionContext.__init__() after all
        # the depreciated functions are removed.
        self.__make_safe_namespace(self.__builtin_namespace)
        self.__make_safe_namespace(self.__local_namespace)
        self.__make_safe_namespace(self.__global_namespace)

        return Function(
                compiled_code = compiled_code,
                local_namespace = {
                    '__builtins': self.__builtin_namespace,
                    '__namespace': self.__local_namespace},
                global_namespace = self.__global_namespace)


    # -------------------------------------------------------------------------
    # Make sure the given Namespace has no invalid identifiers
    # -------------------------------------------------------------------------

    def __make_safe_namespace(self, namespace):
        """
        This function replaces all invalid keys in the dict. @namespace by
        appropriate identifiers.
        """
        for key in namespace.keys():
            safe_id = self._identifier_(key)
            if safe_id != key:
                namespace[safe_id] = namespace[key]
                del namespace[key]


    # -------------------------------------------------------------------------
    # Depreciated functions
    # -------------------------------------------------------------------------

    def bindObject(self, name, aObject, asGlobal = False):
        """
        Add an object to the local or global namespace.
        """
        if asGlobal:
            self.__global_namespace[name] = aObject
        else:
            self.__local_namespace[name] = aObject

    # -------------------------------------------------------------------------

    def bindFunction(self, name, aFunction, asGlobal = False):
        """
        Add a function to the local or global namespace.
        """
        if asGlobal:
            self.__global_namespace[name] = aFunction
        else:
            self.__local_namespace[name] = aFunction

    # -------------------------------------------------------------------------

    def bindBuiltin(self, name, anElement):
        """
        Bind the given element into the builtin-namespace.
        @param name: name of the element within the builtin-namespace
        @param anElement: element to be bound into the builtin-namespace
        """
        self.__builtin_namespace[name] = anElement


# =============================================================================
# Class encapsulating user provided Python code in a function
# =============================================================================

class Function:
    """
    Implementation of a virtual function using Python.
    """

    # -------------------------------------------------------------------------
    # Constructor
    # -------------------------------------------------------------------------

    def __init__(self, compiled_code, local_namespace, global_namespace):

        self.__compiled_code = compiled_code
        self.__local_namespace = local_namespace
        self.__global_namespace = global_namespace


    # -------------------------------------------------------------------------
    # Execute the function
    # -------------------------------------------------------------------------

    def __call__(__self, *args, **params):
        """
        This function creates a local namespace as a copy from the execution
        context's local namespace, adds all parameters to this namespace and
        executes the code.
        """

        # We call our own self parameter "__self" here so that the user
        # function can have a parameter "self".

        __self.__local_namespace['__parameters'] = params

        # FIXME: This allows the "self" parameter to be passed as a non-keyword
        # argument. DEPRECATED.
        if args:
            __self.__local_namespace['__parameters']['self'] = args[0]

        try:
            exec __self.__compiled_code \
                    in __self.__global_namespace, __self.__local_namespace

        except language.AbortRequest:
            # Pass through AbortRequests unchanged
            raise

        except:
            # All others raise a RuntimeError
            (group, name, message, detail) = errors.getException (2)
            if group == 'system':
                group = 'application'
            raise language.RuntimeError, (group, name, message, detail)

        return __self.__local_namespace.get('__result')
