/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2013  Georg Rudoy
 *
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************/

#include "entriesmodel.h"
#include <QColor>
#include <QApplication>
#include <QFontMetrics>
#include <interfaces/core/itagsmanager.h>
#include "core.h"
#include "accountsmanager.h"
#include "operationsmanager.h"
#include "currenciesmanager.h"

namespace LeechCraft
{
namespace Poleemery
{
	EntriesModel::EntriesModel (QObject *parent)
	: QAbstractItemModel (parent)
	, HeaderData_ { tr ("Date"), tr ("Name"), tr ("Price"), tr ("Count"),
			tr ("Shop"), tr ("Categories"),
			tr ("Account"), tr ("Account balance"), tr ("Sum balance") }
	{
	}

	QModelIndex EntriesModel::index (int row, int column, const QModelIndex& parent) const
	{
		if (parent.isValid ())
			return QModelIndex ();

		if (!hasIndex (row, column, parent))
			return QModelIndex ();

		return createIndex (row, column);
	}

	QModelIndex EntriesModel::parent (const QModelIndex&) const
	{
		return {};
	}

	int EntriesModel::rowCount (const QModelIndex& parent) const
	{
		return parent.isValid () ? 0 : Entries_.size ();
	}

	int EntriesModel::columnCount (const QModelIndex&) const
	{
		return HeaderData_.size ();
	}

	Qt::ItemFlags EntriesModel::flags (const QModelIndex& index) const
	{
		auto flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
		switch (index.column ())
		{
		case Columns::AccBalance:
		case Columns::SumBalance:
			break;
		case Columns::Count:
		case Columns::Shop:
		case Columns::Categories:
			if (Entries_.at (index.row ())->GetType () == EntryType::Expense)
				flags |= Qt::ItemIsEditable;
			break;
		default:
			flags |= Qt::ItemIsEditable;
			break;
		}
		return flags;
	}

	namespace
	{
		template<typename T>
		QVariant GetDataIf (EntryBase_ptr entry, EntryType expected, std::function<QVariant (std::shared_ptr<T>)> getter, const QVariant& def = QVariant ())
		{
			if (entry->GetType () != expected)
				return def;

			auto derived = std::dynamic_pointer_cast<T> (entry);
			return getter (derived);
		}
	}

	QVariant EntriesModel::data (const QModelIndex& index, int role) const
	{
		auto entry = Entries_.at (index.row ());

		switch (role)
		{
		case Qt::DisplayRole:
		{
			auto accsManager = Core::Instance ().GetAccsManager ();
			auto acc = accsManager->GetAccount (entry->AccountID_);

			switch (index.column ())
			{
			case Columns::Account:
				return acc.Name_;
			case Columns::Name:
				return entry->Name_;
			case Columns::Price:
				return QString::number (entry->Amount_) + " " + acc.Currency_;
			case Columns::Date:
				return entry->Date_;
			case Columns::Count:
				return GetDataIf<ExpenseEntry> (entry, EntryType::Expense,
						[] (ExpenseEntry_ptr exp) { return exp->Count_; });
			case Columns::Shop:
				return GetDataIf<ExpenseEntry> (entry, EntryType::Expense,
						[] (ExpenseEntry_ptr exp) { return exp->Shop_; });
			case Columns::Categories:
				return GetDataIf<ExpenseEntry> (entry, EntryType::Expense,
						[] (ExpenseEntry_ptr exp) -> QVariant
						{
							auto itm = Core::Instance ().GetCoreProxy ()->GetTagsManager ();
							return itm->Join (exp->Categories_);
						});
			case Columns::AccBalance:
				return QString::number (Sums_ [index.row ()].Accs_ [entry->AccountID_]) + " " + acc.Currency_;
			case Columns::SumBalance:
				return QString::number (Sums_ [index.row ()].Total_) + " " +
						Core::Instance ().GetCurrenciesManager ()->GetUserCurrency ();
			}
			break;
		}
		case Qt::EditRole:
		{
			auto acc = Core::Instance ().GetAccsManager ()->GetAccount (entry->AccountID_);

			switch (index.column ())
			{
			case Columns::Account:
				return static_cast<int> (acc.ID_);
			case Columns::Name:
				return entry->Name_;
			case Columns::Price:
				return entry->Amount_;
			case Columns::Date:
				return entry->Date_;
			case Columns::Count:
				return GetDataIf<ExpenseEntry> (entry, EntryType::Expense,
						[] (ExpenseEntry_ptr exp) { return exp->Count_; });
			case Columns::Shop:
				return GetDataIf<ExpenseEntry> (entry, EntryType::Expense,
						[] (ExpenseEntry_ptr exp) { return exp->Shop_; });
			case Columns::Categories:
				return GetDataIf<ExpenseEntry> (entry, EntryType::Expense,
						[] (ExpenseEntry_ptr exp) { return exp->Categories_; });
			case Columns::AccBalance:
				return QVariant ();
			case Columns::SumBalance:
				return QVariant ();
			}
			break;
		}
		case Qt::BackgroundRole:
			switch (entry->GetType ())
			{
			case EntryType::Expense:
				return QColor (127, 0, 0, 32);
			case EntryType::Receipt:
				return QColor (0, 127, 0, 32);
			}
			break;
		}

		return QVariant ();
	}

	bool EntriesModel::setData (const QModelIndex& index, const QVariant& value, int)
	{
		auto entry = Entries_.at (index.row ());
		bool shouldRecalcSums = false;
		bool resetModel = false;
		switch (index.column ())
		{
		case Columns::Account:
			entry->AccountID_ = value.toInt ();
			shouldRecalcSums = true;
			break;
		case Columns::Name:
			entry->Name_ = value.toString ();
			break;
		case Columns::Price:
			entry->Amount_ = value.toDouble ();
			shouldRecalcSums = true;
			break;
		case Columns::Date:
			entry->Date_ = value.toDateTime ();
			resetModel = true;
			break;
		case Columns::Count:
			std::dynamic_pointer_cast<ExpenseEntry> (entry)->Count_ = value.toDouble ();
			break;
		case Columns::Shop:
			std::dynamic_pointer_cast<ExpenseEntry> (entry)->Shop_ = value.toString ();
			break;
		case Columns::Categories:
			std::dynamic_pointer_cast<ExpenseEntry> (entry)->Categories_ = value.toStringList ();
			break;
		}

		Core::Instance ().GetOpsManager ()->UpdateEntry (entry);

		if (resetModel)
		{
			auto entries = Entries_;
			Entries_.clear ();
			AddEntries (entries);
		}
		else
		{
			emit dataChanged (createIndex (index.row (), 0), createIndex (index.row (), Columns::MaxCount));
			if (shouldRecalcSums)
				recalcSums ();
		}

		return true;
	}

	QVariant EntriesModel::headerData (int section, Qt::Orientation orientation, int role) const
	{
		if (orientation == Qt::Vertical)
			return QVariant ();

		switch (role)
		{
		case Qt::DisplayRole:
			return HeaderData_.at (section);
		}

		return QVariant ();
	}

	namespace
	{
		bool DateLess (EntryBase_ptr e1, EntryBase_ptr e2)
		{
			return e1->Date_ < e2->Date_;
		}

		void AppendEntry (QHash<int, double>& hash, EntryBase_ptr entry)
		{
			auto& val = hash [entry->AccountID_];
			switch (entry->GetType ())
			{
			case EntryType::Expense:
				val -= entry->Amount_;
				break;
			case EntryType::Receipt:
				val += entry->Amount_;
				break;
			}
		}

		double GetTotalSum (const QHash<int, double>& curSum, AccountsManager *accMgr, CurrenciesManager *curMgr)
		{
			double totalSum = 0;
			for (auto id : curSum.keys ())
			{
				auto acc = accMgr->GetAccount (id);
				totalSum += curMgr->ToUserCurrency (acc.Currency_, curSum [id]);
			}
			return totalSum;
		}
	}

	void EntriesModel::AddEntry (EntryBase_ptr entry)
	{
		auto bound = std::upper_bound (Entries_.begin (), Entries_.end (), entry, DateLess);

		auto pos = std::distance (Entries_.begin (), bound);
		beginInsertRows (QModelIndex (), pos, pos);

		Entries_.insert (bound, entry);
		if (bound == Entries_.end ())
		{
			auto hash = Sums_.value (Sums_.size () - 1).Accs_;
			AppendEntry (hash, entry);
			const auto totalSum = GetTotalSum (hash,
					Core::Instance ().GetAccsManager (),
					Core::Instance ().GetCurrenciesManager ());
			Sums_ << BalanceInfo { totalSum, hash };
		}
		else
			recalcSums ();

		endInsertRows ();
	}

	void EntriesModel::AddEntries (QList<EntryBase_ptr> entries)
	{
		beginResetModel ();

		Entries_ += entries;
		std::sort (Entries_.begin (), Entries_.end (), DateLess);

		recalcSums ();

		endResetModel ();
	}

	void EntriesModel::RemoveEntry (const QModelIndex& index)
	{
		beginRemoveRows (QModelIndex (), index.row (), index.row ());
		Entries_.removeAt (index.row ());
		endRemoveRows ();

		recalcSums ();
	}

	EntryBase_ptr EntriesModel::GetEntry (const QModelIndex& index) const
	{
		return Entries_.value (index.row ());
	}

	QList<EntryBase_ptr> EntriesModel::GetEntries () const
	{
		return Entries_;
	}

	QList<BalanceInfo> EntriesModel::GetSumInfos () const
	{
		return Sums_;
	}

	void EntriesModel::recalcSums ()
	{
		Sums_.clear ();

		if (Entries_.isEmpty ())
			return;

		auto accMgr = Core::Instance ().GetAccsManager ();
		auto curMgr = Core::Instance ().GetCurrenciesManager ();

		QHash<int, double> curSum;
		for (auto entry : Entries_)
		{
			AppendEntry (curSum, entry);
			const auto totalSum = GetTotalSum (curSum, accMgr, curMgr);
			Sums_ << BalanceInfo { totalSum, curSum };
		}

		emit dataChanged (createIndex (0, Columns::AccBalance),
				createIndex (Entries_.size () - 1, Columns::SumBalance));
	}
}
}
