/**********************************************************************
 * 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 "flattofoldersproxymodel.h"
#include <QSet>
#include <QMimeData>
#include <QItemSelectionRange>
#include <interfaces/iinfo.h>
#include <interfaces/core/itagsmanager.h>

namespace LeechCraft
{
	struct FlatTreeItem
	{
		QList<FlatTreeItem_ptr> C_;
		FlatTreeItem_ptr Parent_;

		enum Type
		{
			TRoot,
			TFolder,
			TItem
		};

		Type Type_;

		QPersistentModelIndex Index_;
		QString Tag_;

		int Row () const
		{
			if (Parent_)
			{
				QList<FlatTreeItem_ptr> c = Parent_->C_;
				for (int i = 0, size = Parent_->C_.size ();
						i < size; ++i)
					if (c.at (i).get () == this)
						return i;
			}
			return 0;
		}
	};

	FlatTreeItem* ToFlat (const QModelIndex& idx)
	{
		return static_cast<FlatTreeItem*> (idx.internalPointer ());
	}

	namespace Util
	{
		FlatToFoldersProxyModel::FlatToFoldersProxyModel (QObject *parent)
		: QAbstractItemModel (parent)
		, SourceModel_ (0)
		, TM_ (0)
		, Root_ (new FlatTreeItem)
		{
			Root_->Type_ = FlatTreeItem::TRoot;
		}

		void FlatToFoldersProxyModel::SetTagsManager (ITagsManager *tm)
		{
			TM_ = tm;
			reset ();
		}

		int FlatToFoldersProxyModel::columnCount (const QModelIndex&) const
		{
			return SourceModel_ ?
				SourceModel_->columnCount (QModelIndex ()) :
				0;
		}

		QVariant FlatToFoldersProxyModel::data (const QModelIndex& index, int role) const
		{
			FlatTreeItem *fti = ToFlat (index);
			if (fti->Type_ == FlatTreeItem::TItem)
			{
				QModelIndex source = fti->Index_;
				return source.sibling (source.row (), index.column ()).data (role);
			}
			else if (fti->Type_ == FlatTreeItem::TFolder &&
					index.column () == 0)
			{
				if (role == Qt::DisplayRole)
				{
					if (fti->Tag_.isEmpty ())
						return tr ("untagged");

					QString ut = TM_->GetTag (fti->Tag_);
					if (ut.isEmpty ())
						return tr ("<unknown tag>");
					else
						return ut;
				}
				else if (role == RoleTags)
					return fti->Tag_;
				else
					return QVariant ();
			}
			else
				return QVariant ();
		}

		QVariant FlatToFoldersProxyModel::headerData (int section,
				Qt::Orientation orient, int role) const
		{
			if (SourceModel_)
				return SourceModel_->headerData (section, orient, role);
			else
				return QVariant ();
		}

		Qt::ItemFlags FlatToFoldersProxyModel::flags (const QModelIndex& index) const
		{
			auto fti = ToFlat (index);
			if (fti && fti->Type_ == FlatTreeItem::TItem)
				return fti->Index_.flags ();
			else
				return Qt::ItemIsSelectable |
						Qt::ItemIsEnabled |
						Qt::ItemIsDragEnabled |
						Qt::ItemIsDropEnabled;
		}

		QModelIndex FlatToFoldersProxyModel::index (int row, int column,
				const QModelIndex& parent) const
		{
			if (!hasIndex (row, column, parent))
				return QModelIndex ();

			FlatTreeItem *fti = 0;
			if (parent.isValid ())
				fti = ToFlat (parent);
			else
				fti = Root_.get ();

			if (fti->Type_ == FlatTreeItem::TItem)
				return QModelIndex ();
			else
				return createIndex (row, column, fti->C_.at (row).get ());
		}

		QModelIndex FlatToFoldersProxyModel::parent (const QModelIndex& index) const
		{
			FlatTreeItem *fti = 0;
			if (index.isValid ())
				fti = ToFlat (index);
			else
				fti = Root_.get ();

			FlatTreeItem_ptr parent;
			parent = fti->Parent_;

			if (parent &&
					parent->Type_ != FlatTreeItem::TRoot)
				return createIndex (parent->Row (), 0, parent.get ());
			else
				return QModelIndex ();
		}

		int FlatToFoldersProxyModel::rowCount (const QModelIndex& index) const
		{
			if (index.isValid ())
				return ToFlat (index)->C_.size ();
			else
				return Root_->C_.size ();
		}

		Qt::DropActions FlatToFoldersProxyModel::supportedDropActions() const
		{
			return SourceModel_ ?
					SourceModel_->supportedDropActions () :
					QAbstractItemModel::supportedDropActions ();
		}

		QStringList FlatToFoldersProxyModel::mimeTypes() const
		{
			return SourceModel_ ?
					SourceModel_->mimeTypes () :
					QAbstractItemModel::mimeTypes ();
		}

		QMimeData* FlatToFoldersProxyModel::mimeData (const QModelIndexList& indexes) const
		{
			if (!SourceModel_)
				return QAbstractItemModel::mimeData (indexes);

			QModelIndexList sourceIdxs;
			for (const auto& index : indexes)
			{
				auto item = static_cast<FlatTreeItem*> (index.internalPointer ());
				switch (item->Type_)
				{
				case FlatTreeItem::Type::TItem:
					sourceIdxs << MapToSource (index);
					break;
				case FlatTreeItem::Type::TFolder:
					for (const auto& subItem : item->C_)
						sourceIdxs << subItem->Index_;
					break;
				default:
					break;
				}
			}

			return SourceModel_->mimeData (sourceIdxs);
		}

		bool FlatToFoldersProxyModel::dropMimeData (const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
		{
			if (!SourceModel_)
				return false;

			QMimeData modified;
			for (const auto& format : data->formats ())
				modified.setData (format, data->data (format));

			if (auto ptr = static_cast<FlatTreeItem*> (parent.internalPointer ()))
			{
				switch (ptr->Type_)
				{
				case FlatTreeItem::Type::TFolder:
				case FlatTreeItem::Type::TItem:
					modified.setData ("x-leechcraft/tag", ptr->Tag_.toLatin1 ());
					break;
				default:
					break;
				}
			}

			return SourceModel_->dropMimeData (&modified, action, -1, -1, QModelIndex ());
		}

		void FlatToFoldersProxyModel::SetSourceModel (QAbstractItemModel *model)
		{
			if (SourceModel_)
				disconnect (SourceModel_,
						0,
						this,
						0);

			SourceModel_ = model;

			if (model)
			{
				// We don't support changing columns (yet) so don't connect
				// to columns* signals.
				connect (model,
						SIGNAL (headerDataChanged (Qt::Orientation, int, int)),
						this,
						SIGNAL (headerDataChanged (Qt::Orientation, int, int)));
				connect (model,
						SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
						this,
						SLOT (handleDataChanged (const QModelIndex&, const QModelIndex&)));
				connect (model,
						SIGNAL (layoutAboutToBeChanged ()),
						this,
						SIGNAL (layoutAboutToBeChanged ()));
				connect (model,
						SIGNAL (layoutChanged ()),
						this,
						SIGNAL (layoutChanged ()));
				connect (model,
						SIGNAL (modelReset ()),
						this,
						SLOT (handleModelReset ()));
				connect (model,
						SIGNAL (rowsInserted (const QModelIndex&,
								int, int)),
						this,
						SLOT (handleRowsInserted (const QModelIndex&,
								int, int)));
				connect (model,
						SIGNAL (rowsAboutToBeRemoved (const QModelIndex&,
								int, int)),
						this,
						SLOT (handleRowsAboutToBeRemoved (const QModelIndex&,
								int, int)));
			}

			handleModelReset ();
		}

		QAbstractItemModel* FlatToFoldersProxyModel::GetSourceModel () const
		{
			return SourceModel_;
		}

		QModelIndex FlatToFoldersProxyModel::MapToSource (const QModelIndex& proxy) const
		{
			if (!proxy.isValid ())
				return QModelIndex ();

			FlatTreeItem *item = ToFlat (proxy);

			if (item->Type_ != FlatTreeItem::TItem)
				return QModelIndex ();

			return item->Index_;
		}

		QList<QModelIndex> FlatToFoldersProxyModel::MapFromSource (const QModelIndex& source) const
		{
			QList<QModelIndex> result;
			Q_FOREACH (FlatTreeItem_ptr item, Items_.values (QPersistentModelIndex (source)))
				result << item->Index_;
			return result;
		}

		FlatTreeItem_ptr FlatToFoldersProxyModel::GetFolder (const QString& tag)
		{
			QList<FlatTreeItem_ptr>& c = Root_->C_;
			Q_FOREACH (FlatTreeItem_ptr item, c)
				if (item->Tag_ == tag)
					return item;

			FlatTreeItem_ptr item (new FlatTreeItem);
			item->Type_ = FlatTreeItem::TFolder;
			item->Tag_ = tag;
			item->Parent_ = Root_;

			int size = c.size ();
			beginInsertRows (QModelIndex (), size, size);
			c.append (item);
			endInsertRows ();

			return item;
		}

		void FlatToFoldersProxyModel::HandleRowInserted (int i)
		{
			QModelIndex idx = SourceModel_->index (i, 0);

			QStringList tags = idx.data (RoleTags).toStringList ();

			if (tags.isEmpty ())
				tags << QString ();

			QPersistentModelIndex pidx (idx);

			Q_FOREACH (QString tag, tags)
				AddForTag (tag, pidx);
		}

		void FlatToFoldersProxyModel::HandleRowRemoved (int i)
		{
			QAbstractItemModel *model = SourceModel_;
			QModelIndex idx = model->index (i, 0);

			QStringList tags = idx.data (RoleTags).toStringList ();

			if (tags.isEmpty ())
				tags << QString ();

			QPersistentModelIndex pidx (idx);

			Q_FOREACH (QString tag, tags)
				RemoveFromTag (tag, pidx);
		}

		void FlatToFoldersProxyModel::AddForTag (const QString& tag,
				const QPersistentModelIndex& pidx)
		{
			FlatTreeItem_ptr folder = GetFolder (tag);

			FlatTreeItem_ptr item (new FlatTreeItem);
			item->Type_ = FlatTreeItem::TItem;
			item->Index_ = pidx;
			item->Parent_ = folder;
			item->Tag_ = tag;

			int size = folder->C_.size ();
			QModelIndex iidx = index (Root_->C_.indexOf (folder), 0);
			beginInsertRows (iidx, size, size);
			folder->C_.append (item);
			Items_.insert (pidx, item);
			endInsertRows ();
		}

		void FlatToFoldersProxyModel::RemoveFromTag (const QString& tag,
				const QPersistentModelIndex& pidx)
		{
			FlatTreeItem_ptr folder = GetFolder (tag);
			QList<FlatTreeItem_ptr>& c = folder->C_;
			int findex = Root_->C_.indexOf (folder);
			for (int i = 0, size = c.size ();
					i < size; ++i)
			{
				if (c.at (i)->Index_ != pidx)
					continue;

				beginRemoveRows (index (findex, 0), i, i);
				Items_.remove (pidx, c.at (i));
				c.removeAt (i);
				endRemoveRows ();
				break;
			}

			if (c.isEmpty ())
			{
				beginRemoveRows (QModelIndex (), findex, findex);
				Root_->C_.removeAt (findex);
				endRemoveRows ();
			}
		}

		void FlatToFoldersProxyModel::HandleChanged (const QModelIndex& idx)
		{
			QSet<QString> newTags = QSet<QString>::fromList (idx.data (RoleTags).toStringList ());
			if (newTags.isEmpty ())
				newTags << QString ();

			QPersistentModelIndex pidx (idx);
			QList<FlatTreeItem_ptr> items = Items_.values (pidx);

			QSet<QString> oldTags;
			Q_FOREACH (FlatTreeItem_ptr item, items)
				oldTags << item->Tag_;

			QSet<QString> added = QSet<QString> (newTags).subtract (oldTags);
			QSet<QString> removed = QSet<QString> (oldTags).subtract (newTags);
			QSet<QString> changed = QSet<QString> (newTags).intersect (oldTags);

			Q_FOREACH (QString ch, changed)
			{
				FlatTreeItem_ptr folder = GetFolder (ch);

				QList<FlatTreeItem_ptr>& c = folder->C_;
				int findex = Root_->C_.indexOf (folder);
				QModelIndex fmi = index (findex, 0);
				for (int i = 0, size = c.size ();
						i < size; ++i)
				{
					if (c.at (i)->Index_ != pidx)
						continue;

					emit dataChanged (index (i, 0, fmi),
							index (i, columnCount () - 1, fmi));
					break;
				}
			}

			Q_FOREACH (QString rem, removed)
				RemoveFromTag (rem, pidx);

			Q_FOREACH (QString add, added)
				AddForTag (add, pidx);
		}

		void FlatToFoldersProxyModel::handleDataChanged (const QModelIndex& topLeft,
				const QModelIndex& bottomRight)
		{
			QItemSelectionRange range (topLeft.sibling (topLeft.row (), 0),
					bottomRight.sibling (bottomRight.row (), 0));
			QModelIndexList indexes = range.indexes ();
			for (int i = 0, size = indexes.size ();
					i < size; ++i)
				HandleChanged (indexes.at (i));
		}

		void FlatToFoldersProxyModel::handleModelReset ()
		{
			if (const int size = Root_->C_.size ())
			{
				beginRemoveRows (QModelIndex (), 0, size - 1);
				Root_->C_.clear ();
				Items_.clear ();
				endRemoveRows ();
			}

			if (SourceModel_)
			{
				for (int i = 0, size = SourceModel_->rowCount ();
						i < size; ++i)
					HandleRowInserted (i);
				reset ();
			}
		}

		void FlatToFoldersProxyModel::handleRowsInserted (const QModelIndex&,
				int start, int end)
		{
			for (int i = start; i <= end; ++i)
				HandleRowInserted (i);
		}

		void FlatToFoldersProxyModel::handleRowsAboutToBeRemoved (const QModelIndex&,
				int start, int end)
		{
			for (int i = start; i <= end; ++i)
				HandleRowRemoved (i);
		}
	};
};

