/**********************************************************************
 * 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 "poshuku.h"
#include <stdexcept>
#include <QMessageBox>
#include <qwebsettings.h>
#include <QHeaderView>
#include <QToolBar>
#include <QDir>
#include <QUrl>
#include <QTextCodec>
#include <QInputDialog>
#include <QMenu>
#include <QMainWindow>
#include <qwebpage.h>
#if QT_VERSION < 0x050000
#include <qwebkitversion.h>
#endif
#include <QtDebug>
#include <interfaces/entitytesthandleresult.h>
#include <interfaces/core/icoreproxy.h>
#include <interfaces/core/irootwindowsmanager.h>
#include <interfaces/core/ishortcutproxy.h>
#include <util/util.h>
#include <util/xpc/util.h>
#include <util/sys/paths.h>
#include <util/tags/tagscompletionmodel.h>
#include <util/db/backendselector.h>
#include <util/xsd/wkfontswidget.h>
#include "core.h"
#include "xmlsettingsmanager.h"
#include "customwebview.h"
#include "browserwidget.h"
#include "cookieseditdialog.h"

namespace LeechCraft
{
namespace Poshuku
{
	using LeechCraft::Util::TagsCompletionModel;

	Poshuku::~Poshuku ()
	{
		Core::Instance ().setParent (0);
	}

	void Poshuku::Init (ICoreProxy_ptr coreProxy)
	{
		Util::InstallTranslator ("poshuku");

		BrowserWidget::SetParentMultiTabs (this);
		Core::Instance ().setParent (this);
		Core::Instance ().SetProxy (coreProxy);

		auto rootWM = Core::Instance ().GetProxy ()->GetRootWindowsManager ();

		try
		{
			const auto& path = Util::GetUserDir (Util::UserDir::Cache, "poshuku/favicons").absolutePath ();
			QWebSettings::setIconDatabasePath (path);
		}
		catch (const std::runtime_error& e)
		{
			QMessageBox::warning (rootWM->GetPreferredWindow (),
					"LeechCraft",
					e.what ());
		}

		try
		{
			const auto& path = Util::CreateIfNotExists ("poshuku/offlinestorage").absolutePath ();
			QWebSettings::setOfflineStoragePath (path);
		}
		catch (const std::runtime_error& e)
		{
			QMessageBox::warning (rootWM->GetPreferredWindow (),
					"LeechCraft",
					e.what ());
		}

		try
		{
			const auto& path = Util::GetUserDir (Util::UserDir::Cache, "poshuku/offlinewebappcache").absolutePath ();
			QWebSettings::setOfflineWebApplicationCachePath (path);
		}
		catch (const std::runtime_error& e)
		{
			QMessageBox::warning (rootWM->GetPreferredWindow (),
					"LeechCraft",
					e.what ());
		}

		XmlSettingsDialog_.reset (new LeechCraft::Util::XmlSettingsDialog ());
		XmlSettingsDialog_->RegisterObject (XmlSettingsManager::Instance (),
				"poshukusettings.xml");
		XmlSettingsDialog_->SetCustomWidget ("BackendSelector",
				new Util::BackendSelector (XmlSettingsManager::Instance ()));

		const auto fontsSelector = new Util::WkFontsWidget { XmlSettingsManager::Instance () };
		XmlSettingsDialog_->SetCustomWidget ("FontsSelector", fontsSelector);
		connect (fontsSelector,
				SIGNAL (fontChanged (QWebSettings::FontFamily, QFont)),
				this,
				SLOT (handleFontChanged (QWebSettings::FontFamily, QFont)));

		InitConnections ();

		ImportXbel_ = new QAction (tr ("Import XBEL..."),
				this);
		ImportXbel_->setProperty ("ActionIcon", "document-import");

		ExportXbel_ = new QAction (tr ("Export XBEL..."),
				this);
		ExportXbel_->setProperty ("ActionIcon", "document-export");

		CheckFavorites_ = new QAction (tr ("Check favorites..."),
				this);
		CheckFavorites_->setProperty ("ActionIcon", "checkbox");

		ReloadAll_ = new QAction (tr ("Reload all pages"),
				this);
		ReloadAll_->setProperty ("ActionIcon", "system-software-update");


		try
		{
			Core::Instance ().Init ();
		}
		catch (const std::exception& e)
		{
			qWarning () << Q_FUNC_INFO
					<< e.what ();
			QMessageBox::critical (rootWM->GetPreferredWindow (),
					"LeechCraft",
					tr ("Poshuku failed to initialize properly. "
						"Check logs and talk with the developers. "
						"Or, at least, check the storage backend "
						"settings and restart LeechCraft."));
			throw;
		}

		RegisterSettings ();

		connect (Core::Instance ().GetFavoritesModel (),
				SIGNAL (error (const QString&)),
				this,
				SLOT (handleError (const QString&)));

		connect (ImportXbel_,
				SIGNAL (triggered ()),
				&Core::Instance (),
				SLOT (importXbel ()));
		connect (ExportXbel_,
				SIGNAL (triggered ()),
				&Core::Instance (),
				SLOT (exportXbel ()));
		connect (CheckFavorites_,
				SIGNAL (triggered ()),
				this,
				SLOT (handleCheckFavorites ()));
		connect (ReloadAll_,
				SIGNAL (triggered ()),
				this,
				SLOT (handleReloadAll ()));

		IShortcutProxy *proxy = coreProxy->GetShortcutProxy ();
		ImportXbel_->setShortcuts (proxy->GetShortcuts (this, "EAImportXbel_"));
		ExportXbel_->setShortcuts (proxy->GetShortcuts (this, "EAExportXbel_"));
		CheckFavorites_->setShortcuts (proxy->GetShortcuts (this, "EACheckFavorites_"));
		ReloadAll_->setShortcuts (proxy->GetShortcuts (this, "EAReloadAll_"));

		ToolMenu_ = new QMenu ("Poshuku");
		ToolMenu_->setIcon (GetIcon ());
		ToolMenu_->addAction (ImportXbel_);
		ToolMenu_->addAction (ExportXbel_);
	}

	void Poshuku::SecondInit ()
	{
		Core::Instance ().SecondInit ();
		QTimer::singleShot (1000,
				this,
				SLOT (createTabFirstTime ()));
	}

	void Poshuku::Release ()
	{
		Core::Instance ().setParent (0);
		Core::Instance ().Release ();
		XmlSettingsDialog_.reset ();
	}

	QByteArray Poshuku::GetUniqueID () const
	{
		return "org.LeechCraft.Poshuku";
	}

	QString Poshuku::GetName () const
	{
		return tr ("Poshuku Browser");
	}

	QString Poshuku::GetInfo () const
	{
		return tr ("Simple yet functional web browser");
	}

	QStringList Poshuku::Provides () const
	{
		return QStringList ("webbrowser");
	}

	QStringList Poshuku::Needs () const
	{
		return QStringList ();
	}

	QStringList Poshuku::Uses () const
	{
		return QStringList ();
	}

	void Poshuku::SetProvider (QObject*, const QString&)
	{
	}

	QIcon Poshuku::GetIcon () const
	{
		static QIcon icon ("lcicons:/resources/images/poshuku.svg");
		return icon;
	}

	TabClasses_t Poshuku::GetTabClasses () const
	{
		TabClasses_t result;
		result << Core::Instance ().GetTabClass ();
		return result;
	}

	void Poshuku::TabOpenRequested (const QByteArray& tabClass)
	{
		if (tabClass == "Poshuku")
			Core::Instance ().NewURL ("", true);
		else
			qWarning () << Q_FUNC_INFO
					<< "unknown tab class"
					<< tabClass;
	}

	QSet<QByteArray> Poshuku::GetExpectedPluginClasses () const
	{
		return Core::Instance ().GetExpectedPluginClasses ();
	}

	void Poshuku::AddPlugin (QObject *plugin)
	{
		Core::Instance ().AddPlugin (plugin);
	}

	std::shared_ptr<LeechCraft::Util::XmlSettingsDialog> Poshuku::GetSettingsDialog () const
	{
		return XmlSettingsDialog_;
	}

	EntityTestHandleResult Poshuku::CouldHandle (const LeechCraft::Entity& e) const
	{
		return Core::Instance ().CouldHandle (e) ?
				EntityTestHandleResult (EntityTestHandleResult::PIdeal) :
				EntityTestHandleResult ();
	}

	void Poshuku::Handle (LeechCraft::Entity e)
	{
		Core::Instance ().Handle (e);
	}

	void Poshuku::Open (const QString& link)
	{
		Core::Instance ().NewURL (link);
	}

	IWebWidget* Poshuku::GetWidget () const
	{
		return Core::Instance ().GetWidget ();
	}

	QWebView* Poshuku::CreateWindow ()
	{
		return Core::Instance ().MakeWebView ();
	}

	void Poshuku::SetShortcut (const QString& name, const QKeySequences_t& sequences)
	{
		if (name.startsWith ("Browser"))
			Core::Instance ().SetShortcut (name, sequences);
		else
		{
			QAction *act = 0;
			if (name == "EAImportXbel_")
				act = ImportXbel_;
			else if (name == "EAExportXbel_")
				act = ExportXbel_;
			else if (name == "EACheckFavorites_")
				act = CheckFavorites_;
			if (act)
				act->setShortcuts (sequences);
		}
	}

	QMap<QString, ActionInfo> Poshuku::GetActionInfo () const
	{
		BrowserWidget bw;
		QMap<QString, ActionInfo> result = bw.GetActionInfo ();
		result ["EAImportXbel_"] = ActionInfo (ImportXbel_->text (),
				QKeySequence (), ImportXbel_->icon ());
		result ["EAExportXbel_"] = ActionInfo (ExportXbel_->text (),
				QKeySequence (), ExportXbel_->icon ());
		result ["EACheckFavorites_"] = ActionInfo (CheckFavorites_->text (),
				QKeySequence (), CheckFavorites_->icon ());
		return result;
	}

	QString Poshuku::GetDiagInfoString () const
	{
		return QString ("Built with QtWebKit %1, running with QtWebKit %2")
#ifdef QTWEBKIT_VERSION_STR
				.arg (QTWEBKIT_VERSION_STR)
#else
				.arg ("unknown (QTWEBKIT_VERSION_STR is not defined)")
#endif
				.arg (qWebKitVersion ());
	}

	QList<QAction*> Poshuku::GetActions (ActionsEmbedPlace place) const
	{
		QList<QAction*> result;

		switch (place)
		{
		case ActionsEmbedPlace::ToolsMenu:
			result << CheckFavorites_;
			result << ToolMenu_->menuAction ();
			break;
		case ActionsEmbedPlace::CommonContextMenu:
			result << ReloadAll_;
			break;
		default:
			break;
		}

		return result;
	}

	void Poshuku::RecoverTabs (const QList<TabRecoverInfo>& infos)
	{
		for (const auto& info : infos)
		{
			auto bw = Core::Instance ().NewURL (QUrl (), false, info.DynProperties_);
			bw->SetTabRecoverData (info.Data_);
			emit tabRecovered (info.Data_, bw);
		}
	}

	bool Poshuku::HasSimilarTab (const QByteArray& data, const QList<QByteArray>& existing) const
	{
		return StandardSimilarImpl (data, existing,
				[] (const QByteArray& data)
				{
					QUrl url;
					QDataStream str { data };
					str >> url;
					return url;
				});
	}

	void Poshuku::InitConnections ()
	{
		connect (XmlSettingsDialog_.get (),
				SIGNAL (pushButtonClicked (const QString&)),
				this,
				SLOT (handleSettingsClicked (const QString&)));

		connect (&Core::Instance (),
				SIGNAL (addNewTab (const QString&, QWidget*)),
				this,
				SIGNAL (addNewTab (const QString&, QWidget*)));
		connect (&Core::Instance (),
				SIGNAL (removeTab (QWidget*)),
				this,
				SIGNAL (removeTab (QWidget*)));
		connect (&Core::Instance (),
				SIGNAL (changeTabName (QWidget*, const QString&)),
				this,
				SIGNAL (changeTabName (QWidget*, const QString&)));
		connect (&Core::Instance (),
				SIGNAL (changeTabIcon (QWidget*, const QIcon&)),
				this,
				SIGNAL (changeTabIcon (QWidget*, const QIcon&)));
		connect (&Core::Instance (),
				SIGNAL (changeTooltip (QWidget*, QWidget*)),
				this,
				SIGNAL (changeTooltip (QWidget*, QWidget*)));
		connect (&Core::Instance (),
				SIGNAL (statusBarChanged (QWidget*, const QString&)),
				this,
				SIGNAL (statusBarChanged (QWidget*, const QString&)));
		connect (&Core::Instance (),
				SIGNAL (raiseTab (QWidget*)),
				this,
				SIGNAL (raiseTab (QWidget*)));
		connect (&Core::Instance (),
				SIGNAL (gotEntity (const LeechCraft::Entity&)),
				this,
				SIGNAL (gotEntity (const LeechCraft::Entity&)));
		connect (&Core::Instance (),
				SIGNAL (delegateEntity (const LeechCraft::Entity&, int*, QObject**)),
				this,
				SIGNAL (delegateEntity (const LeechCraft::Entity&, int*, QObject**)));
		connect (&Core::Instance (),
				SIGNAL (couldHandle (const LeechCraft::Entity&, bool*)),
				this,
				SIGNAL (couldHandle (const LeechCraft::Entity&, bool*)));
		connect (&Core::Instance (),
				SIGNAL (error (const QString&)),
				this,
				SLOT (handleError (const QString&)));
	}

	void Poshuku::RegisterSettings ()
	{
		QList<QByteArray> viewerSettings;
		viewerSettings << "MinimumFontSize"
			<< "DefaultFontSize"
			<< "DefaultFixedFontSize"
			<< "AutoLoadImages"
			<< "DNSPrefetchEnabled"
			<< "AllowJavascript"
			<< "AllowJava"
			<< "AllowPlugins"
			<< "JavascriptCanOpenWindows"
			<< "JavascriptCanAccessClipboard"
			<< "UserStyleSheet"
			<< "OfflineStorageDB"
			<< "LocalStorageDB"
			<< "OfflineWebApplicationCache"
			<< "EnableXSSAuditing"
			<< "EnableWebGL"
			<< "EnableHyperlinkAuditing"
			<< "EnableSmoothScrolling";
		XmlSettingsManager::Instance ()->RegisterObject (viewerSettings,
				this, "viewerSettingsChanged");

		XmlSettingsManager::Instance ()->RegisterObject ("DeveloperExtrasEnabled",
				this, "developerExtrasChanged");

		viewerSettingsChanged ();
		developerExtrasChanged ();

		QList<QByteArray> cacheSettings;
		cacheSettings << "MaximumPagesInCache"
			<< "MinDeadCapacity"
			<< "MaxDeadCapacity"
			<< "TotalCapacity"
			<< "OfflineStorageQuota";
		XmlSettingsManager::Instance ()->RegisterObject (cacheSettings,
				this, "cacheSettingsChanged");

		cacheSettingsChanged ();
	}

	void Poshuku::createTabFirstTime ()
	{
		bool firstTime = XmlSettingsManager::Instance ()->
				Property ("FirstTimeRun", true).toBool ();
		bool startWithHome = XmlSettingsManager::Instance ()->
				property ("StartWithHomeTab").toBool ();
		if (firstTime || startWithHome)
			Core::Instance ().NewURL ("about:home", true);
		XmlSettingsManager::Instance ()->
				setProperty ("FirstTimeRun", false);
	}

	namespace
	{
		void SetUserStylesheet ()
		{
			const auto& pathStr = XmlSettingsManager::Instance ()->
					property ("UserStyleSheet").toString ();
			if (pathStr.isEmpty ())
			{
				QWebSettings::globalSettings ()->setUserStyleSheetUrl ({});
				return;
			}

			QFile file { pathStr };
			if (!file.open (QIODevice::ReadOnly))
			{
				qWarning () << Q_FUNC_INFO
						<< "cannot open"
						<< pathStr
						<< file.errorString ();
				QWebSettings::globalSettings ()->setUserStyleSheetUrl ({});
				return;
			}

			const auto& contents = file.readAll ();

			const auto& uriContents = "data:text/css;charset=utf-8;base64," + contents.toBase64 ();
			QWebSettings::globalSettings ()->setUserStyleSheetUrl (QUrl::fromEncoded (uriContents));
		}

#if QT_VERSION < 0x050000
		void SetSubsts ()
		{
			const auto& fixedFont = XmlSettingsManager::Instance ()->
					property ("FixedFont").value<QFont> ().family ();
			const auto& knownFamilies = QFontDatabase {}.families ();
			const auto& substs = QFont::substitutions ();

			auto setSubst = [&fixedFont, &knownFamilies, &substs] (const QString& fontName)
			{
				if (knownFamilies.contains (fontName))
					return;

				if (substs.contains (fontName, Qt::CaseInsensitive))
					QFont::removeSubstitution (fontName);

				QFont::insertSubstitutions (fontName, { fixedFont, "monospace" });
			};
			setSubst ("Consolas");
			setSubst ("Menlo");
		}
#endif
	}

	void Poshuku::handleFontChanged (QWebSettings::FontFamily family, const QFont& font)
	{
		QWebSettings::globalSettings ()->setFontFamily (family, font.family ());

#if QT_VERSION < 0x050000
		if (family == QWebSettings::FixedFont)
			SetSubsts ();
#endif
	}

	void Poshuku::viewerSettingsChanged ()
	{
		auto xsm = XmlSettingsManager::Instance ();
		auto setFamily = [this, xsm] (QWebSettings::FontFamily family, const char *prop)
		{
			handleFontChanged (family, xsm->property (prop).value<QFont> ());
		};
		setFamily (QWebSettings::StandardFont, "StandardFont");
		setFamily (QWebSettings::FixedFont, "FixedFont");
		setFamily (QWebSettings::SerifFont, "SerifFont");
		setFamily (QWebSettings::SansSerifFont, "SansSerifFont");
		setFamily (QWebSettings::CursiveFont, "CursiveFont");
		setFamily (QWebSettings::FantasyFont, "FantasyFont");

		auto global = QWebSettings::globalSettings ();

		global->setFontSize (QWebSettings::MinimumFontSize,
				xsm->property ("MinimumFontSize").toInt ());
		global->setFontSize (QWebSettings::DefaultFontSize,
				xsm->property ("DefaultFontSize").toInt ());
		global->setFontSize (QWebSettings::DefaultFixedFontSize,
				xsm->property ("DefaultFixedFontSize").toInt ());
		global->setAttribute (QWebSettings::AutoLoadImages,
				xsm->property ("AutoLoadImages").toBool ());
		global->setAttribute (QWebSettings::DnsPrefetchEnabled,
				xsm->property ("DNSPrefetchEnabled").toBool ());
		global->setAttribute (QWebSettings::JavascriptEnabled,
				xsm->property ("AllowJavascript").toBool ());
		global->setAttribute (QWebSettings::JavaEnabled,
				xsm->property ("AllowJava").toBool ());
		global->setAttribute (QWebSettings::PluginsEnabled,
				xsm->property ("AllowPlugins").toBool ());
		global->setAttribute (QWebSettings::JavascriptCanOpenWindows,
				xsm->property ("JavascriptCanOpenWindows").toBool ());
		global->setAttribute (QWebSettings::JavascriptCanAccessClipboard,
				xsm->property ("JavascriptCanAccessClipboard").toBool ());
		global->setAttribute (QWebSettings::OfflineStorageDatabaseEnabled,
				xsm->property ("OfflineStorageDB").toBool ());
		global->setAttribute (QWebSettings::OfflineWebApplicationCacheEnabled,
				xsm->property ("OfflineWebApplicationCache").toBool ());
		global->setAttribute (QWebSettings::LocalStorageEnabled,
				xsm->property ("LocalStorageDB").toBool ());
		global->setAttribute (QWebSettings::XSSAuditingEnabled,
				xsm->property ("EnableXSSAuditing").toBool ());
		global->setAttribute (QWebSettings::HyperlinkAuditingEnabled,
				xsm->property ("EnableHyperlinkAuditing").toBool ());
		global->setAttribute (QWebSettings::WebGLEnabled,
				xsm->property ("EnableWebGL").toBool ());
#if QT_VERSION >= 0x050000
		global->setAttribute (QWebSettings::ScrollAnimatorEnabled,
				xsm->property ("EnableSmoothScrolling").toBool ());
#endif

		SetUserStylesheet ();
	}

	void Poshuku::developerExtrasChanged ()
	{
		bool enabled = XmlSettingsManager::Instance ()->
				property ("DeveloperExtrasEnabled").toBool ();
		QWebSettings::globalSettings ()->
				setAttribute (QWebSettings::DeveloperExtrasEnabled, enabled);

		if (enabled && sender ())
		{
			auto rootWM = Core::Instance ().GetProxy ()->GetRootWindowsManager ();
			QMessageBox::information (rootWM->GetPreferredWindow (),
					"LeechCraft",
					tr ("Please note that Developer Extras would work correctly "
						"only for pages that are loaded after enabling."));
		}
	}

	void Poshuku::cacheSettingsChanged ()
	{
		QWebSettings::setMaximumPagesInCache (XmlSettingsManager::Instance ()->
				property ("MaximumPagesInCache").toInt ());
		QWebSettings::setObjectCacheCapacities (
				XmlSettingsManager::Instance ()->property ("MinDeadCapacity").toDouble () * 1024 * 1024,
				XmlSettingsManager::Instance ()->property ("MaxDeadCapacity").toDouble () * 1024 * 1024,
				XmlSettingsManager::Instance ()->property ("TotalCapacity").toDouble () * 1024 * 1024
				);
		QWebSettings::setOfflineStorageDefaultQuota (XmlSettingsManager::Instance ()->
				property ("OfflineStorageQuota").toInt () * 1024);
	}

	void Poshuku::handleError (const QString& msg)
	{
		emit gotEntity (Util::MakeNotification ("Poshuku", msg, PWarning_));
	}

	void Poshuku::handleNewTab ()
	{
		Core::Instance ().NewURL ("", true);
	}

	void Poshuku::handleSettingsClicked (const QString& name)
	{
		if (name == "CookiesEdit")
		{
			auto rootWM = Core::Instance ().GetProxy ()->GetRootWindowsManager ();
			auto dia = new CookiesEditDialog (rootWM->GetPreferredWindow ());
			dia->show ();
		}
		else if (name == "ClearIconDatabase")
			QWebSettings::clearIconDatabase ();
		else if (name == "ClearMemoryCaches")
			QWebSettings::clearMemoryCaches ();
		else
			qWarning () << Q_FUNC_INFO
				<< "unknown name"
				<< name;
	}

	void Poshuku::handleCheckFavorites ()
	{
		Core::Instance ().CheckFavorites ();
	}

	void Poshuku::handleReloadAll ()
	{
		Core::Instance ().ReloadAll ();
	}
}
}

LC_EXPORT_PLUGIN (leechcraft_poshuku, LeechCraft::Poshuku::Poshuku);
