/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#define VSTREAM_DOMAIN "test-HDF5MIA"

#include <mia/internal/autotest.hh>

#include <boost/mpl/vector.hpp>
#include <boost/test/test_case_template.hpp>
#include <boost/mpl/insert_range.hpp>

#include <addons/hdf5/hdf5a_mia.hh>

NS_MIA_USE
using namespace std;
namespace bmpl = boost::mpl;

typedef bmpl::vector<int8_t,
        uint8_t,
        int16_t,
        uint16_t,
        int32_t,
        uint32_t,
        int64_t,
        uint64_t,
        float,
        double
        > test_types;

class HDF5CoreFileFixture
{

protected:
       HDF5CoreFileFixture();
       ~HDF5CoreFileFixture();

       const H5File& get_file() const;
private:
       H5Property access_plist;
       H5File  m_core_file;

};



HDF5CoreFileFixture::HDF5CoreFileFixture()
{
       access_plist = H5Property::create(H5P_FILE_ACCESS);
       H5Pset_fapl_core (access_plist, 1024, 0);
       m_core_file = H5File::create("core.h5", H5F_ACC_TRUNC, H5P_DEFAULT, access_plist);
}

HDF5CoreFileFixture::~HDF5CoreFileFixture()
{
}


const H5File& HDF5CoreFileFixture::get_file() const
{
       return m_core_file;
}




BOOST_FIXTURE_TEST_CASE(test_core_hdf5_io_driver,  HDF5CoreFileFixture)
{
}

BOOST_FIXTURE_TEST_CASE(test_simple_dataset,  HDF5CoreFileFixture)
{
       hsize_t dims[2] = {2, 3};
       vector<int32_t> data = {1, 2, 3, 4, 5, 6};
       auto mem_type_in = Mia_to_h5_types<int32_t>::mem_datatype();
       // write the data set
       {
              auto file_type = Mia_to_h5_types<int32_t>::file_datatype();
              auto space = H5Space::create(2, dims);
              auto dataset = H5Dataset::create(get_file(), "/testset", file_type, space);
              dataset.write_data(data, int32_t());
              cvdebug() << "Get the data set back (first)\n";
              auto dataset2 = H5Dataset::open(get_file(), "/testset");
              auto size = dataset2.get_size();
              BOOST_CHECK_EQUAL(size.size(), 2u);
              BOOST_CHECK_EQUAL(size[0], 2u);
              BOOST_CHECK_EQUAL(size[1], 3u);
       }
       // close data set automatically, and now reopen it
       cvdebug() << "second part\n";
       {
              auto dataset = H5Dataset::open(get_file(), "/testset");
              auto size = dataset.get_size();
              BOOST_CHECK_EQUAL(size.size(), 2u);
              BOOST_CHECK_EQUAL(size[0], 2u);
              BOOST_CHECK_EQUAL(size[1], 3u);
              H5Type file_type(H5Dget_type(dataset));
              H5Type mem_type = file_type.get_native_type();
              BOOST_CHECK(H5Tequal( mem_type, mem_type_in ) > 0);
              vector<int32_t> read_data(6);
              dataset.read_data(read_data, int32_t());

              for (int i = 0; i < 6; ++i)
                     BOOST_CHECK_EQUAL(read_data[i], data[i]);
       }
}


BOOST_FIXTURE_TEST_CASE(test_bool_dataset,  HDF5CoreFileFixture)
{
       hsize_t dims[2] = {2, 3};
       vector<bool> data {false, true, true, false, false, true};
       auto mem_type_in = Mia_to_h5_types<bool>::mem_datatype();
       // write the data set
       {
              auto file_type = Mia_to_h5_types<bool>::file_datatype();
              auto space = H5Space::create(2, dims);
              auto dataset = H5Dataset::create(get_file(), "/testset", file_type, space);
              dataset.write_data(data, true);
       }
       // close data set automatically, and now reopen it
       {
              auto dataset = H5Dataset::open(get_file(), "/testset");
              auto size = dataset.get_size();
              BOOST_CHECK_EQUAL(size.size(), 2u);
              BOOST_CHECK_EQUAL(size[0], 2u);
              BOOST_CHECK_EQUAL(size[1], 3u);
              H5Type file_type(H5Dget_type(dataset));
              H5Type mem_type = file_type.get_native_type();
              BOOST_CHECK(H5Tequal( mem_type, mem_type_in ) > 0);
              vector<bool> read_data(6);
              dataset.read_data(read_data, false);

              for (int i = 0; i < 6; ++i)
                     BOOST_CHECK_EQUAL(read_data[i], data[i]);
       }
}

template <typename T>
class TestDatasetFixture : public HDF5CoreFileFixture
{

protected:
       void test(const string& path, const std::vector<hsize_t>& size, const std::vector<T>& data);
private:
       void save(const string& path, const std::vector<hsize_t>& size, const std::vector<T>& data);
       void read_and_test(const string& path, const std::vector<hsize_t>& test_size, const std::vector<T>& test_data);

};

template <typename T>
void TestDatasetFixture<T>::test(const string& path, const std::vector<hsize_t>& size, const std::vector<T>& data)
{
       save(path, size, data);
       read_and_test(path, size, data);
}


template <typename T>
void TestDatasetFixture<T>::save(const string& path, const std::vector<hsize_t>& size, const std::vector<T>& data)
{
       auto file_type = Mia_to_h5_types<T>::file_datatype();
       auto space = H5Space::create(size);
       auto dataset = H5Dataset::create(get_file(), path.c_str(), file_type, space);
       dataset.write_data(data, T());
}

template <typename T>
void TestDatasetFixture<T>::read_and_test(const string& path, const std::vector<hsize_t>& test_size,
              const std::vector<T>& test_data)
{
       auto dataset = H5Dataset::open(get_file(), path.c_str());
       auto size = dataset.get_size();
       BOOST_CHECK_EQUAL(size.size(), test_size.size());
       BOOST_REQUIRE(size.size() == test_size.size());
       size_t length = 1;

       for (size_t i = 0; i < size.size(); ++i) {
              BOOST_CHECK_EQUAL(size[i], test_size[i]);
              length *= test_size[i];
       }

       H5Type file_type(H5Dget_type(dataset));
       H5Type mem_type = file_type.get_native_type();
       BOOST_REQUIRE(H5Tequal( mem_type, Mia_to_h5_types<T>::mem_datatype() ) > 0);
       std::vector<T> read_data(length);
       dataset.read_data(read_data, T());

       for (size_t i = 0; i < length; ++i)
              BOOST_CHECK_EQUAL(read_data[i], test_data[i]);
}

template <typename T>
class TestDatasetIOInGroupFixture : public TestDatasetFixture<T>
{
public:
       void run();

};


template <typename T>
void TestDatasetIOInGroupFixture<T>::run()
{
       vector<hsize_t> dims = {2, 3};
       vector<T>  data = {1, 2, 3, 4, 5, 6};
       const char path[] = "/group1/group2/testset";
       this->test(path, dims, data);
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_dataset_io, T, test_types )
{
       TestDatasetIOInGroupFixture<T>().run();
}


template <typename T>
class TestAttrfixture : public HDF5CoreFileFixture
{
public:
       void run();
};

template <typename T>
void TestAttrfixture<T>::run()
{
       const T value = 10;
       PAttribute attr(new TAttribute<T>(value));
       auto h5attr = H5AttributeTranslatorMap::instance().translate(get_file(), "attr", *attr);
       auto pattr = H5AttributeTranslatorMap::instance().translate("attr", h5attr);
       int test_type = attribute_type<T>::value;
       BOOST_CHECK_EQUAL(pattr->type_id(), test_type);
       auto rattr = dynamic_cast<const TAttribute<T>*>(pattr.get());

       if (!rattr)  {
              cvdebug() << "cast from'" << typeid(attr.get()).name()
                        << "' to '" << typeid(TAttribute<T> *).name() << "' failed\n";
       }

       BOOST_REQUIRE(rattr);
       BOOST_CHECK_EQUAL(*rattr, value);
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_attributes, T, test_types )
{
       TestAttrfixture<T>().run();
}



template <typename T>
class TestVectorAttrfixture : public HDF5CoreFileFixture
{
public:
       void run();
};

template <typename T>
void TestVectorAttrfixture<T>::run()
{
       const vector<T> value = {10, 20, 30};
       PAttribute attr(new TAttribute<vector<T>>(value));
       auto h5attr = H5AttributeTranslatorMap::instance().translate(get_file(), "attr", *attr);
       auto pattr = H5AttributeTranslatorMap::instance().translate("attr", h5attr);
       int test_type = attribute_type<vector<T>>::value;
       BOOST_CHECK_EQUAL(pattr->type_id(), test_type);
       auto rattr = dynamic_cast<const TAttribute<vector<T>> *>(pattr.get());

       if (!rattr)  {
              cvdebug() << "cast from'" << typeid(attr.get()).name()
                        << "' to '" << typeid(TAttribute<T> *).name() << "' failed\n";
       }

       BOOST_REQUIRE(rattr);
       const vector<T> rvalue = *rattr;
       BOOST_CHECK_EQUAL(rvalue.size(), value.size());
       BOOST_REQUIRE(rvalue.size() == value.size());

       for (auto r = rvalue.begin(), v = value.begin(); r != rvalue.end(); ++r, ++v) {
              BOOST_CHECK_EQUAL(*r, *v);
       }
}



BOOST_AUTO_TEST_CASE_TEMPLATE( test_vector_attributes, T, test_types )
{
       TestVectorAttrfixture<T>().run();
}


BOOST_FIXTURE_TEST_CASE (test_string_attribute, HDF5CoreFileFixture)
{
       string value("a test string");
       PAttribute attr(new TAttribute<string>(value));
       auto h5attr = H5AttributeTranslatorMap::instance().translate(get_file(), "attr", *attr);
       auto pattr = H5AttributeTranslatorMap::instance().translate("attr", h5attr);
       BOOST_REQUIRE(pattr);
       int test_type = attribute_type<string>::value;
       BOOST_CHECK_EQUAL(pattr->type_id(), test_type);
       auto& rattr = dynamic_cast<const TAttribute<string>&>(*pattr);
       const string rvalue = rattr;
       BOOST_CHECK_EQUAL(rvalue, value);
};

BOOST_FIXTURE_TEST_CASE (test_vstring_attribute, HDF5CoreFileFixture)
{
       const vector<string> value = {"a test string", "another test string"};
       PAttribute attr(new TAttribute<vector<string>>(value));
       auto h5attr = H5AttributeTranslatorMap::instance().translate(get_file(), "attr", *attr);
       auto pattr = H5AttributeTranslatorMap::instance().translate("attr", h5attr);
       BOOST_REQUIRE(pattr);
       int test_type = attribute_type<vector<string>>::value;
       BOOST_CHECK_EQUAL(pattr->type_id(), test_type);
       auto& rattr = dynamic_cast<const TAttribute<vector<string>>&>(*pattr);
       const vector<string> rvalue = rattr;
       BOOST_CHECK_EQUAL(rvalue.size(), value.size());

       for (auto r = rvalue.begin(), v = value.begin(); r != rvalue.end(); ++r, ++v) {
              BOOST_CHECK_EQUAL(*r, *v);
       }
};

BOOST_FIXTURE_TEST_CASE (test_attribute_list, HDF5CoreFileFixture)
{
       CAttributedData original_data;
       original_data.set_attribute("string", PAttribute(new CStringAttribute("some string")));
       original_data.set_attribute("int", PAttribute(new CIntAttribute(10)));
       original_data.set_attribute("vfloat", PAttribute(new CVFloatAttribute({1.0, 2.3, 3.4})));
       translate_to_hdf5_attributes(get_file(), original_data);
       CAttributedData loaded_data = get_file().read_attributes();
       BOOST_CHECK_EQUAL(loaded_data, original_data);
}
