from sympy import FF, symbols
from unittest import TestCase

from pyecsca.ec.mod import Mod, gcd, extgcd, Undefined, miller_rabin, has_gmp, RawMod, SymbolicMod, jacobi
from pyecsca.ec.error import NonInvertibleError, NonResidueError, NonInvertibleWarning, NonResidueWarning
from pyecsca.misc.cfg import getconfig, TemporaryConfig


class ModTests(TestCase):

    def test_gcd(self):
        self.assertEqual(gcd(15, 20), 5)
        self.assertEqual(extgcd(15, 0), (1, 0, 15))
        self.assertEqual(extgcd(15, 20), (-1, 1, 5))

    def test_jacobi(self):
        self.assertEqual(jacobi(5, 1153486465415345646578465454655646543248656451), 1)
        self.assertEqual(jacobi(564786456646845, 46874698564153465453246546545456849797895547657), -1)
        self.assertEqual(jacobi(564786456646845, 46874698564153465453246546545456849797895), 0)

    def test_miller_rabin(self):
        self.assertTrue(miller_rabin(2))
        self.assertTrue(miller_rabin(3))
        self.assertTrue(miller_rabin(5))
        self.assertFalse(miller_rabin(8))
        self.assertTrue(miller_rabin(0xe807561107ccf8fa82af74fd492543a918ca2e9c13750233a9))
        self.assertFalse(miller_rabin(0x6f6889deb08da211927370810f026eb4c17b17755f72ea005))

    def test_inverse(self):
        p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
        self.assertEqual(Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).inverse(), Mod(0x1cb2e5274bba085c4ca88eede75ae77949e7a410c80368376e97ab22eb590f9d, p))
        with self.assertRaises(NonInvertibleError):
            Mod(0, p).inverse()
        with self.assertRaises(NonInvertibleError):
            Mod(5, 10).inverse()
        getconfig().ec.no_inverse_action = "warning"
        with self.assertRaises(NonInvertibleWarning):
            Mod(0, p).inverse()
        with self.assertRaises(NonInvertibleWarning):
            Mod(5, 10).inverse()
        getconfig().ec.no_inverse_action = "ignore"
        Mod(0, p).inverse()
        Mod(5, 10).inverse()
        getconfig().ec.no_inverse_action = "error"

    def test_is_residue(self):
        self.assertTrue(Mod(4, 11).is_residue())
        self.assertFalse(Mod(11, 31).is_residue())
        self.assertTrue(Mod(0, 7).is_residue())
        self.assertTrue(Mod(1, 2).is_residue())

    def test_sqrt(self):
        p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
        self.assertIn(Mod(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc, p).sqrt(), (0x9add512515b70d9ec471151c1dec46625cd18b37bde7ca7fb2c8b31d7033599d, 0x6522aed9ea48f2623b8eeae3e213b99da32e74c9421835804d374ce28fcca662))
        with self.assertRaises(NonResidueError):
            Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt()
        getconfig().ec.non_residue_action = "warning"
        with self.assertRaises(NonResidueWarning):
            Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt()
        getconfig().ec.non_residue_action = "ignore"
        Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt()
        with TemporaryConfig() as cfg:
            cfg.ec.non_residue_action = "warning"
            with self.assertRaises(NonResidueWarning):
                Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt()
        self.assertEqual(Mod(0, p).sqrt(), Mod(0, p))
        q = 0x75d44fee9a71841ae8403c0c251fbad
        self.assertIn(Mod(0x591e0db18cf1bd81a11b2985a821eb3, q).sqrt(), (0x113b41a1a2b73f636e73be3f9a3716e, 0x64990e4cf7ba44b779cc7dcc8ae8a3f))
        getconfig().ec.non_residue_action = "error"

    def test_eq(self):
        self.assertEqual(Mod(1, 7), 1)
        self.assertNotEqual(Mod(1, 7), "1")
        self.assertEqual(Mod(1, 7), Mod(1, 7))
        self.assertNotEqual(Mod(1, 7), Mod(5, 7))
        self.assertNotEqual(Mod(1, 7), Mod(1, 5))

    def test_pow(self):
        a = Mod(5, 7)
        self.assertEqual(a**(-1), a.inverse())
        self.assertEqual(a**0, Mod(1, 7))
        self.assertEqual(a**(-2), a.inverse()**2)

    def test_wrong_mod(self):
        a = Mod(5, 7)
        b = Mod(4, 11)
        with self.assertRaises(ValueError):
            a + b

    def test_wrong_pow(self):
        a = Mod(5, 7)
        c = Mod(4, 11)
        with self.assertRaises(TypeError):
            a**c

    def test_other(self):
        a = Mod(5, 7)
        b = Mod(3, 7)
        self.assertEqual(int(-a), 2)
        self.assertEqual(str(a), "5")
        self.assertEqual(6 - a, Mod(1, 7))
        self.assertNotEqual(a, b)
        self.assertEqual(a / b, Mod(4, 7))
        self.assertEqual(a // b, Mod(4, 7))
        self.assertEqual(5 / b, Mod(4, 7))
        self.assertEqual(5 // b, Mod(4, 7))
        self.assertEqual(a / 3, Mod(4, 7))
        self.assertEqual(a // 3, Mod(4, 7))
        self.assertEqual(divmod(a, b), (Mod(1, 7), Mod(2, 7)))
        self.assertEqual(a + b, Mod(1, 7))
        self.assertEqual(5 + b, Mod(1, 7))
        self.assertEqual(a + 3, Mod(1, 7))
        self.assertNotEqual(a, 6)
        self.assertIsNotNone(hash(a))

    def test_undefined(self):
        u = Undefined()
        for k, meth in u.__class__.__dict__.items():
            if k in ("__module__", "__new__", "__init__", "__doc__", "__hash__", "__abstractmethods__", "_abc_impl"):
                continue
            args = [5 for _ in range(meth.__code__.co_argcount - 1)]
            if k == "__repr__":
                self.assertEqual(meth(u), "Undefined")
            elif k in ("__eq__", "__ne__"):
                assert not meth(u, *args)
            else:
                with self.assertRaises(NotImplementedError):
                    meth(u, *args)

    def test_implementation(self):
        if not has_gmp:
            self.skipTest("Only makes sense if more Mod implementations are available.")
        with TemporaryConfig() as cfg:
            cfg.ec.mod_implementation = "python"
            self.assertIsInstance(Mod(5, 7), RawMod)

    def test_symbolic(self):
        x, y = symbols("x y")
        p = 13
        k = FF(p)
        sx = SymbolicMod(x, p)
        a = k(3)
        b = k(5)
        r = sx * a + b
        self.assertIsInstance(r, SymbolicMod)
        self.assertEqual(r.n, p)
        sa = SymbolicMod(a, p)
        sb = SymbolicMod(b, p)
        self.assertEqual(sa, 3)
        self.assertEqual(sa.inverse(), SymbolicMod(k(9), p))
        self.assertEqual(1 / sa, SymbolicMod(k(9), p))
        self.assertEqual(sa + sb, 8)
        self.assertEqual(1 + sa, 4)
        self.assertEqual(sa - 1, 2)
        self.assertEqual(1 - sa, 11)
        self.assertEqual(sa + 1, 4)
        self.assertEqual(-sa, 10)
        self.assertEqual(sa / 2, 8)
        self.assertEqual(2 / sa, 5)
        self.assertEqual(sa // 2, 8)
        self.assertEqual(2 // sa, 5)
        self.assertEqual(int(sa), 3)
        self.assertNotEqual(sa, sb)
        self.assertIsNotNone(hash(sa))

from sympy import FF, symbols
from unittest import TestCase

from pyecsca.ec.mod import Mod, gcd, extgcd, Undefined, miller_rabin, has_gmp, RawMod, SymbolicMod, jacobi
from pyecsca.ec.error import NonInvertibleError, NonResidueError, NonInvertibleWarning, NonResidueWarning
from pyecsca.misc.cfg import getconfig, TemporaryConfig


class ModTests(TestCase):

    def test_gcd(self):
        self.assertEqual(gcd(15, 20), 5)
        self.assertEqual(extgcd(15, 0), (1, 0, 15))
        self.assertEqual(extgcd(15, 20), (-1, 1, 5))

    def test_jacobi(self):
        self.assertEqual(jacobi(5, 1153486465415345646578465454655646543248656451), 1)
        self.assertEqual(jacobi(564786456646845, 46874698564153465453246546545456849797895547657), -1)
        self.assertEqual(jacobi(564786456646845, 46874698564153465453246546545456849797895), 0)

    def test_miller_rabin(self):
        self.assertTrue(miller_rabin(2))
        self.assertTrue(miller_rabin(3))
        self.assertTrue(miller_rabin(5))
        self.assertFalse(miller_rabin(8))
        self.assertTrue(miller_rabin(0xe807561107ccf8fa82af74fd492543a918ca2e9c13750233a9))
        self.assertFalse(miller_rabin(0x6f6889deb08da211927370810f026eb4c17b17755f72ea005))

    def test_inverse(self):
        p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
        self.assertEqual(Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).inverse(), Mod(0x1cb2e5274bba085c4ca88eede75ae77949e7a410c80368376e97ab22eb590f9d, p))
        with self.assertRaises(NonInvertibleError):
            Mod(0, p).inverse()
        with self.assertRaises(NonInvertibleError):
            Mod(5, 10).inverse()
        getconfig().ec.no_inverse_action = "warning"
        with self.assertRaises(NonInvertibleWarning):
            Mod(0, p).inverse()
        with self.assertRaises(NonInvertibleWarning):
            Mod(5, 10).inverse()
        getconfig().ec.no_inverse_action = "ignore"
        Mod(0, p).inverse()
        Mod(5, 10).inverse()
        getconfig().ec.no_inverse_action = "error"

    def test_is_residue(self):
        self.assertTrue(Mod(4, 11).is_residue())
        self.assertFalse(Mod(11, 31).is_residue())
        self.assertTrue(Mod(0, 7).is_residue())
        self.assertTrue(Mod(1, 2).is_residue())

    def test_sqrt(self):
        p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
        self.assertIn(Mod(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc, p).sqrt(), (0x9add512515b70d9ec471151c1dec46625cd18b37bde7ca7fb2c8b31d7033599d, 0x6522aed9ea48f2623b8eeae3e213b99da32e74c9421835804d374ce28fcca662))
        with self.assertRaises(NonResidueError):
            Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt()
        getconfig().ec.non_residue_action = "warning"
        with self.assertRaises(NonResidueWarning):
            Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt()
        getconfig().ec.non_residue_action = "ignore"
        Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt()
        with TemporaryConfig() as cfg:
            cfg.ec.non_residue_action = "warning"
            with self.assertRaises(NonResidueWarning):
                Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt()
        self.assertEqual(Mod(0, p).sqrt(), Mod(0, p))
        q = 0x75d44fee9a71841ae8403c0c251fbad
        self.assertIn(Mod(0x591e0db18cf1bd81a11b2985a821eb3, q).sqrt(), (0x113b41a1a2b73f636e73be3f9a3716e, 0x64990e4cf7ba44b779cc7dcc8ae8a3f))
        getconfig().ec.non_residue_action = "error"

    def test_eq(self):
        self.assertEqual(Mod(1, 7), 1)
        self.assertNotEqual(Mod(1, 7), "1")
        self.assertEqual(Mod(1, 7), Mod(1, 7))
        self.assertNotEqual(Mod(1, 7), Mod(5, 7))
        self.assertNotEqual(Mod(1, 7), Mod(1, 5))

    def test_pow(self):
        a = Mod(5, 7)
        self.assertEqual(a**(-1), a.inverse())
        self.assertEqual(a**0, Mod(1, 7))
        self.assertEqual(a**(-2), a.inverse()**2)

    def test_wrong_mod(self):
        a = Mod(5, 7)
        b = Mod(4, 11)
        with self.assertRaises(ValueError):
            a + b

    def test_wrong_pow(self):
        a = Mod(5, 7)
        c = Mod(4, 11)
        with self.assertRaises(TypeError):
            a**c

    def test_other(self):
        a = Mod(5, 7)
        b = Mod(3, 7)
        self.assertEqual(int(-a), 2)
        self.assertEqual(str(a), "5")
        self.assertEqual(6 - a, Mod(1, 7))
        self.assertNotEqual(a, b)
        self.assertEqual(a / b, Mod(4, 7))
        self.assertEqual(a // b, Mod(4, 7))
        self.assertEqual(5 / b, Mod(4, 7))
        self.assertEqual(5 // b, Mod(4, 7))
        self.assertEqual(a / 3, Mod(4, 7))
        self.assertEqual(a // 3, Mod(4, 7))
        self.assertEqual(divmod(a, b), (Mod(1, 7), Mod(2, 7)))
        self.assertEqual(a + b, Mod(1, 7))
        self.assertEqual(5 + b, Mod(1, 7))
        self.assertEqual(a + 3, Mod(1, 7))
        self.assertNotEqual(a, 6)
        self.assertIsNotNone(hash(a))

    def test_undefined(self):
        u = Undefined()
        for k, meth in u.__class__.__dict__.items():
            if k in ("__module__", "__new__", "__init__", "__doc__", "__hash__", "__abstractmethods__", "_abc_impl"):
                continue
            args = [5 for _ in range(meth.__code__.co_argcount - 1)]
            if k == "__repr__":
                self.assertEqual(meth(u), "Undefined")
            elif k in ("__eq__", "__ne__"):
                assert not meth(u, *args)
            else:
                with self.assertRaises(NotImplementedError):
                    meth(u, *args)

    def test_implementation(self):
        if not has_gmp:
            self.skipTest("Only makes sense if more Mod implementations are available.")
        with TemporaryConfig() as cfg:
            cfg.ec.mod_implementation = "python"
            self.assertIsInstance(Mod(5, 7), RawMod)

    def test_symbolic(self):
        x, y = symbols("x y")
        p = 13
        k = FF(p)
        sx = SymbolicMod(x, p)
        a = k(3)
        b = k(5)
        r = sx * a + b
        self.assertIsInstance(r, SymbolicMod)
        self.assertEqual(r.n, p)
        sa = SymbolicMod(a, p)
        sb = SymbolicMod(b, p)
        self.assertEqual(sa, 3)
        self.assertEqual(sa.inverse(), SymbolicMod(k(9), p))
        self.assertEqual(1 / sa, SymbolicMod(k(9), p))
        self.assertEqual(sa + sb, 8)
        self.assertEqual(1 + sa, 4)
        self.assertEqual(sa - 1, 2)
        self.assertEqual(1 - sa, 11)
        self.assertEqual(sa + 1, 4)
        self.assertEqual(-sa, 10)
        self.assertEqual(sa / 2, 8)
        self.assertEqual(2 / sa, 5)
        self.assertEqual(sa // 2, 8)
        self.assertEqual(2 // sa, 5)
        self.assertEqual(int(sa), 3)
        self.assertNotEqual(sa, sb)
        self.assertIsNotNone(hash(sa))
