/*
    File                 : HDF5FilterTest.cpp
    Project              : LabPlot
    Description          : Tests for the HDF5 I/O-filter.
    --------------------------------------------------------------------
    SPDX-FileCopyrightText: 2021-2022 Stefan Gerlach <stefan.gerlach@uni.kn>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "HDF5FilterTest.h"
#include "backend/datasources/filters/HDF5Filter.h"
#include "backend/spreadsheet/Spreadsheet.h"
#include "backend/matrix/Matrix.h"

#include <KLocalizedString>

extern "C" {
#include <gsl/gsl_randist.h>
#include <gsl/gsl_rng.h>
#include <hdf5.h>
}

void HDF5FilterTest::testImportDouble() {
	Spreadsheet spreadsheet("test", false);
	HDF5Filter filter;

	const QString& fileName = QFINDTESTDATA(QLatin1String("data/hdf5_test.h5"));
	filter.setCurrentDataSetName(QLatin1String("/arrays/2D float array"));
	filter.readDataFromFile(fileName, &spreadsheet, AbstractFileFilter::ImportMode::Replace);

	QCOMPARE(spreadsheet.columnCount(), 50);
	QCOMPARE(spreadsheet.rowCount(), 100);
	for (int i = 0; i < spreadsheet.columnCount(); i++) {
		QCOMPARE(spreadsheet.column(i)->columnMode(), AbstractColumn::ColumnMode::Double);
		QCOMPARE(spreadsheet.column(i)->name(), QLatin1String("2D float array_") + QString::number(i+1));
	}

	QCOMPARE(spreadsheet.column(0)->plotDesignation(), AbstractColumn::PlotDesignation::X);
	for (int i = 1; i < spreadsheet.columnCount(); i++)
		QCOMPARE(spreadsheet.column(i)->plotDesignation(), AbstractColumn::PlotDesignation::Y);

	for (int i = 0; i < 4; i++)
	for (int j = 0; j < 4; j++)
		DEBUG(std::setprecision(15) << spreadsheet.column(j)->valueAt(i))

	QCOMPARE(spreadsheet.column(0)->valueAt(0), 0.000123456804431044);
	QCOMPARE(spreadsheet.column(1)->valueAt(0), 0.001234499970451);
	QCOMPARE(spreadsheet.column(2)->valueAt(0), 0);
	QCOMPARE(spreadsheet.column(3)->valueAt(0), 0);
	QCOMPARE(spreadsheet.column(0)->valueAt(1), 0);
	QCOMPARE(spreadsheet.column(1)->valueAt(1), 0.899999976158142);
	QCOMPARE(spreadsheet.column(2)->valueAt(1), 1.70000004768372);
	QCOMPARE(spreadsheet.column(3)->valueAt(1), 2.59999990463257);
	QCOMPARE(spreadsheet.column(0)->valueAt(2), 0);
	QCOMPARE(spreadsheet.column(1)->valueAt(2), 1.70000004768372);
	QCOMPARE(spreadsheet.column(2)->valueAt(2), 3.5);
	QCOMPARE(spreadsheet.column(3)->valueAt(2), 5.19999980926514);
	QCOMPARE(spreadsheet.column(0)->valueAt(3), 0.170000001788139);
	QCOMPARE(spreadsheet.column(1)->valueAt(3), 2.59999990463257);
	QCOMPARE(spreadsheet.column(2)->valueAt(3), 3.5);
	QCOMPARE(spreadsheet.column(3)->valueAt(3), 7.80000019073486);
}

void HDF5FilterTest::testImportDoublePortion() {
	Spreadsheet spreadsheet("test", false);
	HDF5Filter filter;

	const QString& fileName = QFINDTESTDATA(QLatin1String("data/hdf5_test.h5"));
	filter.setCurrentDataSetName(QLatin1String("/arrays/2D float array"));
	// set start/end row/col
	filter.setStartRow(2);
	filter.setEndRow(3);
	filter.setStartColumn(2);
	filter.setEndColumn(3);
	filter.readDataFromFile(fileName, &spreadsheet, AbstractFileFilter::ImportMode::Replace);

	QCOMPARE(spreadsheet.columnCount(), 2);
	QCOMPARE(spreadsheet.rowCount(), 2);
	for (int i = 0; i < 2; i++) {
		QCOMPARE(spreadsheet.column(i)->columnMode(), AbstractColumn::ColumnMode::Double);
		QCOMPARE(spreadsheet.column(i)->name(), QLatin1String("2D float array_") + QString::number(i+1));
	}

	QCOMPARE(spreadsheet.column(0)->plotDesignation(), AbstractColumn::PlotDesignation::X);
	QCOMPARE(spreadsheet.column(1)->plotDesignation(), AbstractColumn::PlotDesignation::Y);

	for (int i = 0; i < 2; i++)
	for (int j = 0; j < 2; j++)
		DEBUG(std::setprecision(15) << spreadsheet.column(j)->valueAt(i))

	QCOMPARE(spreadsheet.column(0)->valueAt(0), 0.899999976158142);
	QCOMPARE(spreadsheet.column(0)->valueAt(1), 1.70000004768372);
	QCOMPARE(spreadsheet.column(1)->valueAt(0), 1.70000004768372);
	QCOMPARE(spreadsheet.column(1)->valueAt(1), 3.5);
}

void HDF5FilterTest::testImportInt() {
	Spreadsheet spreadsheet("test", false);
	HDF5Filter filter;

	const QString& fileName = QFINDTESTDATA(QLatin1String("data/hdf5_test.h5"));
	filter.setCurrentDataSetName(QLatin1String("/arrays/2D int array"));
	filter.readDataFromFile(fileName, &spreadsheet, AbstractFileFilter::ImportMode::Replace);

	QCOMPARE(spreadsheet.columnCount(), 50);
	QCOMPARE(spreadsheet.rowCount(), 100);
	for (int i = 0; i < spreadsheet.columnCount(); i++) {
		QCOMPARE(spreadsheet.column(i)->columnMode(), AbstractColumn::ColumnMode::Integer);
		QCOMPARE(spreadsheet.column(i)->name(), QLatin1String("2D int array_") + QString::number(i+1));
	}

	QCOMPARE(spreadsheet.column(0)->plotDesignation(), AbstractColumn::PlotDesignation::X);
	for (int i = 1; i < spreadsheet.columnCount(); i++)
		QCOMPARE(spreadsheet.column(i)->plotDesignation(), AbstractColumn::PlotDesignation::Y);

	QCOMPARE(spreadsheet.column(0)->valueAt(0), 1000);
	QCOMPARE(spreadsheet.column(1)->valueAt(0), 1001);
	QCOMPARE(spreadsheet.column(2)->valueAt(0), 1002);
	QCOMPARE(spreadsheet.column(3)->valueAt(0), 1003);
	QCOMPARE(spreadsheet.column(0)->valueAt(1), 1100);
	QCOMPARE(spreadsheet.column(1)->valueAt(1), 1101);
	QCOMPARE(spreadsheet.column(2)->valueAt(1), 1102);
	QCOMPARE(spreadsheet.column(3)->valueAt(1), 1103);
	QCOMPARE(spreadsheet.column(0)->valueAt(2), 1200);
	QCOMPARE(spreadsheet.column(1)->valueAt(2), 1207);
	QCOMPARE(spreadsheet.column(2)->valueAt(2), 1202);
	QCOMPARE(spreadsheet.column(3)->valueAt(2), 1203);
	QCOMPARE(spreadsheet.column(0)->valueAt(3), 1300);
	QCOMPARE(spreadsheet.column(1)->valueAt(3), 1301);
	QCOMPARE(spreadsheet.column(2)->valueAt(3), 1302);
	QCOMPARE(spreadsheet.column(3)->valueAt(3), 1303);
}

void HDF5FilterTest::testImportIntPortion() {
	Spreadsheet spreadsheet("test", false);
	HDF5Filter filter;

	const QString& fileName = QFINDTESTDATA(QLatin1String("data/hdf5_test.h5"));
	filter.setCurrentDataSetName(QLatin1String("/arrays/2D int array"));
	// set start/end row/col
	filter.setStartRow(2);
	filter.setEndRow(3);
	filter.setStartColumn(2);
	filter.setEndColumn(3);
	filter.readDataFromFile(fileName, &spreadsheet, AbstractFileFilter::ImportMode::Replace);

	QCOMPARE(spreadsheet.columnCount(), 2);
	QCOMPARE(spreadsheet.rowCount(), 2);
	for (int i = 0; i < 2; i++) {
		QCOMPARE(spreadsheet.column(i)->columnMode(), AbstractColumn::ColumnMode::Integer);
		QCOMPARE(spreadsheet.column(i)->name(), QLatin1String("2D int array_") + QString::number(i+1));
	}

	QCOMPARE(spreadsheet.column(0)->plotDesignation(), AbstractColumn::PlotDesignation::X);
	QCOMPARE(spreadsheet.column(1)->plotDesignation(), AbstractColumn::PlotDesignation::Y);

	QCOMPARE(spreadsheet.column(0)->valueAt(0), 1101);
	QCOMPARE(spreadsheet.column(1)->valueAt(0), 1102);
	QCOMPARE(spreadsheet.column(0)->valueAt(1), 1207);
	QCOMPARE(spreadsheet.column(1)->valueAt(1), 1202);
}

// BENCHMARKS

void HDF5FilterTest::benchDoubleImport_data() {
	QTest::addColumn<size_t>("lineCount");
	// can't transfer file name since needed in clean up

	QTemporaryFile file;
	if (!file.open())	// needed to generate file name
		return;
	file.close();	// only file name is used

	benchDataFileName = file.fileName();
	benchDataFileName.append(".h5");

	QString testName(QString::number(paths) + QLatin1String(" random double paths"));

	QTest::newRow(testName.toLatin1()) << lines;
	DEBUG("CREATE DATA FILE " << STDSTRING(benchDataFileName) <<  ", lines = " << lines)

	gsl_rng_env_setup();
	gsl_rng* r = gsl_rng_alloc(gsl_rng_default);
	gsl_rng_set(r, 12345);

	// create file
        // see https://support.hdfgroup.org/HDF5/Tutor/introductory.html
        hid_t file_id = H5Fcreate(benchDataFileName.toLatin1(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);

        /* Create the data space for the dataset. */
        hsize_t dims[2];
        dims[0] = lines;
        dims[1] = paths;
        hid_t dataspace_id = H5Screate_simple(2, dims, nullptr);

        /* Create the dataset. */
        hid_t dataset_id = H5Dcreate(file_id, "/data", H5T_NATIVE_DOUBLE, dataspace_id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);

	// create data
	double path[paths] = {0.0};
	double *data = new double[paths*lines];

	const double delta = 0.25;
	const int dt = 1;
	const double sigma = delta*delta * dt;
	for (size_t i = 0; i < lines; ++i) {
		//std::cout << "line " << i+1 << std::endl;

		for (int p = 0; p < paths; ++p) {
			path[p] += gsl_ran_gaussian_ziggurat(r, sigma);
			data[p + i*paths] = path[p];
		}
	}

	herr_t status = H5Dwrite(dataset_id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, data);
        status = H5Dclose(dataset_id);
        status = H5Sclose(dataspace_id);
        status = H5Fclose(file_id);

	delete[] data;

	DEBUG(Q_FUNC_INFO << ", DONE")
}

void HDF5FilterTest::benchDoubleImport() {
	Spreadsheet spreadsheet("test", false);
	HDF5Filter filter;
	filter.setCurrentDataSetName(QLatin1String("/data"));

	const int p = paths;	// need local variable
	QBENCHMARK {
		filter.readDataFromFile(benchDataFileName, &spreadsheet, AbstractFileFilter::ImportMode::Replace);

		QCOMPARE(spreadsheet.columnCount(), p);
		QCOMPARE(spreadsheet.rowCount(), lines);

		QCOMPARE(spreadsheet.column(0)->valueAt(0), 0.120997813055);
		QCOMPARE(spreadsheet.column(1)->valueAt(0), 0.119301077563219);
		QCOMPARE(spreadsheet.column(2)->valueAt(0), -0.0209979608555485);
	}
}

void HDF5FilterTest::benchDoubleImport_cleanup() {
	DEBUG("REMOVE DATA FILE " << STDSTRING(benchDataFileName))
	QFile::remove(benchDataFileName);
}

QTEST_MAIN(HDF5FilterTest)
