/*******************************************************************************
***  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 "EventFrequency.h"
using std::array;
using std::ostream;
using ::quint8;
using drn::budgeting::EventFrequency;
using drn::budgeting::numberOfEventFrequencies;

#include <QDate>
using ::QDate;
#include <QDateTime>
using ::QDateTime;
#include <QObject>
using ::QObject;
#include <QString>
using ::QString;
#include <QTime>
using ::QTime;

#include <chrono>
using namespace std::chrono_literals;
using std::chrono::duration_cast;
using std::chrono::milliseconds;
#include <numeric>
using std::numeric_limits;
#include <stdexcept>
using std::logic_error;

#include <foundation/Error.h>
using drn::foundation::Error;
#include <foundation/EnumMacros.hpp>
// using BEGIN_ENUMERATION_TO_OTHER
// using BEGIN_ENUMERATION_TO_WEAK
// using BEGIN_OTHER_TO_ENUMERATION
// using BEGIN_WEAK_TO_ENUMERATION
// using MAP_ENUMERATION_TO_OTHER
// using MAP_ENUMERATION_TO_WEAK
// using MAP_OTHER_TO_ENUMERATION
// using MAP_WEAK_TO_ENUMERATION
// using END_ENUMERATION_TO_OTHER
// using END_ENUMERATION_TO_WEAK
// using END_OTHER_TO_ENUMERATION
// using END_WEAK_TO_ENUMERATION


const array<EventFrequency, numberOfEventFrequencies> drn::budgeting::allEventFrequencies
{
	{
		EventFrequency::Unknown,
		EventFrequency::Hourly,
		EventFrequency::Daily,
		EventFrequency::SemiWeekly,
		EventFrequency::Weekly,
		EventFrequency::BiWeekly,
		EventFrequency::SemiMonthly,
		EventFrequency::Monthly,
		EventFrequency::BiMonthly,
		EventFrequency::Quarterly,
		EventFrequency::SemiAnnually,
		EventFrequency::Annually,
		EventFrequency::Biannually,
		EventFrequency::Triannually,
		EventFrequency::Never
	}
};

EventFrequency drn::budgeting::toEventFrequency(const uint8_t weak)
{
	BEGIN_WEAK_TO_ENUMERATION(EventFrequency);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::Unknown);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::Never);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::Hourly);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::Daily);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::SemiWeekly);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::Weekly);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::BiWeekly);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::SemiMonthly);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::Monthly);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::BiMonthly);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::Quarterly);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::SemiAnnually);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::Annually);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::Biannually);
		MAP_WEAK_TO_ENUMERATION(weak, EventFrequency, EventFrequency::Triannually);
	END_WEAK_TO_ENUMERATION(weak, EventFrequency);
}

uint8_t drn::budgeting::toWeak(const EventFrequency strong)
{
	BEGIN_ENUMERATION_TO_WEAK(strong);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::Unknown);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::Never);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::Hourly);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::Daily);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::SemiWeekly);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::Weekly);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::BiWeekly);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::SemiMonthly);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::Monthly);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::BiMonthly);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::Quarterly);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::SemiAnnually);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::Annually);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::Biannually);
		MAP_ENUMERATION_TO_WEAK(EventFrequency::Triannually);
	END_ENUMERATION_TO_WEAK(strong);
}

EventFrequency drn::budgeting::toEventFrequency(const QString& weak)
{
	BEGIN_OTHER_TO_ENUMERATION(EventFrequency);
		MAP_OTHER_TO_ENUMERATION(weak, "Unknown", EventFrequency::Unknown);
		MAP_OTHER_TO_ENUMERATION(weak, "Never", EventFrequency::Never);
		MAP_OTHER_TO_ENUMERATION(weak, "Hourly", EventFrequency::Hourly);
		MAP_OTHER_TO_ENUMERATION(weak, "Daily", EventFrequency::Daily);
		MAP_OTHER_TO_ENUMERATION(weak, "Semi-weekly", EventFrequency::SemiWeekly);
		MAP_OTHER_TO_ENUMERATION(weak, "Weekly", EventFrequency::Weekly);
		MAP_OTHER_TO_ENUMERATION(weak, "Bi-weekly", EventFrequency::BiWeekly);
		MAP_OTHER_TO_ENUMERATION(weak, "Semi-monthly", EventFrequency::SemiMonthly);
		MAP_OTHER_TO_ENUMERATION(weak, "Monthly", EventFrequency::Monthly);
		MAP_OTHER_TO_ENUMERATION(weak, "Bi-monthly", EventFrequency::BiMonthly);
		MAP_OTHER_TO_ENUMERATION(weak, "Quarterly", EventFrequency::Quarterly);
		MAP_OTHER_TO_ENUMERATION(weak, "Semi-annually", EventFrequency::SemiAnnually);
		MAP_OTHER_TO_ENUMERATION(weak, "Annually", EventFrequency::Annually);
		MAP_OTHER_TO_ENUMERATION(weak, "Bi-annually", EventFrequency::Biannually);
		MAP_OTHER_TO_ENUMERATION(weak, "Tri-annually", EventFrequency::Triannually);
	END_OTHER_TO_ENUMERATION(weak, EventFrequency);
}

ostream& drn::budgeting::operator<<(ostream& o, const EventFrequency ef)
{
	return o << "Value (" << presentationText(ef).toStdString() << ')';
}

QString drn::budgeting::presentationText(const EventFrequency& ef)
{
	BEGIN_ENUMERATION_TO_OTHER(ef);
		MAP_ENUMERATION_TO_OTHER(EventFrequency::Unknown, "Unknown");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::Never, "Never");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::Hourly, "Hourly");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::Daily, "Daily");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::SemiWeekly, "Semi-weekly");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::Weekly, "Weekly");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::BiWeekly, "Bi-weekly");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::SemiMonthly, "Semi-monthly");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::Monthly, "Monthly");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::BiMonthly, "Bi-monthly");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::Quarterly, "Quarterly");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::SemiAnnually, "Semi-annually");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::Annually, "Annually");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::Biannually, "Bi-annually");
		MAP_ENUMERATION_TO_OTHER(EventFrequency::Triannually, "Tri-annually");
	END_ENUMERATION_TO_OTHER(ef, EventFrequency);
}

QDateTime drn::budgeting::nextTimePoint(const QDateTime& current, const EventFrequency& ef)
{
	if ( ! current.isValid())
		throw Error{
			"The supplied current point in time '" + current.toString()
				+ "' is not valid and must be in order to calculate the next point in time."
		};

	switch (ef)
	{
	case EventFrequency::Unknown:
		throw Error{
			QObject::tr(
				"The supplied event frequency period must be known in order to calculate the next "
					"point in time."
			)
		};
	case EventFrequency::Hourly:
		return current.addMSecs(duration_cast<milliseconds>(1h).count());
	case EventFrequency::Daily:
		return current.addDays(1ll);
	case EventFrequency::SemiWeekly:
		return current.addDays(3ll).addMSecs(duration_cast<milliseconds>(12h).count());
	case EventFrequency::Weekly:
		return current.addDays(7ll);
	case EventFrequency::BiWeekly:
		return current.addDays(14ll);
	case EventFrequency::SemiMonthly:
		return current.addDays(15ll);
	case EventFrequency::Monthly:
		return current.addMonths(1);
	case EventFrequency::BiMonthly:
		return current.addMonths(2);
	case EventFrequency::Quarterly:
		return current.addMonths(3);
	case EventFrequency::SemiAnnually:
		return current.addMonths(6);
	case EventFrequency::Annually:
		return current.addYears(1);
	case EventFrequency::Biannually:
		return current.addYears(2);
	case EventFrequency::Triannually:
		return current.addYears(3);
	case EventFrequency::Never:
		return {
			QDate{numeric_limits<decltype(QDate{}.year())>::max(), 12, 31},
			QTime{23, 59, 59, 9999ull}
		};
	default:
		throw logic_error{
			"The event frequency period is not handled in budgeting::nextTimePoint."
		};
	};
}

