from parallels.core.utils.common import is_string, format_list, default
from parallels.plesk.hosting_description.validate.validators.base import ValidatorBase
from parallels.plesk import messages


class ValidatorStruct(ValidatorBase):
    """Check that specified document is a dictionary, that conforms specified restrictions for items
        * list of possible keys is restricted
        * each value has its own type
    """

    def __init__(self, items, required_items=None, context_title=None, context_var=None):
        """
        :type items: dict[
            str | unicode, parallels.plesk.hosting_description.validate.validators.base.ValidatorBase
        ]
        """
        self._items = items
        self._context_title = context_title
        self._context_var = context_var
        self._required_items = default(required_items, list())

    def validate(self, document, context=None):
        """Validate specified document (variable) according to current validator's rules

        Return list of found issues.

        :rtype: list[unicode]
        """
        if not self.match_type(document):
            return [self._format_error(messages.VALIDATOR_EXPECTED_DICT_VALUE, context)]

        if context is None:
            context = []

        if self._context_title is not None and self._context_var is not None:
            context_var_value = document.get(self._context_var)
            if is_string(context_var_value):
                context = context + ["%s '%s'" % (self._context_title, context_var_value)]

        errors = []

        # Check for unexpected keys
        for key, value in document.items():
            if key not in self._items:
                errors.append(self._format_error(
                    messages.VALIDATOR_STRUCT_KEY_NOT_EXPECTED.format(
                        key=key, expected_list=format_list(self._items.keys())
                    ),
                    context
                ))

        # Check for required keys
        for key in self._required_items:
            if key not in document:
                errors.append(self._format_error(
                    messages.VALIDATOR_STRUCT_REQUIRED_KEY_NOT_SPECIFIED.format(
                        key=key
                    ),
                    context
                ))

        # Validate all inner data
        for key, value in document.items():
            if key in self._items:
                errors += self._items[key].validate(value, context + ["element '%s'" % key])

        return errors

    def match_type(self, document):
        """Check whether data type of document matched the validator

        That is necessary condition for "validate" method not to return errors

        :rtype: bool
        """
        return isinstance(document, dict)

    @property
    def items(self):
        """Get items describing dictionary keys and values validation rules

        :rtype: dict[
            str | unicode, parallels.plesk.hosting_description.validate.validators.base.ValidatorBase
        ]
        """
        return self._items

    def __repr__(self):
        return 'ValidatorStruct(%r)' % (self._items,)

    def __eq__(self, other):
        return isinstance(other, ValidatorStruct) and other.items == self.items
