import sys
from functools import wraps


class ConfigError(RuntimeError):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return str(self.value)


def optional(func):
    @wraps(func)
    def wrapper(obj):
        if obj is None:
            return None
        return func(obj)

    return wrapper


def check_type(*obj_types):
    def inner_function(func):
        @wraps(func)
        def wrapper(obj):
            for obj_type in obj_types:
                if isinstance(obj, obj_type):
                    return func(obj)
            raise ConfigError(
                "{} is not a {}".format(repr(obj), repr(obj_types)))

        return wrapper

    return inner_function


def check_min(min_val):
    def inner_function(func):
        @wraps(func)
        def wrapper(val):
            if min_val > val:
                raise ConfigError(
                    "{} must be > {}".format(repr(val), min_val))
            return func(val)

        return wrapper

    return inner_function


def check_max(max_val):
    def inner_function(func):
        @wraps(func)
        def wrapper(val):
            if max_val < val:
                raise ConfigError(
                    "{} must be < {}".format(repr(val), max_val))
            return func(val)

        return wrapper

    return inner_function


def check_min_max(min_val, max_val):
    def inner_function(func):
        @wraps(func)
        def wrapper(val):
            if not (min_val <= val <= max_val):
                raise ConfigError(
                    "{} must be in range [{}:{}]".format(repr(val), min_val, max_val))
            return func(val)

        return wrapper

    return inner_function


def check_set_val(iterable):
    def inner_function(func):
        @wraps(func)
        def wrapper(obj):
            if obj not in iterable:
                raise ConfigError(
                    "{} is not one of {}".format(repr(obj), repr(iterable)))
            return func(obj)

        return wrapper

    return inner_function


@check_type(bool)
def check_bool(val):
    return val


@check_type(dict)
def check_dict(obj):
    return obj

@check_type(list)
def check_list(obj):
    return obj



PROTOCOLS = ('http', 'https')
LOG_LEVELS = ('DEBUG', 'INFO', 'WARN', 'WARNING', 'ERROR', 'CRITICAL')


@check_type(int)
@check_min_max(1, 65535)
def check_port(port):
    return port


@check_type(int)
@check_min(14400)
def check_interval(interval):
    return interval


@check_type(int)
@check_min(5)
def check_sec(interval):
    return interval


@optional
@check_type(bool)
def check_verify_ssl(val):
    return val


@optional
@check_type(bool)
def check_checkin_on_boot(val):
    return val


if sys.version_info[0] < 3:
    @check_type(basestring)
    def check_str(val):
        return val

    @optional
    @check_type(basestring)
    def check_opt_str(val):
        return val

    @check_type(basestring)
    @check_set_val(PROTOCOLS)
    def check_protocol(val):
        return val

    @check_type(basestring)
    @check_set_val(LOG_LEVELS)
    def check_log_level(level):
        return level
else:
    @check_type(str)
    def check_str(val):
        return val


    @optional
    @check_type(str)
    def check_opt_str(val):
        return val


    @check_type(str)
    @check_set_val(PROTOCOLS)
    def check_protocol(val):
        return val


    @check_type(str)
    @check_set_val(LOG_LEVELS)
    def check_log_level(level):
        return level
