/// \author Kyungjoo Kim (kyukim@sandia.gov)

#include "gtest/gtest.h"
#include "Kokkos_Core.hpp"
#include "Kokkos_Random.hpp"

#include "KokkosBatched_Vector.hpp"

#include "KokkosKernels_TestUtils.hpp"

using namespace KokkosBatched;

namespace Test {

  template<typename VectorTagType,int VectorLength>
  void impl_test_batched_vector_misc() {
    /// random data initialization
    typedef Vector<VectorTagType,VectorLength> vector_type;
    
    typedef typename vector_type::value_type value_type;    
    const int vector_length = vector_type::vector_length;
    
    typedef Kokkos::Details::ArithTraits<value_type> ats;
    typedef typename ats::mag_type mag_type;

    vector_type a, b, c;
    const mag_type eps = 1.0e3 * ats::epsilon();

    Random<value_type> random;
    for (int iter=0;iter<100;++iter) {
      for (int k=0;k<vector_length;++k) {
        a[k] = random.value();
        b[k] = random.value();
      }

      {
        c = conditional_assign(a < b, a, b);
        for (int i=0;i<vector_length;++i) {
          const auto cc = a[i] < b[i] ? a[i] : b[i];
          EXPECT_NEAR_KK( c[i], cc, eps*c[i] );
        }

        c = 0; conditional_assign(c, a < b, a, b);
        for (int i=0;i<vector_length;++i) {
          const auto cc = a[i] < b[i] ? a[i] : b[i];
          EXPECT_NEAR_KK( c[i], cc, eps*c[i] );
        }
      }
      {
        c = conditional_assign(a < b, a, value_type(0));
        for (int i=0;i<vector_length;++i) {
          const auto cc = a[i] < b[i] ? a[i] : 0;
          EXPECT_NEAR_KK( c[i], cc, eps*c[i] );
        }

        c = 0; conditional_assign(c, a < b, a, value_type(0));
        for (int i=0;i<vector_length;++i) {
          const auto cc = a[i] < b[i] ? a[i] : 0;
          EXPECT_NEAR_KK( c[i], cc, eps*c[i] );
        }
      }
      {
        c = conditional_assign(a < b, value_type(0), b);
        for (int i=0;i<vector_length;++i) {
          const auto cc = a[i] < b[i] ? 0 : b[i];
          EXPECT_NEAR_KK( c[i], cc, eps*c[i] );
        }

        c = 0; conditional_assign(c, a < b, value_type(0), b);
        for (int i=0;i<vector_length;++i) {
          const auto cc = a[i] < b[i] ? 0 : b[i];
          EXPECT_NEAR_KK( c[i], cc, eps*c[i] );
        }
      }

      {
        typedef Vector<SIMD<bool>,VectorLength> vector_bool_type;
        vector_bool_type cond_all_true, cond_all_false, cond_alternate;
        
        for (int i=0;i<vector_length;++i) {
          cond_all_true [i] = true;
          cond_all_false[i] = false;
          cond_alternate[i] = i%2;
        }
        bool all_true, any_true;
        
        all_true = true; any_true = false;
        for (int i=0;i<vector_length;++i) {
          all_true &= cond_all_true[i];
          any_true |= cond_all_true[i];
        }
        EXPECT_EQ( all_true, true );
        EXPECT_EQ( any_true, true );
        
        all_true = true; any_true = false;
        for (int i=0;i<vector_length;++i) {
          all_true &= cond_all_false[i];
          any_true |= cond_all_false[i];
        }
        EXPECT_EQ( all_true, false );
        EXPECT_EQ( any_true, false );
        
        all_true = true; any_true = false;
        for (int i=0;i<vector_length;++i) {
          all_true &= cond_alternate[i];
          any_true |= cond_alternate[i];
        }
        EXPECT_EQ( all_true, false );
        EXPECT_EQ( any_true, true );
      }
      {
        value_type min_a = a[0], max_a = a[0], sum_a = 0, prod_a = 1;
        for (int i=0;i<vector_length;++i) {
          min_a = min(min_a, a[i]);
          max_a = max(max_a, a[i]);
          sum_a += a[i];
          prod_a *= a[i];
        }
        EXPECT_NEAR_KK( min_a,  min(a),  eps*min_a );
        EXPECT_NEAR_KK( max_a,  max(a),  eps*max_a );
        EXPECT_NEAR_KK( sum_a,  sum(a),  eps*sum_a );
        EXPECT_NEAR_KK( prod_a, prod(a), eps*prod_a );
      } // end test body
    } // end for
  } // impl
} // namespace

template<typename DeviceType,typename VectorTagType,int VectorLength>
int test_batched_vector_misc() {
  static_assert(Kokkos::Impl::SpaceAccessibility<DeviceType,Kokkos::HostSpace >::accessible,
                "vector datatype is only tested on host space");
  Test::impl_test_batched_vector_misc<VectorTagType,VectorLength>();
  
  return 0;
}


///
/// SIMD
///

#if defined(KOKKOSKERNELS_INST_FLOAT)
TEST_F( TestCategory, batched_vector_misc_simd_float3 ) {
  test_batched_vector_misc<TestExecSpace,SIMD<float>,3>();
}
TEST_F( TestCategory, batched_vector_misc_simd_float8 ) {
  test_batched_vector_misc<TestExecSpace,SIMD<float>,8>();
}
#endif

#if defined(KOKKOSKERNELS_INST_DOUBLE)
TEST_F( TestCategory, batched_vector_misc_simd_double3 ) {
  test_batched_vector_misc<TestExecSpace,SIMD<double>,3>();
}
TEST_F( TestCategory, batched_vector_misc_simd_double4 ) {
  test_batched_vector_misc<TestExecSpace,SIMD<double>,4>();
}
#endif

// #if defined(KOKKOSKERNELS_INST_COMPLEX_FLOAT)
// TEST_F( TestCategory, batched_vector_misc_simd_scomplex3 ) {
//   test_batched_vector_misc<TestExecSpace,SIMD<Kokkos::complex<float> >,3>();
// }
// TEST_F( TestCategory, batched_vector_misc_simd_scomplex4 ) {
//   test_batched_vector_misc<TestExecSpace,SIMD<Kokkos::complex<float> >,4>();
// }
// #endif

// #if defined(KOKKOSKERNELS_INST_COMPLEX_DOUBLE)
// TEST_F( TestCategory, batched_vector_misc_simd_dcomplex3 ) {
//   test_batched_vector_misc<TestExecSpace,SIMD<Kokkos::complex<double> >,3>();
// }
// TEST_F( TestCategory, batched_vector_misc_simd_dcomplex2 ) {
//   test_batched_vector_misc<TestExecSpace,SIMD<Kokkos::complex<double> >,2>();
// }
// #endif
