#!/usr/bin/env python
# coding: utf-8

"""
    create at 2017/11/7 by allen
"""

from collections import OrderedDict
from flask import current_app
from app.lib.exceptions import ValidationError, InternalError


class Validator(object):

    def __init__(self, validator):
        self.validator = validator
        self.validate_chain = OrderedDict([
            ('required', self.process_required),
            ('nonempty', self.process_nonempty),
            ('types', self.process_types),
            ('oneof', self.process_oneof),
            # can only do unique for list of hashable objects
            ('unique', self.process_unique),
            ('length', self.process_length),
            ('override', self.process_override),
            ('default', self.process_default),
            ('custom', self.process_custom)  # validate data logic
        ])

    def process_required(self, data):
        required = self.validator['required']
        not_supply = frozenset(required) - frozenset(data.keys())
        if not_supply:
            raise ValidationError(
                u'param %s is required' %
                u','.join(not_supply))

        return True

    def process_nonempty(self, data):
        no_empty = self.validator['nonempty']
        for k in no_empty:
            if not data.get(k, None):
                raise ValidationError(
                    u'param {0} required not empty'.format(k))

        return True

    def process_types(self, data):
        types = self.validator['types']

        for key, tp in types.items():
            if key in data and not isinstance(data[key], tp):
                raise ValidationError(u'param {0} need to be type {1}, your type is {2}'
                                      .format(key, tp, type(data[key])))

        return True

    def process_unique(self, data):
        unique_params = self.validator['unique']

        for k in unique_params:
            if k in data:
                data[k] = list(frozenset(k))

        return True

    def process_length(self, data):
        length = self.validator['length']
        for key, ll in length.items():
            if key not in data:
                continue

            val_len = len(data[key])

            if val_len < ll[0] or val_len > ll[-1]:
                raise ValidationError(u'param {0} length required to between {1} and {2}, '
                                      u'your length is {3}'.format(key, ll[0], ll[-1], val_len))

        return True

    def process_oneof(self, data):
        oneof = self.validator['oneof']

        for key, option_values in oneof.items():
            if key in data and data[key] not in option_values:
                raise ValidationError(u'param {0} must in {1}, your value is {2}'.format(
                    key, u','.join(option_values), data[key]
                ))

        return True

    def process_override(self, data):
        override = self.validator['override']
        for key, override_fn in override.items():
            if key in data:
                try:
                    data[key] = override_fn(key, data[key])
                except Exception as e:
                    current_app.logger.exception(u'process override with key={0}, value={1}, error: {2}'.format(
                        key, data[key], str(e)))

                    raise InternalError(
                        u'oops, validate param occurs internal error')

        return True

    def process_default(self, data):
        default_args = self.validator['default']

        for k, v in default_args.items():
            data.setdefault(k, v)

        return True

    def process_custom(self, data):
        custom = self.validator['custom']

        for validate_fn in custom:
            validate_fn(data)

    def __call__(self, data):
        for validate_name, validate_fn in self.validate_chain.items():
            if validate_name in self.validator:
                validate_fn(data)
