from typing import Any, List, Set, Sized, Tuple, Union

def chk_none(name : str, value : Any) -> Any:
    if value is not None:
        msg = '{}({}) is not None.'
        raise AttributeError(msg.format(str(name), str(value)))
    return value

def chk_not_none(name : str, value : Any) -> Any:
    if value is None:
        msg = '{}({}) is None.'
        raise AttributeError(msg.format(str(name), str(value)))
    return value

def chk_type(name : str, value : Any, type_or_types : Union[type, Set[type]] = set()) -> Any:
    if not isinstance(value, type_or_types):
        msg = '{}({}) is of a wrong type({}). Accepted type_or_types({}).'
        raise AttributeError(msg.format(str(name), str(value), type(value).__name__, str(type_or_types)))
    return value

def chk_length(
    name : str, value : Sized, allow_empty : bool = False,
    min_length : Union[int, None] = None, max_length : Union[int, None] = None,
    allowed_lengths : Union[None, int, Set[int], List[int], Tuple[int]] = None) -> Any:

    length = len(chk_type(name, value, Sized))

    allow_empty = chk_type('allow_empty for {}'.format(name), allow_empty, bool)
    if not allow_empty and length == 0:
        msg = '{}({}) is out of range: allow_empty({}) min_length({}) max_length({}) allowed_lengths({}).'
        raise AttributeError(msg.format(
            str(name), str(value), str(allow_empty), str(min_length), str(max_length), str(allowed_lengths)))


    if min_length is not None:
        min_length = chk_type('min_length for {}'.format(name), min_length, int)
        if length < min_length:
            msg = '{}({}) is out of range: allow_empty({}) min_length({}) max_length({}) allowed_lengths({}).'
            raise AttributeError(msg.format(
                str(name), str(value), str(allow_empty), str(min_length), str(max_length), str(allowed_lengths)))

    if max_length is not None:
        max_length = chk_type('max_length for {}'.format(name), max_length, int)
        if length > max_length:
            msg = '{}({}) is out of range: allow_empty({}) min_value({}) max_value({}) allowed_lengths({}).'
            raise AttributeError(msg.format(
                str(name), str(value), str(allow_empty), str(max_length), str(max_length), str(allowed_lengths)))

    if allowed_lengths is not None:
        chk_type('allowed_lengths for {}'.format(name), allowed_lengths, (int, set, list, tuple))
        if isinstance(allowed_lengths, int):
            fixed_length = allowed_lengths
            if length != fixed_length:
                msg = '{}({}) is out of range: allow_empty({}) min_value({}) max_value({}) allowed_lengths({}).'
                raise AttributeError(msg.format(
                    str(name), str(value), str(allow_empty), str(max_length), str(max_length), str(allowed_lengths)))
        else:
            for i in allowed_lengths: chk_type('allowed_lengths[#{}] for {}'.format(i, name), i, int)
            if length not in allowed_lengths:
                msg = '{}({}) is out of range: allow_empty({}) min_value({}) max_value({}) allowed_lengths({}).'
                raise AttributeError(msg.format(
                    str(name), str(value), str(allow_empty), str(max_length), str(max_length), str(allowed_lengths)))

    return value

def chk_string(
    name : str, value : Any, allow_empty : bool = False,
    min_length : Union[int, None] = None, max_length : Union[int, None] = None,
    allowed_lengths : Union[None, int, Set[int], List[int], Tuple[int]] = None) -> str:

    chk_type(name, value, str)
    chk_length(
        name, value, allow_empty=allow_empty, min_length=min_length, max_length=max_length,
        allowed_lengths=allowed_lengths)
    return value

def chk_float(name, value, type_or_types=(int, float), min_value=None, max_value=None) -> float:
    chk_not_none(name, value)
    chk_type(name, value, type_or_types)
    if min_value is not None:
        chk_type(name, value, type_or_types)
        if value < min_value:
            msg = '{}({}) lower than min_value({}).'
            raise AttributeError(msg.format(str(name), str(value), str(min_value)))
    if max_value is not None:
        chk_type(name, value, type_or_types)
        if value > max_value:
            msg = '{}({}) greater than max_value({}).'
            raise AttributeError(msg.format(str(name), str(value), str(max_value)))
    return value

def chk_integer(name, value, min_value=None, max_value=None) -> int:
    return int(chk_float(name, value, type_or_types=int, min_value=min_value, max_value=max_value))

def chk_options(name, value, options):
    chk_not_none(name, value)
    if value not in options:
        msg = '{}({}) is not one of options({}).'
        raise AttributeError(msg.format(str(name), str(value), str(options)))
    return value
