/*******************************************************************************
***  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 "Algorithm.h"
using drn::foundation::Days;
using std::chrono::hours;
using drn::budgeting::convert;

#include <pecunia/Money.h>
using pecunia::currency::Money;
#include <QtGlobal>
// using qFuzzyIsNull;
#include <QObject>
using ::QObject;

#include <stdexcept>
using std::logic_error;

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


namespace
{

double numberOfHours(const EventFrequency period, const hours& hrInWeek, const Days& dayInWeek)
{
	static const double quartersInYear{4.0};
	static const double weeksInYear{52.0};
	static const double bimonthsInYear{6.0};
	static const double monthsInYear{12.0};
	static const double semiweeksInWeek{2.0};
	static const double biweeksInWeek{2.0};
	static const double semimonthsInMonth{2.0};
	static const double semiyearsInYear{2.0};
	static const double biyearsInYear{2.0};
	static const double triyearsInYear{2.0};

	switch (period)
	{
	case EventFrequency::Unknown:
		throw Error{
			QObject::tr("Cannot convert from the event frequency Unknown to a set number of hours.")
		};
	case EventFrequency::Hourly:
		return 1.0;
	case EventFrequency::Daily:
		return static_cast<double>(hrInWeek.count()) / static_cast<double>(dayInWeek.count());
	case EventFrequency::SemiWeekly:
		return static_cast<double>(hrInWeek.count()) / semiweeksInWeek;
	case EventFrequency::Weekly:
		return static_cast<double>(hrInWeek.count());
	case EventFrequency::BiWeekly:
		return static_cast<double>(hrInWeek.count()) * biweeksInWeek;
	case EventFrequency::SemiMonthly:
		return (weeksInYear * static_cast<double>(hrInWeek.count()))
			/ (semimonthsInMonth * monthsInYear);
	case EventFrequency::Monthly:
		return (weeksInYear * static_cast<double>(hrInWeek.count())) / monthsInYear;
	case EventFrequency::BiMonthly:
		return (weeksInYear * static_cast<double>(hrInWeek.count())) / bimonthsInYear;
	case EventFrequency::Quarterly:
		return (weeksInYear * static_cast<double>(hrInWeek.count())) / quartersInYear;
	case EventFrequency::SemiAnnually:
		return (weeksInYear * static_cast<double>(hrInWeek.count())) / semiyearsInYear;
	case EventFrequency::Annually:
		return weeksInYear * static_cast<double>(hrInWeek.count());
	case EventFrequency::Biannually:
		return weeksInYear * static_cast<double>(hrInWeek.count()) * biyearsInYear;
	case EventFrequency::Triannually:
		return weeksInYear * static_cast<double>(hrInWeek.count()) * triyearsInYear;
	case EventFrequency::Never:
		return 0.0;
	default:
		throw logic_error{
			QObject::tr(
				"Cannot convert from the event frequency '%1' to a set number of hours as the "
					"period is not known."
			)
				.arg(QString::number(static_cast<std::underlying_type_t<EventFrequency>>(period)))
				.toStdString()
		};
	}
}

}

double drn::budgeting::convert(
	const EventFrequency from,
	const EventFrequency to,
	const hours& hrInWeek,
	const Days& dayInWeek
)
{
	const auto fromHours{numberOfHours(from, hrInWeek, dayInWeek)};
	const auto toHours{numberOfHours(to, hrInWeek, dayInWeek)};

	if (qFuzzyIsNull(toHours))
		return 0.0;
	return fromHours / toHours;
}

Money drn::budgeting::adjustTo(
	const EventFrequency period,
	const Money& amount,
	const EventFrequency amountPeriod,
	const hours& hrInWeek,
	const Days& dayInWeek
)
{
	const auto convertionRatio{convert(period, amountPeriod, hrInWeek, dayInWeek)};

	if (amountPeriod == EventFrequency::Never && period != EventFrequency::Never)
		return amount;
	const auto adjustedMoney{amount * convertionRatio};
	return adjustedMoney;
}
