/**********************************************************************
 * 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 "juick.h"
#include <functional>
#include <QCoreApplication>
#include <QIcon>
#include <QMessageBox>
#include <QPushButton>
#include <QSettings>
#include <interfaces/azoth/imessage.h>
#include <interfaces/azoth/iclentry.h>
#include <util/util.h>

namespace LeechCraft
{
namespace Azoth
{
namespace Juick
{
	typedef QString& (QString::*replace_t) (const QString&, const QString&, Qt::CaseSensitivity);

	class Typo
	{
		QString Text_;
		QRegExp CheckRX_;
		QString FixPattern_;
		std::function<QString (QString)> Correction_;
	public:
		Typo (const QString& text, const QString& checkPattern, const QString& fixPattern)
		: Text_ (text)
		, CheckRX_ (checkPattern)
		, FixPattern_ (fixPattern)
		{}

		Typo (const QString& text, const QString& checkPattern,
				std::function<QString (QString)> correction)
		: Text_ (text)
		, CheckRX_ (checkPattern)
		, Correction_ (correction)
		{}

		bool Done ()
		{
			return CheckRX_.indexIn (Text_) != -1;
		}

		QString& Correction ()
		{
			if (Correction_)
				Text_ = Correction_ (Text_);
			else if (CheckRX_.indexIn (Text_) != -1)
				Text_.replace (CheckRX_, FixPattern_);
			return Text_;
		}
	};

	void Plugin::Init (ICoreProxy_ptr)
	{
		Util::InstallTranslator ("azoth_juick");

		UserRX_ = QRegExp ("(@[\\w\\-\\.@\\|]*)\\b([:\\s,.?!])", Qt::CaseInsensitive);
		PostRX_ = QRegExp ("<br />#(\\d+)\\s", Qt::CaseInsensitive);
		IdRX_ = QRegExp ("#(\\d+)(\\s|$|<br />)", Qt::CaseInsensitive);
		ReplyRX_ = QRegExp ("#(\\d+/\\d+)\\s?", Qt::CaseInsensitive);
		UnsubRX_ = QRegExp ("#(\\d+)/(\\d+)\\s(<a href)", Qt::CaseInsensitive);
		AvatarRX_ = QRegExp ("@([\\w\\-\\.@\\|]*):", Qt::CaseInsensitive);
		TagRX_ = QRegExp ("( [*][^*,<]*[^*, <])");
	}

	void Plugin::SecondInit ()
	{
	}

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

	void Plugin::Release ()
	{
	}

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

	QString Plugin::GetInfo () const
	{
		return tr ("Azoth Juick provides nicer support for the juick.com microblogging service.");
	}

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

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

	QString Plugin::FormatBody (QString body)
	{
		//fix this http://i.imgur.com/t579t.png
		if (body.startsWith ("@"))
			body.insert (0, "<br />");

		body.replace (TagRX_,
			     " <a href=\"azoth://msgeditinsert/\\1\">\\1</a> ");

		InsertAvatars (body);
		InsertNickLinks (body);
		body.replace (PostRX_,
				"<br /> <a href=\"azoth://msgeditinsert/%23\\1%20\">#\\1</a> "
				"("
				"<a href=\"azoth://msgeditreplace/S%20%23\\1\">S</a> "
				"<a href=\"azoth://msgeditreplace/%23\\1+\">+</a> "
				"<a href=\"azoth://msgeditreplace/!%20%23\\1\">!</a> "
				") ");
		body.replace (UnsubRX_,
				"#\\1/\\2 (<a href=\"azoth://msgeditreplace/U%20%23\\1\">U</a>) \\3");
		body.replace (IdRX_, "<a href=\"azoth://msgeditinsert/%23\\1%20/%23/%23\\1+\">#\\1</a>\\2");
		body.replace (ReplyRX_, "<a href=\"azoth://msgeditinsert/%23\\1%20\">#\\1</a> ");

		return body;
	}


	bool Plugin::ShouldHandle (QObject* msgObj,
			IMessage::Direction direction, IMessage::Type type)
	{
		IMessage *msg = qobject_cast<IMessage*> (msgObj);

		if (!msg)
		{
			qWarning () << Q_FUNC_INFO
				<< "unable to cast"
				<< msgObj
				<< "to IMessage";
			return false;
		}

		if (msg->GetDirection () != direction ||
			msg->GetMessageType () != type)
		{
			return false;
		}

		ICLEntry *other = qobject_cast<ICLEntry*> (msg->OtherPart ());

		if (!other)
		{
			qWarning () << Q_FUNC_INFO
				<< "unable to cast"
				<< msg->OtherPart ()
				<< "to ICLEntry";
			return false;
		}

		if (!other->GetEntryID ().contains ("juick@juick.com"))
			return false;

		return true;

	}

	void Plugin::hookFormatBodyEnd (IHookProxy_ptr proxy,
			QObject *msgObj)
	{
		if (ShouldHandle (msgObj, IMessage::Direction::In, IMessage::Type::ChatMessage))
			proxy->SetValue ("body", FormatBody (proxy->GetValue ("body").toString ()));
	}

	void Plugin::hookMessageWillCreated (LeechCraft::IHookProxy_ptr proxy,
			QObject *chatTab, QObject *entry, int, QString)
	{
		ICLEntry *other = qobject_cast<ICLEntry*> (entry);

		if (!other)
		{
			qWarning () << Q_FUNC_INFO
				<< "unable to cast"
				<< entry
				<< "to ICLEntry";
			return;
		}

		if (!other->GetEntryID ().contains ("juick@juick.com"))
			return;

		QString text = proxy->GetValue ("text").toString ();

		Typo typos[] = {
			Typo (text, QString::fromUtf8 ("^!\\s+[#№]{2,}(\\d+)"), QString ("! #\\1")),
			Typo (text, "^!\\s+(\\d+)", QString ("! #\\1")),
			Typo (text, QString::fromUtf8 ("^![#№](\\d+)"), QString ("! #\\1")),
			Typo (text, "^!(\\d+)", QString ("! #\\1")),
			Typo (text, QString::fromUtf8 ("^[SЫ]\\s+[#№]{2,}(\\d+)"), QString ("S #\\1")),
			Typo (text, QString::fromUtf8 ("^[SЫ]\\s+(\\d+)"), QString ("S #\\1")),
			Typo (text, QString::fromUtf8 ("^[SЫ][#№](\\d+)"), QString ("S #\\1")),
			Typo (text, QString::fromUtf8 ("^[SЫ](\\d+)"), QString ("S #\\1")),
			Typo (text, QString::fromUtf8 ("^Ы [#№](\\d+)"), QString ("S #\\1")),
			Typo (text, QString::fromUtf8 ("^ЗЬ\\s+@(.*)"), QString ("PM @\\1")),
			Typo (text, "^(\\d+)\\s+(.*)", QString ("#\\1 \\2")),
			Typo (text, "^(\\d+/\\d+)\\s+(.*)", QString ("#\\1 \\2")),
			Typo (text, QString::fromUtf8 ("^№\\+$"), QString ("#+")),
			Typo (text, QString::fromUtf8 ("^\"$"), QString ("@")),
			Typo (text, QString::fromUtf8 ("^В\\s?Д$"), QString ("D L")),
			Typo (text, QString::fromUtf8 ("$Ы^"), QString ("S")),
			Typo (text, QString::fromUtf8 ("^[UГ]\\s+[#№]{2,}(\\d+)"), QString ("U #\\1")),
			Typo (text, QString::fromUtf8 ("^[UГ]\\s+(\\d+)"), QString ("U #\\1")),
			Typo (text, QString::fromUtf8 ("^[UГ][#№](\\d+)"), QString ("U #\\1")),
			Typo (text, QString::fromUtf8 ("^[UГ](\\d+)"), QString ("U #\\1")),
			Typo (text, QString::fromUtf8 ("^Г [#№](\\d+)"), QString ("U #\\1")),
			Typo (text, QString::fromUtf8 ("^В [#№](\\d+)"), QString ("D #\\1")),
			Typo (text, QString::fromUtf8 ("^[DВ][#№](\\d+)"), QString ("D #\\1")),
			Typo (text, QString::fromUtf8 ("^[DВ](\\d+)"), QString ("D #\\1")),
			Typo (text, QString::fromUtf8 ("^РУДЗ$"), QString ("HELP")),
			Typo (text, QString::fromUtf8 ("^ДЩПШТ$"), QString ("LOGIN")),
			Typo (text, QString::fromUtf8 ("^ЩТ(\\+?)$"), QString ("ON\\1")),
			Typo (text, QString::fromUtf8 ("^ЩАА$"), QString ("OFF")),
			Typo (text, QString::fromUtf8 ("^ИД(\\s?)"), QString ("BL\\1")),
			Typo (text, QString::fromUtf8 ("^ЦД(\\s?)"), QString ("WL\\1")),
			Typo (text, QString::fromUtf8 ("^ШТМШЕУ "), QString ("INVITE ")),
			Typo (text, QString::fromUtf8 ("^МСФКВ$"), QString ("VCARD")),
			Typo (text, QString::fromUtf8 ("^ЗШТП$"), QString ("PING")),
			Typo (text, QString::fromUtf8 ("^№+$"), [] (QString str) { return str.replace ("№", "#"); })
		};

		for (int i = 0; i < static_cast<int> (sizeof (typos) / sizeof (Typo)); ++i)
		{
			Typo typo = typos [i];

			if (!typo.Done ())
				continue;

			QSettings settings (QCoreApplication::organizationName (),
				QCoreApplication::applicationName () + "_AzothJuick");
			QWidget* parent = qobject_cast<QWidget*> (chatTab);
			QString correction = typo.Correction ();
			bool askForCorrection = settings.value ("AskForCorrection", true).toBool ();

			if (!parent)
			{
				qWarning () << Q_FUNC_INFO
					<< "unable to cast"
					<< chatTab
					<< "to QWidget";
				return;
			}

			QMessageBox msgbox (QMessageBox::Question,
					tr ("Fix typo?"),
					tr ("Did you mean <em>%1</em> instead of <em>%2</em>?")
						.arg (correction)
						.arg (text),
					QMessageBox::NoButton,
					parent);
			msgbox.addButton (QMessageBox::Yes);
			QPushButton *always = msgbox.addButton (tr ("Always"), QMessageBox::YesRole);
			msgbox.addButton (QMessageBox::No);

			if (!askForCorrection || msgbox.exec () != QMessageBox::No)
			{
				proxy->SetValue ("text", correction);
				if (msgbox.clickedButton () == always)
					settings.setValue ("AskForCorrection", false);
			}
			break;
		}
	}


	bool Plugin::IsBehind (const QString& text, int index, const QString& pattern) const
	{
		const QRegExp behind (pattern);

		if (behind.indexIn (text) == -1)
			return false;

		const int behindIndex = index - behind.matchedLength ();

		return behindIndex >= 0 &&
				behind.indexIn (text.mid (behindIndex, behind.matchedLength ())) != -1;
	}

	void Plugin::InsertAvatars (QString& body)
	{
		int index = AvatarRX_.indexIn (body);
		QRegExp notBehind ("Recommended by ");
		QRegExp behind("Reply by ");

		while (index >= 0)
		{
			if (IsBehind (body, index, "Recommended by "))
			{
				index = AvatarRX_.indexIn (body, index + 1);
				continue;
			}

			bool needNewLine = IsBehind (body, index, "Reply by ") ||
					IsBehind (body, index, "Private message from ");

			const QString& avatar =
				QString ("%1<img style='float:left;margin-right:4px' "
						"width='32px' "
						"height='32px' "
						"src='http://api.juick.com/avatar?uname=%2&size=32'>")
					.arg (needNewLine ? "<br />" : "")
					.arg (AvatarRX_.cap (1));
			body.insert (index, avatar);
			index = AvatarRX_.indexIn (body, index + avatar.length () + AvatarRX_.matchedLength ());
		}
	}

	void Plugin::InsertNickLinks (QString& body)
	{
		int index = UserRX_.indexIn (body);

		while (index >= 0)
		{
			const QString& userLink =
				QString (IsBehind (body, index, "Private message from .*size=32'>") ?
					"<a href=\"azoth://msgeditinsert/%1/%23/PM%20%1\">" :
					"<a href=\"azoth://msgeditinsert/%1/%23/%1+\">")
						.arg (UserRX_.cap (1));

			body.insert (index, userLink);
			index += userLink.length () + UserRX_.cap (1).length ();
			body.insert (index, "</a>");
			index = UserRX_.indexIn (body, index + sizeof ("</a>") - 1);
		}
	}
}
}
}

LC_EXPORT_PLUGIN (leechcraft_azoth_juick, LeechCraft::Azoth::Juick::Plugin);
