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

from collections.abc import Collection, Container
from pathlib import Path
from typing import Any, Callable, Union

from ycontract import ContractError

__all__ = [
    'more_than', 'less_than', 'more_than_or_eq', 'less_than_or_eq', 'in_range', 'exists_path'
]


def _to_native_number(x) -> float:
    if hasattr(x, '__float__'):
        return float(x)
    elif hasattr(x, '__int__'):
        return int(x)
    else:
        raise ContractError(f"{x} is not a number.")


def more_than(more_than_target) -> Callable[[Any], bool]:
    more_than_target = _to_native_number(more_than_target)

    def _more_than(x) -> bool:
        return _to_native_number(x) > more_than_target

    return _more_than


is_positive = more_than(0.0)


def less_than(less_than_target) -> Callable[[Any], bool]:
    less_than_target = _to_native_number(less_than_target)

    def _less_than(x) -> bool:
        return _to_native_number(x) < less_than_target

    return _less_than


is_negative = less_than(0.0)


def more_than_or_eq(more_than_target) -> Callable[[Any], bool]:
    more_than_target = _to_native_number(more_than_target)

    def _more_than_or_eq(x) -> bool:
        return _to_native_number(x) >= more_than_target

    return _more_than_or_eq


def less_than_or_eq(less_than_target) -> Callable[[Any], bool]:
    less_than_target = _to_native_number(less_than_target)

    def _less_than_or_eq(x) -> bool:
        return _to_native_number(x) <= less_than_target

    return _less_than_or_eq


def in_range(a, b=None) -> Callable[[Any], bool]:
    if b is None:
        start, until = 0, a
    else:
        start, until = a, b

    def _in_range(x) -> bool:
        return start <= x < until

    return _in_range


def is_sub(sup: Collection) -> Callable[[Any], bool]:
    set_sup = set(sup)

    def _is_sub(sub: Collection):
        return set(sub) <= set_sup

    return _is_sub


def is_type(type_: type) -> Callable[[Any], bool]:

    def _is_type(x) -> bool:
        return type(x) is type_

    return _is_type


def is_instance(type_: type) -> Callable[[Any], bool]:

    def _is_instance(x) -> bool:
        return isinstance(x, type_)

    return _is_instance


def is_in(c: Container) -> Callable[[Any], bool]:

    def _is_in(x) -> bool:
        return x in c

    return _is_in


def exists_path(p: Union[str, Path]) -> bool:
    return Path(p).exists()
