/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  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 "newtabmenumanager.h"
#include <algorithm>
#include <QMenu>
#include <QtDebug>
#include "interfaces/iinfo.h"
#include "interfaces/ihavetabs.h"
#include <interfaces/iplugin2.h>
#include "xmlsettingsmanager.h"
#include "core.h"

namespace LeechCraft
{
	NewTabMenuManager::NewTabMenuManager (QObject *parent)
	: QObject (parent)
	, NewTabMenu_ (new QMenu (tr ("New tab")))
	, AdditionalTabMenu_ (new QMenu (tr ("Additional")))
	{
	}

	void NewTabMenuManager::AddObject (QObject *obj)
	{
		IHaveTabs *imt = qobject_cast<IHaveTabs*> (obj);
		if (!imt || RegisteredMultiTabs_.contains (obj))
			return;

		IInfo *ii = qobject_cast<IInfo*> (obj);

		for (const auto& info : imt->GetTabClasses ())
		{
			if (!(info.Features_ & TFOpenableByRequest))
				continue;

			QAction *newAct = new QAction (info.Icon_,
					AccelerateName (info.VisibleName_),
					this);
			connect (newAct,
					SIGNAL (triggered ()),
					this,
					SLOT (handleNewTabRequested ()));
			newAct->setProperty ("PluginObj", QVariant::fromValue<QObject*> (obj));
			newAct->setProperty ("TabClass", info.TabClass_);
			newAct->setProperty ("Single",
					static_cast<bool> (info.Features_ & TFSingle));
			newAct->setStatusTip (info.Description_);
			newAct->setToolTip (info.Description_);

			InsertAction (newAct);

			if (info.Features_ & TFByDefault)
			{
				const QByteArray& id = ii->GetUniqueID () + '|' + info.TabClass_;
				const bool hide = XmlSettingsManager::Instance ()->Property ("Hide" + id, false).toBool ();
				if (!hide)
				{
					OpenTab (newAct);
					XmlSettingsManager::Instance ()->setProperty ("Hide" + id, true);
				}
			}
		}
	}

	void NewTabMenuManager::SetToolbarActions (QList<QList<QAction*>> lists)
	{
		QList<QAction*> ones;
		Q_FOREACH (QList<QAction*> list, lists)
			if (list.size () == 1)
			{
				ones += list;
				lists.removeAll (list);
			}

		if (ones.size ())
			lists.prepend (ones);

		for (const auto& list : lists)
		{
			if (!list.size ())
				continue;

			AdditionalTabMenu_->addSeparator ();
			AdditionalTabMenu_->addActions (list);
		}
	}

	void NewTabMenuManager::SingleRemoved (ITabWidget *itw)
	{
		const QByteArray& tabClass = itw->GetTabClassInfo ().TabClass_;
		QAction *act = HiddenActions_ [itw->ParentMultiTabs ()] [tabClass];
		if (!act)
		{
			qWarning () << Q_FUNC_INFO
					<< "no hidden action for"
					<< itw->GetTabClassInfo ().TabClass_
					<< "in"
					<< itw->ParentMultiTabs ();
			return;
		}
		InsertAction (act);
		ToggleHide (itw, true);
	}

	QMenu* NewTabMenuManager::GetNewTabMenu () const
	{
		return NewTabMenu_;
	}

	QMenu* NewTabMenuManager::GetAdditionalMenu ()
	{
		if (!AdditionalTabMenu_->actions ().isEmpty ())
			AdditionalTabMenu_->insertMenu (AdditionalTabMenu_->actions ().first (),
					NewTabMenu_);
		else
			AdditionalTabMenu_->addMenu (NewTabMenu_);

		return AdditionalTabMenu_;
	}

	void NewTabMenuManager::ToggleHide (ITabWidget *itw, bool hide)
	{
		ToggleHide (itw->ParentMultiTabs (), itw->GetTabClassInfo ().TabClass_, hide);
	}

	void NewTabMenuManager::HideAction (ITabWidget *itw)
	{
		QObject *pObj = itw->ParentMultiTabs ();
		const auto& tabClass = itw->GetTabClassInfo ().TabClass_;
		for (auto action : NewTabMenu_->actions ())
			if (action->property ("TabClass").toByteArray () == tabClass &&
					action->property ("PluginObj").value<QObject*> () == pObj)
			{
				NewTabMenu_->removeAction (action);
				HiddenActions_ [pObj] [tabClass] = action;
			}
	}

	QString NewTabMenuManager::AccelerateName (QString name)
	{
		for (int i = 0, length = name.length ();
				i < length; ++i)
		{
			QChar c = name.at (i);
			if (UsedAccelerators_.contains (c))
				continue;

			UsedAccelerators_ << c;
			name.insert (i, '&');
			break;
		}
		return name;
	}

	void NewTabMenuManager::ToggleHide (QObject *obj,
			const QByteArray& tabClass, bool hide)
	{
		if (!hide)
			return;

		IInfo *ii = qobject_cast<IInfo*> (obj);
		if (!ii)
		{
			qWarning () << Q_FUNC_INFO
					<< obj
					<< "doesn't implement IInfo";
			return;
		}

		const auto& id = ii->GetUniqueID () + '|' + tabClass;
		XmlSettingsManager::Instance ()->setProperty ("Hide" + id, hide);
	}

	void NewTabMenuManager::OpenTab (QAction *action)
	{
		QObject *pObj = action->property ("PluginObj").value<QObject*> ();
		IHaveTabs *tabs = qobject_cast<IHaveTabs*> (pObj);
		if (!tabs)
		{
			qWarning () << Q_FUNC_INFO
					<< pObj
					<< "doesn't implement IHaveTabs";
			return;
		}

		const QByteArray& tabClass = action->property ("TabClass").toByteArray ();
		tabs->TabOpenRequested (tabClass);

		const auto& classes = tabs->GetTabClasses ();
		const auto pos = std::find_if (classes.begin (), classes.end (),
				[&tabClass] (decltype (classes.front ()) item) { return item.TabClass_ == tabClass; });
		if (action->property ("Single").toBool ())
		{
			NewTabMenu_->removeAction (action);
			HiddenActions_ [pObj] [tabClass] = action;
			ToggleHide (pObj, tabClass, false);
		}
		else if (pos != classes.end () && pos->Features_ & TFByDefault)
			ToggleHide (pObj, tabClass, false);
	}

	namespace
	{
		QAction* FindActionBefore (const QString& name, QMenu *menu)
		{
			for (auto otherAct : menu->actions ())
				if (otherAct->isSeparator () ||
						QString::localeAwareCompare (otherAct->text (), name) > 0)
					return otherAct;
			return nullptr;
		}
	}

	void NewTabMenuManager::InsertAction (QAction *act)
	{
		auto pObj = act->property ("PluginObj").value<QObject*> ();

		auto ip2 = qobject_cast<IPlugin2*> (pObj);
		if (!ip2)
		{
			InsertActionWParent (act, pObj, false);
			return;
		}

		const auto pm = Core::Instance ().GetPluginManager ();
		for (auto plugin : pm->GetFirstLevels (ip2->GetPluginClasses ()))
			InsertActionWParent (act, plugin, true);
	}

	void NewTabMenuManager::InsertActionWParent (QAction *act, QObject *pObj, bool sub)
	{
		const auto& tabClasses = qobject_cast<IHaveTabs*> (pObj)->GetTabClasses ();
		const auto& tcCount = std::count_if (tabClasses.begin (), tabClasses.end (),
				[] (const TabClassInfo& tc) { return tc.Features_ & TFOpenableByRequest; });

		const auto ii = qobject_cast<IInfo*> (pObj);
		const auto& name = ii->GetName ();

		auto rootMenu = NewTabMenu_;
		if (sub || tcCount > 1)
		{
			bool menuFound = false;
			for (auto menuAct : rootMenu->actions ())
				if (menuAct->menu () && menuAct->text () == name)
				{
					rootMenu = menuAct->menu ();
					menuFound = true;
					break;
				}

			if (!menuFound)
			{
				auto menu = new QMenu (name, rootMenu);
				menu->setIcon (ii->GetIcon ());
				rootMenu->insertMenu (FindActionBefore (name, rootMenu), menu);
				rootMenu = menu;
			}
		}

		rootMenu->insertAction (FindActionBefore (act->text (), rootMenu), act);
	}

	void NewTabMenuManager::handleNewTabRequested ()
	{
		QAction *action = qobject_cast<QAction*> (sender ());
		if (!action)
		{
			qWarning () << Q_FUNC_INFO
					<< sender ()
					<< "is not an action";
			return;
		}

		OpenTab (action);
	}
}
