import inspect

from importlib import import_module
from os import listdir
from os.path import dirname, join, splitext

from django.conf import settings

from graphite.functions.params import Param, ParamTypes, ParamTypeAggFunc  # noqa
from graphite.functions.aggfuncs import getAggFunc  # noqa
from graphite.logger import log

customDir = join(dirname(__file__), 'custom')
customModPrefix = 'graphite.functions.custom.'

_SeriesFunctions = {}
_PieFunctions = {}


def loadFunctions(force=False):
    if _SeriesFunctions and not force:
        return

    from graphite.render import functions

    _SeriesFunctions.clear()
    _SeriesFunctions.update(functions.SeriesFunctions)

    _PieFunctions.clear()
    _PieFunctions.update(functions.PieFunctions)

    custom_modules = []
    for filename in listdir(customDir):
        module_name, extension = splitext(filename)
        if extension != '.py' or module_name == '__init__':
            continue
        custom_modules.append(customModPrefix + module_name)

    for module_name in custom_modules + settings.FUNCTION_PLUGINS:
        try:
            module = import_module(module_name)
        except Exception as e:
            log.warning('Error loading function plugin %s: %s' % (module_name, e))
            continue

        for func_name, func in getattr(module, 'SeriesFunctions', {}).items():
            try:
                addFunction(_SeriesFunctions, func, func_name)
            except Exception as e:
                log.warning('Error loading function plugin %s: %s' % (module_name, e))

        for func_name, func in getattr(module, 'PieFunctions', {}).items():
            try:
                addFunction(_PieFunctions, func, func_name)
            except Exception as e:
                log.warning('Error loading function plugin %s: %s' % (module_name, e))


def addFunction(dest, func, func_name):
    if not hasattr(func, 'group'):
        func.group = 'Ungrouped'

    if not hasattr(func, 'params'):
        raise Exception('No params defined for %s' % func_name)

    for param in func.params:
        if not isinstance(param, Param):
            raise Exception('Invalid param specified for %s' % func_name)
        dest[func_name] = func


def SeriesFunctions():
    loadFunctions()
    return _SeriesFunctions


def SeriesFunction(name):
    loadFunctions()
    try:
        return _SeriesFunctions[name]
    except KeyError:
        raise KeyError('Function "%s" not found' % name)


def PieFunctions():
    loadFunctions()
    return _PieFunctions


def PieFunction(name):
    loadFunctions()
    try:
        return _PieFunctions[name]
    except KeyError:
        raise KeyError('Function "%s" not found' % name)


def functionInfo(name, func):
    argspec = inspect.getargspec(func)
    argformat = inspect.formatargspec(argspec[0][1:], argspec[1], argspec[2], argspec[3])
    return {
        'name': name,
        'function': name + argformat,
        'description': inspect.getdoc(func),
        'module': inspect.getmodule(func).__name__,
        'group': getattr(func, 'group', 'Ungrouped'),
        'params': getattr(func, 'params', None),
    }
