# coding=utf-8
#
# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis-python
#
# Most of this work is copyright (C) 2013-2017 David R. MacIver
# (david@drmaciver.com), but it contains contributions by others. See
# CONTRIBUTING.rst for a full list of people who may hold copyright, and
# consult the git log if you need to determine who owns an individual
# contribution.
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at http://mozilla.org/MPL/2.0/.
#
# END HEADER

from __future__ import division, print_function, absolute_import

import math
from numbers import Rational

from hypothesis.errors import InvalidArgument
from hypothesis.internal.compat import integer_types
from hypothesis.internal.coverage import check_function


@check_function
def check_type(typ, arg, name=''):
    if name:
        name += '='
    if not isinstance(arg, typ):
        if isinstance(typ, type):
            typ_string = typ.__name__
        else:
            typ_string = 'one of %s' % (
                ', '.join(t.__name__ for t in typ))
        raise InvalidArgument('Expected %s but got %s%r (type=%s)'
                              % (typ_string, name, arg, type(arg).__name__))


@check_function
def check_strategy(arg, name=''):
    from hypothesis.searchstrategy import SearchStrategy
    check_type(SearchStrategy, arg, name)


@check_function
def check_valid_integer(value):
    """Checks that value is either unspecified, or a valid integer.

    Otherwise raises InvalidArgument.

    """
    if value is None:
        return
    check_type(integer_types, value)


@check_function
def check_valid_bound(value, name):
    """Checks that value is either unspecified, or a valid interval bound.

    Otherwise raises InvalidArgument.

    """
    if value is None or isinstance(value, integer_types + (Rational,)):
        return
    if math.isnan(value):
        raise InvalidArgument(u'Invalid end point %s=%r' % (name, value))


@check_function
def try_convert(typ, value, name):
    if value is None:
        return None
    if isinstance(value, typ):
        return value
    try:
        return typ(value)
    except TypeError:
        raise InvalidArgument(
            'Cannot convert %s=%r of type %s to type %s' % (
                name, value, type(value).__name__, typ.__name__
            )
        )
    except (OverflowError, ValueError):
        raise InvalidArgument(
            'Cannot convert %s=%r to type %s' % (
                name, value, typ.__name__
            )
        )


@check_function
def check_valid_size(value, name):
    """Checks that value is either unspecified, or a valid non-negative size
    expressed as an integer/float.

    Otherwise raises InvalidArgument.

    """
    if value is None:
        return
    check_type(integer_types + (float,), value)
    if value < 0:
        raise InvalidArgument(u'Invalid size %s=%r < 0' % (name, value))
    if isinstance(value, float) and math.isnan(value):
        raise InvalidArgument(u'Invalid size %s=%r' % (name, value))


@check_function
def check_valid_interval(lower_bound, upper_bound, lower_name, upper_name):
    """Checks that lower_bound and upper_bound are either unspecified, or they
    define a valid interval on the number line.

    Otherwise raises InvalidArgument.

    """
    if lower_bound is None or upper_bound is None:
        return
    if upper_bound < lower_bound:
        raise InvalidArgument(
            'Cannot have %s=%r < %s=%r' % (
                upper_name, upper_bound, lower_name, lower_bound
            ))


@check_function
def check_valid_sizes(min_size, average_size, max_size):
    check_valid_size(min_size, 'min_size')
    check_valid_size(max_size, 'max_size')
    check_valid_size(average_size, 'average_size')
    check_valid_interval(min_size, max_size, 'min_size', 'max_size')
    check_valid_interval(average_size, max_size, 'average_size', 'max_size')
    check_valid_interval(min_size, average_size, 'min_size', 'average_size')

    if (
        average_size == 0 and (
            max_size is None or
            max_size > 0
        )
    ):
        raise InvalidArgument(
            'Cannot have average_size=%r with non-zero max_size=%r' % (
                average_size, min_size
            ))
