# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2022, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import math
import operator
import sys

import pytest

from kiwisolver import Constraint, Expression, Term, Variable, strength


def test_variable_methods() -> None:
    """Test the variable modification methods."""
    v = Variable()
    assert v.name() == ""
    v.setName("γ")
    assert v.name() == "γ"
    v.setName("foo")
    assert v.name() == "foo"
    with pytest.raises(TypeError):
        v.setName(1)  # type: ignore
    if sys.version_info >= (3,):
        with pytest.raises(TypeError):
            v.setName(b"r")  # type: ignore

    assert v.value() == 0.0

    assert v.context() is None
    ctx = object()
    v.setContext(ctx)
    assert v.context() is ctx

    assert str(v) == "foo"

    with pytest.raises(TypeError):
        Variable(1)  # type: ignore


def test_variable_neg() -> None:
    """Test neg on a variable."""
    v = Variable("foo")

    neg = -v
    assert isinstance(neg, Term)
    assert neg.variable() is v and neg.coefficient() == -1


def test_variable_mul() -> None:
    """Test variable multiplications."""
    v = Variable("foo")
    v2 = Variable("bar")

    for mul in (v * 2.0, 2 * v):
        assert isinstance(mul, Term)
        assert mul.variable() is v and mul.coefficient() == 2

    with pytest.raises(TypeError):
        v * v2  # type: ignore


def test_variable_division() -> None:
    """Test variable divisions."""
    v = Variable("foo")
    v2 = Variable("bar")

    div = v / 2.0
    assert isinstance(div, Term)
    assert div.variable() is v and div.coefficient() == 0.5

    with pytest.raises(TypeError):
        v / v2  # type: ignore

    with pytest.raises(ZeroDivisionError):
        v / 0


def test_variable_addition() -> None:
    """Test variable additions."""
    v = Variable("foo")
    v2 = Variable("bar")

    for add in (v + 2, 2.0 + v):
        assert isinstance(add, Expression)
        assert add.constant() == 2
        terms = add.terms()
        assert (
            len(terms) == 1 and terms[0].variable() is v and terms[0].coefficient() == 1
        )

    add2 = v + v2
    assert isinstance(add2, Expression)
    assert add2.constant() == 0
    terms = add2.terms()
    assert (
        len(terms) == 2
        and terms[0].variable() is v
        and terms[0].coefficient() == 1
        and terms[1].variable() is v2
        and terms[1].coefficient() == 1
    )

    with pytest.raises(TypeError):
        v + ""  # type: ignore


def test_variable_sub() -> None:
    """Test variable substractions."""
    v = Variable("foo")
    v2 = Variable("bar")

    for sub, diff in zip((v - 2, 2 - v), (-2, 2)):
        assert isinstance(sub, Expression)
        assert sub.constant() == diff
        terms = sub.terms()
        assert (
            len(terms) == 1
            and terms[0].variable() is v
            and terms[0].coefficient() == -math.copysign(1, diff)
        )

    sub2 = v - v2
    assert isinstance(sub2, Expression)
    assert sub2.constant() == 0
    terms = sub2.terms()
    assert (
        len(terms) == 2
        and terms[0].variable() is v
        and terms[0].coefficient() == 1
        and terms[1].variable() is v2
        and terms[1].coefficient() == -1
    )


def test_variable_rich_compare_operations() -> None:
    """Test using comparison on variables."""
    v = Variable("foo")
    v2 = Variable("γ")

    for op, symbol in ((operator.le, "<="), (operator.eq, "=="), (operator.ge, ">=")):
        c = op(v, v2 + 1)
        assert isinstance(c, Constraint)
        e = c.expression()
        t = e.terms()
        assert len(t) == 2
        if t[0].variable() is not v:
            t = (t[1], t[0])
        assert (
            t[0].variable() is v
            and t[0].coefficient() == 1
            and t[1].variable() is v2
            and t[1].coefficient() == -1
        )
        assert e.constant() == -1
        assert c.op() == symbol and c.strength() == strength.required

    for op in (operator.lt, operator.ne, operator.gt):
        with pytest.raises(TypeError):
            op(v, v2)
