/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include <catch2/catch_all.hpp>

#include "IO/Files/Pfm.hpp"

#include <sstream>
#include <string>
#include <iomanip>
#include <cstring>
#include <memory>
#include <vector>

using namespace OpenVulkano;

namespace
{
	bool almostEqual(float a, float b, float epsilon = 0.001f) { return std::fabs(a - b) < epsilon; }
}

TEST_CASE("Invalid input", "[Pfm]")
{
	std::stringstream ss("Invalid\n1024 768\n1.0\n");
	PfmHeader header;

	REQUIRE_THROWS_AS(ss >> header, std::runtime_error);
}

TEST_CASE("Extreme maxValue handling", "[Pfm]")
{
	PfmHeader largeMaxValueHeader(1024, 768, 1000000.0f, true);
	std::stringstream ss;
	ss << largeMaxValueHeader;

	REQUIRE(ss.str() == "PF\n1024 768\n-1000000.0\n");

	std::stringstream inputSS("PF\n1024 768\n-1000000.0\n");
	PfmHeader parsedHeader;
	inputSS >> parsedHeader;
	REQUIRE(parsedHeader.maxValue == 1000000.0f);
	REQUIRE(parsedHeader.littleEndian == true);
}

TEST_CASE("Negative maxValue indicating endian", "[Pfm]")
{
	std::stringstream ss("PF\n640 480\n-1.0\n");
	PfmHeader header;
	ss >> header;

	REQUIRE(header.color == true);
	REQUIRE(header.width == 640);
	REQUIRE(header.height == 480);
	REQUIRE(header.maxValue == 1.0f);
	REQUIRE(header.littleEndian == true);
}

TEST_CASE("Operator<< output precision", "[Pfm]")
{
	PfmHeader header(640, 480, 1.234567f, true);
	std::stringstream ss;
	ss << header;

	REQUIRE(ss.str() == "PF\n640 480\n-1.2\n");
}

TEST_CASE("Zero width or height", "[Pfm]")
{
	PfmHeader zeroWidth(0, 768, 1.0f, true);
	PfmHeader zeroHeight(1024, 0, 1.0f, false);

	REQUIRE(zeroWidth.GetElementCount() == 0);
	REQUIRE(zeroHeight.GetElementCount() == 0);
	REQUIRE(zeroWidth.GetImageSize() == 0);
	REQUIRE(zeroHeight.GetImageSize() == 0);
}

TEST_CASE("Large width and height", "[Pfm]")
{
	PfmHeader largeHeader(10000, 8000, 1.0f, true);

	REQUIRE(largeHeader.GetElementCount() == 10000 * 8000 * 3);
	REQUIRE(largeHeader.GetImageSize() == 10000 * 8000 * 3 * sizeof(float));
}

TEST_CASE("Comparing headers", "[Pfm]")
{
	PfmHeader header1(640, 480, 1.0f, true);
	PfmHeader header2(640, 480, 1.0f, true);
	PfmHeader header3(640, 480, 1.0f, false);
	PfmHeader header4(1024, 768, 1.0f, true);

	REQUIRE(header1.width == header2.width);
	REQUIRE(header1.height == header2.height);
	REQUIRE(header1.color == header2.color);

	REQUIRE(header1.color != header3.color);
	REQUIRE(header1.width != header4.width);
	REQUIRE(header1.height != header4.height);
}

TEST_CASE("Read with incorrect header", "[Pfm]")
{
	std::stringstream ss("Invalid\n800 600\n-1.0\n");
	REQUIRE_THROWS_AS(PfmImage::ReadImage(ss), std::runtime_error);
}

TEST_CASE("Endian little-endian", "[Pfm]")
{
	std::stringstream ss;
	ss << "Pf\n2 2\n-1.0\n";
	std::vector<float> imageData = { 0.1f, 0.2f, 0.3f, 0.4f };
	ss.write(reinterpret_cast<const char*>(imageData.data()), imageData.size() * sizeof(float));
	PfmImage image = PfmImage::ReadImage(ss);
	REQUIRE(image.header.width == 2);
	REQUIRE(image.header.height == 2);
	REQUIRE(image.header.color == false);
	REQUIRE(image.header.maxValue == 1.0f);
	for (size_t i = 0; i < imageData.size(); ++i)
	{
		REQUIRE(almostEqual(image.image[i],imageData[i]));
	}
}

TEST_CASE("Read with exact data match", "[Pfm]")
{
	std::stringstream ss;
	ss << "Pf\n2 2\n-1.0\n";
	std::vector<float> imageData = { 0.1f, 0.2f, 0.3f, 0.4f };
	ss.write(reinterpret_cast<const char*>(imageData.data()), imageData.size() * sizeof(float));

	PfmImage image = PfmImage::ReadImage(ss);
	REQUIRE(image.header.width == 2);
	REQUIRE(image.header.height == 2);
	REQUIRE(image.header.color == false);
	REQUIRE(image.header.maxValue == 1.0f);

	for (size_t i = 0; i < imageData.size(); ++i)
	{
		REQUIRE(almostEqual(image.image[i], imageData[i]));
	}
}

TEST_CASE("ReadImage with big-endian data", "[Pfm]")
{
	std::stringstream ss;
	ss << "Pf\n2 2\n1.0\n";
	std::vector<float> imageData = { 0.1f, 0.2f, 0.3f, 0.4f };

	{
		std::vector<float> imageDataBigEndian = imageData;
		char* data = reinterpret_cast<char*>(imageDataBigEndian.data());
		for (size_t i = 0; i < imageDataBigEndian.size(); i++)
		{
			size_t idx = i * sizeof(float);
			std::reverse(&data[idx], &data[idx + sizeof(float)]);
		}
		ss.write(reinterpret_cast<const char*>(imageDataBigEndian.data()), imageDataBigEndian.size() * sizeof(float));
	}
	PfmImage image = PfmImage::ReadImage(ss);

	for (size_t i = 0; i < imageData.size(); ++i)
	{
		REQUIRE(almostEqual(image.image[i], imageData[i]));
	}
}