# debpartial-mirror - partial debian mirror package tool
# (c) 2004 Otavio Salvador <otavio@debian.org>, Nat Budin <natb@brandeis.edu>
#
# This program 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 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
# $Id$

from logging import *
from ConfigParser import ConfigParser
from sys import exit

class InvalidOption(Exception):
    """
    Exception called when a invalid option is found in configuration
    file.

    Attributes:
        section -- Where the invalid option was set;
        option -- The name of invalid option.
    """
    def __init__(self, section, option):
        self.section = section
        self.option = option

class RequiredOptionMissing(Exception):
    """
    Exception called when a required option in the config file is not
    present.

    Attributes:
        section -- Where the invalid option was set;
        option -- The name of invalid option.
    """
    def __init__(self, section, option):
        self.section = section
        self.option = option

class InvalidSection(Exception):
    """
    Exception called when a invalid section is found in configuration
    file.

    Attributes:
        section -- The wrong section name.
    """
    def __init__(self, section):
        self.section = section

### TODO: Rewrite the Config parser structure to use:
###     Config
###        ConfigGlobal
###        ConfigBackend
###           ConfigBackendMirror
###           ConfigBackendMerge
class Config(ConfigParser):
    """
    Store the configurations used by our system.
    """

    _required_in_global = [
        'mirror_dir',
        'architectures',
        'sections',
        'distributions',
        'get_suggests',
        'get_recommends',
        'get_provides',
        ]

    _allowed_in_global = [
        'debug',
        ]

    ### HACK.  Split out later to generalize section types better.
    _allowed_in_mirror_backend = [
        'server',
        'architectures',
        'sections',
        'distributions',
        'filter',
        'get_suggests',
        'get_recommends',
        'get_provides',
	'include_from_task',
	'exclude_from_task',
	'resolve_deps_using',
        ]

    _allowed_in_merge_backend = [
        'filter_@BACKEND@',
	'backends',
	'name',
        ]

    _allowed_in_filter_field = [
        'subsection',
        'priority',
        'name'
        ]

    # if not included here, it's string
    _options_with_type = {
        'architectures': 'list',
        'backends': 'list',
        'distributions': 'list',
        'filter': 'dict',
        'filter_@BACKEND@': 'dict',
        'get_provides': 'boolean',
        'get_recommends': 'boolean',
        'get_suggests': 'boolean',
        'sections': 'list',
        }

    def __castOption(self, option, value):
        if option in self._options_with_type:
            if self._options_with_type[option] == 'list':
                return value.split()
            elif self._options_with_type[option] == 'boolean':
                return bool(value)
#            elif self._options_with_type[option] == 'dict':
#                return map(value.split())
        else:
            return value

    def __init__(self, filename):
        ConfigParser.__init__(self)
        self.read(filename)

	# Read the global section.
        self.confs = {}
        self.confs['GLOBAL'] = {}
        global_items = self._allowed_in_global + self._required_in_global
        for item, value in self.items('GLOBAL'):
            if item not in global_items:
                debug("[%s] is not allowed in global section." % (item))
                raise InvalidOption('GLOBAL', item)
            self.confs['GLOBAL'][item] = value

        for item in self._required_in_global:
            if not self.confs['GLOBAL'].has_key(item):
                debug("Required option [%s] not found in global section." % (item))
                raise RequiredOptionMissing('GLOBAL', item)

	for section in self.sections():
            if section == 'GLOBAL': continue
	    self.confs[section] = {}

            ###HACK.  Split this out later into subclasses.
            # detect which config type this is
            if 'backends' in self.options(section):
                allowed_in_section = self._allowed_in_merge_backend
            elif 'server' in self.options(section):
                allowed_in_section = self._allowed_in_mirror_backend
            else:
                debug("Unknown section type in section [%s]." % (section))
                raise InvalidSection(section)
            
	    for item, value in self.items(section):
                value = self.__castOption(item, value)
		if item not in allowed_in_section:
		    for allowed_key in allowed_in_section:
			# check the allowed_in_backend keys with
			# @VARIABLES@
			if allowed_key.find('@') != -1:
			    left_length = allowed_key.find('@')
			    right_length = allowed_key[left_length+1:].find('@') + left_length + 2
			    if (allowed_key[:left_length] == item[:left_length]
                                and allowed_key[right_length:] == item[right_length:]):
				# found it!
                                variable = allowed_key[left_length:right_length]
                                match = item[left_length:len(item)-(len(allowed_key)-right_length)]
                                if variable == '@BACKEND@':
                                    if match in self.sections():
                                        break
                                else:
                                    print("You found a bug: [%s] matches unknown variable [%s]! "
                                          "Please report it." % (item, variable))
                                    exit(1)
                    else:
			debug("[%s] is not allowed in a backend section (it was found in [%s])."
                              % (item, section))
                        raise InvalidOption(section, item)
		self.confs[section][item] = value

    def getOption(self, option, section='GLOBAL'):
	# get a config value for a certain section.  if it's not
	# specified, fall back to GLOBAL
	if not self.confs.has_key(section):
	    debug("no config section called [%s]." % (section))
	    raise InvalidSection(section)
	if self.confs[section].has_key(option):
	    return self.confs[section][option]
        else:
            if section != 'GLOBAL':
                debug("[%s] is not present in section [%s]. Fallback to global section." % (option, section))
                try:
                    self.getOption(option, 'GLOBAL')
                except InvalidOption, msg:
                    debug("[%s] is not present in section [%s]." % (option, section))
                    raise InvalidOption(section, msg.option)
                except InvalidSection, msg:
                    raise InvalidSection(msg.section)
            else:
                debug("[%s] is not present in section [%s]." % (option, section))
                raise InvalidOption(section, option)

	    return self.confs[section][option]
        

    def dump(self):
        for section, options in self.confs.items():
            print '\n' + section
            for item, value in options.items():
                print "  %s = %s" %(item, self.getOption(item, section))

