# This file is part of versuchung.
# 
# versuchung 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 3 of the License, or (at your option) any later
# version.
# 
# versuchung 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
# versuchung.  If not, see <http://www.gnu.org/licenses/>.

import logging
import sys
from functools import wraps

class JavascriptStyleDictAccess(dict):
    def __init__(self, d):
        self.update(d)
    def __getattribute__(self, name):
        try:
            return dict.__getattribute__(self, name)
        except AttributeError:
            pass
        if name in self:
            return self[name]
        name = name.replace("_", "-")
        if name in self:
            return self[name]
        raise AttributeError



def setup_logging(log_level):
    """ setup the logging module with the given log_level """

    l = logging.WARNING # default
    if log_level == 1:
        l = logging.INFO
    elif log_level >= 2:
        l = logging.DEBUG

    logging.basicConfig(level=l)

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(
                cls, *args, **kwargs)
        return cls._instance


class AdviceManager(Singleton):
    def __init__(self):
        if not "before" in dir(self):
            self.before = dict()
            self.around = dict()
            self.after = dict()

    def around_wrapper(self, func, last = None):
        def wrapped(args, kwargs):
            if last:
                return func(last, args, kwargs)
            else:
                return func(*args, **kwargs)
        return wrapped

    @staticmethod
    def advicable(func):
        """Decorator to mark a function as advicable"""
        if not "__call__" in dir(func):
            raise ValueError("No function adviced")
        full_name = "%s.%s" % (func.__module__, func.__name__)

        self = AdviceManager()

        if full_name in self.before:
            raise RuntimeError("Function already marked as advicable")
        self.before[full_name] = []
        self.around[full_name] = []
        self.after[full_name] = []

        def wrapped(*args, **kwargs):
            am = AdviceManager()
            for f in am.before[full_name]:
                ret = f(args, kwargs)
                if ret:
                    (args, kwargs) = ret

            if len(am.around[full_name]) > 0:
                func_ = am.around_wrapper(func, None)
                for f in am.around[full_name]:
                    func_ = am.around_wrapper(f, func_)

                ret = func_(args, kwargs)
            else:
                ret = func(*args, **kwargs)

            for f in am.after[full_name]:
                ret = f(ret)

            return ret
        wrapped.__doc__ = func.__doc__
        return wrapped


class Advice:
    def __init__(self, method, enabled = False):
        self.method = method
        am = AdviceManager()
        self.am = am
        if not method in am.before:
            raise RuntimeError("Function was not marked @advicable")
        self.enabled = False
        if enabled:
            self.enable()

    def disable(self):
        am = self.am
        am.before[self.method] = [ x for x in am.before[self.method]
                                   if x != self.before ]
        am.around[self.method] = [ x for x in am.around[self.method]
                                   if x != self.around ]
        am.after[self.method] = [ x for x in am.after[self.method]
                                  if x != self.after ]

    def enable(self):
        am = self.am
        if self.enabled:
            return
        # Hook only in if the methods are overwritten
        if sys.version_info[0] == 2:
            if self.before.im_func != Advice.before.im_func:
                am.before[self.method].append(self.before)
            if self.around.im_func != Advice.around.im_func:
                am.around[self.method].append(self.around)
            if self.after.im_func != Advice.after.im_func:
                am.after[self.method].append(self.after)
            self.enabled = True
        elif sys.version_info[0] == 3:
            if self.before.__func__ != Advice.before:
                am.before[self.method].append(self.before)
            if self.around.__func__ != Advice.around:
                am.around[self.method].append(self.around)
            if self.after.__func__ != Advice.after:
                am.after[self.method].append(self.after)
            self.enabled = True

    def before(self, args, kwargs):
        return (args, kwargs)
    def around(self, func, args, kwargs):
        return func(args, kwargs)
    def after(self, ret):
        return ret

