//----------------------------------------------------------------------------//
//                                                                            //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation  //
// and distributed under the MIT License (MIT).                               //
//                                                                            //
// Copyright (c) Guillaume Blanc                                              //
//                                                                            //
// Permission is hereby granted, free of charge, to any person obtaining a    //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation  //
// the rights to use, copy, modify, merge, publish, distribute, sublicense,   //
// and/or sell copies of the Software, and to permit persons to whom the      //
// Software is furnished to do so, subject to the following conditions:       //
//                                                                            //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software.                        //
//                                                                            //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        //
// DEALINGS IN THE SOFTWARE.                                                  //
//                                                                            //
//----------------------------------------------------------------------------//

#include "gtest/gtest.h"
#include "ozz/base/gtest_helper.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/maths/soa_quaternion.h"

using ozz::math::SimdInt4;
using ozz::math::SoaFloat3;
using ozz::math::SoaFloat4;
using ozz::math::SoaQuaternion;

TEST(SoaQuaternionConstant, ozz_soa_math) {
  EXPECT_SOAQUATERNION_EQ(SoaQuaternion::identity(), 0.f, 0.f, 0.f, 0.f, 0.f,
                          0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f,
                          1.f);
}

TEST(SoaQuaternionArithmetic, ozz_soa_math) {
  const SoaQuaternion a = SoaQuaternion::Load(
      ozz::math::simd_float4::Load(.70710677f, 0.f, 0.f, .382683432f),
      ozz::math::simd_float4::Load(0.f, 0.f, .70710677f, 0.f),
      ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f),
      ozz::math::simd_float4::Load(.70710677f, 1.f, .70710677f, .9238795f));
  const SoaQuaternion b = SoaQuaternion::Load(
      ozz::math::simd_float4::Load(0.f, .70710677f, 0.f, -.382683432f),
      ozz::math::simd_float4::Load(0.f, 0.f, .70710677f, 0.f),
      ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f),
      ozz::math::simd_float4::Load(1.f, .70710677f, .70710677f, .9238795f));
  const SoaQuaternion denorm =
      SoaQuaternion::Load(ozz::math::simd_float4::Load(.5f, 0.f, 2.f, 3.f),
                          ozz::math::simd_float4::Load(4.f, 0.f, 6.f, 7.f),
                          ozz::math::simd_float4::Load(8.f, 0.f, 10.f, 11.f),
                          ozz::math::simd_float4::Load(12.f, 1.f, 14.f, 15.f));

  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(a)));
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(b)));
  EXPECT_SIMDINT_EQ(ozz::math::IsNormalized(denorm), 0, 0xffffffff, 0, 0);

  const SoaQuaternion conjugate = Conjugate(a);
  EXPECT_SOAQUATERNION_EQ(conjugate, -.70710677f, -0.f, -0.f, -.382683432f,
                          -0.f, -0.f, -.70710677f, -0.f, -0.f, -0.f, -0.f, -0.f,
                          .70710677f, 1.f, .70710677f, .9238795f);
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(conjugate)));

  const SoaQuaternion negate = -a;
  EXPECT_SOAQUATERNION_EQ(negate, -.70710677f, 0.f, 0.f, -.382683432f, 0.f, 0.f,
                          -.70710677f, 0.f, 0.f, 0.f, 0.f, 0.f, -.70710677f,
                          -1.f, -.70710677f, -.9238795f);

  const SoaQuaternion add = a + b;
  EXPECT_SOAQUATERNION_EQ(add, .70710677f, .70710677f, 0.f, 0.f, 0.f, 0.f,
                          1.41421354f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.70710677f,
                          1.70710677f, 1.41421354f, 1.847759f);

  const SoaQuaternion muls = a * ozz::math::simd_float4::Load1(2.f);
  EXPECT_SOAQUATERNION_EQ(muls, 1.41421354f, 0.f, 0.f, .765366864f, 0.f, 0.f,
                          1.41421354f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.41421354f,
                          2.f, 1.41421354f, 1.847759f);

  const SoaQuaternion mul0 = a * conjugate;
  EXPECT_SOAQUATERNION_EQ(mul0, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
                          0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f);
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(mul0)));

  const SoaQuaternion mul1 = conjugate * a;
  EXPECT_SOAQUATERNION_EQ(mul1, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
                          0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f);
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(mul1)));

  const ozz::math::SimdFloat4 dot = Dot(a, b);
  EXPECT_SOAFLOAT1_EQ(dot, .70710677f, .70710677f, 1.f, .70710677f);

  const SoaQuaternion normalize = Normalize(denorm);
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(normalize)));
  EXPECT_SOAQUATERNION_EQ(normalize, .033389f, 0.f, .1091089f, .1492555f,
                          .267112f, 0.f, .3273268f, .348263f, .53422445f, 0.f,
                          .545544f, .547270f, .80133667f, 1.f, .763762f,
                          .74627789f);

  const SoaQuaternion normalize_est = NormalizeEst(denorm);
  EXPECT_SOAQUATERNION_EQ_EST(normalize_est, .033389f, 0.f, .1091089f,
                              .1492555f, .267112f, 0.f, .3273268f, .348263f,
                              .53422445f, 0.f, .545544f, .547270f, .80133667f,
                              1.f, .763762f, .74627789f);
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalizedEst(normalize_est)));

  const SoaQuaternion lerp_0 = Lerp(a, b, ozz::math::simd_float4::zero());
  EXPECT_SOAQUATERNION_EQ(lerp_0, .70710677f, 0.f, 0.f, .382683432f, 0.f, 0.f,
                          .70710677f, 0.f, 0.f, 0.f, 0.f, 0.f, .70710677f, 1.f,
                          .70710677f, .9238795f);

  const SoaQuaternion lerp_1 = Lerp(a, b, ozz::math::simd_float4::one());
  EXPECT_SOAQUATERNION_EQ(lerp_1, 0.f, .70710677f, 0.f, -.382683432f, 0.f, 0.f,
                          .70710677f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, .70710677f,
                          .70710677f, .9238795f);

  const SoaQuaternion lerp_0_2 = Lerp(a, b, ozz::math::simd_float4::Load1(.2f));
  EXPECT_SOAQUATERNION_EQ(lerp_0_2, .565685416f, .14142136f, 0.f, .22961006f,
                          0.f, 0.f, .70710677f, 0.f, 0.f, 0.f, 0.f, 0.f,
                          .76568544f, .94142133f, .70710677f, .92387950f);

  const SoaQuaternion lerp_m =
      Lerp(a, b, ozz::math::simd_float4::Load(0.f, 1.f, 1.f, .2f));
  EXPECT_SOAQUATERNION_EQ(lerp_m, .70710677f, .70710677f, 0.f, .22961006f, 0.f,
                          0.f, .70710677f, 0.f, 0.f, 0.f, 0.f, 0.f, .70710677f,
                          .70710677f, .70710677f, .92387950f);

  const SoaQuaternion nlerp_0 = NLerp(a, b, ozz::math::simd_float4::zero());
  EXPECT_SOAQUATERNION_EQ(nlerp_0, .70710677f, 0.f, 0.f, .382683432f, 0.f, 0.f,
                          .70710677f, 0.f, 0.f, 0.f, 0.f, 0.f, .70710677f, 1.f,
                          .70710677f, .9238795f);
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(nlerp_0)));

  const SoaQuaternion nlerp_1 = NLerp(a, b, ozz::math::simd_float4::one());
  EXPECT_SOAQUATERNION_EQ(nlerp_1, 0.f, .70710677f, 0.f, -.382683432f, 0.f, 0.f,
                          .70710677f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, .70710677f,
                          .70710677f, .9238795f);
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(nlerp_1)));

  const SoaQuaternion nlerp_0_2 =
      NLerp(a, b, ozz::math::simd_float4::Load1(.2f));
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(nlerp_0_2)));
  EXPECT_SOAQUATERNION_EQ(nlerp_0_2, .59421712f, .14855431f, 0.f, .24119100f,
                          0.f, 0.f, .70710683f, 0.f, 0.f, 0.f, 0.f, 0.f,
                          .80430466f, .98890430f, .70710683f, .97047764f);
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(nlerp_0_2)));

  const SoaQuaternion nlerp_m =
      NLerp(a, b, ozz::math::simd_float4::Load(0.f, 1.f, 1.f, .2f));
  EXPECT_SOAQUATERNION_EQ(nlerp_m, .70710677f, .70710677f, 0.f, .24119100f, 0.f,
                          0.f, .70710677f, 0.f, 0.f, 0.f, 0.f, 0.f, .70710677f,
                          .70710677f, .70710677f, .97047764f);
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalized(nlerp_m)));

  const SoaQuaternion nlerp_est_m =
      NLerpEst(a, b, ozz::math::simd_float4::Load(0.f, 1.f, 1.f, .2f));
  EXPECT_SOAQUATERNION_EQ_EST(nlerp_est_m, .70710677f, .70710677f, 0.f,
                              .24119100f, 0.f, 0.f, .70710677f, 0.f, 0.f, 0.f,
                              0.f, 0.f, .70710677f, .70710677f, .70710677f,
                              .97047764f);
  EXPECT_TRUE(ozz::math::AreAllTrue(IsNormalizedEst(nlerp_est_m)));

  const SimdInt4 same_aa = Compare(a, a, ozz::math::simd_float4::Load1(.99f));
  EXPECT_TRUE(ozz::math::AreAllTrue(same_aa));

  const SimdInt4 same_ana =
      Compare(a, Normalize(a), ozz::math::simd_float4::Load1(.99f));
  EXPECT_TRUE(ozz::math::AreAllTrue(same_ana));

  const SimdInt4 same_anega =
      Compare(a, -a, ozz::math::simd_float4::Load1(.99f));
  EXPECT_TRUE(ozz::math::AreAllTrue(same_anega));

  const SimdInt4 same_ab = Compare(a, b, ozz::math::simd_float4::Load1(.99f));
  EXPECT_SIMDINT_EQ(same_ab, 0, 0, 0xffffffff, 0);
}
