# Copyright Louis Paternault 2015
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Provide hook mechanism to builder"""

class Hook:
    """Hook, containing functions to be called before and after a certain event.
    """
    # pylint: disable=too-few-public-methods

    data = None

    def __init__(self, plugin, shared):
        self.shared = shared
        self.plugin = plugin
        self.local = self.plugin.local

class ContextHook(Hook):
    """Hook executed before and after a context"""

    def pre(self, *args, **kwargs):
        # pylint: disable=no-self-use, unused-argument
        """Hook called before the context.

        Takes as arguments the arguments ``args`` and ``kwargs`` passed to the
        the :meth:`evariste.plugins.AllPlugins.contexthook` method.

        :return: Nothing.
        """
        return

    def post(self):
        # pylint: disable=no-self-use
        """Hook called after the context."""
        return

class MethodHook(Hook):
    """Hook executed before and after a method call.

    .. note::

        For both :meth:`pre` and :meth:`post` methods, do note that the
        calling order of all the functions hooked to a particular event is
        undefined. Do not assume anything about this.
    """

    def pre(self, *args, **kwargs):
        """Hook called before the method.

        Get as parameters the parameters passed to the method (including
        `self`). It can return:
        - a tuple ``args``, ``kwargs``, which will be passed to the method
          (this can be used to change the value of the arguments);
        - ``None``, which is a shortcut for "I did not change the arguments".
        """
        # pylint: disable=no-self-use
        return args, kwargs

    def post(self, value):
        """Hook called after the method."""
        # pylint: disable=no-self-use
        return value

def methodhooks(name=None, *, pre=True, post=True, getter=None):
    """Decorator to add hooks to a class method.

    :param str name: Name of the hook to register.
    :param bool pre: Enable pre-hook.
    :param bool post: Enable post-hook.
    :param function getter: Function that, given the instance object as argument,
        returns a :class:`plugins.AllPlugins` object. If ``None``, the default
        ``self.shared.builder.plugins`` is used (``self`` is supposed to have
        this attribute).
    """

    def decorator(function):
        """Actual decorator."""
        if name == None:
            hook = function.__name__
        else:
            hook = name

        def wrap(*args, **kwargs):
            """Wrapped function."""
            self = args[0]
            if getter is None:
                plugins = self.shared.builder.plugins
            else:
                plugins = getter(*args, **kwargs)
            return plugins.apply_hook(hook, function, pre=pre, post=post, args=args, kwargs=kwargs)

        return wrap

    return decorator
