/**********************************************************************
 * 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 "aggregatorapp.h"
#include <QObject>
#include <QThread>
#include <QtDebug>
#include <Wt/WText.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WBoxLayout.h>
#include <Wt/WCheckBox.h>
#include <Wt/WTreeView.h>
#include <Wt/WTableView.h>
#include <Wt/WStandardItemModel.h>
#include <Wt/WStandardItem.h>
#include <Wt/WOverlayLoadingIndicator.h>
#include <Wt/WPanel.h>
#include <Wt/WPopupMenu.h>
#include <Wt/WCssTheme.h>
#include <interfaces/aggregator/iproxyobject.h>
#include <interfaces/aggregator/channel.h>
#include <interfaces/aggregator/iitemsmodel.h>
#include <util/aggregator/itemsmodeldecorator.h>
#include "readchannelsfilter.h"
#include "util.h"
#include "q2wproxymodel.h"
#include "readitemsfilter.h"

namespace LeechCraft
{
namespace Aggregator
{
namespace WebAccess
{
	namespace
	{
		class WittyThread : public QThread
		{
			Wt::WApplication * const App_;
		public:
			WittyThread (Wt::WApplication *app)
			: App_ { app }
			{
				setObjectName ("Aggregator WebAccess (Wt Thread)");
			}
		protected:
			void run ()
			{
				App_->attachThread (true);
				QThread::run ();
				App_->attachThread (false);
			}
		};
	}

	AggregatorApp::AggregatorApp (IProxyObject *ap, ICoreProxy_ptr cp, const Wt::WEnvironment& environment)
	: WApplication { environment }
	, AP_ { ap }
	, CP_ { cp }
	, ObjsThread_ { new WittyThread (this) }
	, ChannelsModel_ { std::make_shared<Q2WProxyModel> (AP_->GetChannelsModel (), this) }
	, ChannelsFilter_ { std::make_shared<ReadChannelsFilter> () }
	, SourceItemModel_ { AP_->CreateItemsModel () }
	, ItemsModel_ { std::make_shared<Q2WProxyModel> (SourceItemModel_, this) }
	, ItemsFilter_ { std::make_shared<ReadItemsFilter> () }
	{
		ChannelsModel_->SetRoleMappings ({
				{ ChannelRole::UnreadCount, Aggregator::ChannelRoles::UnreadCount },
				{ ChannelRole::CID, Aggregator::ChannelRoles::ChannelID }
			});

		ItemsModel_->SetRoleMappings ({
				{ ItemRole::IID, Aggregator::IItemsModel::ItemRole::ItemId },
				{ ItemRole::IsRead, Aggregator::IItemsModel::ItemRole::IsRead }
			});
		ItemsModel_->AddDataMorphism ([] (const QModelIndex& idx, Wt::ItemDataRole role) -> Wt::cpp17::any
			{
				if (role != Wt::ItemDataRole::StyleClass)
					return {};

				if (!idx.data (Aggregator::IItemsModel::ItemRole::IsRead).toBool ())
					return Wt::WString ("unreadItem");

				return {};
			});

		auto initThread = [this] (QObject *obj)
		{
			obj->moveToThread (ObjsThread_);
			QObject::connect (ObjsThread_,
					SIGNAL (finished ()),
					obj,
					SLOT (deleteLater ()));
		};
		initThread (ChannelsModel_.get ());
		initThread (SourceItemModel_);
		initThread (ItemsModel_.get ());

		ObjsThread_->start ();

		ChannelsFilter_->setSourceModel (ChannelsModel_);
		ItemsFilter_->setSourceModel (ItemsModel_);

		setTitle ("Aggregator WebAccess");
		setLoadingIndicator (std::make_unique<Wt::WOverlayLoadingIndicator> ());

		SetupUI ();

		enableUpdates (true);
	}

	AggregatorApp::~AggregatorApp ()
	{
		ObjsThread_->quit ();
		ObjsThread_->wait (1000);
		if (!ObjsThread_->isFinished ())
		{
			qWarning () << Q_FUNC_INFO
					<< "objects thread hasn't finished yet, terminating...";
			ObjsThread_->terminate ();
		}

		delete ObjsThread_;
	}

	void AggregatorApp::HandleChannelClicked (const Wt::WModelIndex& idx, const Wt::WMouseEvent&)
	{
		ItemView_->setText ({});

		const auto cid = Wt::cpp17::any_cast<IDType_t> (idx.data (ChannelRole::CID));
		const auto fid = Wt::cpp17::any_cast<IDType_t> (idx.data (ChannelRole::FID));

		ItemsFilter_->ClearCurrentItem ();
		ItemsModelDecorator { SourceItemModel_ }.Reset (cid, fid);
	}

	void AggregatorApp::HandleItemClicked (const Wt::WModelIndex& idx, const Wt::WMouseEvent& event)
	{
		if (!idx.isValid ())
			return;

		const auto& src = ItemsModel_->MapToSource (ItemsFilter_->mapToSource (idx));
		const auto itemId = Wt::cpp17::any_cast<IDType_t> (idx.data (ItemRole::IID));
		const auto& item = AP_->GetItem (itemId);
		if (!item)
			return;

		ItemsFilter_->SetCurrentItem (itemId);

		switch (event.button ())
		{
		case Wt::MouseButton::Left:
			ShowItem (src, *item);
			break;
		case Wt::MouseButton::Right:
			ShowItemMenu (src, *item, event);
			break;
		default:
			break;
		}
	}

	void AggregatorApp::ShowItem (const QModelIndex& src, const Item& item)
	{
		ItemsModelDecorator { SourceItemModel_ }.Selected (src);

		auto text = Wt::WString ("<div><a href='{1}' target='_blank'>{2}</a><br />{3}<br /><hr/>{4}</div>")
				.arg (ToW (item.Link_))
				.arg (ToW (item.Title_))
				.arg (ToW (item.PubDate_.toString ()))
				.arg (ToW (item.Description_));
		ItemView_->setText (text);
	}

	void AggregatorApp::ShowItemMenu (const QModelIndex&, const Item& item, const Wt::WMouseEvent& event)
	{
		Wt::WPopupMenu menu;
		const auto itemId = item.ItemID_;
		if (item.Unread_)
			menu.addItem (ToW (tr ("Mark as read")))->
					triggered ().connect ([this, itemId] { AP_->SetItemRead (itemId, true); });
		else
			menu.addItem (ToW (tr ("Mark as unread")))->
					triggered ().connect ([this, itemId] { AP_->SetItemRead (itemId, false); });
		menu.exec (event);
	}

	void AggregatorApp::SetupUI ()
	{
		setTheme (std::make_shared<Wt::WCssTheme> ("polished"));
		setLocale ({ QLocale {}.name ().toUtf8 ().constData () });

		styleSheet ().addRule (".unreadItem", "font-weight: bold;");

		auto rootLay = root ()->setLayout (std::make_unique<Wt::WBoxLayout> (Wt::LayoutDirection::LeftToRight));

		auto leftPaneLay = rootLay->addLayout (std::make_unique<Wt::WBoxLayout> (Wt::LayoutDirection::TopToBottom), 2);

		auto showReadChannels = leftPaneLay->addWidget (std::make_unique<Wt::WCheckBox> (ToW (tr ("Include read channels"))));
		showReadChannels->setToolTip (ToW (tr ("Also display channels that have no unread items.")));
		showReadChannels->setChecked (false);
		showReadChannels->checked ().connect ([this] { ChannelsFilter_->SetHideRead (false); });
		showReadChannels->unChecked ().connect ([this] { ChannelsFilter_->SetHideRead (true); });

		auto channelsTree = leftPaneLay->addWidget (std::make_unique<Wt::WTreeView> (), 1, Wt::AlignmentFlag::Top);
		channelsTree->setModel (ChannelsFilter_);
		channelsTree->setSelectionMode (Wt::SelectionMode::Single);
		channelsTree->clicked ().connect (this, &AggregatorApp::HandleChannelClicked);
		channelsTree->setAlternatingRowColors (true);

		auto rightPaneLay = rootLay->addLayout (std::make_unique<Wt::WBoxLayout> (Wt::LayoutDirection::TopToBottom), 7);

		auto showReadItems = rightPaneLay->addWidget (std::make_unique<Wt::WCheckBox> (ToW (tr ("Show read items"))));
		showReadItems->setChecked (false);
		showReadItems->checked ().connect ([this] { ItemsFilter_->SetHideRead (false); });
		showReadItems->unChecked ().connect ([this] { ItemsFilter_->SetHideRead (true); });

		ItemsTable_ = rightPaneLay->addWidget (std::make_unique<Wt::WTableView> (), 2, Wt::AlignmentFlag::Justify);
		ItemsTable_->setModel (ItemsFilter_);
		ItemsTable_->mouseWentUp ().connect (this, &AggregatorApp::HandleItemClicked);
		ItemsTable_->setAlternatingRowColors (true);
		ItemsTable_->setColumnWidth (0, { 550, Wt::LengthUnit::Pixel });
		ItemsTable_->setSelectionMode (Wt::SelectionMode::Single);
		ItemsTable_->setAttributeValue ("oncontextmenu",
				"event.cancelBubble = true; event.returnValue = false; return false;");

		auto itemPanel = rightPaneLay->addWidget (std::make_unique<Wt::WPanel> (), 5);

		auto scrollArea = itemPanel->setCentralWidget (std::make_unique<Wt::WContainerWidget> ());
		scrollArea->setOverflow (Wt::Overflow::Scroll, Wt::Orientation::Vertical);

		ItemView_ = scrollArea->addWidget (std::make_unique<Wt::WText> ());
		ItemView_->setTextFormat (Wt::TextFormat::UnsafeXHTML);
	}
}
}
}
