import os, os.path, re, sys, textwrap, ConfigParser

__all__ = [
    'config_parser',
    'config_reader',
    'config_reader_arch',
]

_marker = object()

class schema_item_boolean(object):
    def __call__(self, i):
        i = i.strip().lower()
        if i in ("true", "1"):
            return True
        if i in ("false", "0"):
            return False
        raise Error

class schema_item_list(object):
    def __init__(self, type = "\s+"):
        self.type = type

    def __call__(self, i):
        i = i.strip()
        if not i:
            return []
        return [j.strip() for j in re.split(self.type, i)]

class config_reader(dict):
    config_name = "defines"

    def __init__(self, dirs = []):
        self._dirs = dirs

    def __getitem__(self, key):
        return self.get(key)

    def _get_files(self, name):
        return [os.path.join(i, name) for i in self._dirs if i]

    def _update(self, ret, inputkey):
        for key, value in super(config_reader, self).get(tuple(inputkey), {}).iteritems():
            ret[key] = value

    def get(self, key, default = _marker):
        if isinstance(key, basestring):
            key = key,

        ret = super(config_reader, self).get(tuple(key), default)
        if ret == _marker:
            raise KeyError, key
        return ret

    def merge(self, section, *args):
        ret = {}
        for i in xrange(0, len(args) + 1):
            ret.update(self.get(tuple([section] + list(args[:i])), {}))
        return ret

    def sections(self):
        return super(config_reader, self).keys()

class config_reader_arch(config_reader):
    schema = {
        'arches': schema_item_list(),
        'available': schema_item_boolean(),
        'configs': schema_item_list(),
        'flavours': schema_item_list(),
        'initramfs': schema_item_boolean(),
        'initramfs-generators': schema_item_list(),
        'modules': schema_item_boolean(),
        'subarches': schema_item_list(),
        'versions': schema_item_list(),
    }

    def __init__(self, dirs = []):
        super(config_reader_arch, self).__init__(dirs)
        self._read_base()

    def _read_arch(self, arch):
        files = self._get_files("%s/%s" % (arch, self.config_name))
        config = config_parser(self.schema, files)

        subarches = config['base',].get('subarches', [])
        flavours = config['base',].get('flavours', [])

        for section in iter(config):
            real = list(section)
            # TODO
            if real[-1] in subarches:
                real[0:0] = ['base', arch]
            elif real[-1] in flavours:
                real[0:0] = ['base', arch, 'none']
            else:
                real[0:0] = [real.pop()]
                if real[-1] in flavours:
                    real[1:1] = [arch, 'none']
                else:
                    real[1:1] = [arch]
            real = tuple(real)
            s = self.get(real, {})
            s.update(config[section])
            self[tuple(real)] = s

        for subarch in subarches:
            if self.has_key(('base', arch, subarch)):
                avail = self['base', arch, subarch].get('available', True)
            else:
                avail = True
            if avail:
                self._read_subarch(arch, subarch)

        if flavours:
            base = self['base', arch]
            subarches.insert(0, 'none')
            base['subarches'] = subarches
            del base['flavours']
            self['base', arch] = base
            self['base', arch, 'none'] = {'flavours': flavours}
            for flavour in flavours:
                self._read_flavour(arch, 'none', flavour)

    def _read_base(self):
        files = self._get_files(self.config_name)
        config = config_parser(self.schema, files)

        arches = config['base',]['arches']

        for section in iter(config):
            real = list(section)
            if real[-1] in arches:
                real.insert(0, 'base')
            else:
                real.insert(0, real.pop())
            self[tuple(real)] = config[section]

        for arch in arches:
            try:
                avail = self['base', arch].get('available', True)
            except KeyError:
                avail = True
            if avail:
                self._read_arch(arch)

    def _read_flavour(self, arch, subarch, flavour):
        if not self.has_key(('base', arch, subarch, flavour)):
            if subarch == 'none':
                import warnings
                warnings.warn('No config entry for flavour %s, subarch none, arch %s' % (flavour, arch), DeprecationWarning)
            self['base', arch, subarch, flavour] = {}

    def _read_subarch(self, arch, subarch):
        files = self._get_files("%s/%s/%s" % (arch, subarch, self.config_name))
        config = config_parser(self.schema, files)

        flavours = config['base',].get('flavours', [])

        for section in iter(config):
            real = list(section)
            if real[-1] in flavours:
                real[0:0] = ['base', arch, subarch]
            else:
                real[0:0] = [real.pop(), arch, subarch]
            real = tuple(real)
            s = self.get(real, {})
            s.update(config[section])
            self[tuple(real)] = s

        for flavour in flavours:
            self._read_flavour(arch, subarch, flavour)

    def merge(self, section, arch = None, subarch = None, flavour = None):
        ret = {}
        ret.update(self.get((section,), {}))
        if arch:
            ret.update(self.get((section, arch), {}))
        if flavour and subarch and subarch != 'none':
            ret.update(self.get((section, arch, 'none', flavour), {}))
        if subarch:
            ret.update(self.get((section, arch, subarch), {}))
        if flavour:
            ret.update(self.get((section, arch, subarch, flavour), {}))
        return ret

class config_parser(object):
    __slots__ = 'configs', 'schema'

    def __init__(self, schema, files):
        self.configs = []
        self.schema = schema
        for file in files:
            config = ConfigParser.ConfigParser()
            config.read(file)
            self.configs.append(config)

    def __getitem__(self, key):
        return self.items(key)

    def __iter__(self):
        return iter(self.sections())

    def items(self, section, var = {}):
        ret = {}
        section = '_'.join(section)
        exceptions = []
        for config in self.configs:
            try:
                items = config.items(section)
            except ConfigParser.NoSectionError, e:
                exceptions.append(e)
            else:
                for key, value in items:
                    try:
                        value = self.schema[key](value)
                    except KeyError: pass
                    ret[key] = value
        if len(exceptions) == len(self.configs):
            raise exceptions[0]
        return ret

    def sections(self):
        sections = []
        for config in self.configs:
            for section in config.sections():
                section = tuple(section.split('_'))
                if section not in sections:
                    sections.append(section)
        return sections

if __name__ == '__main__':
    import sys
    config = config_reader()
    sections = config.sections()
    sections.sort()
    for section in sections:
        print "[%s]" % (section,)
        items = config[section]
        items_keys = items.keys()
        items_keys.sort()
        for item in items:
            print "%s: %s" % (item, items[item])
        print

