import logging
from inspect import getmembers
from itertools import chain
from functools import partial
from triedict import triedict
from collections import Mapping

from .function import Function
from .config import Configuration

logger = logging.getLogger(__name__)

class Program(Mapping):
    def __init__(self, function, *configurations, name=None):
        '''
        :param functions: Base function or functions
        :param Configuration configurations: Configuration files and other
            configuration sources
        :param name: Force the program to have a particular name.
        '''
        if name is None and hasattr(function, '__call__'):
            if isinstance(function, partial):
                name = function.func.__name__
            else:
                name = function.__name__
        self.name = name
        self._function = function
        self._configurations = tuple(map(Configuration, configurations))
        self._d = triedict(_sub(tuple(), function))

    def subset(self, prefix):
        for section, function in self.items():
            if section[:len(prefix)] == prefix:
                yield section

    def __repr__(self):
        tpl = '%(class)s(%(function)s, %(configuration)s, name=%(name)s)'
        return tpl % {
            'class': self.__class__.__name__,
            'function': getattr(self._function, '__name__', repr(self._function)),
            'configuration': repr(self.configuration()),
            'name': repr(self.name),
        }

    def configuration(self):
        return Configuration.merge(*self._configurations)

    def __getitem__(self, section):
        c = self.configuration()
        defaults = {}
        for name, value in chain(c[None].items(), c.get(section, {}).items()):
            defaults[name] = value
        return Function(self._d[section], defaults)

    def __copy__(self):
        return Program(self._function, *self._configurations, name=self.name)

    def __iter__(self):
        for section, function in self._d.items():
            if callable(function):
                yield section

    def __len__(self):
        return len(self._d)

def _sub(section, fs):
    '''
    :param tuple section: Sections that this sub-program is under
    '''
    if hasattr(fs, 'items'):
        items = fs.items()
    elif hasattr(fs, '__iter__'):
        gs = list(fs)
        if all(hasattr(g, '__name__') for g in gs):
            items = ((g.__name__, g) for g in gs)
        else:
            raise TypeError('Bad function component: %s' % repr(fs))
    elif hasattr(fs, '__call__'):
        yield section, fs
        if isinstance(fs, type):
            items = (item for item in getmembers(fs) \
                     if not item[0].startswith('_'))
        else:
            items = []
    else:
        raise TypeError('Bad function component: %s' % repr(fs))

    for name, f in items:
        _validate_param_name(*name)
        yield from _sub(section + (name,), f)

def _validate_param_name(*xs):
    msg = 'It is best if you remove "%s" from the argument name "%s".'
    for x in xs:
        for c in '/?[]<>():':
            if x in c:
                logger.warning(msg % (c, x))
