/**********************************************************************
 * 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 "networkaccessmanager.h"
#include <stdexcept>
#include <algorithm>
#include <QNetworkRequest>
#include <QDir>
#include <QFile>
#include <QAuthenticator>
#include <QNetworkReply>
#include <QNetworkProxy>
#include <QTimer>
#include <util/util.h>
#include <util/network/customcookiejar.h>
#include <util/network/networkdiskcache.h>
#include <util/xpc/defaulthookproxy.h>
#include "core.h"
#include "authenticationdialog.h"
#include "xmlsettingsmanager.h"
#include "mainwindow.h"
#include "storagebackend.h"
#include "sslerrorshandler.h"

Q_DECLARE_METATYPE (QNetworkReply*);

using namespace LeechCraft;
using namespace LeechCraft::Util;

NetworkAccessManager::NetworkAccessManager (QObject *parent)
: QNetworkAccessManager (parent)
, CookieSaveTimer_ (new QTimer (this))
{
	connect (this,
			SIGNAL (authenticationRequired (QNetworkReply*,
					QAuthenticator*)),
			this,
			SLOT (handleAuthentication (QNetworkReply*,
					QAuthenticator*)));
	connect (this,
			SIGNAL (proxyAuthenticationRequired (const QNetworkProxy&,
					QAuthenticator*)),
			this,
			SLOT (handleAuthentication (const QNetworkProxy&,
					QAuthenticator*)));
	connect (this,
			SIGNAL (sslErrors (QNetworkReply*, QList<QSslError>)),
			this,
			SLOT (handleSslErrors (QNetworkReply*, QList<QSslError>)));

	CookieJar_ = new CustomCookieJar (this);
	setCookieJar (CookieJar_);

	XmlSettingsManager::Instance ()->RegisterObject ("FilterTrackingCookies",
			this,
			"handleFilterTrackingCookies");
	XmlSettingsManager::Instance ()->RegisterObject ("DeleteCookiesOnExit",
			this,
			"saveCookies");
	XmlSettingsManager::Instance ()->RegisterObject ("EnableCookies",
			this,
			"setCookiesEnabled");
	XmlSettingsManager::Instance ()->RegisterObject ("MatchDomainExactly",
			this,
			"setMatchDomainExactly");
	XmlSettingsManager::Instance ()->RegisterObject ({ "CookiesWhitelist", "CookiesBlacklist" },
			this,
			"setCookiesLists");

	handleFilterTrackingCookies ();
	setCookiesEnabled ();
	setMatchDomainExactly ();
	setCookiesLists ();

	try
	{
		auto cache = new Util::NetworkDiskCache ("core", this);
		setCache (cache);

		XmlSettingsManager::Instance ()->RegisterObject ("CacheSize",
				this, "handleCacheSize");
		handleCacheSize ();
	}
	catch (const std::runtime_error& e)
	{
		qWarning () << Q_FUNC_INFO
			<< e.what ()
			<< "so continuing without cache";
	}

	QFile file (QDir::homePath () +
			"/.leechcraft/core/cookies.txt");
	if (file.open (QIODevice::ReadOnly))
		CookieJar_->Load (file.readAll ());
	else
		qWarning () << Q_FUNC_INFO
			<< "could not open file"
			<< file.fileName ()
			<< file.errorString ();

	connect (CookieSaveTimer_,
			SIGNAL (timeout ()),
			this,
			SLOT (saveCookies ()));
	CookieSaveTimer_->start (10000);
}

NetworkAccessManager::~NetworkAccessManager ()
{
	saveCookies ();
}

QNetworkReply* NetworkAccessManager::createRequest (QNetworkAccessManager::Operation op,
		const QNetworkRequest& req, QIODevice *out)
{
	QNetworkRequest r = req;

	DefaultHookProxy_ptr proxy (new DefaultHookProxy);
	proxy->SetValue ("request", QVariant::fromValue<QNetworkRequest> (r));
	emit hookNAMCreateRequest (proxy, this, &op, &out);

	if (proxy->IsCancelled ())
	{
		const auto reply = proxy->GetReturnValue ().value<QNetworkReply*> ();
		emit requestCreated (op, r, reply);
		return reply;
	}

	proxy->FillValue ("request", r);

	if (XmlSettingsManager::Instance ()->property ("SetDNT").toBool ())
	{
		const bool dnt = XmlSettingsManager::Instance ()->property ("DNTValue").toBool ();
		r.setRawHeader ("DNT", dnt ? "1" : "0");
	}

	QNetworkReply *result = QNetworkAccessManager::createRequest (op, r, out);
	emit requestCreated (op, r, result);
	return result;
}

void LeechCraft::NetworkAccessManager::DoCommonAuth (const QString& msg, QAuthenticator *authen)
{
	const auto& realm = authen->realm ();

	auto suggestedUser = authen->user ();
	auto suggestedPassword = authen->password ();

	const auto backend = Core::Instance ().GetStorageBackend ();

	if (suggestedUser.isEmpty ())
		backend->GetAuth (realm, suggestedUser, suggestedPassword);

	AuthenticationDialog dia (msg, suggestedUser, suggestedPassword, qApp->activeWindow ());
	if (dia.exec () == QDialog::Rejected)
		return;

	const auto& login = dia.GetLogin ();
	const auto& password = dia.GetPassword ();
	authen->setUser (login);
	authen->setPassword (password);

	if (dia.ShouldSave ())
		backend->SetAuth (realm, login, password);
}

void LeechCraft::NetworkAccessManager::handleAuthentication (QNetworkReply *reply,
		QAuthenticator *authen)
{
	QString msg = tr ("%1<br /><em>%2</em><br />requires authentication.")
		.arg (authen->realm ())
		.arg (QApplication::fontMetrics ()
				.elidedText (reply->url ().toString (),
						Qt::ElideMiddle,
						300));

	DoCommonAuth (msg, authen);
}

void LeechCraft::NetworkAccessManager::handleAuthentication (const QNetworkProxy& proxy,
		QAuthenticator *authen)
{
	QString msg = tr ("%1<br /><em>%2</em><br />requires authentication.")
		.arg (authen->realm ())
		.arg (proxy.hostName ());

	DoCommonAuth (msg, authen);
}

void LeechCraft::NetworkAccessManager::handleSslErrors (QNetworkReply *replyObj,
		const QList<QSslError>& errors)
{
	new SslErrorsHandler { replyObj, errors };
}

void LeechCraft::NetworkAccessManager::saveCookies () const
{
	QDir dir = QDir::home ();
	dir.cd (".leechcraft");
	if (!dir.exists ("core") &&
			!dir.mkdir ("core"))
	{
		emit error (tr ("Could not create Core directory."));
		return;
	}

	QFile file (QDir::homePath () +
			"/.leechcraft/core/cookies.txt");
	if (!file.open (QIODevice::WriteOnly | QIODevice::Truncate))
	{
		emit error (tr ("Could not save cookies, error opening cookie file."));
		qWarning () << Q_FUNC_INFO
			<< file.errorString ();
		return;
	}

	const bool saveEnabled = !XmlSettingsManager::Instance ()->
			property ("DeleteCookiesOnExit").toBool ();
	file.write (saveEnabled ? CookieJar_->Save () : QByteArray ());
}

void LeechCraft::NetworkAccessManager::handleFilterTrackingCookies ()
{
	CookieJar_->SetFilterTrackingCookies (XmlSettingsManager::Instance ()->
				property ("FilterTrackingCookies").toBool ());
}

void NetworkAccessManager::setCookiesEnabled ()
{
	CookieJar_->SetEnabled (XmlSettingsManager::Instance ()->
			property ("EnableCookies").toBool ());
}

void NetworkAccessManager::setMatchDomainExactly ()
{
	CookieJar_->SetExactDomainMatch (XmlSettingsManager::Instance ()->
			property ("MatchDomainExactly").toBool ());
}

namespace
{
	QList<QRegExp> GetList (const QByteArray& setting)
	{
		const auto& stringList = XmlSettingsManager::Instance ()->
				property (setting).toStringList ();
		QList<QRegExp> result;
		for (const auto& str : stringList)
			result << QRegExp (str);
		return result;
	}
}

void NetworkAccessManager::setCookiesLists ()
{
	CookieJar_->SetWhitelist (GetList ("CookiesWhitelist"));
	CookieJar_->SetBlacklist (GetList ("CookiesBlacklist"));
}

void NetworkAccessManager::handleCacheSize ()
{
	auto ourCache = qobject_cast<Util::NetworkDiskCache*> (cache ());
	ourCache->setMaximumCacheSize (XmlSettingsManager::Instance ()->
			property ("CacheSize").toInt () * 1048576);
}
