#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2021, Anaconda, Inc., and Bokeh Contributors.
# All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
import pytest ; pytest

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

# Standard library imports
import datetime as dt

# External imports
import mock

# Bokeh imports
from _util_models import check_properties_existence
from bokeh.core.validation import check_integrity

# Module under test
from bokeh.models import Range1d, DataRange1d, FactorRange # isort:skip

#-----------------------------------------------------------------------------
# Setup
#-----------------------------------------------------------------------------


#-----------------------------------------------------------------------------
# General API
#-----------------------------------------------------------------------------


class Test_Range1d:
    def test_basic(self) -> None:
        r = Range1d()
        check_properties_existence(r, [
            "start",
            "end",
            "reset_start",
            "reset_end",
            "bounds",
            "min_interval",
            "max_interval"],
        )

    def test_init_with_timedelta(self) -> None:
        range1d = Range1d(start=-dt.timedelta(seconds=5), end=dt.timedelta(seconds=3))
        assert range1d.start == -dt.timedelta(seconds=5)
        assert range1d.end == dt.timedelta(seconds=3)
        assert range1d.bounds is None

    def test_init_with_datetime(self) -> None:
        range1d = Range1d(start=dt.datetime(2016, 4, 28, 2, 20, 50), end=dt.datetime(2017, 4, 28, 2, 20, 50))
        assert range1d.start == dt.datetime(2016, 4, 28, 2, 20, 50)
        assert range1d.end == dt.datetime(2017, 4, 28, 2, 20, 50)
        assert range1d.bounds is None

    def test_init_with_float(self) -> None:
        range1d = Range1d(start=-1.0, end=3.0)
        assert range1d.start == -1.0
        assert range1d.end == 3.0
        assert range1d.bounds is None

    def test_init_with_int(self) -> None:
        range1d = Range1d(start=-1, end=3)
        assert range1d.start == -1
        assert range1d.end == 3
        assert range1d.bounds is None

    def test_init_with_positional_arguments(self) -> None:
        range1d = Range1d(1, 2)
        assert range1d.start == 1
        assert range1d.end == 2
        assert range1d.bounds is None

    def test_init_with_keyword_arguments(self) -> None:
        range1d = Range1d(start=1, end=2)
        assert range1d.start == 1
        assert range1d.end == 2
        assert range1d.bounds is None

    def test_cannot_initialize_with_both_keyword_and_positional_arguments(self) -> None:
        with pytest.raises(ValueError):
            Range1d(1, 2, start=1, end=2)


    def test_cannot_initialize_with_three_positional_arguments(self) -> None:
        with pytest.raises(ValueError):
            Range1d(1, 2, 3)


    def test_with_max_bound_smaller_than_min_bounded_raises_valueerror(self) -> None:
        with pytest.raises(ValueError):
            Range1d(1, 2, bounds=(1, 0))
        with pytest.raises(ValueError):
            Range1d(1, 2, bounds=[1, 0])


    def test_bounds_with_text_rejected_as_the_correct_value_error(self) -> None:
        with pytest.raises(ValueError) as e:
            Range1d(1, 2, bounds="21")  # The string is indexable, so this may not fail properly
        assert "expected either None or" in e.value.args[0]


    def test_bounds_with_three_item_tuple_raises_valueerror(self) -> None:
        with pytest.raises(ValueError):
            Range1d(1, 2, bounds=(0, 1, 2))


class Test_DataRange1d:
    def test_basic(self) -> None:
        r = DataRange1d()
        check_properties_existence(r, [
            "names",
            "renderers",
            "range_padding",
            "range_padding_units",
            "flipped",
            "follow",
            "follow_interval",
            "default_span",
            "start",
            "end",
            "bounds",
            "min_interval",
            "max_interval",
            "only_visible"],
        )

    def test_init_with_no_arguments(self) -> None:
        datarange1d = DataRange1d()
        assert datarange1d.start is None
        assert datarange1d.end is None
        assert datarange1d.bounds is None

    def test_init_with_timedelta(self) -> None:
        datarange1d = DataRange1d(start=-dt.timedelta(seconds=5), end=dt.timedelta(seconds=3))
        assert datarange1d.start == -dt.timedelta(seconds=5)
        assert datarange1d.end == dt.timedelta(seconds=3)
        assert datarange1d.bounds is None

    def test_init_with_datetime(self) -> None:
        datarange1d = DataRange1d(start=dt.datetime(2016, 4, 28, 2, 20, 50), end=dt.datetime(2017, 4, 28, 2, 20, 50))
        assert datarange1d.start == dt.datetime(2016, 4, 28, 2, 20, 50)
        assert datarange1d.end == dt.datetime(2017, 4, 28, 2, 20, 50)
        assert datarange1d.bounds is None

    def test_init_with_float(self) -> None:
        datarange1d = DataRange1d(start=-1.0, end=3.0)
        assert datarange1d.start == -1.0
        assert datarange1d.end == 3.0
        assert datarange1d.bounds is None

    def test_init_with_int(self) -> None:
        datarange1d = DataRange1d(start=-1, end=3)
        assert datarange1d.start == -1
        assert datarange1d.end == 3
        assert datarange1d.bounds is None

    def test_init_with_follow_sets_bounds_to_none(self) -> None:
        datarange1d = DataRange1d(follow="start")
        assert datarange1d.follow == "start"
        assert datarange1d.bounds is None

    def test_init_with_bad_bounds(self) -> None:
        with pytest.raises(ValueError):
            DataRange1d(1, 2, bounds=(1, 0))
        with pytest.raises(ValueError):
            DataRange1d(1, 2, bounds=[1, 0])
        with pytest.raises(ValueError):
            Range1d(1, 2, bounds="21")


class Test_FactorRange:
    def test_basic(self) -> None:
        r = FactorRange()
        check_properties_existence(r, [
            "factors",
            "factor_padding",
            "group_padding",
            "subgroup_padding",
            "range_padding",
            "range_padding_units",
            "start",
            "end",
            "bounds",
            "min_interval",
            "max_interval"],
        )

    def test_init_defauls(self) -> None:
        factor_range = FactorRange("a", "b")
        assert factor_range.factors == ["a", "b"]
        assert factor_range.range_padding == 0
        assert factor_range.range_padding_units == "percent"
        assert factor_range.factor_padding == 0
        assert factor_range.group_padding == 1.4
        assert factor_range.subgroup_padding == 0.8
        assert factor_range.bounds == None
        assert factor_range.min_interval == None
        assert factor_range.max_interval == None

    def test_init_with_positional_arguments(self) -> None:
        factor_range = FactorRange("a", "b")
        assert factor_range.factors == ["a", "b"]

        factor_range = FactorRange(["a", "x"], ["b", "y"])
        assert factor_range.factors == [["a", "x"], ["b", "y"]]

        factor_range = FactorRange(["a", "x", "1'"], ["b", "y", "2"])
        assert factor_range.factors == [["a", "x", "1'"], ["b", "y", "2"]]

    def test_init_with_keyword_arguments(self) -> None:
        factor_range = FactorRange(factors=["a", "b", "c", "d", "e"])
        assert factor_range.factors == ["a", "b", "c", "d", "e"]

    def test_cannot_initialize_with_both_keyword_and_positional_arguments(self) -> None:
        with pytest.raises(ValueError):
            FactorRange(["a", "b", "c"], factors=["a", "b", "c"])

    def test_duplicate_factors_raises_validation_error(self) -> None:
        r = FactorRange("foo", "bar", "foo")
        with mock.patch('bokeh.core.validation.check.log') as mock_logger:
            check_integrity([r])
        assert mock_logger.error.call_count == 1

        r = FactorRange(factors=[("foo", "a"), ("foo", "b"),  ("foo", "a")])
        with mock.patch('bokeh.core.validation.check.log') as mock_logger:
            check_integrity([r])
        assert mock_logger.error.call_count == 1

        r = FactorRange(factors=[("foo", "a", "1"), ("foo", "a", "2"),  ("foo", "a", "1")])
        with mock.patch('bokeh.core.validation.check.log') as mock_logger:
            check_integrity([r])
        assert mock_logger.error.call_count == 1

#-----------------------------------------------------------------------------
# Dev API
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Private API
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------
