/*
 * 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 <cmath>

#include "Math/ByteSize.hpp"

using namespace OpenVulkano;

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

TEST_CASE("BuildFactors and BuildDivisors", "[ByteSize]")
{
	constexpr auto factors = ByteSizeUnitHelper::BuildFactors();
	constexpr auto divisors = ByteSizeUnitHelper::BuildDivisors();

	REQUIRE(factors.size() == 13);
	REQUIRE(factors[0] == 1);
	REQUIRE(factors[1] == (1uLL << 10)); // KiB
	REQUIRE(factors[6] == (1uLL << 60)); // EiB
	REQUIRE(factors[7] == 1000); // kB
	REQUIRE(factors[12] == 1'000'000'000'000'000'000); // EB

	REQUIRE(divisors.size() == 13);
	REQUIRE(almostEqual(divisors[0], 1.0));
	REQUIRE(almostEqual(divisors[1], 1.0 / (1uLL << 10))); // KiB
	REQUIRE(almostEqual(divisors[12], 1.0 / 1'000'000'000'000'000'000)); // EB
}

TEST_CASE("GetFactor, GetDivisor, and GetName", "[ByteSize]")
{
	ByteSizeUnit kib(ByteSizeUnit::kiB);
	ByteSizeUnit gb(ByteSizeUnit::GB);

	REQUIRE(kib.GetFactor() == (1uLL << 10)); // KiB
	REQUIRE(almostEqual(kib.GetDivisor(), 1.0 / (1uLL << 10)));
	REQUIRE(kib.GetName() == "kiB");

	REQUIRE(gb.GetFactor() == 1'000'000'000); // GB
	REQUIRE(almostEqual(gb.GetDivisor(), 1.0 / 1'000'000'000));
	REQUIRE(gb.GetName() == "GB");
}

TEST_CASE("FromName and GetClosestUnit", "[ByteSize]")
{
	auto kib = ByteSizeUnit::FromName("kiB");
	auto tb = ByteSizeUnit::FromName("TB");

	REQUIRE(kib.GetFactor() == (1uLL << 10));
	REQUIRE(tb.GetFactor() == 1'000'000'000'000);

	uint64_t size1 = 2 * 1'000'000'000;
	uint64_t size2 = 3 * (1uLL << 40);
	ByteSizeUnit unit1 = ByteSizeUnit::GetClosestUnit(size1, true); // SI
	ByteSizeUnit unit2 = ByteSizeUnit::GetClosestUnit(size2, false); // Binary

	REQUIRE(unit1.GetName() == "GB");
	REQUIRE(unit2.GetName() == "TiB");
}

TEST_CASE("Constructors and parsing from string", "[ByteSize]")
{
	ByteSize size1(1'024, ByteSizeUnit(ByteSizeUnit::kiB));
	ByteSize size2("1.5 MiB");

	REQUIRE(static_cast<uint64_t>(size1) == 1'024 * (1uLL << 10));
	REQUIRE(almostEqual(static_cast<uint64_t>(size2), 1.5 * (1uLL << 20)));

	REQUIRE_THROWS_AS(ByteSize("invalid size"), std::runtime_error);
}

TEST_CASE("Formatting", "[ByteSize]")
{
	ByteSize size2(1'024 + 512, ByteSizeUnit(ByteSizeUnit::kiB)); // This is 1.5 MiB

	REQUIRE(size2.Format(true) == "1.57 MB");
	REQUIRE(size2.Format(false) == "1.5 MiB");
}

TEST_CASE("Operator overloads", "[ByteSize]")
{
	ByteSize size1(1'024, ByteSizeUnit(ByteSizeUnit::kiB));
	ByteSize size2(512, ByteSizeUnit(ByteSizeUnit::kiB));

	ByteSize result = size1 + size2;
	REQUIRE(static_cast<uint64_t>(result) == (1'024 + 512) * (1uLL << 10));

	result = size1 - size2;
	REQUIRE(static_cast<uint64_t>(result) == (1'024 - 512) * (1uLL << 10));

	size1 += size2;
	REQUIRE(static_cast<uint64_t>(size1) == (1'024 + 512) * (1uLL << 10));
}

TEST_CASE("Literals", "[ByteSize]")
{
	REQUIRE(static_cast<uint64_t>(1.0_kiB) == 1uLL << 10);
	REQUIRE(static_cast<uint64_t>(1.0_MiB) == 1uLL << 20);
	REQUIRE(static_cast<uint64_t>(1.0_GiB) == 1uLL << 30);
	REQUIRE(static_cast<uint64_t>(1.0_TiB) == 1uLL << 40);
	REQUIRE(static_cast<uint64_t>(1.0_PiB) == 1uLL << 50);
	REQUIRE(static_cast<uint64_t>(1.0_EiB) == 1uLL << 60);

	REQUIRE(static_cast<uint64_t>(1.0_kB) == 1'000);
	REQUIRE(static_cast<uint64_t>(1.0_MB) == 1'000'000);
	REQUIRE(static_cast<uint64_t>(1.0_GB) == 1'000'000'000);
	REQUIRE(static_cast<uint64_t>(1.0_TB) == 1'000'000'000'000);
	REQUIRE(static_cast<uint64_t>(1.0_PB) == 1'000'000'000'000'000);
	REQUIRE(static_cast<uint64_t>(1.0_EB) == 1'000'000'000'000'000'000);

	REQUIRE(static_cast<uint64_t>(1_kiB) == 1uLL << 10);
	REQUIRE(static_cast<uint64_t>(1_MiB) == 1uLL << 20);
	REQUIRE(static_cast<uint64_t>(1_GiB) == 1uLL << 30);
	REQUIRE(static_cast<uint64_t>(1_TiB) == 1uLL << 40);
	REQUIRE(static_cast<uint64_t>(1_PiB) == 1uLL << 50);
	REQUIRE(static_cast<uint64_t>(1_EiB) == 1uLL << 60);

	REQUIRE(static_cast<uint64_t>(1_kB) == 1'000);
	REQUIRE(static_cast<uint64_t>(1_MB) == 1'000'000);
	REQUIRE(static_cast<uint64_t>(1_GB) == 1'000'000'000);
	REQUIRE(static_cast<uint64_t>(1_TB) == 1'000'000'000'000);
	REQUIRE(static_cast<uint64_t>(1_PB) == 1'000'000'000'000'000);
	REQUIRE(static_cast<uint64_t>(1_EB) == 1'000'000'000'000'000'000);
}