/*******************************************************************************
***  This file is part of Dux Rei Nummariae.                                 ***
***                                                                          ***
***  Copyright (C) 2017, 2018, 2019, 2020, 2021                              ***
***  John Schneiderman <Licensing _AT_ Schneiderman _DOT_ me>                ***
***                                                                          ***
***  This program is free software: you can redistribute it and/or modify it ***
***  under the terms of the GNU Affero 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 Affero General Public License for more details.             ***
***                                                                          ***
***  You should have received a copy of the GNU Affero General Public License***
***  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
*******************************************************************************/
#include "ConversionMap.h"
using pecunia::currency::allIso4217Codes;
using pecunia::currency::Iso4217Codes;
using pecunia::currency::toStdString;
using std::chrono::system_clock;
using std::map;
using std::set;
using drn::conversion::ConversionMap;
using drn::conversion::ConversionRatio;

#include <QtGlobal>
//using qFuzzyCompare;
#include <QObject>
using ::QObject;

#include <utility>
using std::move;

#include <foundation/Error.h>
using drn::foundation::Error;


namespace
{

set<ConversionRatio> allUnknownConversionRatios()
{
	set<ConversionRatio> allRatios;

	for (const auto& toCode : allIso4217Codes)
		allRatios.emplace(ConversionRatio{toCode, 1.0});
	return allRatios;
}

}

//{ ConversionRatio

ConversionRatio::ConversionRatio(const Iso4217Codes& convertTo, const double ratio) :
	convertTo_{convertTo},
	ratio_{ratio}
{}

bool ConversionRatio::operator==(const ConversionRatio& other) const
{
	return this->convertTo_ == other.convertTo_;
}

bool ConversionRatio::operator!=(const ConversionRatio& other) const
{
	return ! (*this == other);
}

bool ConversionRatio::operator<(const ConversionRatio& other) const
{
	return this->convertTo_ < other.convertTo_;
}

//}

//{ ConversionMap

ConversionMap::ConversionMap() :
	ConversionMap{
		system_clock::now(),
		map<Iso4217Codes, set<ConversionRatio>>{{Iso4217Codes::XXX, allUnknownConversionRatios()}}
	}
{
	for (const auto& fromCode : allIso4217Codes)
	{
		this->setConversion(fromCode, Iso4217Codes::XXX, 1.0L);
		this->setConversion(fromCode, fromCode, 1.0L);
	}
}

ConversionMap::ConversionMap(
	system_clock::time_point lastFetched,
	map<Iso4217Codes, set<ConversionRatio>> ratios
) :
	map<Iso4217Codes, set<ConversionRatio>>{move(ratios)},
	lastFetched_{move(lastFetched)}
{
	for (const auto& fromToConversions : *this)
		for (const auto& toConversion : fromToConversions.second)
			if (
				fromToConversions.first == toConversion.convertTo_
				&& ! qFuzzyCompare(toConversion.ratio_, 1.0)
			)
				throw Error{
					QObject::tr("The conversion ratio from '%1' to '%2' must be 1.0 not %3.")
						.arg(QString::fromStdString(toStdString(fromToConversions.first)))
						.arg(QString::fromStdString(toStdString(toConversion.convertTo_)))
						.arg(toConversion.ratio_)
				};
}

const system_clock::time_point& ConversionMap::lastFetched() const
{
	return this->lastFetched_;
}

bool ConversionMap::hasFrom(const Iso4217Codes& from) const
{
	return this->count(from) == 1u;
}

bool ConversionMap::hasConversion(const Iso4217Codes& from, const Iso4217Codes& to) const
{
	return this->hasFrom(from) && this->at(from).find(to) != this->at(from).cend();
}

void ConversionMap::setConversion(
	const Iso4217Codes& from,
	const Iso4217Codes& to,
	const double ratio
)
{
	if (from == to && ! qFuzzyCompare(ratio,  1.0))
		throw Error{
			QObject::tr("The conversion ratio from '%1' to '%2' must be 1.0 not %3.")
				.arg(QString::fromStdString(toStdString(from)))
				.arg(QString::fromStdString(toStdString(to)))
				.arg(ratio)
		};

	if (this->hasConversion(from, to))
		(*this)[from].erase(to);
	(*this)[from].emplace(ConversionRatio{to, ratio});
	this->lastFetched_ = system_clock::now();
}

//}
