/***************************************************************************
                          ArrayOperationsTest.h  -  description
                             -------------------
    begin                : Jul 15, 2013
    copyright            : (C) 2013 by Tomas Oberhuber
    email                : tomas.oberhuber@fjfi.cvut.cz
 ***************************************************************************/

/* See Copyright Notice in tnl/Copyright */

#pragma once

#ifdef HAVE_GTEST 
#include <TNL/Containers/Algorithms/ArrayOperations.h>
#include <TNL/Devices/Cuda.h>

#include "gtest/gtest.h"

using namespace TNL;
using namespace TNL::Containers;
using namespace TNL::Containers::Algorithms;

constexpr int ARRAY_TEST_SIZE = 5000;

// test fixture for typed tests
template< typename Element >
class ArrayOperationsTest : public ::testing::Test
{
protected:
   using ElementType = Element;
};

// types for which ArrayTest is instantiated
using ElementTypes = ::testing::Types< short int, int, long, float, double >;

TYPED_TEST_CASE( ArrayOperationsTest, ElementTypes );

TYPED_TEST( ArrayOperationsTest, allocateMemory_host )
{
   using ElementType = typename TestFixture::ElementType;

   ElementType* data;
   ArrayOperations< Devices::Host >::allocateMemory( data, ARRAY_TEST_SIZE );
   ASSERT_NE( data, nullptr );

   ArrayOperations< Devices::Host >::freeMemory( data );
}

TYPED_TEST( ArrayOperationsTest, setMemoryElement_host )
{
   using ElementType = typename TestFixture::ElementType;
   const int size = ARRAY_TEST_SIZE;

   ElementType *data;
   ArrayOperations< Devices::Host >::allocateMemory( data, size );
   for( int i = 0; i < size; i++ ) {
      ArrayOperations< Devices::Host >::setMemoryElement( data + i, (ElementType) i );
      EXPECT_EQ( data[ i ], i );
      EXPECT_EQ( ArrayOperations< Devices::Host >::getMemoryElement( data + i ), i );
   }
   ArrayOperations< Devices::Host >::freeMemory( data );
}

TYPED_TEST( ArrayOperationsTest, setMemory_host )
{
   using ElementType = typename TestFixture::ElementType;
   const int size = ARRAY_TEST_SIZE;

   ElementType *data;
   ArrayOperations< Devices::Host >::allocateMemory( data, size );
   ArrayOperations< Devices::Host >::setMemory( data, (ElementType) 13, size );
   for( int i = 0; i < size; i ++ )
      EXPECT_EQ( data[ i ], 13 );
   ArrayOperations< Devices::Host >::freeMemory( data );
}

TYPED_TEST( ArrayOperationsTest, copyMemory_host )
{
   using ElementType = typename TestFixture::ElementType;
   const int size = ARRAY_TEST_SIZE;

   ElementType *data1, *data2;
   ArrayOperations< Devices::Host >::allocateMemory( data1, size );
   ArrayOperations< Devices::Host >::allocateMemory( data2, size );
   ArrayOperations< Devices::Host >::setMemory( data1, (ElementType) 13, size );
   ArrayOperations< Devices::Host >::copyMemory< ElementType, ElementType >( data2, data1, size );
   for( int i = 0; i < size; i ++ )
      EXPECT_EQ( data1[ i ], data2[ i ]);
   ArrayOperations< Devices::Host >::freeMemory( data1 );
   ArrayOperations< Devices::Host >::freeMemory( data2 );
}

TYPED_TEST( ArrayOperationsTest, copyMemoryWithConversion_host )
{
   using ElementType = typename TestFixture::ElementType;
   const int size = ARRAY_TEST_SIZE;

   int *data1;
   float *data2;
   ArrayOperations< Devices::Host >::allocateMemory( data1, size );
   ArrayOperations< Devices::Host >::allocateMemory( data2, size );
   ArrayOperations< Devices::Host >::setMemory( data1, 13, size );
   ArrayOperations< Devices::Host >::copyMemory< float, int >( data2, data1, size );
   for( int i = 0; i < size; i ++ )
      EXPECT_EQ( data1[ i ], data2[ i ] );
   ArrayOperations< Devices::Host >::freeMemory( data1 );
   ArrayOperations< Devices::Host >::freeMemory( data2 );
}

TYPED_TEST( ArrayOperationsTest, compareMemory_host )
{
   using ElementType = typename TestFixture::ElementType;
   const int size = ARRAY_TEST_SIZE;

   ElementType *data1, *data2;
   ArrayOperations< Devices::Host >::allocateMemory( data1, size );
   ArrayOperations< Devices::Host >::allocateMemory( data2, size );
   ArrayOperations< Devices::Host >::setMemory( data1, (ElementType) 7, size );
   ArrayOperations< Devices::Host >::setMemory( data2, (ElementType) 0, size );
   EXPECT_FALSE( ( ArrayOperations< Devices::Host >::compareMemory< ElementType, ElementType >( data1, data2, size ) ) );
   ArrayOperations< Devices::Host >::setMemory( data2, (ElementType) 7, size );
   EXPECT_TRUE( ( ArrayOperations< Devices::Host >::compareMemory< ElementType, ElementType >( data1, data2, size ) ) );
   ArrayOperations< Devices::Host >::freeMemory( data1 );
   ArrayOperations< Devices::Host >::freeMemory( data2 );
}

TYPED_TEST( ArrayOperationsTest, compareMemoryWithConversion_host )
{
   const int size = ARRAY_TEST_SIZE;

   int *data1;
   float *data2;
   ArrayOperations< Devices::Host >::allocateMemory( data1, size );
   ArrayOperations< Devices::Host >::allocateMemory( data2, size );
   ArrayOperations< Devices::Host >::setMemory( data1, 7, size );
   ArrayOperations< Devices::Host >::setMemory( data2, (float) 0.0, size );
   EXPECT_FALSE( ( ArrayOperations< Devices::Host >::compareMemory< int, float >( data1, data2, size ) ) );
   ArrayOperations< Devices::Host >::setMemory( data2, (float) 7.0, size );
   EXPECT_TRUE( ( ArrayOperations< Devices::Host >::compareMemory< int, float >( data1, data2, size ) ) );
   ArrayOperations< Devices::Host >::freeMemory( data1 );
   ArrayOperations< Devices::Host >::freeMemory( data2 );
}


#ifdef HAVE_CUDA
TYPED_TEST( ArrayOperationsTest, allocateMemory_cuda )
{
   using ElementType = typename TestFixture::ElementType;
   const int size = ARRAY_TEST_SIZE;

   ElementType* data;
   ArrayOperations< Devices::Cuda >::allocateMemory( data, size );
   ASSERT_TRUE( TNL_CHECK_CUDA_DEVICE );
   ASSERT_NE( data, nullptr );

   ArrayOperations< Devices::Cuda >::freeMemory( data );
   ASSERT_TRUE( TNL_CHECK_CUDA_DEVICE );
}

TYPED_TEST( ArrayOperationsTest, setMemoryElement_cuda )
{
   using ElementType = typename TestFixture::ElementType;
   const int size = ARRAY_TEST_SIZE;

   ElementType* data;
   ArrayOperations< Devices::Cuda >::allocateMemory( data, size );
   ASSERT_TRUE( TNL_CHECK_CUDA_DEVICE );

   for( int i = 0; i < size; i++ )
      ArrayOperations< Devices::Cuda >::setMemoryElement( &data[ i ], (ElementType) i );

   for( int i = 0; i < size; i++ )
   {
      ElementType d;
      ASSERT_EQ( cudaMemcpy( &d, &data[ i ], sizeof( ElementType ), cudaMemcpyDeviceToHost ), cudaSuccess );
      EXPECT_EQ( d, i );
      EXPECT_EQ( ArrayOperations< Devices::Cuda >::getMemoryElement( &data[ i ] ), i );
   }

   ArrayOperations< Devices::Cuda >::freeMemory( data );
   ASSERT_TRUE( TNL_CHECK_CUDA_DEVICE );
}

TYPED_TEST( ArrayOperationsTest, setMemory_cuda )
{
   using ElementType = typename TestFixture::ElementType;
   const int size = ARRAY_TEST_SIZE;

   ElementType *hostData, *deviceData;
   ArrayOperations< Devices::Host >::allocateMemory( hostData, size );
   ArrayOperations< Devices::Cuda >::allocateMemory( deviceData, size );
   ArrayOperations< Devices::Host >::setMemory( hostData, (ElementType) 0, size );
   ArrayOperations< Devices::Cuda >::setMemory( deviceData, (ElementType) 13, size );
   ASSERT_TRUE( TNL_CHECK_CUDA_DEVICE );
   ArrayOperations< Devices::Host, Devices::Cuda >::copyMemory< ElementType, ElementType >( hostData, deviceData, size );
   ASSERT_TRUE( TNL_CHECK_CUDA_DEVICE );
   for( int i = 0; i < size; i++ )
      EXPECT_EQ( hostData[ i ], 13 );
   ArrayOperations< Devices::Host >::freeMemory( hostData );
   ArrayOperations< Devices::Cuda >::freeMemory( deviceData );
}

TYPED_TEST( ArrayOperationsTest, copyMemory_cuda )
{
   using ElementType = typename TestFixture::ElementType;
   const int size = ARRAY_TEST_SIZE;

   ElementType *hostData, *hostData2, *deviceData, *deviceData2;
   ArrayOperations< Devices::Host >::allocateMemory( hostData, size );
   ArrayOperations< Devices::Host >::allocateMemory( hostData2, size );
   ArrayOperations< Devices::Cuda >::allocateMemory( deviceData, size );
   ArrayOperations< Devices::Cuda >::allocateMemory( deviceData2, size );
   ArrayOperations< Devices::Host >::setMemory( hostData, (ElementType) 13, size );
   ArrayOperations< Devices::Cuda, Devices::Host >::copyMemory< ElementType >( deviceData, hostData, size );
   ArrayOperations< Devices::Cuda >::copyMemory< ElementType, ElementType >( deviceData2, deviceData, size );
   ArrayOperations< Devices::Host, Devices::Cuda >::copyMemory< ElementType, ElementType >( hostData2, deviceData2, size );
   EXPECT_TRUE( ( ArrayOperations< Devices::Host >::compareMemory< ElementType, ElementType >( hostData, hostData2, size) ) );
   ArrayOperations< Devices::Host >::freeMemory( hostData );
   ArrayOperations< Devices::Host >::freeMemory( hostData2 );
   ArrayOperations< Devices::Cuda >::freeMemory( deviceData );
   ArrayOperations< Devices::Cuda >::freeMemory( deviceData2 );
}

TYPED_TEST( ArrayOperationsTest, copyMemoryWithConversions_cuda )
{
   const int size = ARRAY_TEST_SIZE;

   int *hostData;
   double *hostData2;
   long *deviceData;
   float *deviceData2;
   ArrayOperations< Devices::Host >::allocateMemory( hostData, size );
   ArrayOperations< Devices::Host >::allocateMemory( hostData2, size );
   ArrayOperations< Devices::Cuda >::allocateMemory( deviceData, size );
   ArrayOperations< Devices::Cuda >::allocateMemory( deviceData2, size );
   ArrayOperations< Devices::Host >::setMemory( hostData, 13, size );
   ArrayOperations< Devices::Cuda, Devices::Host >::copyMemory< long, int >( deviceData, hostData, size );
   ArrayOperations< Devices::Cuda >::copyMemory< float, long >( deviceData2, deviceData, size );
   ArrayOperations< Devices::Host, Devices::Cuda >::copyMemory< double, float >( hostData2, deviceData2, size );
   for( int i = 0; i < size; i ++ )
      EXPECT_EQ( hostData[ i ], hostData2[ i ] );
   ArrayOperations< Devices::Host >::freeMemory( hostData );
   ArrayOperations< Devices::Host >::freeMemory( hostData2 );
   ArrayOperations< Devices::Cuda >::freeMemory( deviceData );
   ArrayOperations< Devices::Cuda >::freeMemory( deviceData2 );
}

TYPED_TEST( ArrayOperationsTest, compareMemory_cuda )
{
   using ElementType = typename TestFixture::ElementType;
   const int size = ARRAY_TEST_SIZE;

   ElementType *hostData, *deviceData, *deviceData2;
   ArrayOperations< Devices::Host >::allocateMemory( hostData, size );
   ArrayOperations< Devices::Cuda >::allocateMemory( deviceData, size );
   ArrayOperations< Devices::Cuda >::allocateMemory( deviceData2, size );

   ArrayOperations< Devices::Host >::setMemory( hostData, (ElementType) 7, size );
   ArrayOperations< Devices::Cuda >::setMemory( deviceData, (ElementType) 8, size );
   ArrayOperations< Devices::Cuda >::setMemory( deviceData2, (ElementType) 9, size );
   EXPECT_FALSE(( ArrayOperations< Devices::Host, Devices::Cuda >::compareMemory< ElementType, ElementType >( hostData, deviceData, size ) ));
   EXPECT_FALSE(( ArrayOperations< Devices::Cuda, Devices::Host >::compareMemory< ElementType, ElementType >( deviceData, hostData, size ) ));
   EXPECT_FALSE(( ArrayOperations< Devices::Cuda >::compareMemory< ElementType, ElementType >( deviceData, deviceData2, size ) ));

   ArrayOperations< Devices::Cuda >::setMemory( deviceData, (ElementType) 7, size );
   ArrayOperations< Devices::Cuda >::setMemory( deviceData2, (ElementType) 7, size );
   EXPECT_TRUE(( ArrayOperations< Devices::Host, Devices::Cuda >::compareMemory< ElementType, ElementType >( hostData, deviceData, size ) ));
   EXPECT_TRUE(( ArrayOperations< Devices::Cuda, Devices::Host >::compareMemory< ElementType, ElementType >( deviceData, hostData, size ) ));
   EXPECT_TRUE(( ArrayOperations< Devices::Cuda >::compareMemory< ElementType, ElementType >( deviceData, deviceData2, size ) ));

   ArrayOperations< Devices::Host >::freeMemory( hostData );
   ArrayOperations< Devices::Cuda >::freeMemory( deviceData );
   ArrayOperations< Devices::Cuda >::freeMemory( deviceData2 );
}

TYPED_TEST( ArrayOperationsTest, compareMemoryWithConversions_cuda )
{
   const int size = ARRAY_TEST_SIZE;

   int *hostData;
   float *deviceData;
   double *deviceData2;
   ArrayOperations< Devices::Host >::allocateMemory( hostData, size );
   ArrayOperations< Devices::Cuda >::allocateMemory( deviceData, size );
   ArrayOperations< Devices::Cuda >::allocateMemory( deviceData2, size );

   ArrayOperations< Devices::Host >::setMemory( hostData, 7, size );
   ArrayOperations< Devices::Cuda >::setMemory( deviceData, (float) 8, size );
   ArrayOperations< Devices::Cuda >::setMemory( deviceData2, (double) 9, size );
   EXPECT_FALSE(( ArrayOperations< Devices::Host, Devices::Cuda >::compareMemory< int, float >( hostData, deviceData, size ) ));
   EXPECT_FALSE(( ArrayOperations< Devices::Cuda, Devices::Host >::compareMemory< float, int >( deviceData, hostData, size ) ));
   EXPECT_FALSE(( ArrayOperations< Devices::Cuda >::compareMemory< float, double >( deviceData, deviceData2, size ) ));

   ArrayOperations< Devices::Cuda >::setMemory( deviceData, (float) 7, size );
   ArrayOperations< Devices::Cuda >::setMemory( deviceData2, (double) 7, size );
   EXPECT_TRUE(( ArrayOperations< Devices::Host, Devices::Cuda >::compareMemory< int, float >( hostData, deviceData, size ) ));
   EXPECT_TRUE(( ArrayOperations< Devices::Cuda, Devices::Host >::compareMemory< float, int >( deviceData, hostData, size ) ));
   EXPECT_TRUE(( ArrayOperations< Devices::Cuda >::compareMemory< float, double >( deviceData, deviceData2, size ) ));

   ArrayOperations< Devices::Host >::freeMemory( hostData );
   ArrayOperations< Devices::Cuda >::freeMemory( deviceData );
   ArrayOperations< Devices::Cuda >::freeMemory( deviceData2 );
}
#endif // HAVE_CUDA
#endif // HAVE_GTEST


#include "../GtestMissingError.h"
int main( int argc, char* argv[] )
{
#ifdef HAVE_GTEST
   ::testing::InitGoogleTest( &argc, argv );
   return RUN_ALL_TESTS();
#else
   throw GtestMissingError();
#endif
}
