# -*- coding: UTF-8 -*-
"""
Functionality to support user-specific configuration data (userdata).
"""

from behave._types import Unknown


# -----------------------------------------------------------------------------
# FUNCTIONS:
# -----------------------------------------------------------------------------
def parse_bool(text):
    """Parses a boolean text and converts it into boolean value (if possible).
    Supported truth string values:

      * true:   "true", "yes", "on", "1"
      * false:  "false", "no", "off", "0"

    :raises: ValueError, if text is invalid
    """
    from distutils.util import strtobool
    return bool(strtobool(text))


def parse_user_define(text):
    """Parse "{name}={value}" text and return parts as tuple.
    Used for command-line definitions, like "... -D name=value".

    SUPPORTED SCHEMA:

      * "{name}={value}"
      * "{name}"                (boolean flag; value="true")
      * '"{name}={value}"'      (double-quoted name-value pair)
      * "'{name}={value}'"      (single-quoted name-value pair)
      * '{name}="{value}"'      (double-quoted value)
      * "{name}='{value}'"      (single-quoted value)
      * "  {name} = {value}  "  (whitespace padded)

    .. note:: Leading/trailing Quotes are stripped.

    :param text:    Text to parse (as string).
    :return: (name, value) pair as tuple.
    """
    text = text.strip()
    if "=" in text:
        text = unqote(text)
        name, value = text.split("=", 1)
        name = name.strip()
        value = unqote(value.strip())
    else:
        # -- ASSUMPTION: Boolean definition (as flag)
        name = text
        value = "true"
    return (name, value)


def unqote(text):
    """Strip pair of leading and trailing quotes from text."""
    # -- QUOTED: Strip single-quote or double-quote pair.
    if ((text.startswith('"') and text.endswith('"')) or
        (text.startswith("'") and text.endswith("'"))):
        text = text[1:-1]
    return text

# -----------------------------------------------------------------------------
# CLASSES:
# -----------------------------------------------------------------------------
class UserData(dict):
    """Dictionary-like user-data with some additional features:

      * type-converter methods, similar to configparser.ConfigParser.getint()

    """

    def getas(self, convert, name, default=None, valuetype=None):
        """Converts the value of user-data parameter from a string into a
        specific value type.

        :param convert: Converter function to use (string to value-type).
        :param name:    Variable name to use.
        :param default: Default value, used if parameter is not found.
        :param valuetype: Value type(s), needed if convert != valuetype()
        :return: Converted textual value (type: valuetype)
        :return: Default value, if parameter is unknown.
        :raises ValueError: If type conversion fails.
        """
        if valuetype is None:
            # -- ASSUME: Converter function is the type constructor.
            valuetype = convert

        value = self.get(name, Unknown)
        if value is Unknown:
            return default
        elif isinstance(value, valuetype):
            # -- PRESERVE: Pre-converted value if type matches.
            return value
        else:
            # -- CASE: Textual value (expected)
            # Raise ValueError if parse/conversion fails.
            assert callable(convert)
            return convert(value)

    def getint(self, name, default=0):
        """Convert parameter value (as string) into a integer value.
        :return: Parameter value as integer number (on success).
        :raises: ValueError, if type conversion fails.
        """
        return self.getas(int, name, default)

    def getfloat(self, name, default=0.0):
        """Convert parameter value (as string) into a float value.
        :return: Parameter value as float number (on success).
        :raises: ValueError, if type conversion fails.
        """
        return self.getas(float, name, default)

    def getbool(self, name, default=False):
        """Converts user-data string-value into boolean value (if possible).
        Supported truth string values:

          * true:   "true", "yes", "on", "1"
          * false:  "false", "no", "off", "0"

        :param name: Parameter name (as string).
        :param default: Default value, if parameter is unknown (=False).
        :return: Boolean value of parameter
        :raises: ValueError, if type conversion fails.
        """
        return self.getas(parse_bool, name, default, valuetype=bool)