/*******************************************************************************
***  This file is part of Pecunia.                                           ***
***                                                                          ***
***  Copyright (C) 2017, 2018, 2019, 2020, 2021, 2023                        ***
***  John Schneiderman <Licencing _AT_ Schneiderman _DOT_ me>                ***
***                                                                          ***
***  This program is free software: you can redistribute it and/or modify it ***
***  under the terms of the GNU Lesser 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 Lesser General Public License for more details.             ***
***                                                                          ***
***  You should have received a copy of the GNU Lesser General Public License***
***  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
*******************************************************************************/
#include <QDebug>
// using qDebug
#include <QObject>
using ::QObject;
// using Q_OBJECT
#include <QTest>
// using QTEST_MAIN
// using QVERIFY_EXCEPTION_THROWN

#include <cassert>
// using assert;
#include <stdexcept>
using std::runtime_error;
#include <vector>
using std::vector;

#include <pecunia/Algorithm.hpp>
using pecunia::sort;
#include <pecunia/FloatingPoint.h>
using pecunia::FloatingPoint;
#include <pecunia/Codes.h>
using pecunia ::currency::Iso4217Codes;
#include <pecunia/Money.h>
using pecunia::currency::Money;


namespace
{

const std::string testLocale{
#ifdef _WIN32
	"English_United States.1251"
#else
	"en_US.UTF-8"
#endif
};

FloatingPoint testConverter(const Iso4217Codes& from, const Iso4217Codes& to)
{
	/*
	 1 PLN = 0.238928 USD
	 1 USD = 4.18536 PLN

	 1 PLN = 0.22487 EUR
	 1 EUR = 4.44702 PLN

	 1 USD = 0.941161 EUR
	 1 EUR = 1.06252 USD
	*/

	switch (from)
	{
	case Iso4217Codes::EUR:
		switch (to)
		{
		case Iso4217Codes::XXX:
		case Iso4217Codes::EUR:
			return 1.0;
		case Iso4217Codes::PLN:
			qDebug() << "EUR->PLN" << 4.44702;
			return 4.44702;
		case Iso4217Codes::USD:
			qDebug() << "EUR->USD" << 1.06252;
			return 1.06252;
		default:
			throw runtime_error{"Unknown to currency."};
		};
	case Iso4217Codes::PLN:
		switch (to)
		{
		case Iso4217Codes::EUR:
			qDebug() << "PLN->EUR" << 0.22487;
			return 0.22487;
		case Iso4217Codes::XXX:
		case Iso4217Codes::PLN:
			return 1.0;
		case Iso4217Codes::USD:
			qDebug() << "PLN->USD" << 0.238928;
			return 0.238928;
		default:
			throw runtime_error{"Unknown to currency."};
		};
	case Iso4217Codes::USD:
		switch (to)
		{
		case Iso4217Codes::EUR:
			qDebug() << "USD->EUR" << 0.941161;
			return 0.941161;
		case Iso4217Codes::PLN:
			qDebug() << "USD->PLN" << 4.18536;
			return 4.18536;
		case Iso4217Codes::XXX:
		case Iso4217Codes::USD:
			return 1.0;
		default:
			throw runtime_error{"Unknown to currency."};
		};
	case Iso4217Codes::XXX:
		return 1.0;
	default:
		throw runtime_error{"Unknown from currency."};
	};
}

}

namespace pecunia
{
namespace tests
{

struct AlgorithmUT : QObject
{
	Q_OBJECT

private slots:
	void initTestCase()
	{
		converter = &testConverter;
	}

	void assert_shouldBePresentOnlyInDebug()
	{
		bool wasAsserted{};
		assert(wasAsserted = true);
#ifdef NDEBUG
		QVERIFY( ! wasAsserted);
#else
		QVERIFY(wasAsserted);
#endif
	}

	//{ sort Tests

	void sort_zeroElements_ShouldBeSorted()
	{
		vector<Money> monies{};
		sort(monies);
		QVERIFY2(monies.empty(), "With nothing in the container, it should be empty.");
	}

	void sort_oneElement_ShouldBeSorted()
	{
		const Money m0{30, 0u, Iso4217Codes::USD};
		vector<Money> monies{{m0}};
		sort(monies);
		QCOMPARE(monies.at(0), m0);
	}

	void sort_twoElements_ShouldBeSorted()
	{
		const Money m0{30, 0u, Iso4217Codes::USD};
		const Money m1{10, 0u, Iso4217Codes::USD};
		vector<Money> monies{{m0, m1}};
		sort(monies);
		QCOMPARE(monies.at(0), m1);
		QCOMPARE(monies.at(1), m0);
	}

	void sort_sameCurrencyValuesUnsorted_ShouldBeSorted()
	{
		const Money m0{30, 0u, Iso4217Codes::USD};
		const Money m1{10, 0u, Iso4217Codes::USD};
		const Money m2{20, 0u, Iso4217Codes::USD};
		const Money m3{60, 0u, Iso4217Codes::USD};
		const Money m4{40, 0u, Iso4217Codes::USD};
		const Money m5{50, 0u, Iso4217Codes::USD};
		vector<Money> monies{{m0, m1, m2, m3, m4, m5}};
		sort(monies);
		QCOMPARE(monies.at(0), m1);
		QCOMPARE(monies.at(1), m2);
		QCOMPARE(monies.at(2), m0);
		QCOMPARE(monies.at(3), m4);
		QCOMPARE(monies.at(4), m5);
		QCOMPARE(monies.at(5), m3);
	}

	void sort_sameCurrencyValuesUnsortedWithDuplicates_ShouldBeSorted()
	{
		const Money m0{30, 0u, Iso4217Codes::USD};
		const Money m1{30, 0u, Iso4217Codes::USD};
		const Money m2{10, 0u, Iso4217Codes::USD};
		const Money m3{20, 0u, Iso4217Codes::USD};
		const Money m4{20, 0u, Iso4217Codes::USD};
		const Money m5{60, 0u, Iso4217Codes::USD};
		const Money m6{40, 0u, Iso4217Codes::USD};
		const Money m7{50, 0u, Iso4217Codes::USD};
		const Money m8{40, 0u, Iso4217Codes::USD};
		vector<Money> monies{{m0, m1, m2, m3, m4, m5, m6, m7, m8}};
		sort(monies);
		QCOMPARE(monies.at(0), m2);
		QCOMPARE(monies.at(1), m3);
		QCOMPARE(monies.at(2), m4);
		QCOMPARE(monies.at(3), m0);
		QCOMPARE(monies.at(4), m1);
		QCOMPARE(monies.at(5), m6);
		QCOMPARE(monies.at(6), m8);
		QCOMPARE(monies.at(7), m7);
		QCOMPARE(monies.at(8), m5);
	}

	void sort_sameCurrencyValuesUnsortedReversed_ShouldBeSorted()
	{
		const Money m0{60, 0u, Iso4217Codes::USD};
		const Money m1{50, 0u, Iso4217Codes::USD};
		const Money m2{40, 0u, Iso4217Codes::USD};
		const Money m3{30, 0u, Iso4217Codes::USD};
		const Money m4{20, 0u, Iso4217Codes::USD};
		const Money m5{10, 0u, Iso4217Codes::USD};
		vector<Money> monies{{m0, m1, m2, m3, m4, m5}};
		sort(monies);
		QCOMPARE(monies.at(0), m5);
		QCOMPARE(monies.at(1), m4);
		QCOMPARE(monies.at(2), m3);
		QCOMPARE(monies.at(3), m2);
		QCOMPARE(monies.at(4), m1);
		QCOMPARE(monies.at(5), m0);
	}

	void sort_mixedCurrencyValuesUnsorted_ShouldBeSortedAndKeepCurrency()
	{
		const Money m0{30, 0u, Iso4217Codes::USD};
		const Money m1{36, 8600u, Iso4217Codes::PLN}; // Converted 10 USD
		const Money m2{16, 5000u, Iso4217Codes::EUR}; // Converted 20 USD
		vector<Money> monies{{m0, m1, m2}};
		sort(monies);
		QCOMPARE(monies.at(0), m1);
		QCOMPARE(monies.at(0).code(), Iso4217Codes::PLN);
		QCOMPARE(monies.at(1), m2);
		QCOMPARE(monies.at(1).code(), Iso4217Codes::EUR);
		QCOMPARE(monies.at(2), m0);
		QCOMPARE(monies.at(2).code(), Iso4217Codes::USD);
	}

	void sort_mixedCurrencyValuesSorted_ShouldBeSortedAndKeepCurrency()
	{
		const Money m0{36, 8600u, Iso4217Codes::PLN}; // Converted 10 USD
		const Money m1{16, 5000u, Iso4217Codes::EUR}; // Converted 20 USD
		const Money m2{30, 0u, Iso4217Codes::USD};
		vector<Money> monies{{m0, m1, m2}};
		sort(monies);
		QCOMPARE(monies.at(0), m0);
		QCOMPARE(monies.at(0).code(), Iso4217Codes::PLN);
		QCOMPARE(monies.at(1), m1);
		QCOMPARE(monies.at(1).code(), Iso4217Codes::EUR);
		QCOMPARE(monies.at(2), m2);
		QCOMPARE(monies.at(2).code(), Iso4217Codes::USD);
	}

	void sort_sameValuesSorted_ShouldBeSorted()
	{
		const Money m0{10, 0u, Iso4217Codes::USD};
		const Money m1{10, 0u, Iso4217Codes::USD};
		const Money m2{10, 0u, Iso4217Codes::USD};
		vector<Money> monies{{m0, m1, m2}};
		sort(monies);
		QCOMPARE(monies.at(0), m0);
		QCOMPARE(monies.at(1), m1);
		QCOMPARE(monies.at(2), m2);
	}

	//}
};


}}

QTEST_MAIN(pecunia::tests::AlgorithmUT)
#include "algorithm-unit-tests.moc"
