/*
 * 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/Pnm.hpp"

#include <sstream>
#include <cmath>
#include <memory>
#include <stdexcept>
#include <string_view>
#include <string>

using namespace OpenVulkano;

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

TEST_CASE("Default constructor initializes with expected values", "[Pnm]")
{
	PnmHeader header;

	REQUIRE(header.width == 0);
	REQUIRE(header.height == 0);
	REQUIRE(header.maxValue == 0);
	REQUIRE(header.color == PnmHeader::ColorMode::GRAY);
	REQUIRE(header.ascii == false);
}

TEST_CASE("Constructor initializes with correct values", "[Pnm]")
{
	PnmHeader header;

	header = PnmHeader(100, 200, true);

	REQUIRE(header.width == 100);
	REQUIRE(header.height == 200);
	REQUIRE(header.maxValue == 255);
	REQUIRE(header.color == PnmHeader::ColorMode::COLOR);
	REQUIRE(header.ascii == false);

	header = PnmHeader(100, 200, PnmHeader::ColorMode::BW, 127, true);

	REQUIRE(header.width == 100);
	REQUIRE(header.height == 200);
	REQUIRE(header.maxValue == 127);
	REQUIRE(header.color == PnmHeader::ColorMode::BW);
	REQUIRE(header.ascii == true);
}

TEST_CASE("GetFileType returns correct file type", "[Pnm]")
{
	PnmHeader header;

	header = PnmHeader(100, 200, PnmHeader::ColorMode::BW);
	REQUIRE(header.GetFileType() == "PBM");

	header = PnmHeader(100, 200, PnmHeader::ColorMode::GRAY);
	REQUIRE(header.GetFileType() == "PGM");

	header = PnmHeader(100, 200, PnmHeader::ColorMode::COLOR);
	REQUIRE(header.GetFileType() == "PPM");
}

TEST_CASE("GetFileExtension returns correct file extension", "[Pnm]")
{
	PnmHeader header;

	header = PnmHeader(100, 200, PnmHeader::ColorMode::BW);
	REQUIRE(header.GetFileExtension() == ".pbm");

	header = PnmHeader(100, 200, PnmHeader::ColorMode::GRAY);
	REQUIRE(header.GetFileExtension() == ".pgm");

	header = PnmHeader(100, 200, PnmHeader::ColorMode::COLOR);
	REQUIRE(header.GetFileExtension() == ".ppm");
}

TEST_CASE("GetMagicNumberValue and GetMagicNumberChar return correct values", "[Pnm]")
{
	PnmHeader header;

	header = PnmHeader(100, 200, PnmHeader::ColorMode::BW, 255, true);
	REQUIRE(header.GetMagicNumberValue() == 1);
	REQUIRE(header.GetMagicNumberChar() == '1');

	header = PnmHeader(100, 200, PnmHeader::ColorMode::COLOR, 255, false);
	REQUIRE(header.GetMagicNumberValue() == 6);
	REQUIRE(header.GetMagicNumberChar() == '6');
}

TEST_CASE("GetImageSize returns correct size", "[Pnm]")
{
	PnmHeader header;
	header = PnmHeader(8, 8, PnmHeader::ColorMode::BW);
	REQUIRE(header.GetImageSize() == 8);

	header = PnmHeader(100, 100, PnmHeader::ColorMode::GRAY);
	REQUIRE(header.GetImageSize() == 10000);

	header = PnmHeader(50, 50, PnmHeader::ColorMode::COLOR);
	REQUIRE(header.GetImageSize() == 7500);

	header = PnmHeader(50, 50, PnmHeader::ColorMode::COLOR, 65535);
	REQUIRE(header.GetImageSize() == 15000);
}

TEST_CASE("Ostream operator<< outputs correct header", "[Pnm]")
{
	PnmHeader header(100, 200, PnmHeader::ColorMode::COLOR, 255, false);
	std::stringstream ss;
	ss << header;

	std::string expectedOutput = "P6\n100 200\n255\n";
	REQUIRE(ss.str() == expectedOutput);
}

TEST_CASE("Istream operator>> reads correctly formatted PNM header", "[Pnm]")
{
	std::string pnmData = "P6\n100 200\n255\n";
	std::stringstream ss(pnmData);

	PnmHeader header;
	ss >> header;

	REQUIRE(header.width == 100);
	REQUIRE(header.height == 200);
	REQUIRE(header.maxValue == 255);
	REQUIRE(header.color == PnmHeader::ColorMode::COLOR);
	REQUIRE(header.ascii == false);
}

TEST_CASE("Istream operator>> throws on malformed header", "[Pnm]")
{
	{
		std::string malformedData = "X6\n100 200\n255\n";
		std::stringstream ss(malformedData);

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

	{
		std::string malformedData = "P9\n100 200\n255\n";
		std::stringstream ss(malformedData);

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

	{
		std::string malformedData = "P6\n100\n255\n";
		std::stringstream ss(malformedData);

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

	{
		std::string malformedData = "GarbageInput";
		std::stringstream ss(malformedData);

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

TEST_CASE("Read throws on unsupported ascii mode", "[Pnm]")
{
	std::string pnmData = "P3\n100 100\n255\n";
	std::stringstream ss(pnmData);

	PnmImage image;
	REQUIRE_THROWS_AS(image.Read(ss), std::runtime_error);
}

TEST_CASE("Read successfully reads binary image data", "[Pnm]")
{
	std::string pnmHeader = "P6\n2 2\n255\n";
	std::string pnmImage = "\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF\xFF\x00\x00";
	std::stringstream ss(pnmHeader + pnmImage);

	PnmImage image;
	REQUIRE_NOTHROW(image.Read(ss));

	REQUIRE(image.header.width == 2);
	REQUIRE(image.header.height == 2);
	REQUIRE(image.header.maxValue == 255);
	REQUIRE(image.header.color == PnmHeader::ColorMode::COLOR);

	REQUIRE(image.image[0] == '\xFF');
	REQUIRE(image.image[1] == '\x00');
	REQUIRE(image.image[2] == '\x00');
}