// SPDX-License-Identifier: Apache-2.0
// 
// Copyright 2011-2017 Ryan Curtin (http://www.ratml.org/)
// Copyright 2017 National ICT Australia (NICTA)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

#include <cstdio>
#include <armadillo>

#include "catch.hpp"

using namespace arma;

#if defined(ARMA_USE_HDF5)

TEST_CASE("hdf5_u8_test")
  {
  arma::Mat<u8> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<u8> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<u8> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_u16_test")
  {
  arma::Mat<u16> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<u16> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<u16> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_u32_test")
  {
  arma::Mat<u32> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<u32> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<u32> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



#ifdef ARMA_USE_U64S64
TEST_CASE("hdf5_u64_test")
  {
  arma::Mat<u64> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<u64> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<u64> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }
#endif



TEST_CASE("hdf5_s8_test")
  {
  arma::Mat<s8> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<s8> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<s8> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_s16_test")
  {
  arma::Mat<s16> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<s16> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<s16> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_s32_test")
  {
  arma::Mat<s32> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<s32> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<s32> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



#ifdef ARMA_USE_U64S64
TEST_CASE("hdf5_s64_test")
  {
  arma::Mat<s64> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<s64> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<s64> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }
#endif



TEST_CASE("hdf5_char_test")
  {
  arma::Mat<char> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<char> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<char> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_int_test")
  {
  arma::Mat<signed int> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<signed int> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<signed int> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_uint_test")
  {
  arma::Mat<unsigned int> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<unsigned int> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<unsigned int> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_short_test")
  {
  arma::Mat<signed short> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<signed short> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<signed short> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_ushort_test")
  {
  arma::Mat<unsigned short> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<unsigned short> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<unsigned short> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_long_test")
  {
  arma::Mat<signed long> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<signed long> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<signed long> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_ulong_test")
  {
  arma::Mat<unsigned long> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<unsigned long> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<unsigned long> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



#ifdef ARMA_USE_U64S64
TEST_CASE("hdf5_llong_test")
  {
  arma::Mat<signed long long> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<signed long long> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<signed long long> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_ullong_test")
  {
  arma::Mat<unsigned long long> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<unsigned long long> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<unsigned long long> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }
#endif



TEST_CASE("hdf5_float_test")
  {
  arma::Mat<float> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<float> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<float> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_double_test")
  {
  arma::Mat<double> a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<double> b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<double> c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_complex_float_test")
  {
  arma::Mat<std::complex<float> > a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<std::complex<float> > b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == b[i] );
    }

  // Now autoload.
  arma::Mat<std::complex<float> > c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_complex_double_test")
  {
  arma::Mat<std::complex<double> > a;
  a.randu(20, 20);

  // Save first.
  a.save("file.h5", hdf5_binary);

  // Load as different matrix.
  arma::Mat<std::complex<double> > b;
  b.load("file.h5", hdf5_binary);

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    REQUIRE( a[i] == b[i] );

  // Now autoload.
  arma::Mat<std::complex<double> > c;
  c.load("file.h5");

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_dataset_append_test")
  {
  arma::Mat<double> a;
  a.randu(20, 20);

  // Save first dataset.
  a.save( hdf5_name("file.h5", "dataset1") );

  arma::Mat<double> b;
  b.randu(10, 10);

  // Save second dataset.
  b.save( hdf5_name("file.h5", "dataset2", hdf5_opts::append) );

  // Load first dataset as different matrix.
  arma::Mat<double> c;
  c.load( hdf5_name("file.h5", "dataset1") );

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  // Load second dataset as different matrix.
  arma::Mat<double> d;
  d.load( hdf5_name("file.h5", "dataset2") );

  // Check that they are the same.
  for (uword i = 0; i < b.n_elem; ++i)
    {
    REQUIRE( b[i] == d[i] );
    }

  std::remove("file.h5");
  }

TEST_CASE("hdf5_cube_dataset_append_test")
  {
  arma::Mat<double> a;
  a.randu(20, 20);

  // Save first dataset.
  a.save( hdf5_name("file.h5", "dataset1") );

  arma::Cube<double> b;
  b.randu(10, 10, 10);

  // Save second dataset.
  b.save( hdf5_name("file.h5", "dataset2", hdf5_opts::append) );

  // Load first dataset as different matrix.
  arma::Mat<double> c;
  c.load( hdf5_name("file.h5", "dataset1") );

  // Check that they are the same.
  for (uword i = 0; i < a.n_elem; ++i)
    {
    REQUIRE( a[i] == c[i] );
    }

  // Load second dataset as different matrix.
  arma::Cube<double> d;
  d.load( hdf5_name("file.h5", "dataset2") );

  // Check that they are the same.
  for (uword i = 0; i < b.n_elem; ++i)
    {
    REQUIRE( b[i] == d[i] );
    }

  std::remove("file.h5");
  }


TEST_CASE("hdf5_dataset_append-overwrite-test")
  {
  arma::Mat<double> a;
  a.randu(20, 20);

  // Save first dataset.
  a.save( hdf5_name("file.h5", "dataset1") );

  arma::Mat<double> b;
  b.randu(10, 10);

  // Save second dataset.
  b.save( hdf5_name("file.h5", "dataset2") );

  // Load first dataset as different matrix and check that first dataset has been overwritten.
  arma::Mat<double> c;
  REQUIRE_FALSE( c.load( hdf5_name("file.h5", "dataset1") ) );

  // Load second dataset as different matrix.
  arma::Mat<double> d;
  d.load( hdf5_name("file.h5", "dataset2") );

  // Check that they are the same.
  for (uword i = 0; i < b.n_elem; ++i)
    {
    REQUIRE( b[i] == d[i] );
    }

  std::remove("file.h5");
  }



TEST_CASE("hdf5_dataset_same_dataset_twice_test")
  {
  arma::Mat<double> a;
  a.randu(20, 20);

  // Save first dataset.
  a.save(hdf5_name("file.h5", "dataset1"), hdf5_binary);

  arma::Mat<double> b;
  b.randu(10, 10);

  // Append second dataset with same name, causing failure.
  REQUIRE_FALSE( b.save(hdf5_name("file.h5", "dataset1", hdf5_opts::append) ) );

  std::remove("file.h5");
  }

#endif
