import base64
from distutils.util import strtobool
from typing import Union, Optional, Any, TypeVar, Callable, Tuple

import six

try:
    from typing import Text
except ImportError:
    # windows conda-less hack
    Text = Any


ConverterType = TypeVar("ConverterType", bound=Callable[[Any], Any])


def text_to_int(value, default=0):
    # type: (Any, int) -> int
    try:
        return int(value)
    except (ValueError, TypeError):
        return default


def base64_to_text(value):
    # type: (Any) -> Text
    return base64.b64decode(value).decode("utf-8")


def text_to_bool(value):
    # type: (Text) -> bool
    return bool(strtobool(value))


def safe_text_to_bool(value):
    # type: (Text) -> bool
    try:
        return text_to_bool(value)
    except ValueError:
        return bool(value)


def any_to_bool(value):
    # type: (Optional[Union[int, float, Text]]) -> bool
    if isinstance(value, six.text_type):
        return text_to_bool(value)
    return bool(value)


def or_(*converters, **kwargs):
    # type: (ConverterType, Tuple[Exception, ...]) -> ConverterType
    """
    Wrapper that implements an "optional converter" pattern. Allows specifying a converter
    for which a set of exceptions is ignored (and the original value is returned)
    :param converters: A converter callable
    :param exceptions: A tuple of exception types to ignore
    """
    # noinspection PyUnresolvedReferences
    exceptions = kwargs.get("exceptions", (ValueError, TypeError))

    def wrapper(value):
        for converter in converters:
            try:
                return converter(value)
            except exceptions:
                pass
        return value

    return wrapper