# Copyright (C) 2010 Canonical Ltd
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

""" General parameter parsing and validation support."""

from bzrlib.lazy_import import lazy_import
lazy_import(globals(), '''

from bzrlib import (
    commands,
    errors,
    )
''')
from bzrlib.option import Option, RegistryOption


class UnknownParameter(errors.BzrError):

    _fmt = "Unknown parameter %(parameter)s. %(help)s"

    def __init__(self, parameter, help):
        self.parameter = parameter
        self.help = help


class IllegalParameterValue(errors.BzrError):

    _fmt = "Illegal value %(value)s for parameter %(key)s - %(expected)s expected."

    def __init__(self, key, value, expected):
        self.key = key
        self.value = value
        self.expected = expected


class ParameterParser(object):

    def __init__(self, definitions):
        """Create a parser.

        :param definitions: a list of Option definitions
        """
        self._definitions = definitions
        self._legal = dict([(d.name, d) for d in definitions])

    def parse_list(self, parameters):
        """Parse a list of parameters into a dictionary of parameters.
        
        Parameters values must be in the format key=value or key.
        If just the key is given, a value of True is assumed.

        :param parameters: a list of parameter strings

        :return: a dictionary, possibly empty. If an illegal parameter is
            encountered, an UnknownParameter or IllegalParameterValue
            exception is thrown.
        """
        result = {}
        if parameters:
            for p in parameters:
                if p.find('=') >= 0:
                    key, value = p.split("=", 1)
                else:
                    key, value = p, True
                self._save_parameter(result, key, value)
        return result

    def _save_parameter(self, dict, key, value):
        """Store a parameter into dict after validating it's legal.
        
        :param dict: the dictionary to update
        :param key: the proposed key
        :param value: the proposed value
        """
        try:
            param_type = self._legal[key]
        except KeyError:
            param_help = self.help()
            raise UnknownParameter(key, param_help)
        try:
            dict[key] = self._parse_value(value, param_type)
        except ValueError:
            expected = self._expected_value_info(param_type)
            raise IllegalParameterValue(key, value, expected)

    def help(self):
        """Return a help string showing the legal parameters."""
        if self._legal:
            lines = ["Supported parameters are:", ""]
            for p in sorted(self._legal):
                opt = self._legal[p]
                lines.append("* %s: %s" % (p, opt.help))
                if isinstance(opt, RegistryOption):
                    value_info = self._expected_value_info(opt)
                    default = opt.registry.default_key
                    lines.append("  Either %s." % (value_info,))
                    lines.append("  The default is %s." % (default,))
            return "\n".join(lines)
        else:
            return "No parameters are defined."

    def _parse_value(self, value, param_type):
        """Parse a validate a value.

        If the value is illegal given the type, raise ValueError.
        """
        if isinstance(param_type, RegistryOption):
            if value in param_type.registry.keys():
                return value
            else:
                raise ValueError()
        elif param_type.type is None:
            # Boolean is the default type
            if  value in [True, False]:
                return value
            elif value in ["True", "False"]:
                return value == "True"
            else:
                raise ValueError()
        else:
            # Assume string parameter
            return value

    def _expected_value_info(self, param_type):
        """Return information about the expected value."""
        if isinstance(param_type, RegistryOption):
            keys = param_type.registry.keys()
            return " or ". join(keys)
        elif param_type.type is None:
            return "True or False"
        else:
            return "string"
