/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  Georg Rudoy
 *
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
 **********************************************************************/

#include "metaentry.h"
#include <algorithm>
#include <QDateTime>
#include <QVariant>
#include <QImage>
#include <QAction>
#include <QtDebug>
#include <util/sll/qtutil.h>
#include <util/sll/prelude.h>
#include <util/util.h>
#include <util/azoth/emitters/clentry.h>
#include "metaaccount.h"
#include "metamessage.h"
#include "managecontactsdialog.h"
#include "core.h"

namespace LC
{
namespace Azoth
{
namespace Metacontacts
{
	MetaEntry::MetaEntry (const QString& id, MetaAccount *account)
	: QObject (account)
	, Account_ (account)
	, ID_ (id)
	{
		ActionMCSep_ = Util::CreateSeparator (this);
		ActionManageContacts_ = new QAction (tr ("Manage contacts..."),
				this);
		connect (ActionManageContacts_,
				SIGNAL (triggered ()),
				this,
				SLOT (handleManageContacts ()));
	}

	QList<ICLEntry*> MetaEntry::GetAvailEntryObjs () const
	{
		return AvailableRealEntries_;
	}

	QStringList MetaEntry::GetRealEntries () const
	{
		return UnavailableRealEntries_ + Util::Map (AvailableRealEntries_, &ICLEntry::GetEntryID);
	}

	void MetaEntry::SetRealEntries (const QStringList& ids)
	{
		UnavailableRealEntries_ = ids;
	}

	void MetaEntry::AddRealObject (ICLEntry *entry)
	{
		AvailableRealEntries_ << entry;
		UnavailableRealEntries_.removeAll (entry->GetEntryID ());

		HandleRealVariantsChanged (entry, entry->Variants ());
		for (const auto msg : entry->GetAllMessages ())
			HandleRealGotMessage (msg->GetQObject ());

		emit Emitter_.statusChanged (GetStatus (QString ()), QString ());

		ConnectStandardSignals (entry);

		if (const auto entryObj = entry->GetQObject ();
			qobject_cast<IAdvancedCLEntry*> (entryObj))
			ConnectAdvancedSiganls (entryObj);
	}

	QString MetaEntry::GetMetaVariant (QObject *entry, const QString& realVar) const
	{
		return Variant2RealVariant_.key ({ qobject_cast<ICLEntry*> (entry), realVar });
	}

	QObject* MetaEntry::GetQObject ()
	{
		return this;
	}

	IAccount* MetaEntry::GetParentAccount () const
	{
		return Account_;
	}

	ICLEntry::Features MetaEntry::GetEntryFeatures () const
	{
		return FPermanentEntry | FSupportsGrouping | FSupportsRenames;
	}

	ICLEntry::EntryType MetaEntry::GetEntryType () const
	{
		return EntryType::Chat;
	}

	QString MetaEntry::GetEntryName () const
	{
		return Name_;
	}

	void MetaEntry::SetEntryName (const QString& name)
	{
		Name_ = name;
		emit Emitter_.nameChanged (name);
	}

	QString MetaEntry::GetEntryID () const
	{
		return ID_;
	}

	QString MetaEntry::GetHumanReadableID () const
	{
		return GetEntryName () + "@metacontact";
	}

	QStringList MetaEntry::Groups () const
	{
		return Groups_;
	}

	void MetaEntry::SetGroups (const QStringList& groups)
	{
		Groups_ = groups;
		emit Emitter_.groupsChanged (groups);
	}

	QStringList MetaEntry::Variants () const
	{
		QStringList result;
		for (const auto entry : AvailableRealEntries_)
		{
			const auto& name = entry->GetEntryName ();
			auto variants = entry->Variants ();
			if (!variants.contains (QString ()))
				variants.prepend (QString ());
			for (const auto& var : variants)
			{
				const auto& full = name + '/' + var;
				if (!Variant2RealVariant_.contains (full))
				{
					qWarning () << Q_FUNC_INFO
							<< "skipping out-of-sync with variant map:"
							<< name
							<< var
							<< Variant2RealVariant_;
					continue;
				}

				result << full;
			}
		}
		return result;
	}

	template<typename Target>
	auto CastEntry (ICLEntry *src)
	{
		if constexpr (std::is_same_v<Target, ICLEntry*>)
			return src;
		else
			return qobject_cast<Target> (src->GetQObject ());
	}

	template<typename F>
	auto MetaEntry::ActWithVariant (F&& func, const QString& variant) const
	{
		using R = Util::RetType_t<std::decay_t<F>>;
		using Arg = Util::ArgType_t<std::decay_t<F>, 0>;

		if (variant.isEmpty ())
		{
			if (!AvailableRealEntries_.isEmpty ())
				return func (CastEntry<Arg> (AvailableRealEntries_.first ()), QString ());
			return R ();
		}

		if (!Variant2RealVariant_.contains (variant))
		{
			qWarning () << variant << "doesn't exist";
			return R ();
		}

		const auto& [realEntry, realVar] = Variant2RealVariant_ [variant];
		return func (CastEntry<Arg> (realEntry), realVar);
	}

	void MetaEntry::SendMessage (const OutgoingMessage& message)
	{
		auto f = [message = message] (ICLEntry *e, const QString& v) mutable
		{
			message.Variant_ = v;
			e->SendMessage (message);
		};
		ActWithVariant (f, message.Variant_.value_or ({}));
	}

	QList<IMessage*> MetaEntry::GetAllMessages () const
	{
		return Messages_;
	}

	void MetaEntry::PurgeMessages (const QDateTime& from)
	{
		for (const auto entry : AvailableRealEntries_)
			entry->PurgeMessages (from);
	}

	void MetaEntry::SetChatPartState (ChatPartState state, const QString& variant)
	{
		auto f = [state] (ICLEntry *e, const QString& v) { e->SetChatPartState (state, v); };
		ActWithVariant (f, variant);
	}

	EntryStatus MetaEntry::GetStatus (const QString& variant) const
	{
		auto f = [] (ICLEntry *e, const QString& v) { return e->GetStatus (v); };
		return ActWithVariant (f, variant);
	}

	void MetaEntry::ShowInfo ()
	{
	}

	QList<QAction*> MetaEntry::GetActions () const
	{
		auto result = Util::ConcatMap (AvailableRealEntries_, &ICLEntry::GetActions);
		if (!result.isEmpty ())
			result << ActionMCSep_;
		result << ActionManageContacts_;
		return result;
	}

	QMap<QString, QVariant> MetaEntry::GetClientInfo (const QString& variant) const
	{
		auto f = [] (ICLEntry *e, const QString& v) { return e->GetClientInfo (v); };
		return ActWithVariant (f, variant);
	}

	void MetaEntry::MarkMsgsRead ()
	{
	}

	void MetaEntry::ChatTabClosed ()
	{
	}

	IAdvancedCLEntry::AdvancedFeatures MetaEntry::GetAdvancedFeatures () const
	{
		return AFSupportsAttention;
	}

	void MetaEntry::DrawAttention (const QString& text, const QString& variant)
	{
		auto f = [text] (IAdvancedCLEntry *e, const QString& v) { e->DrawAttention (text, v); };
		ActWithVariant (f, variant);
	}

	void MetaEntry::ConnectStandardSignals (ICLEntry *entry)
	{
		auto& emitter = entry->GetCLEntryEmitter ();
		connect (&emitter,
				&Emitters::CLEntry::gotMessage,
				this,
				&MetaEntry::HandleRealGotMessage);
		connect (&emitter,
				&Emitters::CLEntry::statusChanged,
				this,
				[this, entry] (const EntryStatus& status, const QString& var)
				{
					emit Emitter_.statusChanged (status, entry->GetEntryName () + '/' + var);
				});
		connect (&emitter,
				&Emitters::CLEntry::availableVariantsChanged,
				this,
				std::bind_front (&MetaEntry::HandleRealVariantsChanged, this, entry));
		connect (&emitter,
				&Emitters::CLEntry::nameChanged,
				this,
				[this, entry] { HandleRealVariantsChanged (entry, entry->Variants ()); });
		connect (&emitter,
				&Emitters::CLEntry::chatPartStateChanged,
				this,
				[this, entry] (const ChatPartState& cps, const QString& var)
				{
					emit Emitter_.chatPartStateChanged (cps, entry->GetEntryName () + '/' + var);
				});
		connect (&emitter,
				&Emitters::CLEntry::entryGenerallyChanged,
				&Emitter_,
				&Emitters::CLEntry::entryGenerallyChanged);

		connect (&entry->GetParentAccount ()->GetAccountEmitter (),
				&Emitters::Account::removedCLItems,
				this,
				[this] (const QList<ICLEntry*>& entries)
				{
					auto remaining = AvailableRealEntries_;
					for (const auto entry : entries)
						remaining.removeAll (entry);

					if (remaining.size () != AvailableRealEntries_.size ())
						SetNewEntryList (remaining, false);
				});

		/*
		IAccount *account = qobject_cast<IAccount*> (entry->GetParentAccount ());
		connect (account->GetParentProtocol (),
				SIGNAL (accountRemoved (QObject*)),
				this,
				SLOT (handleAccountRemoved (QObject*)),
				Qt::QueuedConnection);
				*/
	}

	void MetaEntry::ConnectAdvancedSiganls (QObject *entryObj)
	{
		connect (entryObj,
				SIGNAL (attentionDrawn (const QString&, const QString&)),
				this,
				SLOT (handleRealAttentionDrawn (const QString&, const QString&)));
	}

	void MetaEntry::PerformRemoval (ICLEntry *entry)
	{
		const auto entryObj = entry->GetQObject ();
		for (auto i = Messages_.begin (); i != Messages_.end (); )
		{
			const auto metaMsg = dynamic_cast<MetaMessage*> (*i);
			const auto origMsg = metaMsg->GetOriginalMessage ();

			if (origMsg->OtherPart () == entryObj)
				i = Messages_.erase (i);
			else
				++i;
		}

		for (auto i = Variant2RealVariant_.begin (); i != Variant2RealVariant_.end (); )
		{
			const auto& var = i.key ();
			const auto& pair = i.value ();

			if (pair.first == entry)
			{
				emit Emitter_.statusChanged ({ SOffline, QString () }, var);
				i = Variant2RealVariant_.erase (i);
			}
			else
				++i;
		}

		emit Emitter_.availableVariantsChanged (Variants ());

		entry->GetCLEntryEmitter ().disconnect (this);
	}

	void MetaEntry::SetNewEntryList (const QList<ICLEntry*>& newList, bool readdRemoved)
	{
		if (newList == AvailableRealEntries_)
			return;

		const auto removedContacts = Util::Filter (AvailableRealEntries_,
				[&newList] (ICLEntry *obj) { return !newList.contains (obj); });

		AvailableRealEntries_ = newList;

		for (const auto entry : removedContacts)
			PerformRemoval (entry);

		Core::Instance ().HandleEntriesRemoved (removedContacts, readdRemoved);

		if (AvailableRealEntries_.isEmpty () &&
				UnavailableRealEntries_.isEmpty ())
		{
			emit shouldRemoveThis ();
			return;
		}

		emit Emitter_.availableVariantsChanged (Variants ());
		emit Emitter_.statusChanged (GetStatus (QString ()), QString ());
	}

	void MetaEntry::HandleRealGotMessage (QObject *msgObj)
	{
		IMessage *msg = qobject_cast<IMessage*> (msgObj);
		if (!msg)
		{
			qWarning () << Q_FUNC_INFO
					<< msgObj
					<< "doesn't implement IMessage";
			return;
		}

		MetaMessage *message = new MetaMessage (msgObj, this);

		const bool shouldSort = !Messages_.isEmpty () &&
				Messages_.last ()->GetDateTime () > msg->GetDateTime ();

		Messages_ << message;
		if (shouldSort)
			std::stable_sort (Messages_.begin (), Messages_.end (), Util::ComparingBy (&IMessage::GetDateTime));

		emit Emitter_.gotMessage (message);
	}

	void MetaEntry::HandleRealVariantsChanged (ICLEntry *entry, QStringList variants)
	{
		for (auto it = Variant2RealVariant_.begin (); it != Variant2RealVariant_.end ();)
		{
			if (it.value ().first == entry)
				it = Variant2RealVariant_.erase (it);
			else
				++it;
		}

		if (!variants.contains (QString ()))
			variants.prepend (QString ());

		for (const auto& var : variants)
			Variant2RealVariant_ [entry->GetEntryName () + '/' + var] = QPair { entry, var };

		emit Emitter_.availableVariantsChanged (Variants ());

		for (const auto& var : variants)
		{
			const auto& str = entry->GetEntryName () + '/' + var;
			emit Emitter_.statusChanged (GetStatus (str), str);
		}
	}

	void MetaEntry::handleRealAttentionDrawn (const QString& text, const QString& var)
	{
		ICLEntry *entry = qobject_cast<ICLEntry*> (sender ());
		emit attentionDrawn (text, entry->GetEntryName () + '/' + var);
	}

	void MetaEntry::handleManageContacts ()
	{
		ManageContactsDialog dia (AvailableRealEntries_);
		if (dia.exec () == QDialog::Rejected)
			return;

		SetNewEntryList (dia.GetObjects (), true);
	}
}
}
}
