"""
TEST_INTMOD

Tests for the IntegerModN and IntegerModNRing classes.
"""

import pytest
from flint import fmpz, nmod  # pylint: disable=no-name-in-module
from jacamar.rings.integers import ZZ, ZZ_py
from jacamar.rings.intmod import IntegerModN, IntegerModNRing
from jacamar.rings.polynomials import PolynomialRing

r1 = IntegerModNRing(ZZ(24))
r2 = IntegerModNRing(ZZ(173))
r3 = IntegerModNRing(ZZ(214102727))


class TestIntegerModN:
    """Tests for the IntegerModN class."""

    def test_simple(self):
        """Test that modular arithmetic works as expected with our interface"""
        n = ZZ(103)
        r = IntegerModNRing(n)
        x = r(5)
        assert x ** ZZ(3) == r(22)

    def test_simple_2(self):
        """Test that creating an IntegerModN instance directly works"""
        r = IntegerModNRing(ZZ(7))
        x = IntegerModN(r, 5)
        assert r(5) == x

    def test_simple_fail(self):
        """Test that inequivalent expressions are evaluated as inequivalent"""
        n = ZZ(60)
        r = IntegerModNRing(n)
        x = r(5)
        assert x ** ZZ(3) != r(4)

    def test_rings(self):
        """The ring attribute of an element generated by the ring is the same ring"""
        assert r3(129127208515966992384).ring == r3
        assert r2(10).ring != r3

    def test_class_equality(self):
        """Tests that rings/elements are equal for equivalent inputs from different
        data classes"""
        print(isinstance(IntegerModNRing(ZZ(134)).modulus.data, fmpz))
        print(isinstance(IntegerModNRing(134).modulus.data, fmpz))
        # assert IntegerModNRing(ZZ(134)) == IntegerModNRing(134)
        # assert IntegerModNRing(134) == IntegerModNRing(ZZ_py(134))
        assert r2(200) == r2(ZZ(200))
        assert r1(123) == r1(ZZ_py(123))
        assert (IntegerModNRing(ZZ(134)))(3) == (IntegerModNRing(ZZ(134)))(3)

    def test_data_class(self):
        """The data class of IntegerModN is flint.nmod as expected"""
        assert IntegerModN(r3, 3).data_class == nmod

    def test_str(self):
        """Test __str__ method output"""
        assert str(r1) == "The ring of Integers modulo 24."
        assert str(r1(10)) == "10"
        assert str(r1(27)) == "3"

    def test_repr(self):
        """Test __repr__ method output"""
        assert repr(r1) == "The ring of Integers modulo 24."
        assert repr(r1(10)) == "10"
        assert repr(r1(27)) == "3"

    def test_add(self):
        """Tests that the addition operator works correctly."""
        assert r2(30) + r2(35) == r2(65)
        assert r2(1000) + r2(146) == r2(108)

    def test_iadd(self):
        """Tests that the addition assignment operator works correctly."""
        x = r2(69)
        x += r2(21)
        assert x == r2(90)
        x += r2(83)
        assert x == r2(0)

    def test_mul(self):
        """Tests that the multiplication operator works correctly."""
        assert r1(12) * r1(2) == r1(0)
        assert r1(12) * r1(3) == r1(12)

    def test_imul(self):
        """Tests that the multiplication assignment operator works correctly."""
        x = r1(13)
        x *= r1(3)
        assert x == r1(15)

    def test_sub(self):
        """Tests that the subtraction operator works correctly."""
        assert r2(100) - r2(40) == r2(60)
        assert r1(100) - r1(40) == r1(60)
        assert r3(3) - r3(-6) == r3(9)
        assert r1(12) - r1(14) == r1(22)

    def test_isub(self):
        """Tests that the subtraction assignment operator works correctly."""
        x = r2(12)
        x -= r2(12)
        assert x == r2(0)

    def test_neg(self):
        """Tests that negation works correctly."""
        x = r1(5)
        x = -x
        assert x == r1(19)

    def test_truediv(self):
        """Tests that the (true) division operator works correctly."""
        r = IntegerModNRing(ZZ(7))
        assert r(3) / r(3) == r(1)
        assert r(3) / r(2) == r(5)

    def test_itruediv(self):
        """Tests that the (true) division assignment operator works correctly."""
        r = IntegerModNRing(ZZ(7))
        x = r(3)
        x /= r(2)
        assert x == r(5)

    def test_floordiv(self):
        """The floor division operator should not be implemented."""
        with pytest.raises(TypeError):
            x = r1(7) // r1(7)
            return x

    def test_ifloordiv(self):
        """The floor division assignment operator should not be implemented."""
        with pytest.raises(TypeError):
            x = r1(7)
            x //= r1(7)

    def test_divmod(self):
        """The divmod operator should not be implemented."""
        with pytest.raises(TypeError):
            x = divmod(r1(7), r1(7))
            return x

    def test_mod(self):
        """The modulo operator should not be implemented."""
        with pytest.raises(TypeError):
            x = r1(7) % r1(2)
            return x

    def test_imod(self):
        """The modulo assignment operator should not be implemented."""
        with pytest.raises(TypeError):
            x = r1(7)
            x %= r1(7)

    def test_pow(self):
        """Tests that the exponentiation (pow) operator works correctly."""
        assert r1(5) ** ZZ(2) == r1(1)
        assert r1(0) ** ZZ(5) == r1(0)
        assert r1(8) ** ZZ(0) == r1(1)
        assert r1(0) ** ZZ(0) == r1(1)

    def test_ipow(self):
        """Tests that the exponentiation (pow) assignment operator works correctly."""
        x = r3(9)
        x **= ZZ(3)
        assert x == r3(729)

    def test_abs(self):
        """The abs operator should not be implemented."""
        with pytest.raises(AttributeError):
            x = abs(r1(-7))
            return x

    def test_eq(self):
        """Tests that the equality operator works correctly."""
        assert r1(2) == r1(2)
        assert r1(2) == r1(26)
        assert not r1(2) == r1(3)
        with pytest.raises(AttributeError):
            return r1(2) == 2

    def test_ge(self):
        """The greater or equal operator should not be implemented."""
        with pytest.raises(TypeError):
            return r1(7) >= r1(7)

    def test_gt(self):
        """The greater operator should not be implemented."""
        with pytest.raises(TypeError):
            return r1(7) > r1(7)

    def test_le(self):
        """The less or equal operator should not be implemented."""
        with pytest.raises(TypeError):
            return r1(7) <= r1(7)

    def test_lt(self):
        """The less operator should not be implemented."""
        with pytest.raises(TypeError):
            return r1(7) < r1(7)

    def test_ne(self):
        """Tests that the inequality operator works correctly."""
        assert r1(2) != r1(3)
        assert not r1(2) != r1(26)
        assert not r1(2) != r1(2)
        with pytest.raises(AttributeError):
            return r1(2) != 3

    def test_polynomial(self):
        """Tests creation of polynomials with base ring of integers modulo n."""
        r = PolynomialRing(base_ring=r2, ngens=2, prefix="x")
        x0, x1 = r.gens
        f = r2(1) * x0
        assert f ** ZZ(2) == (x0 ** ZZ(2))
        assert f * x1 == x0 * x1
        assert r2(-2) * f == r2(171) * x0
