/*******************************************************************************
***  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 "BudgetedMoney.h"
using ::QDate;
using pecunia::currency::Money;
using drn::budgeting::BudgetedMoney;
using drn::budgeting::EventFrequency;
using drn::budgeting::nextTimePoint;

#include <pecunia/Codes.h>
using pecunia::currency::Iso4217Codes;
#include <QObject>
using ::QObject;
#include <QDateTime>
using ::QDateTime;

#include <cassert>
// using assert
#include <exception>
using std::exception;
#include <ostream>
using std::ostream;
#include <utility>
using std::move;

#include "BudgetingErrors.h"
using drn::budgeting::BudgetItemError;
#include "BudgetSource.h"
using drn::budgeting::BudgetSource;


BudgetedMoney::BudgetedMoney() :
	source_{},
	amount_{},
	period_{EventFrequency::Unknown},
	nextOccurOn_{}
{}

BudgetedMoney::BudgetedMoney(const BudgetSource& source) :
	source_{source},
	amount_{},
	period_{EventFrequency::Unknown},
	nextOccurOn_{}
{
	if (this->source_.isEmpty())
		throw BudgetItemError{this->source_, QObject::tr("The source cannot empty.")};
}

BudgetedMoney::BudgetedMoney(
	const BudgetSource& source,
	const Money& amount,
	const EventFrequency& period,
	const QDate& nextOccurOn
) :
	source_{source},
	amount_{amount},
	period_{period},
	nextOccurOn_{nextOccurOn}
{
	if (this->source_.isEmpty())
		throw BudgetItemError{this->source_, QObject::tr("The source cannot empty.")};

	if (this->amount_ < 0)
		throw BudgetItemError{this->source_, QObject::tr("The amount cannot be less-than zero.")};

	if (this->amount_.code() == Iso4217Codes::XXX)
		throw BudgetItemError{
			this->source_,
			QObject::tr("The amount must be in some issued currency.")
		};

	if (this->period_ == EventFrequency::Unknown)
		throw BudgetItemError{
			this->source_,
			QObject::tr("The event frequency period must be known.")
		};

	if ( ! this->nextOccurOn_.isValid())
		throw BudgetItemError{
			this->source_,
			QObject::tr("The date '%1' is invalid.").arg(this->nextOccurOn_.toString())
		};
}

const BudgetSource& BudgetedMoney::source() const
{
	return this->source_;
}

const Money& BudgetedMoney::amount() const
{
	return this->amount_;
}

const EventFrequency& BudgetedMoney::period() const
{
	return this->period_;
}

const QDate& BudgetedMoney::nextOccurOn() const
{
	return this->nextOccurOn_;
}

void BudgetedMoney::setSource(BudgetSource source)
{
	if (source.isEmpty())
		throw BudgetItemError{this->source_, QObject::tr("The new source cannot empty.")};
	this->source_ = move(source);
}

bool BudgetedMoney::operator==(const BudgetedMoney& other) const
{
	return this->source_ == other.source_
			&& this->amount_ == other.amount_
			&& this->period_ == other.period_
			&& this->nextOccurOn_ == other.nextOccurOn_;
}

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

void BudgetedMoney::updateNextOccurOn()
try
{
	const QDateTime current{this->nextOccurOn_.startOfDay()};
	const auto next{nextTimePoint(current, this->period_)};
	assert(next.isValid() && "The next time point should be valid.");
	this->nextOccurOn_ = next.date();
}
catch (const exception& error)
{
	throw BudgetItemError{
		this->source_,
		QObject::tr("Failure while trying to update the next occurrence date."),
		error
	};
}

ostream& drn::budgeting::operator<<(ostream& o, const BudgetedMoney& bm)
{
	return o << "Source (" << bm.source() << "), Amount (" << bm.amount() << "), Period ("
			<< bm.period() << "), Next Occur On (" << bm.nextOccurOn().toString().toStdString()
			<< ')';
}
