/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2013  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 "dbupdatethreadworker.h"
#include <stdexcept>
#include <QUrl>
#include <QtDebug>
#include <util/util.h>
#include <util/defaulthookproxy.h>
#include "xmlsettingsmanager.h"
#include "core.h"
#include "storagebackend.h"
#include "regexpmatchermanager.h"
#include "tovarmaps.h"

namespace LeechCraft
{
namespace Aggregator
{
	DBUpdateThreadWorker::DBUpdateThreadWorker (QObject *parent)
	: QObject (parent)
	{
		try
		{
			const QString& strType = XmlSettingsManager::Instance ()->
					property ("StorageType").toString ();
			SB_ = StorageBackend::Create (strType, "_UpdateThread");
		}
		catch (const std::runtime_error& s)
		{
			qWarning () << Q_FUNC_INFO
					<< s.what ();
			return;
		}
		catch (...)
		{
			qWarning () << Q_FUNC_INFO
					<< "unknown exception";
			return;
		}

		SB_->Prepare ();

		connect (SB_.get (),
				SIGNAL (channelDataUpdated (Channel_ptr)),
				this,
				SLOT (handleChannelDataUpdated (Channel_ptr)));
		connect (SB_.get (),
				SIGNAL (itemDataUpdated (Item_ptr, Channel_ptr)),
				this,
				SIGNAL (itemDataUpdated (Item_ptr, Channel_ptr)));
		connect (SB_.get (),
				SIGNAL (itemsRemoved (QSet<IDType_t>)),
				this,
				SIGNAL (itemsRemoved (QSet<IDType_t>)));
	}

	void DBUpdateThreadWorker::toggleChannelUnread (IDType_t channel, bool state)
	{
		SB_->ToggleChannelUnread (channel, state);
	}

	void DBUpdateThreadWorker::updateFeed (channels_container_t channels, QString url)
	{
		IDType_t feedId = SB_->FindFeed (url);
		if (feedId == static_cast<IDType_t> (-1))
		{
			qWarning () << Q_FUNC_INFO
				<< "skipping"
				<< url
				<< "cause seems like it's not in storage yet";
			return;
		}

		const int defaultDays = XmlSettingsManager::Instance ()->
			property ("ItemsMaxAge").toInt ();
		const unsigned defaultIpc = XmlSettingsManager::Instance ()->
			property ("ItemsPerChannel").value<unsigned> ();
		bool downloadEnclosures = false;

		int days = defaultDays;
		unsigned ipc = defaultIpc;
		try
		{
			const auto& settings = SB_->GetFeedSettings (feedId);
			if (settings.ItemAge_)
				days = settings.ItemAge_;
			if (settings.NumItems_)
				ipc = settings.NumItems_;
			downloadEnclosures = settings.AutoDownloadEnclosures_;
		}
		catch (const StorageBackend::FeedSettingsNotFoundError&)
		{
		}
		catch (const std::exception& e)
		{
			qWarning () << Q_FUNC_INFO
					<< "unable to get feed settings for"
					<< feedId
					<< url
					<< e.what ();
		}

		QDateTime current = QDateTime::currentDateTime ();
		Q_FOREACH (Channel_ptr channel, channels)
		{
			Channel_ptr ourChannel;
			try
			{
				IDType_t ourChannelID = SB_->FindChannel (channel->Title_,
						channel->Link_, feedId);
				ourChannel = SB_->GetChannel (ourChannelID, feedId);
			}
			catch (const StorageBackend::ChannelNotFoundError&)
			{
				size_t truncateAt = (channel->Items_.size () <= ipc) ?
					channel->Items_.size () : ipc;
				for (size_t j = 0; j < channel->Items_.size (); j++)
					if (channel->Items_ [j]->PubDate_.daysTo (current) > days)
					{
						truncateAt = std::min (j, truncateAt);
						break;
					}
				channel->Items_.resize (truncateAt);

				emit gotNewChannel (channel->ToShort ());
				SB_->AddChannel (channel);
				QString str = tr ("Added channel \"%1\" (%n item(s))",
						"", channel->Items_.size ())
					.arg (channel->Title_);
				emit gotEntity (Util::MakeNotification ("Aggregator", str, PInfo_));
				continue;
			}

			const QVariantMap& channelPart = GetItemMapChannelPart (ourChannel);

			int newItems = 0;
			int updatedItems = 0;

			Core::Instance ().GetPool (PTChannel).FreeID (channel->ChannelID_);

			Q_FOREACH (Item_ptr item, channel->Items_)
			{
				Item_ptr ourItem;
				try
				{
					IDType_t ourItemID = SB_->FindItem (item->Title_, item->Link_,
							ourChannel->ChannelID_);
					ourItem = SB_->GetItem (ourItemID);
				}
				catch (const StorageBackend::ItemNotFoundError&)
				{
					if (item->PubDate_.isValid ())
					{
						if (item->PubDate_.daysTo (current) >= days)
							continue;
					}
					else
						item->FixDate ();

					item->ChannelID_ = ourChannel->ChannelID_;
					SB_->AddItem (item);

					RegexpMatcherManager::Instance ().HandleItem (item);

					QVariantList itemData;
					itemData << GetItemMapItemPart (item).unite (channelPart);
					emit hookGotNewItems (Util::DefaultHookProxy_ptr (new Util::DefaultHookProxy),
							itemData);

					if (downloadEnclosures)
						Q_FOREACH (Enclosure e, item->Enclosures_)
						{
							Entity de = Util::MakeEntity (QUrl (e.URL_),
									XmlSettingsManager::Instance ()->
										property ("EnclosuresDownloadPath").toString (),
									0,
									e.Type_);
							de.Additional_ [" Tags"] = channel->Tags_;
							emit gotEntity (de);
						}
					++newItems;
					continue;
				}

				if (!IsModified (ourItem, item))
					continue;

				ourItem->Description_ = item->Description_;
				ourItem->Categories_ = item->Categories_;
				ourItem->NumComments_ = item->NumComments_;
				ourItem->CommentsLink_ = item->CommentsLink_;
				ourItem->CommentsPageLink_ = item->CommentsPageLink_;
				ourItem->Latitude_ = item->Latitude_;
				ourItem->Longitude_ = item->Longitude_;

				Q_FOREACH (Enclosure enc, item->Enclosures_)
				{
					if (ourItem->Enclosures_.contains (enc))
						Core::Instance ().GetPool (PTEnclosure).FreeID (enc.EnclosureID_);
					else
					{
						enc.ItemID_ = ourItem->ItemID_;
						ourItem->Enclosures_ << enc;
					}
				}

				Q_FOREACH (MRSSEntry entry, item->MRSSEntries_)
				{
					if (ourItem->MRSSEntries_.contains (entry))
					{
						Core::Instance ().GetPool (PTMRSSEntry).FreeID (entry.MRSSEntryID_);

						Q_FOREACH (MRSSComment comment, entry.Comments_)
							Core::Instance ().GetPool (PTMRSSComment).FreeID (comment.MRSSCommentID_);
						Q_FOREACH (MRSSCredit credit, entry.Credits_)
							Core::Instance ().GetPool (PTMRSSCredit).FreeID (credit.MRSSCreditID_);
						Q_FOREACH (MRSSPeerLink peerLink, entry.PeerLinks_)
							Core::Instance ().GetPool (PTMRSSPeerLink).FreeID (peerLink.MRSSPeerLinkID_);
						Q_FOREACH (MRSSThumbnail thumb, entry.Thumbnails_)
							Core::Instance ().GetPool (PTMRSSThumbnail).FreeID (thumb.MRSSThumbnailID_);
						Q_FOREACH (MRSSScene scene, entry.Scenes_)
							Core::Instance ().GetPool (PTMRSSScene).FreeID (scene.MRSSSceneID_);
					}
					else
					{
						entry.ItemID_ = ourItem->ItemID_;
						ourItem->MRSSEntries_ << entry;
					}
				}

				Core::Instance ().GetPool (PTItem).FreeID (item->ItemID_);

				SB_->UpdateItem (ourItem);
				++updatedItems;
			}

			QString method = XmlSettingsManager::Instance ()->
					property ("NotificationsFeedUpdateBehavior").toString ();
			bool shouldShow = true;
			if (method == "ShowNo")
				shouldShow = false;
			else if (method == "ShowNew")
				shouldShow = newItems;
			else if (method == "ShowAll")
				shouldShow = newItems + updatedItems;

			if (shouldShow)
			{
				QStringList substrs;
				if (newItems)
					substrs << tr ("%n new item(s)", "Channel update", newItems);
				if (updatedItems)
					substrs << tr ("%n updated item(s)", "Channel update", updatedItems);
				const auto& str = tr ("Updated channel \"%1\" (%2).")
						.arg (channel->Title_)
						.arg (substrs.join (", "));
				emit gotEntity (Util::MakeNotification ("Aggregator", str, PInfo_));
			}

			SB_->TrimChannel (ourChannel->ChannelID_,
					days, ipc);
		}
	}

	void DBUpdateThreadWorker::handleChannelDataUpdated (Channel_ptr ch)
	{
		emit channelDataUpdated (ch->ChannelID_, ch->FeedID_);
	}
}
}
