/**********************************************************************
 * 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 "lastseen.h"
#include <QTimer>
#include <QSettings>
#include <QCoreApplication>
#include <QIcon>
#include <QTranslator>
#include <QFutureWatcher>
#include <QtConcurrentRun>
#include <util/util.h>
#include <util/sll/onetimerunner.h>
#include <interfaces/azoth/iclentry.h>

Q_DECLARE_METATYPE (LeechCraft::Azoth::LastSeen::LastHash_t);

namespace LeechCraft
{
namespace Azoth
{
namespace LastSeen
{
	void Plugin::Init (ICoreProxy_ptr)
	{
		SaveScheduled_ = false;

		Util::InstallTranslator ("azoth_lastseen");

		qRegisterMetaType<LastHash_t> ("LeechCraft::Azoth::LastSeen::LastHash_t");
		qRegisterMetaTypeStreamOperators<LastHash_t> ("LeechCraft::Azoth::LastSeen::LastHash_t");

		Load ();
	}

	void Plugin::SecondInit ()
	{
	}

	QByteArray Plugin::GetUniqueID () const
	{
		return "org.LeechCraft.Azoth.LastSeen";
	}

	void Plugin::Release ()
	{
	}

	QString Plugin::GetName () const
	{
		return "Azoth LastSeen";
	}

	QString Plugin::GetInfo () const
	{
		return tr ("Azoth LastSeen displays when a contact has been online and available for the last time.");
	}

	QIcon Plugin::GetIcon () const
	{
		static QIcon icon ("lcicons:/azoth/lastseen/resources/images/lastseen.svg");
		return icon;
	}

	QSet<QByteArray> Plugin::GetPluginClasses () const
	{
		QSet<QByteArray> result;
		result << "org.LeechCraft.Plugins.Azoth.Plugins.IGeneralPlugin";
		return result;
	}

	namespace
	{
		bool IsGoodEntry (QObject *entryObj)
		{
			ICLEntry *entry = qobject_cast<ICLEntry*> (entryObj);
			if (!entry)
			{
				qWarning () << Q_FUNC_INFO
						<< entryObj
						<< "doesn't implement ICLEntry";
				return false;
			}

			return true;
		}

		const int SaveTimeout = 60 * 1000;
	}

	void Plugin::ScheduleSave ()
	{
		if (SaveScheduled_)
			return;

		if (!IsSaving_)
			QTimer::singleShot (SaveTimeout,
					this,
					SLOT (save ()));
		SaveScheduled_ = true;
	}

	namespace
	{
		struct LoadResult
		{
			LastHash_t Avail_;
			LastHash_t Online_;
			LastHash_t StatusChange_;
		};
	}

	void Plugin::Load ()
	{
		auto watcher = new QFutureWatcher<LoadResult> ();
		new Util::OneTimeRunner
		{
			[this, watcher] ()
			{
				const auto& result = watcher->result ();
				watcher->deleteLater ();

				LastAvailable_ = result.Avail_;
				LastOnline_ = result.Online_;
				LastStatusChange_ = result.StatusChange_;

				IsLoaded_ = true;
			},
			watcher,
			SIGNAL (finished ()),
			watcher
		};
		watcher->setFuture (QtConcurrent::run ([] () -> LoadResult
				{
					QSettings settings (QCoreApplication::organizationName (),
							QCoreApplication::applicationName () + "_Azoth_LastSeen");
					const auto& avail = settings.value ("LastAvailable").value<LastHash_t> ();
					const auto& online = settings.value ("LastOnline").value<LastHash_t> ();
					const auto& status = settings.value ("LastStatusChange").value<LastHash_t> ();

					return { avail, online, status };
				}));
	}

	void Plugin::save ()
	{
		if (IsSaving_)
			return;

		SaveScheduled_ = false;
		IsSaving_ = true;

		LoadResult res { LastAvailable_, LastOnline_, LastStatusChange_ };

		auto watcher = new QFutureWatcher<void> ();
		new Util::OneTimeRunner
		{
			[this, watcher] () -> void
			{
				watcher->deleteLater ();
				IsSaving_ = false;

				if (SaveScheduled_)
					QTimer::singleShot (SaveTimeout,
							this,
							SLOT (save ()));
			},
			watcher,
			SIGNAL (finished ()),
			watcher
		};
		watcher->setFuture (QtConcurrent::run ([this, res] () -> void
				{
					QSettings settings (QCoreApplication::organizationName (),
							QCoreApplication::applicationName () + "_Azoth_LastSeen");
					settings.setValue ("LastAvailable", QVariant::fromValue (res.Avail_));
					settings.setValue ("LastOnline", QVariant::fromValue (res.Online_));
					settings.setValue ("LastStatusChange", QVariant::fromValue (res.StatusChange_));
				}));
	}

	void Plugin::hookEntryStatusChanged (IHookProxy_ptr, QObject *entryObj, QString)
	{
		if (!IsLoaded_ || !IsGoodEntry (entryObj))
			return;

		ICLEntry *entry = qobject_cast<ICLEntry*> (entryObj);
		const QString& id = entry->GetEntryID ();
		const EntryStatus& status = entry->GetStatus ();

		const auto& now = QDateTime::currentDateTime ();
		LastStatusChange_ [id] = now;

		ScheduleSave ();

		if (!LastState_.contains (id))
		{
			LastState_ [id] = status.State_;
			return;
		}

		const State oldState = LastState_ [id];
		LastState_ [id] = status.State_;

		switch (oldState)
		{
		case SOffline:
		case SProbe:
		case SError:
		case SInvalid:
		case SConnecting:
			return;
		case SOnline:
			LastAvailable_ [id] = now;
		default:
			LastOnline_ [id] = now;
			break;
		}
	}

	void Plugin::hookTooltipBeforeVariants (IHookProxy_ptr proxy, QObject *entryObj)
	{
		if (!IsGoodEntry (entryObj))
			return;

		ICLEntry *entry = qobject_cast<ICLEntry*> (entryObj);
		const QString& id = entry->GetEntryID ();

		QString addition;

		const State curState = entry->GetStatus ().State_;

		if (curState != SOnline)
		{
			const QDateTime& avail = LastAvailable_.value (id);
			if (avail.isValid ())
				addition += tr ("Was available: %1")
					.arg (avail.toString ());
		}

		if (curState == SOffline ||
				curState == SError ||
				curState == SInvalid)
		{
			const QDateTime& online = LastOnline_.value (id);
			if (LastOnline_.contains (id))
			{
				if (!addition.isEmpty ())
					addition += "<br/>";
				addition += tr ("Was online: %1")
					.arg (online.toString ());
			}
		}

		const auto& lastChange = LastStatusChange_.value (id);
		if (lastChange.isValid ())
		{
			if (!addition.isEmpty ())
				addition += "<br/>";
			addition += tr ("Last status change: %1")
					.arg (lastChange.toString ());
		}

		if (addition.isEmpty ())
			return;

		const QString& tip = proxy->GetValue ("tooltip").toString ();
		proxy->SetValue ("tooltip", tip + "<br/><br/>" + addition + "<br/>");
	}
}
}
}

LC_EXPORT_PLUGIN (leechcraft_azoth_lastseen, LeechCraft::Azoth::LastSeen::Plugin);
