import numpy.testing as nt
import matplotlib.pyplot as plt
import unittest

"""
we will assume that the primitives rotx,trotx, etc. all work
"""
from math import pi
from spatialmath.pose2d import *
# from spatialmath import super_pose as sp
from spatialmath.base import *
import spatialmath.base.argcheck as argcheck
import spatialmath as sm
from spatialmath.baseposematrix import BasePoseMatrix
from spatialmath.twist import BaseTwist

def array_compare(x, y):
    if isinstance(x, BasePoseMatrix):
        x = x.A
    if isinstance(y, BasePoseMatrix):
        y = y.A
    if isinstance(x, BaseTwist):
        x = x.S
    if isinstance(y, BaseTwist):
        y = y.S
    nt.assert_array_almost_equal(x, y)


class TestSO2(unittest.TestCase):

    @classmethod
    def tearDownClass(cls):
        plt.close('all')

    def test_constructor(self):
        
        
        # null case
        x = SO2()
        self.assertIsInstance(x, SO2)
        self.assertEqual(len(x), 1)
        array_compare(x.A, np.eye(2,2))
        
        ## from angle
        
        array_compare(SO2(0).A, np.eye(2))
        array_compare(SO2(pi / 2).A, rot2(pi / 2))
        array_compare(SO2(90, unit='deg').A, rot2(pi / 2))
       
        ## from R
        
        array_compare(SO2(np.eye(2,2)).A, np.eye(2,2))
    
        array_compare(SO2( rot2(pi / 2)).A, rot2(pi / 2))
        array_compare(SO2( rot2(pi)).A, rot2(pi))
           
        
        ## R,T
        array_compare(SO2( np.eye(2)).R, np.eye(2))
       
        array_compare(SO2( rot2(pi / 2)).R, rot2(pi / 2))
        
        
        ## vectorised forms of R
        R = SO2.Empty()
        for theta in [-pi / 2, 0, pi / 2, pi]:
            R.append(SO2(theta))
        self.assertEqual(len(R), 4)
        array_compare(R[0], rot2(-pi / 2))
        array_compare(R[3], rot2(pi))

        # TODO self.assertEqual(SO2(R).R, R)
        
        ## copy constructor
        r = SO2(0.3)
        c = SO2(r)
        array_compare(r, c)
        r = SO2(0.4)
        array_compare(c, SO2(0.3))
    
    def test_concat(self):
        x = SO2()
        xx = SO2([x, x, x, x])
        
        self.assertIsInstance(xx, SO2)
        self.assertEqual(len(xx), 4)
    
    def test_primitive_convert(self):
        # char
        
        s = str( SO2())
        self.assertIsInstance(s, str)
    
    def test_shape(self):
        a = SO2()
        self.assertEqual(a._A.shape, a.shape)

    def test_constructor_Exp(self):

        array_compare(SO2.Exp( skew(0.3)).R, rot2(0.3))
        array_compare(SO2.Exp( 0.3).R, rot2(0.3))

        x = SO2.Exp([0, 0.3, 1])
        self.assertEqual(len(x), 3)
        array_compare(x[0], rot2(0))
        array_compare(x[1], rot2(0.3))
        array_compare(x[2], rot2(1))

        x = SO2.Exp([skew(x) for x in [0, 0.3, 1]])
        self.assertEqual(len(x), 3)
        array_compare(x[0], rot2(0))
        array_compare(x[1], rot2(0.3))
        array_compare(x[2], rot2(1))
    
    def test_isa(self):
        
        self.assertTrue(SO2.isvalid(rot2(0)))
    
        self.assertFalse(SO2.isvalid(1))
    
    def test_resulttype(self):
        
        r = SO2()
        self.assertIsInstance(r, SO2)
    
        self.assertIsInstance(r * r, SO2)
        
    
        self.assertIsInstance(r / r, SO2)
        
        self.assertIsInstance(r.inv(), SO2)
    
    
    def test_multiply(self):
        
        vx = np.r_[1, 0]
        vy = np.r_[0, 1]
        
        r0 = SO2(0)
        r1 = SO2(pi / 2)
        r2 = SO2(pi)
        u = SO2()
        
        ## SO2-SO2, product
        # scalar x scalar
        
        array_compare(r0 * u, r0)
        array_compare(u * r0, r0)
        
        # vector x vector
        array_compare(SO2([r0, r1, r2]) * SO2([r2, r0, r1]), SO2([r0 * r2, r1 * r0, r2 * r1]))
        
        # scalar x vector
        array_compare(r1 * SO2([r0, r1, r2]), SO2([r1 * r0, r1 * r1, r1 * r2]))
        
        # vector x scalar
        array_compare(SO2([r0, r1, r2]) * r2, SO2([r0 * r2, r1 * r2, r2 * r2]))
        
        ## SO2-vector product
        # scalar x scalar
        
        array_compare(r1 * vx, np.c_[vy])
        
        # vector x vector
        #array_compare(SO2([r0, r1, r0]) * np.c_[vy, vx, vx], np.c_[vy, vy, vx])
        
        # scalar x vector
        array_compare(r1 * np.c_[vx, vy, -vx], np.c_[vy, -vx, -vy])
        
        # vector x scalar
        array_compare(SO2([r0, r1, r2]) * vy, np.c_[vy, -vx, -vy])

    
    def test_divide(self):
        
        r0 = SO2(0)
        r1 = SO2(pi / 2)
        r2 = SO2(pi)
        u = SO2()
        
        # scalar / scalar
        # implicity tests inv
    
        array_compare(r1 / u, r1)
        array_compare(r1 / r1, u)
    
        # vector / vector
        array_compare(SO2([r0, r1, r2]) / SO2([r2, r1, r0]), SO2([r0 / r2, r1 / r1, r2 / r0]))
        
        # vector / scalar
        array_compare(SO2([r0, r1, r2]) / r1, SO2([r0 / r1, r1 / r1, r2 / r1]))
    
    
    def test_conversions(self):
        
        T = SO2(pi / 2).SE2()

        self.assertIsInstance(T, SE2)
    
        
        ## Lie stuff
        th = 0.3
        RR = SO2(th)
        array_compare(RR.log(), skew(th))
    
    
    def test_miscellany(self):
        
        r = SO2( 0.3,)
        self.assertAlmostEqual(np.linalg.det(r.A), 1)
        
        self.assertEqual(r.N, 2)
        
        self.assertFalse(r.isSE)
    
    
    def test_printline(self):
        
        R = SO2( 0.3)
        
        R.printline()
        # s = R.printline(file=None)
        # self.assertIsInstance(s, str)

        R = SO2([0.3, 0.4, 0.5])
        s = R.printline(file=None)
        # self.assertIsInstance(s, str)
        # self.assertEqual(s.count('\n'), 2)
        
    def test_plot(self):
        plt.close('all')
        
        R = SO2( 0.3)
        R.plot(block=False)
        
        R2 = SO2(0.6)
        # R.animate()
        # R.animate(start=R2)
        
        
# ============================== SE2 =====================================#

class TestSE2(unittest.TestCase):

    @classmethod
    def tearDownClass(cls):
        plt.close('all')

    def test_constructor(self):
        
        self.assertIsInstance(SE2(), SE2)
    
        ## null
        array_compare(SE2().A, np.eye(3,3))
        
        # from x,y
        x = SE2(2, 3)
        self.assertIsInstance(x, SE2)
        self.assertEqual(len(x), 1)
        array_compare(x.A, np.array([[1,0,2],[0,1,3],[0,0,1]]))
        
        x = SE2([2, 3])
        self.assertIsInstance(x, SE2)
        self.assertEqual(len(x), 1)
        array_compare(x.A, np.array([[1,0,2],[0,1,3],[0,0,1]]))

        # from x,y,theta
        x = SE2(2, 3, pi / 2)
        self.assertIsInstance(x, SE2)
        self.assertEqual(len(x), 1)
        array_compare(x.A, np.array([[0,-1,2],[1,0,3],[0,0,1]]))
        
        x = SE2([2, 3, pi / 2])
        self.assertIsInstance(x, SE2)
        self.assertEqual(len(x), 1)
        array_compare(x.A, np.array([[0,-1,2],[1,0,3],[0,0,1]]))
        
        x = SE2(2, 3, 90, unit='deg')
        self.assertIsInstance(x, SE2)
        self.assertEqual(len(x), 1)
        array_compare(x.A, np.array([[0,-1,2],[1,0,3],[0,0,1]]))
        
        x = SE2([2, 3, 90], unit='deg')
        self.assertIsInstance(x, SE2)
        self.assertEqual(len(x), 1)
        array_compare(x.A, np.array([[0,-1,2],[1,0,3],[0,0,1]]))
    
        
        ## T
        T = transl2(1, 2) @ trot2(0.3)
        x = SE2(T)
        self.assertIsInstance(x, SE2)
        self.assertEqual(len(x), 1)
        array_compare(x.A, T)
        
        
        ## copy constructor
        TT = SE2(x)
        array_compare(SE2(TT).A, T)
        x = SE2()
        array_compare(SE2(TT).A, T)
        
        ## vectorised versions
        
        T1 = transl2(1,2) @ trot2(0.3)
        T2 = transl2(1,-2) @ trot2(-0.4)
        
        x =SE2([T1, T2, T1, T2])
        self.assertIsInstance(x, SE2)
        self.assertEqual(len(x), 4)
        array_compare(x[0], T1)
        array_compare(x[1], T2)

    def test_shape(self):
        a = SE2()
        self.assertEqual(a._A.shape, a.shape)

    def test_concat(self):
        x = SE2()
        xx = SE2([x, x, x, x])
        
        self.assertIsInstance(xx, SE2)
        self.assertEqual(len(xx), 4)
    
    
    def test_constructor_Exp(self):

        array_compare(SE2.Exp(skewa([1,2,0])), transl2(1,2))
        array_compare(SE2.Exp(np.r_[1,2,0]), transl2(1,2))

        x = SE2.Exp([(1,2,0), (3,4,0), (5,6,0)])
        self.assertEqual(len(x), 3)
        array_compare(x[0], transl2(1,2))
        array_compare(x[1], transl2(3,4))
        array_compare(x[2], transl2(5,6))

        x = SE2.Exp([skewa(x) for x in [(1,2,0), (3,4,0), (5,6,0)]])
        self.assertEqual(len(x), 3)
        array_compare(x[0], transl2(1,2))
        array_compare(x[1], transl2(3,4))
        array_compare(x[2], transl2(5,6))
    
    def test_isa(self):
        
        self.assertTrue(SE2.isvalid(trot2(0)))
        self.assertFalse(SE2.isvalid(1))

    
    def test_resulttype(self):
        
        t = SE2()
        self.assertIsInstance(t, SE2)
        self.assertIsInstance(t * t, SE2)
        self.assertIsInstance(t / t, SE2)
        self.assertIsInstance(t.inv(), SE2)
        self.assertIsInstance(t + t, np.ndarray)
        self.assertIsInstance(t + 1, np.ndarray)
        self.assertIsInstance(t - 1, np.ndarray)
        self.assertIsInstance(1 + t, np.ndarray)
        self.assertIsInstance(1 - t, np.ndarray)
        self.assertIsInstance(2 * t, np.ndarray)
        self.assertIsInstance(t * 2, np.ndarray)

    
    def test_inverse(self):    
        
        T1 = transl2(1, 2) @ trot2(0.3)
        TT1 = SE2(T1)
        
        # test inverse
        array_compare(TT1.inv().A, np.linalg.inv(T1))
        
        array_compare(TT1 * TT1.inv(),  np.eye(3))
        array_compare(TT1.inv() * TT1, np.eye(3))
        
        # vector case
        TT2 = SE2([TT1, TT1])
        u = [np.eye(3), np.eye(3)]
        array_compare(TT2.inv() * TT1, u)
    
    
    def test_Rt(self):
       
        
        TT1 = SE2.Rand()
        T1 = TT1.A
        R1 = t2r(T1)
        t1 = transl2(T1)
        
        array_compare(TT1.A, T1)
        array_compare(TT1.R, R1)
        array_compare(TT1.t, t1)
        
        TT = SE2([TT1, TT1, TT1])
        array_compare(TT.t, [t1, t1, t1])
    
    
    def test_arith(self):
        
        
        TT1 = SE2.Rand()
        T1 = TT1.A
        TT2 = SE2.Rand()
        T2 = TT2.A
    
        I = SE2()
        
        ## SE2, * SE2, product
        # scalar x scalar
        
        array_compare(TT1 * TT2, T1 @ T2)
        array_compare(TT2 * TT1, T2 @ T1)
        array_compare(TT1 * I, T1)
        array_compare(TT2 * I, TT2)

        
        # vector x vector
        array_compare(SE2([TT1, TT1, TT2]) * SE2([TT2, TT1, TT1]), SE2([TT1*TT2, TT1*TT1, TT2*TT1]))
        
        # scalar x vector
        array_compare(TT1 * SE2([TT2, TT1]), SE2([TT1*TT2, TT1*TT1]))
        
        # vector x scalar
        array_compare(SE2([TT1, TT2]) * TT2, SE2([TT1*TT2, TT2*TT2]))
        
        ## SE2, * vector product
        vx = np.r_[1, 0]
        vy = np.r_[0, 1]
    
        # scalar x scalar
        
        array_compare(TT1 * vy, h2e( T1 @ e2h(vy)))
        
        # # vector x vector
        # array_compare(SE2([TT1, TT2]) * np.c_[vx, vy], np.c_[h2e(T1 @ e2h(vx)), h2e(T2 @ e2h(vy))])
        
        # scalar x vector
        array_compare(TT1 * np.c_[vx, vy], h2e( T1 @ e2h(np.c_[vx, vy])))
        
        # vector x scalar
        array_compare(SE2([TT1, TT2, TT1]) * vy, np.c_[h2e(T1 @ e2h(vy)), h2e(T2 @ e2h(vy)), h2e(T1 @ e2h(vy))])
    
    def test_defs(self):
        
        # log
        # x = SE2.Exp([2, 3, 0.5])
        # array_compare(x.log(), np.array([[0, -0.5, 2], [0.5, 0, 3], [0, 0, 0]]))
        pass
    
    def test_conversions(self):
        
        
        ##  SE2,                     convert to SE2, class
    
        TT = SE2(1, 2, 0.3)
        
        array_compare(TT, transl2(1, 2) @ trot2(0.3))
        
        ## xyt
        array_compare(TT.xyt(), np.r_[1, 2, 0.3])
        
        ## Lie stuff
        x = TT.log()
        self.assertTrue(isskewa(x))

    
    def test_interp(self):
        TT = SE2(2, -4, 0.6)
        I = SE2()
        
        z = I.interp(TT, s=0)
        self.assertIsInstance(z, SE2)
        
        array_compare(I.interp(TT, s=0), I)
        array_compare(I.interp(TT, s=1), TT)
        array_compare(I.interp(TT, s=0.5), SE2(1, -2, 0.3))
    
    def test_miscellany(self):
        
        TT = SE2(1, 2, 0.3)
        
        self.assertEqual(TT.A.shape, (3,3))
            
        self.assertTrue(TT.isSE)
        
        self.assertIsInstance(TT, SE2)
    
    def test_display(self):
        
        T1 = SE2.Rand()
        
        T1.printline()
        
    def test_graphics(self):
        
        plt.close('all')
        T1 = SE2.Rand()
        T2 = SE2.Rand()
        
        T1.plot(block=False, dims=[-2,2])
        
        T1.animate(repeat=False, dims=[-2,2], nframes=10)
        T1.animate(T0=T2, repeat=False, dims=[-2,2], nframes=10)


# ---------------------------------------------------------------------------------------#
if __name__ == '__main__':

    unittest.main(buffer=True)
