/*******************************************************************************
***  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/>.    ***
*******************************************************************************/
#ifndef DRN_BUDGETING_BUDGET_H_
#define DRN_BUDGETING_BUDGET_H_

#include <pecunia/Money.h>

#include <iosfwd>
#include <iterator>
#include <map>
#include <set>
#include <typeindex>
#include <utility>

#include "BillMap.hpp"
#include "budgeting_export.h"
#include "BudgetItemIdentifier.h"
#include <foundation/ObserverPtr.hpp>
#include "GoalMap.h"
#include "NontrackMap.hpp"
#include "WageMap.hpp"
#include "DebtMap.h"


namespace drn
{
namespace foundation
{

template<typename>
class Optional;

}
namespace budgeting
{

enum class BudgetItemTypes : quint8;
class BudgetedMoney;
class BudgetSource;

/**
 * @brief The representation of a budget and its components.
 */
class DRN_BUDGETING_EXPORT Budget
{
public:
	class DRN_BUDGETING_EXPORT Iterator
	{
		DRN_BUDGETING_EXPORT friend bool operator==(const Iterator& lhs, const Iterator& rhs);
		DRN_BUDGETING_EXPORT friend bool operator!=(const Iterator& lhs, const Iterator& rhs);

		foundation::ObserverPtr<const Budget> budget_;
		BillMap::const_iterator billIter_;
		DebtMap::const_iterator debtIter_;
		GoalMap::const_iterator goalIter_;
		NontrackMap::const_iterator nontrackIter_;
		WageMap::const_iterator wageIter_;

	public:
		using iterator_category = std::forward_iterator_tag;
		using difference_type = std::ptrdiff_t;
		using value_type = std::pair<
			BudgetItemIdentifier,
			foundation::ObserverPtr<const BudgetedMoney>
		>;
		using const_pointer = const value_type*;
		using const_reference = const value_type&;

		/**
		 * @brief Fully initialises the object to point to the budget. Upon successful construction
		 * will be pointing just past the end of the budget.
		 *
		 * @param budget The budget to iterate over.
		 */
		explicit Iterator(const Budget& budget);
		const_reference operator*() const;
		const_pointer operator->() const;
		/**
		 * @brief Prefix increment operator.
		 *
		 * @return The object after the increment operation.
		 */
		Iterator& operator++();
		/**
		 * @brief Postfix increment operator.
		 *
		 * @return The object before the increment operation.
		 */
		Iterator operator++(int);
		Iterator begin() const;
		Iterator end() const;

	private:
		static const value_type endValue_;

		value_type current_;
	};

	Budget(
		WageMap wages = {},
		BillMap bills = {},
		DebtMap debts = {},
		GoalMap goals = {},
		NontrackMap nontracks = {}
	);
	Budget(const Budget& other) = default;
	Budget(Budget&& other) noexcept = default;
	~Budget() = default;
	Budget& operator=(const Budget& other) = default;
	Budget& operator=(Budget&& other) noexcept = default;
	/**
	 * @brief Equality operator compares all members.
	 *
	 * @param other The other object whose members should all be equal.
	 *
	 * @return When all member variables are the same it'll give true, else false.
	 */
	bool operator==(const Budget& other) const;
	/**
	 * @brief Determines if the budget is empty.
	 *
	 * @return When the budget doesn't contain any wages, bills, debts, nor goals gives
	 * true, false else-wise
	 */
	bool isEmpty() const;
	/**
	 * @brief Calculates the size of all the budget items in the current budget.
	 *
	 * @return The number of items in the budget.
	 */
	std::size_t size() const;
	/**
	 * @brief Provides all the sources in the budget.
	 *
	 * @return The container mapping the type to a container of budget sources in the budget.
	 */
	std::map<std::type_index, std::set<BudgetSource>> sources() const;
	Iterator begin() const;
	Iterator end() const;

	/// The container of all the wage coming into the budget.
	WageMap wages_;
	/// The container of all the living bills leaving the budget.
	BillMap bills_;
	/// The container of all the borrowed money leaving the budget.
	DebtMap debts_;
	/// The container of all the money being saved in the budget.
	GoalMap goals_;
	/// The container of all the money that is not tracked in the budget.
	NontrackMap nontracks_;
};
/**
 * @brief Calculates the totals for all budgetary items for a given period.
 */
struct DRN_BUDGETING_EXPORT BudgetTotals
{
	/**
	 * @brief Constructor for calculating the totals.
	 *
	 * @param budget The budget from which all totals come from.
	 * @param currency The currency the totals should be calculated in.
	 * @param period The frequency for which the total should be calculated in.
	 * @param hrInWeek The number of hours considered in a full work week.
	 * @param dayInWeek The number of days considered in a full work week.
	 */
	BudgetTotals(
		const Budget& budget,
		const pecunia::currency::Iso4217Codes& currency,
		const EventFrequency period,
		const std::chrono::hours& hrInWeek,
		const foundation::Days& dayInWeek
	);

	/// The total of all the wage for the requested period.
	const pecunia::currency::Money wage_;
	/// The total of all the goals for the requested period.
	const pecunia::currency::Money goals_;
	/// The total of all the bills for the requested period.
	const pecunia::currency::Money bills_;
	/// The total of all the debts for the requested period.
	const pecunia::currency::Money debts_;
	/// The total of all the nontrack spending for the requested period.
	const pecunia::currency::Money nontrack_;
	/// The frequency period all totals are calculated to.
	const EventFrequency period_;
	/// The total of money remaining after all the incoming and outgoing monies are calculated for
	/// the period.
	const pecunia::currency::Money remaining_;
};

/**
 * @brief Finds all the unique currencies used throughout the budget.
 *
 * @param b The budget to examine for currencies.
 *
 * @return Only the unique currencies in the budget.
 */
DRN_BUDGETING_EXPORT std::set<pecunia::currency::Iso4217Codes> containedCurrencies(const Budget& b);
DRN_BUDGETING_EXPORT std::ostream& operator<<(std::ostream& o, const Budget& b);
DRN_BUDGETING_EXPORT bool operator!=(const Budget& lhs, const Budget& rhs);
template<typename BudgetItemType>
foundation::Optional<BudgetItemType> findBudgetItem(
	const BudgetSource& needle,
	const Budget& haystack
);
template<>
DRN_BUDGETING_EXPORT foundation::Optional<Bill> findBudgetItem(
	const BudgetSource& needle,
	const Budget& haystack
);
template<>
DRN_BUDGETING_EXPORT foundation::Optional<Debt> findBudgetItem(
	const BudgetSource& needle,
	const Budget& haystack
);
template<>
DRN_BUDGETING_EXPORT foundation::Optional<Goal> findBudgetItem(
	const BudgetSource& needle,
	const Budget& haystack
);
template<>
DRN_BUDGETING_EXPORT foundation::Optional<Nontrack> findBudgetItem(
	const BudgetSource& needle,
	const Budget& haystack
);
template<>
DRN_BUDGETING_EXPORT foundation::Optional<Wage> findBudgetItem(
	const BudgetSource& needle,
	const Budget& haystack
);
DRN_BUDGETING_EXPORT const BudgetedMoney& findBudgetItem(
	const BudgetItemIdentifier& id,
	const Budget& b
);
DRN_BUDGETING_EXPORT bool operator==(const Budget::Iterator& lhs, const Budget::Iterator& rhs);
DRN_BUDGETING_EXPORT bool operator!=(const Budget::Iterator& lhs, const Budget::Iterator& rhs);

}}

#endif
