/**********************************************************************
 * 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 <stdexcept>
#include <numeric>
#include <algorithm>
#include <QtDebug>
#include <QImage>
#include <QDir>
#include <QDesktopServices>
#include <QUrl>
#include <QTimer>
#include <QTextCodec>
#include <QXmlStreamReader>
#include <QNetworkReply>
#include <interfaces/iwebbrowser.h>
#include <interfaces/core/icoreproxy.h>
#include <interfaces/core/itagsmanager.h>
#include <interfaces/core/ipluginsmanager.h>
#include <interfaces/core/ientitymanager.h>
#include <util/models/mergemodel.h>
#include <util/xpc/util.h>
#include <util/sys/paths.h>
#include <util/xpc/defaulthookproxy.h>
#include <util/sll/prelude.h>
#include <util/sll/qtutil.h>
#include <util/sll/visitor.h>
#include <util/sll/either.h>
#include <util/gui/util.h>
#include "core.h"
#include "xmlsettingsmanager.h"
#include "parserfactory.h"
#include "opmlparser.h"
#include "opmlwriter.h"
#include "jobholderrepresentation.h"
#include "importopml.h"
#include "addfeeddialog.h"
#include "dbupdatethread.h"
#include "dbupdatethreadworker.h"
#include "dumbstorage.h"
#include "storagebackendmanager.h"
#include "parser.h"
#include "opmladder.h"

namespace LeechCraft
{
namespace Aggregator
{
	Core& Core::Instance ()
	{
		static Core core;
		return core;
	}

	void Core::Release ()
	{
		StorageBackend_.reset ();

		XmlSettingsManager::Instance ()->Release ();
	}

	void Core::SetProxy (ICoreProxy_ptr proxy)
	{
		Proxy_ = proxy;
	}

	ICoreProxy_ptr Core::GetProxy () const
	{
		return Proxy_;
	}

	bool Core::CouldHandle (const Entity& e)
	{
		if (!e.Entity_.canConvert<QUrl> () ||
				!Initialized_)
			return false;

		if (OpmlAdder_->IsOpmlEntity (e))
			return true;

		const auto& url = e.Entity_.toUrl ();
		if (e.Mime_ == "text/xml")
		{
			if (url.scheme () != "http" &&
					url.scheme () != "https")
				return false;

			const auto& pageData = e.Additional_ ["URLData"].toString ();
			QXmlStreamReader xmlReader (pageData);
			if (!xmlReader.readNextStartElement ())
				return false;
			return xmlReader.name () == "rss" || xmlReader.name () == "atom";
		}
		else
		{
			if (url.scheme () == "feed")
				return true;
			if (url.scheme () == "itpc")
				return true;
			if (url.scheme () != "http" &&
					url.scheme () != "https" &&
					url.scheme () != "itpc")
				return false;

			if (e.Mime_ != "application/atom+xml" &&
					e.Mime_ != "application/rss+xml")
				return false;

			const auto& linkRel = e.Additional_ ["LinkRel"].toString ();
			if (!linkRel.isEmpty () &&
					linkRel != "alternate")
				return false;
		}

		return true;
	}

	void Core::Handle (Entity e)
	{
		if (!OpmlAdder_->HandleOpmlEntity (e))
		{
			QUrl url = e.Entity_.toUrl ();
			QString str = url.toString ();
			if (str.startsWith ("feed://"))
				str.replace (0, 4, "http");
			else if (str.startsWith ("feed:"))
				str.remove  (0, 5);
			else if (str.startsWith ("itpc://"))
				str.replace (0, 4, "http");

			AddFeedDialog af { Proxy_->GetTagsManager (), str };
			if (af.exec () == QDialog::Accepted)
				AddFeed (af.GetURL (),
						af.GetTags ());
		}
	}

	void Core::StartAddingOPML (const QString& file)
	{
		OpmlAdder_->StartAddingOpml (file);
	}

	bool Core::DoDelayedInit ()
	{
		bool result = true;
		if (!ReinitStorage ())
			result = false;

		// TODO replace with std::bind_front in C++20
		OpmlAdder_ = std::make_shared<OpmlAdder> ([this] (auto... args) { AddFeed (args...); }, Proxy_);

		Initialized_ = true;

		return result;
	}

	bool Core::ReinitStorage ()
	{
		return Util::Visit (StorageBackendManager::Instance ().CreatePrimaryStorage (),
				[this] (const StorageBackend_ptr& backend)
				{
					StorageBackend_ = backend;
					return true;
				},
				[this] (const auto& error)
				{
					ErrorNotification (tr ("Storage error"), error.Message_);
					return false;
				});
	}

	void Core::AddFeed (QString url, const QStringList& tags, const std::optional<Feed::FeedSettings>& maybeFeedSettings)
	{
		const auto& fixedUrl = QUrl::fromUserInput (url);
		url = fixedUrl.toString ();
		if (StorageBackend_->FindFeed (url))
		{
			ErrorNotification (tr ("Feed addition error"),
					tr ("The feed %1 is already added")
						.arg (url));
			return;
		}

		Feed feed;
		feed.URL_ = url;
		StorageBackend_->AddFeed (feed);

		if (maybeFeedSettings)
		{
			auto fs = *maybeFeedSettings;
			fs.FeedID_ = feed.FeedID_;
			StorageBackend_->SetFeedSettings (fs);
		}

		emit updateRequested (feed.FeedID_);
	}

	void Core::AddFeeds (const feeds_container_t& feeds, const QString& tagsString)
	{
		auto tags = Proxy_->GetTagsManager ()->Split (tagsString);
		tags.removeDuplicates ();

		for (const auto& feed : feeds)
		{
			for (const auto& channel : feed->Channels_)
			{
				channel->Tags_ += tags;
				channel->Tags_.removeDuplicates ();
			}

			StorageBackend_->AddFeed (*feed);
		}
	}

	void Core::ErrorNotification (const QString& h, const QString& body, bool wait) const
	{
		auto e = Util::MakeNotification (h, body, Priority::Critical);
		e.Additional_ ["UntilUserSees"] = wait;
		Proxy_->GetEntityManager ()->HandleEntity (e);
	}
}
}
