ALLOW_EXTRA = 0  # extra keys not in schema will be included in output
REMOVE_EXTRA = 1  # extra keys not in schema will be excluded from output
REMOVE_NONE = 2  # the keys which value is None will be excluded from output


class Schema(object):
    def __init__(self, schema, required=False, extra=REMOVE_EXTRA):
        if extra not in [ALLOW_EXTRA, REMOVE_EXTRA, REMOVE_NONE, REMOVE_EXTRA | REMOVE_NONE]:
            raise ValueInvalid(f"Unknown extract {extra}")
        if type(schema) != dict:
            raise ValueInvalid(f"Unsupported type {schema}")

        self.schema = schema
        self.required = required
        self.extra = int(extra)

    def __call__(self, data):
        """validate data against the schema"""
        return self._validate(data)

    def _validate(self, data):
        """Create validator for given mapping."""

        result = dict()

        for key, value in self.schema.items():

            if self.extra == ALLOW_EXTRA:
                result.update({key(): data[key()]})
            elif self.extra == REMOVE_NONE:
                if data[key()]:
                    result.update({key(): data[key()]})
            else:
                if not key() in data:
                    if isinstance(key, Required):
                        raise RequiredFieldInvalid(f"required key not provided @ data['{key()}']")
                else:
                    if type(data[key()]) != value:
                        raise ValueInvalid(f"expected {value.__name__} but received {type(data[key()])} with key: {key()}")
                    else:
                        if self.extra == REMOVE_EXTRA | REMOVE_NONE:
                            if data[key()]:
                                result.update({key(): data[key()]})
                        else:
                            result.update({key(): data[key()]})

        return result


class Maker(object):
    def __init__(self, key):
        self._key = key

    def __call__(self, *args, **kwargs):
        return self._key


class Required(Maker):
    """Field which is required."""
    pass


class Optional(Maker):
    """Field which is optional."""
    pass


class Error(Exception):
    """Base validation exception."""


class RequiredFieldInvalid(Error):
    pass


class ValueInvalid(Error):
    pass