/**********************************************************************
 * 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 "core.h"
#include <memory>
#include <numeric>
#include <typeinfo>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <QFile>
#include <QDir>
#include <QFileInfo>
#include <QSettings>
#include <QToolBar>
#include <QTimer>
#include <QMenu>
#include <QDomDocument>
#include <QDomElement>
#include <QDomNode>
#include <QtDebug>
#include <QApplication>
#include <QStandardItemModel>
#include <QDomElement>
#include <QDomDocument>
#include <QXmlStreamWriter>
#include <QTemporaryFile>
#include <QMessageBox>
#include <QUrl>
#include <QTextCodec>
#include <QDataStream>
#include <QDesktopServices>

#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#endif

#include <libtorrent/bencode.hpp>
#include <libtorrent/entry.hpp>
#include <libtorrent/create_torrent.hpp>
#include <libtorrent/file_pool.hpp>
#include <libtorrent/hasher.hpp>
#include <libtorrent/storage.hpp>
#include <libtorrent/file.hpp>
#include <libtorrent/magnet_uri.hpp>
#include <libtorrent/ip_filter.hpp>
#include <libtorrent/version.hpp>
#include <libtorrent/session.hpp>
#include <interfaces/entitytesthandleresult.h>
#include <interfaces/core/icoreproxy.h>
#include <interfaces/core/itagsmanager.h>
#include <interfaces/core/ientitymanager.h>
#include <interfaces/ijobholder.h>
#include <interfaces/an/constants.h>
#include <util/tags/tagscompletionmodel.h>
#include <util/shortcuts/shortcutmanager.h>
#include <util/util.h>
#include <util/xpc/util.h>
#include <util/xpc/notificationactionhandler.h>
#include <util/sll/util.h>
#include "xmlsettingsmanager.h"
#include "piecesmodel.h"
#include "peersmodel.h"
#include "torrentfilesmodel.h"
#include "livestreammanager.h"
#include "torrentmaker.h"
#include "notifymanager.h"
#include "sessionsettingsmanager.h"

Q_DECLARE_METATYPE (QMenu*)
Q_DECLARE_METATYPE (QToolBar*)

using namespace LeechCraft::Util;

namespace LeechCraft
{
namespace BitTorrent
{
	Core::PerTrackerAccumulator::PerTrackerAccumulator (Core::pertrackerstats_t& stats)
	: Stats_ (stats)
	{
	}

	int Core::PerTrackerAccumulator::operator() (int,
			const Core::TorrentStruct& str)
	{
		const auto& s = str.Handle_.status (0);
		QString domain = QUrl (s.current_tracker.c_str ()).host ();
		if (domain.size ())
		{
			Stats_ [domain].DownloadRate_ += s.download_payload_rate;
			Stats_ [domain].UploadRate_ += s.upload_payload_rate;
		}
		return 0;
	}

	Core* Core::Instance ()
	{
		static Core core;
		return &core;
	}

	Core::Core ()
	: NotifyManager_ (new NotifyManager (this))
	, Session_ (0)
	, CurrentTorrent_ (-1)
	, FinishedTimer_ (new QTimer ())
	, WarningWatchdog_ (new QTimer ())
	, LiveStreamManager_ (new LiveStreamManager ())
	, SaveScheduled_ (false)
	, Toolbar_ (0)
	, TabWidget_ (0)
	, Menu_ (0)
	, ShortcutMgr_ (0)
	, TorrentIcon_ ("lcicons:/resources/images/bittorrent.svg")
	{
		setObjectName ("BitTorrent Core");
		ExternalAddress_ = tr ("Unknown");

		connect (LiveStreamManager_.get (),
				SIGNAL (gotEntity (const LeechCraft::Entity&)),
				this,
				SIGNAL (gotEntity (const LeechCraft::Entity&)));
	}

	void Core::SetWidgets (QToolBar *tool, QWidget *tab)
	{
		Toolbar_ = tool;
		TabWidget_ = tab;
	}

	void Core::SetMenu (QMenu *menu)
	{
		Menu_ = menu;
	}

	void Core::DoDelayedInit ()
	{
		try
		{
			QString peerIDstring = "LC";

			// Build peer_id
			// Get the tag name.
			QString ver = Proxy_->GetVersion ();
			if (ver.isEmpty ())
				ver = "0.5.0";
			// Get the part before the '-'.
			ver = ver.split ('-', QString::SkipEmptyParts).at (0);
			QStringList vers = ver.split ('.', QString::SkipEmptyParts);
			if (vers.size () != 3)
				throw std::runtime_error ("Malformed version string " + ver.toStdString ());
			ver = QString ("%1%2")
				.arg (vers.at (1).toInt (),
						2, 10, QChar ('0'))
				.arg (vers.at (2).toInt (),
						2, 10, QChar ('0'));


			if (ver.size () != 4)
				ver = "1111";

			Session_ = new libtorrent::session (libtorrent::fingerprint
					(peerIDstring.toLatin1 ().constData (),
						ver.at (0).digitValue (),
						ver.at (1).digitValue (),
						ver.at (2).digitValue (),
						ver.at (3).digitValue ()),
					0);

			SessionSettingsMgr_ = new SessionSettingsManager { Session_, Proxy_, this };

#if defined (ENABLE_GEOIP) && !defined (TORRENT_DISABLE_GEO_IP)
			const QStringList geoipCands
			{
				"/usr/share/GeoIP",
				"/usr/local/share/GeoIP",
				"/var/lib/GeoIP"
			};
			for (const auto& cand : geoipCands)
			{
				const auto& name = cand + "/GeoIP.dat";
				if (QFile::exists (name))
				{
					Session_->load_country_db (name.toUtf8 ().constData ());
					break;
				}
			}
#endif

			auto sstateVariant = XmlSettingsManager::Instance ()->
					property ("SessionState");
			if (sstateVariant.isValid () &&
					!sstateVariant.toByteArray ().isEmpty ())
			{
				libtorrent::lazy_entry state;
				if (DecodeEntry (sstateVariant.toByteArray (), state))
					Session_->load_state (state);
			}
		}
		catch (const std::exception& e)
		{
			qWarning () << Q_FUNC_INFO << typeid (e).name () << e.what ();
		}

		Headers_ << "#"
				<< tr ("Name")
				<< tr ("State")
				<< tr ("Progress")
				<< tr ("Down speed")
				<< tr ("Up speed")
				<< tr ("Leechers")
				<< tr ("Seeders")
				<< tr ("Size")
				<< tr ("Total downloaded")
				<< tr ("Total uploaded")
				<< tr ("Ratio");

		connect (FinishedTimer_.get (),
				SIGNAL (timeout ()),
				this,
				SLOT (checkFinished ()));
		FinishedTimer_->start (10000);

		connect (WarningWatchdog_.get (),
				SIGNAL (timeout ()),
				this,
				SLOT (queryLibtorrentForWarnings ()));
		WarningWatchdog_->start (2000);

		connect (SessionSettingsMgr_,
				SIGNAL (scrapeRequested ()),
				this,
				SLOT (scrape ()));
		connect (SessionSettingsMgr_,
				SIGNAL (saveSettingsRequested ()),
				this,
				SLOT (writeSettings ()));

		RestoreTorrents ();
	}

	void Core::Release ()
	{
		Session_->pause ();
		writeSettings ();

		FinishedTimer_.reset ();
		WarningWatchdog_.reset ();

		QObjectList kids = children ();
		for (int i = 0; i < kids.size (); ++i)
		{
			delete kids.at (i);
			kids [i] = 0;
		}

		Session_->stop_dht ();
		delete Session_;
		Session_ = 0;
	}

	void Core::SetProxy (ICoreProxy_ptr proxy)
	{
		Proxy_ = proxy;
		ShortcutMgr_ = new ShortcutManager (proxy, this);
	}

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

	Util::ShortcutManager* Core::GetShortcutManager () const
	{
		return ShortcutMgr_;
	}

	SessionSettingsManager* Core::GetSessionSettingsManager () const
	{
		return SessionSettingsMgr_;
	}

	EntityTestHandleResult Core::CouldDownload (const Entity& e) const
	{
		if (e.Entity_.canConvert<QUrl> ())
		{
			QUrl url = e.Entity_.toUrl ();
			if (url.scheme () == "magnet")
			{
#if QT_VERSION < 0x050000
				for (const auto& item : url.queryItems ())
#else
				for (const auto& item : QUrlQuery { url }.queryItems ())
#endif
					if (item.first == "xt" && item.second.startsWith ("urn:btih:"))
						return EntityTestHandleResult (EntityTestHandleResult::PIdeal);
				return EntityTestHandleResult ();
			}
			else if (url.scheme () == "file")
			{
				QString str = url.toLocalFile ();
				QFile file (str);
				if (!file.exists () ||
						!file.open (QIODevice::ReadOnly))
					return EntityTestHandleResult ();

				if (file.size () > XmlSettingsManager::Instance ()->
						property ("MaxAutoTorrentSize").toInt () * 1024 * 1024)
				{
					if (str.endsWith (".torrent", Qt::CaseInsensitive) &&
							XmlSettingsManager::Instance ()->
								property ("NotifyAboutTooBig").toBool ())
					{
						const QString text = tr ("Rejecting file %1 because it's "
								"bigger than current auto limit.").arg (str);
						emit const_cast<Core*> (this)->
								gotEntity (Util::MakeNotification ("BitTorrent",
										text, PWarning_));
					}
					return EntityTestHandleResult ();
				}
				else
					return IsValidTorrent (file.readAll ()) ?
							EntityTestHandleResult (EntityTestHandleResult::PIdeal) :
							EntityTestHandleResult ();
			}
			else
				return EntityTestHandleResult ();
		}
		else if (e.Entity_.canConvert<QByteArray> ())
			return IsValidTorrent (e.Entity_.toByteArray ()) ?
					EntityTestHandleResult (EntityTestHandleResult::PIdeal) :
					EntityTestHandleResult ();
		else
			return EntityTestHandleResult ();
	}

	EntityTestHandleResult Core::CouldHandle (const Entity&) const
	{
		return EntityTestHandleResult ();
	}

	void Core::Handle (Entity)
	{
	}

	PiecesModel* Core::GetPiecesModel (int idx)
	{
		return idx >= 0 ? new PiecesModel (idx) : 0;
	}

	PeersModel* Core::GetPeersModel (int idx)
	{
		return idx >= 0 ? new PeersModel (idx) : 0;
	}

	QAbstractItemModel* Core::GetWebSeedsModel (int idx)
	{
		if (idx < 0)
			return 0;

		auto model = new QStandardItemModel;
		model->setHorizontalHeaderLabels ({tr ("URL"), tr ("Standard") });
		Q_FOREACH (std::string url,
				Handles_.at (idx).Handle_.url_seeds ())
		{
			QList<QStandardItem*> items;
			items << new QStandardItem (QString::fromUtf8 (url.c_str ()));
			items << new QStandardItem ("BEP 19");
			model->appendRow (items);
		}
		Q_FOREACH (std::string url,
				Handles_.at (idx).Handle_.http_seeds ())
		{
			QList<QStandardItem*> items;
			items << new QStandardItem (QString::fromUtf8 (url.c_str ()));
			items << new QStandardItem ("BEP 17");
			model->appendRow (items);
		}
		return model;
	}

	TorrentFilesModel* Core::GetTorrentFilesModel (int idx)
	{
		if (idx < 0)
			return 0;

		const auto tfm = new TorrentFilesModel (idx);
		connect (this,
				SIGNAL (fileRenamed (int, int, QString)),
				tfm,
				SLOT (handleFileRenamed (int, int, QString)));
		return tfm;
	}

	int Core::columnCount (const QModelIndex&) const
	{
		return Headers_.size ();
	}

	namespace
	{
		QString GetStringForState (libtorrent::torrent_status::state_t state)
		{
			switch (state)
			{
				case libtorrent::torrent_status::queued_for_checking:
					return Core::tr ("Queued for checking");
				case libtorrent::torrent_status::checking_files:
					return Core::tr ("Checking files");
				case libtorrent::torrent_status::downloading_metadata:
					return Core::tr ("Downloading metadata");
				case libtorrent::torrent_status::downloading:
					return Core::tr ("Downloading");
				case libtorrent::torrent_status::finished:
					return Core::tr ("Finished");
				case libtorrent::torrent_status::seeding:
					return Core::tr ("Seeding");
				case libtorrent::torrent_status::allocating:
					return Core::tr ("Allocating");
				case libtorrent::torrent_status::checking_resume_data:
					return Core::tr ("Checking resume data");
			}
			return "Uninitialized?!";
		}

		QString GetStringForStatus (const libtorrent::torrent_status& status)
		{
			const auto& stateStr = GetStringForState (status.state);
			if (status.state == libtorrent::torrent_status::downloading)
			{
				if (!status.error.empty ())
				{
					static const auto errorStr = Core::tr ("Error");
					return errorStr;
				}

				if (status.paused)
				{
					static const auto pausedStr = Core::tr ("Paused");
					return pausedStr;
				}

				const auto remaining = status.total_wanted - status.total_wanted_done;
				const auto time = static_cast<double> (remaining) / status.download_rate;
				return QString ("%1 (ETA: %2)")
					.arg (stateStr)
					.arg (Util::MakeTimeFromLong (time));
			}
			else if (status.paused)
			{
				static const auto idleStr = Core::tr ("Idle");
				return idleStr;
			}
			else
				return stateStr;
		}
	}

	QVariant Core::data (const QModelIndex& index, int role) const
	{
		if (role == RoleControls)
			return QVariant::fromValue<QToolBar*> (Toolbar_);
		if (role == RoleAdditionalInfo)
			return QVariant::fromValue<QWidget*> (TabWidget_);
		if (role == RoleContextMenu)
			return QVariant::fromValue<QMenu*> (Menu_);

		const int row = index.row ();
		const int column = index.column ();

		if (!CheckValidity (row))
			return QVariant ();

		const auto& h = Handles_.at (row).Handle_;
		const auto& status = GetCachedStatus (h);

		switch (role)
		{
		case Qt::DecorationRole:
			if (column != ColumnName)
				return {};

			if (!status.error.empty ())
				return QIcon::fromTheme ("dialog-error");

			if (status.paused)
				return QIcon::fromTheme ("media-playback-stop");

			switch (status.state)
			{
			case libtorrent::torrent_status::queued_for_checking:
			case libtorrent::torrent_status::checking_files:
			case libtorrent::torrent_status::checking_resume_data:
				return QIcon::fromTheme ("tools-check-spelling");
			case libtorrent::torrent_status::downloading:
			case libtorrent::torrent_status::downloading_metadata:
				return QIcon::fromTheme ("arrow-down");
			case libtorrent::torrent_status::allocating:
				return QIcon::fromTheme ("media-playback-start");
			case libtorrent::torrent_status::finished:
				return QIcon::fromTheme ("arrow-up");
			case libtorrent::torrent_status::seeding:
				return QIcon::fromTheme ("arrow-up-double");
			}
		case Roles::SortRole:
			switch (column)
			{
			case ColumnID:
				return row + 1;
			case ColumnName:
				return QString::fromUtf8 (h.name ().c_str ());
			case ColumnState:
				return status.paused ?
						-1 :
						static_cast<int> (status.state);
			case ColumnProgress:
				return status.progress;
			case ColumnDownSpeed:
				return status.download_payload_rate;
			case ColumnUpSpeed:
				return status.upload_payload_rate;
			case ColumnLeechers:
				return status.num_peers - status.num_seeds;
			case ColumnSeeders:
				return status.num_seeds;
			case ColumnDownloaded:
				return static_cast<quint64> (status.all_time_download);
			case ColumnSize:
				return static_cast<quint64> (status.total_wanted);
			case ColumnUploaded:
				return static_cast<quint64> (status.all_time_upload);
			case ColumnRatio:
				if (status.all_time_download)
					return static_cast<double> (status.all_time_upload) / status.all_time_download;

				return status.all_time_upload ?
						std::numeric_limits<double>::max () :
						0;
			default:
				return {};
			}
		case Roles::FullLengthText:
		case Qt::DisplayRole:
			switch (column)
			{
			case ColumnID:
				return row + 1;
			case ColumnName:
				return QString::fromUtf8 (h.name ().c_str ());
			case ColumnState:
				return GetStringForStatus (status);
			{
			}
			case ColumnProgress:
				if (role == Roles::FullLengthText)
				{
					if (status.state == libtorrent::torrent_status::downloading)
					{
						static const auto templ = tr ("%1% (%2 of %3 at %4 from %5 peers)");
						return templ
								.arg (status.progress * 100, 0, 'f', 2)
								.arg (Util::MakePrettySize (status.total_wanted_done))
								.arg (Util::MakePrettySize (status.total_wanted))
								.arg (Util::MakePrettySize (status.download_payload_rate) +
										tr ("/s"))
								.arg (status.num_peers);
					}
					else if (!status.paused &&
								(status.state == libtorrent::torrent_status::finished ||
								status.state == libtorrent::torrent_status::seeding))
					{
						auto total = status.num_incomplete;
						if (total <= 0)
							total = status.list_peers - status.list_seeds;
						static const auto templ = tr ("%1, seeding at %2 to %3 leechers (of around %4)");
						return templ
								.arg (Util::MakePrettySize (status.total_wanted))
								.arg (Util::MakePrettySize (status.upload_payload_rate) +
										tr ("/s"))
								.arg (status.num_peers - status.num_seeds)
								.arg (total);
					}
					else
					{
						static const auto templ = tr ("%1% (%2 of %3)");
						return templ
								.arg (status.progress * 100, 0, 'f', 2)
								.arg (Util::MakePrettySize (status.total_wanted_done))
								.arg (Util::MakePrettySize (status.total_wanted));
					}
				}
				else
				{
					if (status.state == libtorrent::torrent_status::downloading)
					{
						static const auto templ = tr ("%1% (%2 of %3)");
						return templ
								.arg (status.progress * 100, 0, 'f', 2)
								.arg (Util::MakePrettySize (status.total_wanted_done))
								.arg (Util::MakePrettySize (status.total_wanted));
					}
					else if (!status.paused &&
								(status.state == libtorrent::torrent_status::finished ||
								status.state == libtorrent::torrent_status::seeding))
					{
						static const auto templ = tr ("100% (%1)");
						return templ
								.arg (Util::MakePrettySize (status.total_wanted));
					}
					else
					{
						static const auto templ = tr ("%1% (%2 of %3)");
						return templ
								.arg (status.progress * 100, 0, 'f', 2)
								.arg (Util::MakePrettySize (status.total_wanted_done))
								.arg (Util::MakePrettySize (status.total_wanted));
					}
				}
			case ColumnDownSpeed:
				return Util::MakePrettySize (status.download_payload_rate) + tr ("/s");
			case ColumnUpSpeed:
				return Util::MakePrettySize (status.upload_payload_rate) + tr ("/s");
			case ColumnLeechers:
				return QString::number (status.num_peers - status.num_seeds);
			case ColumnSeeders:
				return QString::number (status.num_seeds);
			case ColumnDownloaded:
				return Util::MakePrettySize (status.all_time_download);
			case ColumnSize:
				return Util::MakePrettySize (status.total_wanted);
			case ColumnUploaded:
				return Util::MakePrettySize (status.all_time_upload);
			case ColumnRatio:
				if (status.all_time_download)
				{
					const auto ratio = static_cast<double> (status.all_time_upload) / status.all_time_download;
					return QString::number (ratio, 'f', 2);
				}

				return status.all_time_upload ?
						QString::fromUtf8 ("∞") :
						"0";
			default:
				return QVariant ();
			}
		case Qt::ToolTipRole:
		{
			QString result;
			result += tr ("Name:") + " " + QString::fromUtf8 (h.name ().c_str ()) + "\n";
			result += tr ("Destination:") + " " +
				QString::fromUtf8 (h.save_path ().c_str ()) + "\n";
			result += tr ("Progress:") + " " +
				QString (tr ("%1% (%2 of %3)")
						.arg (status.progress * 100, 0, 'f', 2)
						.arg (Util::MakePrettySize (status.total_wanted_done))
						.arg (Util::MakePrettySize (status.total_wanted))) + "\n";
			result += tr ("Status:") + " " + GetStringForStatus (status);
			if (!status.error.empty ())
				result += " (" + QString::fromUtf8 (status.error.c_str ()) + ")";
			result += "\n";

			result += tr ("Downloading speed:") + " " +
				Util::MakePrettySize (status.download_payload_rate) + tr ("/s") +
				tr ("; uploading speed:") + " " +
				Util::MakePrettySize (status.upload_payload_rate) + tr ("/s") + "\n";
			result += tr ("Peers/seeds: %1/%2").arg (status.num_peers).arg (status.num_seeds);
			return result;
		}
		case RoleTags:
			return Handles_.at (row).Tags_;
		case CustomDataRoles::RoleJobHolderRow:
			return QVariant::fromValue<JobHolderRow> (JobHolderRow::DownloadProgress);
		case JobHolderRole::ProcessState:
		{
			ProcessStateInfo::State state = ProcessStateInfo::State::Running;
			if (!status.error.empty ())
				state = ProcessStateInfo::State::Error;
			else if (status.paused)
				state = ProcessStateInfo::State::Paused;

			return QVariant::fromValue<ProcessStateInfo> ({
					status.total_wanted_done,
					status.total_wanted,
					Handles_.at (row).Parameters_,
					state
				});
		}
		default:
			return QVariant ();
		}
	}

	Qt::ItemFlags Core::flags (const QModelIndex&) const
	{
		return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
	}

	bool Core::hasChildren (const QModelIndex& index) const
	{
		return !index.isValid ();
	}

	QModelIndex Core::index (int row, int column, const QModelIndex&) const
	{
		if (!hasIndex (row, column))
			return QModelIndex ();

		return createIndex (row, column);
	}

	QVariant Core::headerData (int column, Qt::Orientation orient, int role) const
	{
		if (orient == Qt::Vertical)
			return QVariant ();

		if (role != Qt::DisplayRole)
			return QVariant ();

		return Headers_ [column];
	}

	QModelIndex Core::parent (const QModelIndex&) const
	{
		return QModelIndex ();
	}

	int Core::rowCount (const QModelIndex& index) const
	{
		if (index.isValid ())
			return 0;

		return Handles_.size ();
	}

	QIcon Core::GetTorrentIcon (int) const
	{
		return TorrentIcon_;
	}

	libtorrent::torrent_handle Core::GetTorrentHandle (int idx) const
	{
		if (idx < 0)
			idx = CurrentTorrent_;

		if (idx >= Handles_.size ())
			return {};

		return Handles_.at (idx).Handle_;
	}

	libtorrent::torrent_info Core::GetTorrentInfo (const QString& filename)
	{
		QFile file (filename);
		if (!file.open (QIODevice::ReadOnly))
		{
			emit error (tr ("Could not open file %1 for read: %2")
					.arg (filename).arg (file.errorString ()));
			return libtorrent::torrent_info (libtorrent::sha1_hash ());
		}
		return GetTorrentInfo (file.readAll ());
	}

	libtorrent::torrent_info Core::GetTorrentInfo (const QByteArray& data)
	{
		try
		{
			libtorrent::torrent_info result (data.constData (), data.size ());
			return result;
		}
		catch (const libtorrent::libtorrent_exception& e)
		{
			HandleLibtorrentException (e);
			return libtorrent::torrent_info (libtorrent::sha1_hash ());
		}
	}

	bool Core::IsValidTorrent (const QByteArray& torrentData) const
	{
		try
		{
			libtorrent::torrent_info result (torrentData.constData (), torrentData.size ());
		}
		catch (...)
		{
			return false;
		}
		return true;
	}

	std::unique_ptr<TorrentInfo> Core::GetTorrentStats (int idx) const
	{
		if (!CheckValidity (idx))
			throw std::runtime_error ("Invalid torrent for stats");

		const auto& handle = Handles_.at (idx).Handle_;

		std::unique_ptr<TorrentInfo> result (new TorrentInfo);
#if LIBTORRENT_VERSION_NUM >= 10000
		if (const auto info = handle.torrent_file ())
			result->Info_.reset (new libtorrent::torrent_info (*info));
#else
		result->Info_.reset (new libtorrent::torrent_info (handle.get_torrent_info ()));
#endif
		result->Status_ = handle.status ();
		result->Destination_ = QString::fromUtf8 (handle.save_path ().c_str ());
		result->State_ = GetStringForStatus (result->Status_);

		if (!result->Status_.error.empty ())
			result->State_ += " (" + QString::fromUtf8 (result->Status_.error.c_str ()) + ")";

		return result;
	}

	libtorrent::session_status Core::GetOverallStats () const
	{
		return Session_->status ();
	}

	void Core::GetPerTracker (Core::pertrackerstats_t& stats) const
	{
		std::accumulate (Handles_.begin (), Handles_.end (), 0,
				PerTrackerAccumulator (stats));
	}

	int Core::GetListenPort () const
	{
		return Session_->listen_port ();
	}

	libtorrent::cache_status Core::GetCacheStats () const
	{
		return Session_->get_cache_status ();
	}

	QList<PeerInfo> Core::GetPeers (int idx) const
	{
		if (idx < 0)
			idx = CurrentTorrent_;

		if (!CheckValidity (idx))
			return QList<PeerInfo> ();

		QList<PeerInfo> result;
		std::vector<libtorrent::peer_info> peerInfos;
		Handles_.at (idx).Handle_.get_peer_info (peerInfos);

		const auto& localPieces = Handles_.at (idx).Handle_.status (libtorrent::torrent_handle::query_pieces).pieces;

		QList<int> ourMissing;
		for (auto i = localPieces.begin (), end = localPieces.end (); i != end; ++i)
		{
			const bool res = *i;
			if (!res)
				ourMissing << res;
		}

		for (size_t i = 0; i < peerInfos.size (); ++i)
		{
			const libtorrent::peer_info& pi = peerInfos [i];

			int interesting = 0;
			Q_FOREACH (const int mis, ourMissing)
				if (pi.pieces [mis])
					++interesting;

			PeerInfo ppi =
			{
				QString::fromStdString (pi.ip.address ().to_string ()),
				QString::fromUtf8 (pi.client.c_str ()),
				interesting,
#if defined (ENABLE_GEOIP) && !defined (TORRENT_DISABLE_GEO_IP)
				QString::fromLatin1 (QByteArray (pi.country, 2)).toLower (),
#else
				QString (),
#endif
				std::shared_ptr<libtorrent::peer_info> (new libtorrent::peer_info (pi))
			};
			result << ppi;
		}

		return result;
	}

	QStringList Core::GetTagsForIndex (int torrent) const
	{
		if (torrent != -1)
			return GetTagsForIndexImpl (torrent);
		else
			return GetTagsForIndexImpl (CurrentTorrent_);
	}

	void Core::UpdateTags (const QStringList& tags, int torrent)
	{
		if (torrent != -1)
			UpdateTagsImpl (tags, torrent);
		else
			UpdateTagsImpl (tags, CurrentTorrent_);
	}

	namespace
	{
		libtorrent::storage_mode_t GetCurrentStorageMode ()
		{
			QString sm = XmlSettingsManager::Instance ()->
				property ("AllocationMode").toString ();
			if (sm == "full")
				return libtorrent::storage_mode_allocate;
			else
				return libtorrent::storage_mode_sparse;
		}
	};

	int Core::AddMagnet (const QString& magnet,
			const QString& path,
			const QStringList& tags,
			TaskParameters params)
	{
		libtorrent::torrent_handle handle;
		try
		{
			libtorrent::add_torrent_params atp;
			atp.storage_mode = GetCurrentStorageMode ();
			atp.save_path = std::string (path.toUtf8 ().constData ());
			atp.url = magnet.toStdString ();
			if (params & NoAutostart)
				atp.flags |= libtorrent::add_torrent_params::flag_paused;
			atp.flags |= libtorrent::add_torrent_params::flag_duplicate_is_error;
			handle = Session_->add_torrent (atp);

			if (XmlSettingsManager::Instance ()->property ("ResolveCountries").toBool ())
				handle.resolve_countries (true);
		}
		catch (const libtorrent::libtorrent_exception& e)
		{
			HandleLibtorrentException (e);
			return -1;
		}
		catch (const std::exception& e)
		{
			qWarning () << Q_FUNC_INFO << e.what ();
			return -1;
		}

		const TorrentStruct tmp
		{
			handle,
			tags,
			Proxy_->GetID (),
			params
		};

		beginInsertRows ({}, Handles_.size (), Handles_.size ());
		Handles_ << tmp;
		endInsertRows ();

		return tmp.ID_;
	}

	int Core::AddFile (const QString& filename,
			const QString& path,
			const QStringList& tags,
			bool tryLive,
			const QVector<bool>& files,
			TaskParameters params)
	{
		if (!QFileInfo (filename).exists () || !QFileInfo (filename).isReadable ())
		{
			emit error (tr ("File %1 doesn't exist or could not be read").arg (filename));
			return -1;
		}

		libtorrent::torrent_handle handle;
		bool autoManaged = !(params & NoAutostart);

		libtorrent::add_torrent_params atp;
		try
		{
			atp.ti = new libtorrent::torrent_info (GetTorrentInfo (filename));
			atp.storage_mode = GetCurrentStorageMode ();
			atp.save_path = std::string (path.toUtf8 ().constData ());
			if (!autoManaged)
				atp.flags &= ~libtorrent::add_torrent_params::flag_auto_managed;
			if (tryLive || (params & NoAutostart))
				atp.flags |= libtorrent::add_torrent_params::flag_paused;
			atp.flags |= libtorrent::add_torrent_params::flag_duplicate_is_error;

			handle = Session_->add_torrent (atp);
			if (XmlSettingsManager::Instance ()->property ("ResolveCountries").toBool ())
				handle.resolve_countries (true);
		}
		catch (const libtorrent::libtorrent_exception& e)
		{
			HandleLibtorrentException (e);
			return -1;
		}
		catch (const std::runtime_error&)
		{
			emit error (tr ("Runtime error"));
			return -1;
		}

		std::vector<int> priorities (atp.ti->num_files (), 1);

		if (!files.isEmpty ())
		{
			for (int i = 0; i < files.size (); ++i)
				priorities [i] = files [i];

			handle.prioritize_files (priorities);
		}
		QFile file (filename);
		file.open (QIODevice::ReadOnly);
		QByteArray contents = file.readAll ();
		file.close ();

		handle.auto_managed (autoManaged);

		beginInsertRows (QModelIndex (), Handles_.size (), Handles_.size ());
		QString torrentFileName = QString::fromUtf8 (handle.name ().c_str ());
		if (!torrentFileName.endsWith (".torrent"))
			torrentFileName.append (".torrent");

		const auto newId = Proxy_->GetID ();
		Handles_.append ({
				priorities,
				handle,
				contents,
				torrentFileName,
				tags,
				autoManaged,
				newId,
				params
			});
		endInsertRows ();

		if (tryLive)
		{
			LiveStreamManager_->EnableOn (handle);
			handle.resume ();
		}

		ScheduleSave ();
		return newId;
	}

	void Core::KillTask (int id)
	{
		for (int i = 0, size = Handles_.size (); i != size; ++i)
			if (Handles_.at (i).ID_ == id)
			{
				RemoveTorrent (id);
				return;
			}
		qWarning () << Q_FUNC_INFO
			<< "not found"
			<< id
			<< Handles_.size ();
	}

	void Core::RemoveTorrent (int pos, int roptions)
	{
		if (!CheckValidity (pos))
			return;

		beginRemoveRows (QModelIndex (), pos, pos);
		Session_->remove_torrent (Handles_.at (pos).Handle_, roptions);
		int id = Handles_.at (pos).ID_;
		Handles_.removeAt (pos);
		Proxy_->FreeID (id);
		endRemoveRows ();

		ScheduleSave ();
		emit taskRemoved (id);
	}

	void Core::PauseTorrent (int pos)
	{
		if (!CheckValidity (pos))
			return;

		Handles_.at (pos).Handle_.pause ();
		Handles_.at (pos).Handle_.auto_managed (false);
		checkFinished ();
	}

	void Core::ResumeTorrent (int pos)
	{
		if (!CheckValidity (pos))
			return;

		Handles_.at (pos).Handle_.resume ();
		Handles_ [pos].State_ = TSIdle;
		Handles_.at (pos).Handle_.auto_managed (Handles_.at (pos).AutoManaged_);
		checkFinished ();
	}

	void Core::ForceReannounce (int pos)
	{
		if (!CheckValidity (pos))
			return;

		try
		{
			Handles_.at (pos).Handle_.force_reannounce ();
		}
		catch (const libtorrent::libtorrent_exception& e)
		{
			HandleLibtorrentException (e);
			emit error (tr ("Torrent %1 could not be reannounced at the "
						"moment, try again later.").arg (pos));
		}
	}

	void Core::ForceRecheck (int pos)
	{
		if (!CheckValidity (pos))
			return;

		const auto& handle = Handles_.at (pos).Handle_;
		const auto& status = handle.status (0);
		switch (status.state)
		{
		case libtorrent::torrent_status::checking_files:
		case libtorrent::torrent_status::checking_resume_data:
		case libtorrent::torrent_status::queued_for_checking:
			return;
		default:
			break;
		}

		handle.force_recheck ();

		if (status.paused && !status.auto_managed)
		{
			handle.resume ();
			Handles_ [pos].PauseAfterCheck_ = true;
		}
	}

	void Core::SetTorrentDownloadRate (int val, int idx)
	{
		if (CheckValidity (idx))
			Handles_.at (idx).Handle_.set_download_limit (val == 0 ? -1 : val * 1024);
	}

	void Core::SetTorrentUploadRate (int val, int idx)
	{
		if (CheckValidity (idx))
			Handles_.at (idx).Handle_.set_upload_limit (val == 0 ? -1 : val * 1024);
	}

	int Core::GetTorrentDownloadRate (int idx) const
	{
		if (CheckValidity (idx))
			return Handles_.at (idx).Handle_.download_limit () / 1024;
		else
			return -1;
	}

	int Core::GetTorrentUploadRate (int idx) const
	{
		if (CheckValidity (idx))
			return Handles_.at (idx).Handle_.upload_limit () / 1024;
		else
			return -1;
	}

	void Core::AddPeer (const QString& ip, int port, int idx)
	{
		if (!CheckValidity (idx))
			return;

		Handles_.at (idx).Handle_.connect_peer (
					libtorrent::tcp::endpoint (
						libtorrent::address::from_string (ip.toStdString ()),
						port
						)
					);
	}

	void Core::AddWebSeed (const QString& ws, bool url, int idx)
	{
		if (!CheckValidity (idx))
			return;

		if (url)
			Handles_.at (idx).Handle_.add_url_seed (ws.toStdString ());
		else
			Handles_.at (idx).Handle_.add_http_seed (ws.toStdString ());
	}

	void Core::RemoveWebSeed (const QString& ws, bool url, int idx)
	{
		if (!CheckValidity (idx))
			return;

		if (url)
			Handles_.at (idx).Handle_.remove_url_seed (ws.toStdString ());
		else
			Handles_.at (idx).Handle_.remove_http_seed (ws.toStdString ());
	}

	void Core::SetFilePriority (int file, int priority, int idx)
	{
		if (!CheckValidity (idx))
			return;

		if (priority > 7)
			priority = 7;
		else if (priority < 0)
			priority = 0;

		try
		{
			Handles_ [idx].FilePriorities_.at (file) = priority;
			Handles_.at (idx).Handle_.prioritize_files (Handles_.at (idx).FilePriorities_);
		}
		catch (...)
		{
			qWarning () << Q_FUNC_INFO
				<< QString ("index for torrent %1, file %2 is out of bounds")
					.arg (idx).arg (file);
		}
	}

	void Core::SetFilename (int index, const QString& name, int idx)
	{
		if (!CheckValidity (idx))
			return;

		Handles_ [idx].Handle_.rename_file (index, std::string (name.toUtf8 ().data ()));
	}

	std::vector<libtorrent::announce_entry> Core::GetTrackers (const boost::optional<int>& row) const
	{
		int tor = row ? *row : CurrentTorrent_;
		if (!CheckValidity (tor))
			return std::vector<libtorrent::announce_entry> ();

		return Handles_.at (tor).Handle_.trackers ();
	}

	void Core::SetTrackers (const std::vector<libtorrent::announce_entry>& trackers,
			const boost::optional<int>& row)
	{
		int tor = row ? *row : CurrentTorrent_;
		if (!CheckValidity (tor))
			return;

		Handles_ [tor].Handle_.replace_trackers (trackers);
		Handles_ [tor].Handle_.force_reannounce ();
	}

	QString Core::GetMagnetLink (int idx) const
	{
		if (!CheckValidity (idx))
			return QString ();

		const std::string& result = libtorrent::make_magnet_uri (Handles_ [idx].Handle_);
		return QString::fromStdString (result);
	}

	QString Core::GetTorrentDirectory (int idx) const
	{
		if (!CheckValidity (idx))
			return QString ();

		const auto& path = Handles_.at (idx).Handle_.save_path ();
		return QString::fromUtf8 (path.c_str ());
	}

	bool Core::MoveTorrentFiles (const QString& newDir, int idx)
	{
		if (!CheckValidity (idx) || newDir == GetTorrentDirectory (idx))
			return false;

		Handles_.at (idx).Handle_.move_storage (newDir.toUtf8 ().constData ());
		return true;
	}

	void Core::SetCurrentTorrent (int torrent)
	{
		CurrentTorrent_ = torrent;
	}

	int Core::GetCurrentTorrent () const
	{
		return CurrentTorrent_;
	}

	bool Core::IsTorrentManaged (int idx) const
	{
		if (!CheckValidity (idx))
			return false;

		return GetCachedStatus (Handles_.at (idx).Handle_).auto_managed;
	};

	void Core::SetTorrentManaged (bool man, int idx)
	{
		if (!CheckValidity (idx))
			return;

		Handles_.at (idx).Handle_.auto_managed (man);
		Handles_ [idx].AutoManaged_ = man;
	}

	bool Core::IsTorrentSequentialDownload (int idx) const
	{
		if (!CheckValidity (idx))
			return false;

		return GetCachedStatus (Handles_.at (idx).Handle_).sequential_download;
	}

	void Core::SetTorrentSequentialDownload (bool seq, int idx)
	{
		if (!CheckValidity (idx))
			return;

		Handles_.at (idx).Handle_.set_sequential_download (seq);
	}

	bool Core::IsTorrentSuperSeeding (int idx) const
	{
		if (!CheckValidity (idx))
			return false;

		return GetCachedStatus (Handles_.at (idx).Handle_).super_seeding;
	}

	void Core::SetTorrentSuperSeeding (bool sup, int idx)
	{
		if (!CheckValidity (idx))
			return;

		Handles_.at (idx).Handle_.super_seeding (sup);
	}

	void Core::MakeTorrent (const NewTorrentParams& params) const
	{
		const auto tm = new TorrentMaker { Proxy_ };
		tm->Start (params);
	}

	void Core::SetExternalAddress (const QString& address)
	{
		ExternalAddress_ = address;
	}

	QString Core::GetExternalAddress () const
	{
		return ExternalAddress_;
	}

	void Core::BanPeers (const BanRange_t& peers, bool block)
	{
		libtorrent::ip_filter filter = Session_->get_ip_filter ();
		filter.add_rule (libtorrent::address::from_string (peers.first.toStdString ()),
				libtorrent::address::from_string (peers.second.toStdString ()),
				block ?
					libtorrent::ip_filter::blocked :
					0);
		Session_->set_ip_filter (filter);

		ScheduleSave ();
	}

	void Core::ClearFilter ()
	{
		Session_->set_ip_filter (libtorrent::ip_filter ());
		ScheduleSave ();
	}

	namespace
	{
		template<typename Range>
		BanRange_t GetBanRange (const Range& range)
		{
			return
			{
				QString::fromStdString (range.first.to_string ()),
				QString::fromStdString (range.last.to_string ())
			};
		}
	}

	QMap<BanRange_t, bool> Core::GetFilter () const
	{
		const auto& both = Session_->get_ip_filter ().export_filter ();

		QMap<BanRange_t, bool> result;
		for (const auto& range : both.get<0> ())
			result [GetBanRange (range)] = range.flags;
		for (const auto& range : both.get<1> ())
			result [GetBanRange (range)] = range.flags;
		return result;
	}

	void Core::SaveResumeData (const libtorrent::save_resume_data_alert& a) const
	{
		const auto torrent = FindHandle (a.handle);
		if (torrent == Handles_.end ())
		{
			qWarning () << Q_FUNC_INFO
				<< "this torrent doesn't exist anymore";
			return;
		}

		const auto& status = a.handle.status (0);
		if (!status.error.empty ())
		{
			qWarning () << Q_FUNC_INFO
					<< "not saving erroneous torrent:"
					<< a.handle.name ().c_str ();

			return;
		}

		QFile file (QDir::homePath () +
				"/.leechcraft/bittorrent/" +
				torrent->TorrentFileName_ +
				".resume");

		if (!file.open (QIODevice::WriteOnly))
		{
			qWarning () << QString ("Could not open file %1 for write: %2")
				.arg (file.fileName ())
				.arg (file.errorString ());
			return;
		}

		std::deque<char> outbuf;
		libtorrent::bencode (std::back_inserter (outbuf), *a.resume_data.get ());

		for (size_t i = 0; i < outbuf.size (); ++i)
			file.write (&outbuf.at (i), 1);
	}

	void Core::HandleMetadata (const libtorrent::metadata_received_alert& a)
	{
		const auto torrent = FindHandle (a.handle);
		if (torrent == Handles_.end ())
		{
			qWarning () << Q_FUNC_INFO
				<< "this torrent doesn't exist anymore";
			return;
		}

#if LIBTORRENT_VERSION_NUM >= 10000
		const auto& file = a.handle.torrent_file ();
		if (!file)
		{
			qWarning () << Q_FUNC_INFO
					<< "torrent doesn't have a torrent file yet";
			return;
		}

		const auto& info = *file;
#else
		const auto& info = a.handle.get_torrent_info ();
#endif
		torrent->TorrentFileName_ = QString::fromUtf8 (info.name ().c_str ()) + ".torrent";
		torrent->FilePriorities_.resize (info.num_files ());
		std::fill (torrent->FilePriorities_.begin (),
				torrent->FilePriorities_.end (), 1);

		const auto& infoE = libtorrent::bdecode (info.metadata ().get (),
				info.metadata ().get () + info.metadata_size ());
		libtorrent::entry e;
		e ["info"] = infoE;
		libtorrent::bencode (std::back_inserter (torrent->TorrentFileContents_), e);

		qDebug () << "HandleMetadata"
			<< std::distance (Handles_.begin (), torrent)
			<< torrent->TorrentFileName_;

		ScheduleSave ();
	}

	void Core::PieceRead (const libtorrent::read_piece_alert& a)
	{
		LiveStreamManager_->PieceRead (a);
	}

	void Core::UpdateStatus (const std::vector<libtorrent::torrent_status>& statuses)
	{
		for (const auto& status : statuses)
		{
			const auto handle = status.handle;
			Handle2Status_ [handle] = status;

			const auto pos = FindHandle (handle);
			if (pos == Handles_.end ())
			{
				qWarning () << Q_FUNC_INFO
						<< "unknown handle";
				continue;
			}

			const auto row = std::distance (Handles_.begin (), pos);
			emit dataChanged (index (row, 0), index (row, columnCount () - 1));
		}
	}

	void Core::HandleTorrentChecked (const libtorrent::torrent_handle& h)
	{
		const auto pos = FindHandle (h);
		if (pos == Handles_.end ())
		{
			qWarning () << Q_FUNC_INFO
					<< "unknown torrent handle"
					<< QString::fromUtf8 (h.name ().c_str ());
			return;
		}

		if (!pos->PauseAfterCheck_)
			return;

		pos->PauseAfterCheck_ = false;
		h.pause ();
	}

	void Core::MoveUp (const std::vector<int>& selections)
	{
		if (!selections.size ())
			return;

		for (auto i = selections.begin (),
				end = selections.end (); i != end; ++i)
			if (*i <= 0 || !CheckValidity (*i))
				return;

		for (auto i = selections.begin (),
				end = selections.end (); i != end; ++i)
		{
			Handles_.at (*i).Handle_.queue_position_up ();
			std::swap (Handles_ [*i],
					Handles_ [*i - 1]);

			emit dataChanged (index (*i - 1, 0),
					index (*i, columnCount () - 1));
		}
	}

	void Core::MoveDown (const std::vector<int>& selections)
	{
		if (!selections.size ())
			return;

		for (auto i = selections.begin (),
				end = selections.end (); i != end; ++i)
			if (*i < 0 || !CheckValidity (*i) || !CheckValidity (*i + 1))
				return;

		for (auto i = selections.rbegin (),
				end = selections.rend (); i != end; ++i)
		{
			Handles_.at (*i).Handle_.queue_position_down ();
			std::swap (Handles_ [*i],
					Handles_ [*i + 1]);

			emit dataChanged (index (*i, 0),
					index (*i + 1, columnCount () - 1));
		}
	}

	void Core::MoveToTop (const std::vector<int>& selections)
	{
		if (!selections.size ())
			return;

		for (auto i = selections.begin (),
				end = selections.end (); i != end; ++i)
			if (*i <= 0 || !CheckValidity (*i))
				return;

		for (auto i = selections.rbegin (),
				end = selections.rend (); i != end; ++i)
			MoveToTop (*i);
	}

	void Core::MoveToBottom (const std::vector<int>& selections)
	{
		if (!selections.size ())
			return;

		for (auto i = selections.begin (),
				end = selections.end (); i != end; ++i)
			if (*i < 0 || !CheckValidity (*i))
				return;

		for (auto i = selections.begin (),
				end = selections.end (); i != end; ++i)
			MoveToBottom (*i);
	}

	QList<FileInfo> Core::GetTorrentFiles (int idx) const
	{
		if (idx == -1)
			idx = CurrentTorrent_;

		if (!CheckValidity (idx))
			return {};

		QList<FileInfo> result;
		const auto& handle = Handles_.at (idx).Handle_;
		const auto& info = handle.get_torrent_info ();
		std::vector<libtorrent::size_type> prbytes;

		int flags = 0;
		if (!XmlSettingsManager::Instance ()->
				property ("AccurateFileProgress").toBool ())
			flags |= libtorrent::torrent_handle::piece_granularity;
		handle.file_progress (prbytes, flags);

		for (int i = 0, numFiles = info.num_files (); i < numFiles; ++i)
		{
			const auto& entry = info.file_at (i);

			FileInfo fi;
			fi.Path_ = boost::filesystem::path (entry.path);
			fi.Size_ = entry.size;
			fi.Priority_ = Handles_.at (idx).FilePriorities_.at (i);
			fi.Progress_ = fi.Size_ ?
					prbytes.at (i) / static_cast<float> (fi.Size_) :
					1;
			result << fi;
		}

		return result;
	}

	auto Core::FindHandle (const libtorrent::torrent_handle& h) -> HandleDict_t::iterator
	{
		return std::find_if (Handles_.begin (), Handles_.end (),
				[&h] (const TorrentStruct& ts) { return ts.Handle_ == h; });
	}

	auto Core::FindHandle (const libtorrent::torrent_handle& h) const -> HandleDict_t::const_iterator
	{
		return std::find_if (Handles_.begin (), Handles_.end (),
				[&h] (const TorrentStruct& ts) { return ts.Handle_ == h; });
	}

	libtorrent::torrent_status Core::GetCachedStatus (const libtorrent::torrent_handle& handle) const
	{
		if (Handle2Status_.contains (handle))
			return Handle2Status_ [handle];

		const auto& status = handle.status (0);
		Handle2Status_ [handle] = status;
		return status;
	}

	void Core::MoveToTop (int row)
	{
		Handles_.at (row).Handle_.queue_position_top ();

		beginRemoveRows (QModelIndex (), row, row);
		TorrentStruct tmp = Handles_.takeAt (row);
		endRemoveRows ();

		beginInsertRows (QModelIndex (), 0, 0);
		Handles_.push_front (tmp);
		endInsertRows ();
	}

	void Core::MoveToBottom (int row)
	{
		Handles_.at (row).Handle_.queue_position_bottom ();

		beginRemoveRows (QModelIndex (), row, row);
		TorrentStruct tmp = Handles_.takeAt (row);
		endRemoveRows ();

		beginInsertRows (QModelIndex (), Handles_.size (), Handles_.size ());
		Handles_.push_back (tmp);
		endInsertRows ();
	}

	void Core::RestoreTorrents ()
	{
		QSettings settings (QCoreApplication::organizationName (),
				QCoreApplication::applicationName () + "_Torrent");
		settings.beginGroup ("Core");
		int torrents = settings.beginReadArray ("AddedTorrents");
		qDebug () << Q_FUNC_INFO << "gonna restore" << torrents << "torrents";
		for (int i = 0; i < torrents; ++i)
		{
			settings.setArrayIndex (i);
			const auto& pathStr = settings.value ("SavePath").toString ();
			const auto& path = std::string (pathStr.toUtf8 ().constData ());
			QString filename = settings.value ("Filename").toString ();
			QFile torrent (QDir::homePath () + "/.leechcraft/bittorrent/" + filename);
			if (!torrent.open (QIODevice::ReadOnly))
			{
				emit error (tr ("Could not open saved torrent %1 for read.").arg (filename));
				continue;
			}
			QByteArray data = torrent.readAll ();
			torrent.close ();
			if (data.isEmpty ())
			{
				qWarning () << Q_FUNC_INFO
						<< "empty torrent data for"
						<< filename;
				continue;
			}

			QFile resumeDataFile (QDir::homePath () + "/.leechcraft/bittorrent/" +
					filename + ".resume");
			QByteArray resumed;
			if (resumeDataFile.open (QIODevice::ReadOnly))
			{
				resumed = resumeDataFile.readAll ();
				resumeDataFile.close ();
			}

			bool automanaged = settings.value ("AutoManaged", true).toBool ();
			TaskParameters taskParameters = static_cast<TaskParameters> (settings
						.value ("Parameters").toInt ());

			auto handle = RestoreSingleTorrent (data,
					resumed,
					path,
					automanaged,
					taskParameters & NoAutostart);
			if (!handle.is_valid ())
			{
				qWarning () << Q_FUNC_INFO
						<< "got invalid handle for"
						<< filename;
				continue;
			}

			std::vector<int> priorities;
			QByteArray prioritiesLine = settings.value ("Priorities").toByteArray ();
			std::copy (prioritiesLine.begin (), prioritiesLine.end (),
					std::back_inserter (priorities));

			if (priorities.empty ())
			{
				priorities.resize (handle.get_torrent_info ().num_files ());
				std::fill (priorities.begin (), priorities.end (), 1);
			}

			handle.prioritize_files (priorities);

			beginInsertRows ({}, Handles_.size (), Handles_.size ());
			Handles_.append ({
					priorities,
					handle,
					data,
					filename,
					settings.value ("Tags").toStringList (),
					automanaged,
					Proxy_->GetID (),
					taskParameters
				});
			endInsertRows ();
			qDebug () << "restored a torrent";
		}
		settings.endArray ();

		int filters = settings.beginReadArray ("IPFilter");
		for (int i = 0; i < filters; ++i)
		{
			settings.setArrayIndex (i);
			BanRange_t range (settings.value ("First").toString (),
					settings.value ("Last").toString ());
			bool block = settings.value ("Block").toBool ();
			BanPeers (range, block);
		}
		settings.endArray ();
		settings.endGroup ();
	}

	bool Core::DecodeEntry (const QByteArray& data, libtorrent::lazy_entry& e)
	{
		boost::system::error_code ec;
		if (libtorrent::lazy_bdecode (data.constData (), data.constData () + data.size (), e, ec))
		{
			emit error (tr ("Bad bencoding in saved torrent data: %1")
						.arg (QString::fromUtf8 (ec.message ().c_str ())));
			return false;
		}

		return true;
	}

	libtorrent::torrent_handle Core::RestoreSingleTorrent (const QByteArray& data,
			const QByteArray& resumeData,
			const boost::filesystem::path& path,
			bool automanaged,
			bool pause)
	{
		libtorrent::torrent_handle handle;

		libtorrent::lazy_entry e;
		if (!DecodeEntry (data, e))
			return handle;

		try
		{
			libtorrent::add_torrent_params atp;
			atp.ti = new libtorrent::torrent_info (e);
			atp.storage_mode = GetCurrentStorageMode ();
			atp.save_path = path.string ();
			if (!automanaged)
				atp.flags &= ~libtorrent::add_torrent_params::flag_auto_managed;
			if (pause)
				atp.flags |= libtorrent::add_torrent_params::flag_paused;
			atp.flags |= libtorrent::add_torrent_params::flag_duplicate_is_error;

#if LIBTORRENT_VERSION_NUM >= 10000
			std::copy (resumeData.constData (),
					resumeData.constData () + resumeData.size (),
					std::back_inserter (atp.resume_data));
#else
			atp.resume_data = new std::vector<char>;
			std::copy (resumeData.constData (),
					resumeData.constData () + resumeData.size (),
					std::back_inserter (*atp.resume_data));
#endif

			handle = Session_->add_torrent (atp);
			if (XmlSettingsManager::Instance ()->property ("ResolveCountries").toBool ())
				handle.resolve_countries (true);
		}
		catch (const libtorrent::libtorrent_exception& e)
		{
			qWarning () << Q_FUNC_INFO << e.what ();
			HandleLibtorrentException (e);
		}

		return handle;
	}

	void Core::HandleSingleFinished (int i)
	{
		TorrentStruct torrent = Handles_.at (i);
		const auto& info = torrent.Handle_.get_torrent_info ();

		if (LiveStreamManager_->IsEnabledOn (torrent.Handle_) &&
				torrent.Handle_.status (libtorrent::torrent_handle::query_pieces).num_pieces !=
					torrent.Handle_.get_torrent_info ().num_pieces ())
			return;

		QString name = QString::fromUtf8 (info.name ().c_str ());

		auto notifyE = Util::MakeAN ("BitTorrent",
				tr ("Torrent finished: %1").arg (name),
				PInfo_,
				"org.LeechCraft.BitTorrent",
				AN::CatDownloads,
				AN::TypeDownloadFinished,
				"org.LC.Plugins.BitTorrent.DLFinished/" + name,
				QStringList (name));

		const auto& savePath = torrent.Handle_.save_path ();
		const auto& savePathStr = QString::fromUtf8 (savePath.c_str ());

		auto nah = new Util::NotificationActionHandler (notifyE);
		if (info.files ().num_files () == 1)
		{
			const auto& path = QByteArray ((savePath + '/' + info.files ().at (0).path).c_str ());
			nah->AddFunction (tr ("Open..."), [this, path] ()
					{
						auto e = Util::MakeEntity (QUrl::fromLocalFile (path),
								QString (),
								LeechCraft::FromUserInitiated);
						emit gotEntity (e);
					});
		}
		nah->AddFunction (tr ("Show folder"),
				[savePathStr] () -> void
				{
					const auto& dirPath = QFileInfo (savePathStr).absolutePath ();
					QDesktopServices::openUrl (QUrl::fromLocalFile (dirPath));
				});
		emit gotEntity (notifyE);

		auto localeCodec = QTextCodec::codecForLocale ();
		Entity e;
		e.Parameters_ = IsDownloaded;
		e.Location_ = torrent.TorrentFileName_;
		e.Additional_ [" Tags"] = torrent.Tags_;
		e.Additional_ ["IgnorePlugins"] = QStringList ("org.LeechCraft.BitTorrent");

		for (int i = 0, numFiles = info.num_files (); i < numFiles; ++i)
		{
			const auto& entry = info.file_at (i);
			const auto& path = QByteArray ((savePath + '/' + entry.path).c_str ());
			e.Entity_ = QUrl::fromLocalFile (localeCodec->toUnicode (path));
			emit gotEntity (e);
		}

		emit taskFinished (torrent.ID_);
	}

	void Core::HandleFileRenamed (const libtorrent::file_renamed_alert& a)
	{
		const auto pos = FindHandle (a.handle);
		if (pos == Handles_.end ())
		{
			qWarning () << Q_FUNC_INFO
					<< "unknown handle";
			return;
		}

		emit fileRenamed (std::distance (Handles_.begin (), pos),
				a.index, QString::fromUtf8 (a.name.c_str ()));
	}

	QStringList Core::GetTagsForIndexImpl (int torrent) const
	{
		if (!CheckValidity (torrent))
			return QStringList ();

		QStringList result;
		Q_FOREACH (QString id, Handles_.at (torrent).Tags_)
			result << Proxy_->GetTagsManager ()->GetTag (id);
		return result;
	}

	void Core::UpdateTagsImpl (const QStringList& tags, int torrent)
	{
		if (!CheckValidity (torrent))
			return;

		Handles_ [torrent].Tags_.clear ();
		Q_FOREACH (QString tag, tags)
			Handles_ [torrent].Tags_ << Proxy_->GetTagsManager ()->GetID (tag);
	}

	void Core::ScheduleSave ()
	{
		if (SaveScheduled_)
			return;

		QTimer::singleShot (500,
				this,
				SLOT (writeSettings ()));

		SaveScheduled_ = true;
	}

	void Core::HandleLibtorrentException (const libtorrent::libtorrent_exception& e)
	{
		emit error (tr ("Error code %1 of category:<blockquote>%2</blockquote>"
					"error message:<blockquote>%3</blockquote>"
					"raw exception message:<blockquote>%4</blockquote>")
				.arg (e.error ().value ())
				.arg (e.error ().category ().name ())
				.arg (QString::fromUtf8 (e.error ().message ().c_str ()))
				.arg (e.what ()));
	}

	void Core::writeSettings ()
	{
		SaveScheduled_ = false;
		QDir home = QDir::home ();
		if (!home.exists (".leechcraft/bittorrent"))
			if (!home.mkdir (".leechcraft/bittorrent"))
			{
				emit error (QDir::toNativeSeparators (tr ("Could not create path %1/.leechcraft/bittorrent"))
						.arg (QDir::toNativeSeparators (QDir::homePath ())));
				return;
			}

		QSettings settings (QCoreApplication::organizationName (),
				QCoreApplication::applicationName () + "_Torrent");
		settings.beginGroup ("Core");
		settings.beginWriteArray ("AddedTorrents");
		for (int i = 0; i < Handles_.size (); ++i)
		{
			settings.setArrayIndex (i);
			if (!CheckValidity (i))
			{
				qWarning () << Q_FUNC_INFO
					<< "invalid torrent"
					<< i;
				continue;
			}
			if (Handles_.at (i).TorrentFileName_.isEmpty ())
			{
				qWarning () << Q_FUNC_INFO
					<< "empty file name"
					<< i;
				continue;
			}
			int oldCurrent = CurrentTorrent_;
			CurrentTorrent_ = i;
			try
			{
				QFile file_info (QDir::homePath () +
						"/.leechcraft/bittorrent/" +
						Handles_.at (i).TorrentFileName_);
				if (!file_info.open (QIODevice::WriteOnly))
					emit error (QString ("Cannot write settings! "
								"Cannot open file %1 for write!")
							.arg (Handles_.at (i).TorrentFileName_));
				else
				{
					file_info.write (Handles_.at (i).TorrentFileContents_);
					file_info.close ();

					const auto& handle = Handles_.at (i).Handle_;
					if (handle.need_save_resume_data ())
						handle.save_resume_data ();

					settings.setValue ("SavePath",
							QString::fromUtf8 (handle.save_path ().c_str ()));
					settings.setValue ("Filename",
							Handles_.at (i).TorrentFileName_);
					settings.setValue ("Tags",
							Handles_.at (i).Tags_);
					settings.setValue ("ID",
							Handles_.at (i).ID_);
					settings.setValue ("Parameters",
							static_cast<int> (Handles_.at (i).Parameters_));
					settings.setValue ("AutoManaged",
							Handles_.at (i).AutoManaged_);

					QByteArray prioritiesLine;
					std::copy (Handles_.at (i).FilePriorities_.begin (),
							Handles_.at (i).FilePriorities_.end (),
							std::back_inserter (prioritiesLine));
					settings.setValue ("Priorities", prioritiesLine);
				}
			}
			catch (const std::exception& e)
			{
				qWarning () << Q_FUNC_INFO << e.what ();
			}
			catch (...)
			{
				qWarning () << Q_FUNC_INFO << "unknown exception";
			}
			CurrentTorrent_ = oldCurrent;
		}
		settings.endArray ();

		settings.beginWriteArray ("IPFilter");
		settings.remove ("");
		QMap<BanRange_t, bool> filter = GetFilter ();
		QList<BanRange_t> keys = filter.keys ();
		int i = 0;
		Q_FOREACH (BanRange_t key, keys)
		{
			settings.setArrayIndex (i++);
			settings.setValue ("First", key.first);
			settings.setValue ("Last", key.second);
			settings.setValue ("Block", filter [key]);
		}
		settings.endArray ();
		settings.endGroup ();

		boost::uint32_t saveflags = 0xffffffff;
		if (!Session_->is_dht_running ())
			saveflags &= ~libtorrent::session::save_dht_state;

		libtorrent::entry sessionState;
		Session_->save_state (sessionState, saveflags);

		QByteArray sessionStateBA;
		libtorrent::bencode (std::back_inserter (sessionStateBA), sessionState);
		XmlSettingsManager::Instance ()->setProperty ("SessionState", sessionStateBA);

		Session_->wait_for_alert (libtorrent::time_duration (5));

		queryLibtorrentForWarnings ();
	}

	void Core::checkFinished ()
	{
		for (int i = 0; i < Handles_.size (); ++i)
		{
			if (Handles_.at (i).State_ == TSSeeding)
				continue;

			const auto& status = Handles_.at (i).Handle_.status (0);
			libtorrent::torrent_status::state_t state = status.state;

			if (status.paused)
			{
				Handles_ [i].State_ = TSIdle;
				continue;
			}

			switch (state)
			{
				case libtorrent::torrent_status::queued_for_checking:
				case libtorrent::torrent_status::checking_files:
				case libtorrent::torrent_status::checking_resume_data:
				case libtorrent::torrent_status::allocating:
				case libtorrent::torrent_status::downloading_metadata:
					Handles_ [i].State_ = TSPreparing;
					break;
				case libtorrent::torrent_status::downloading:
					Handles_ [i].State_ = TSDownloading;
					break;
				case libtorrent::torrent_status::finished:
				case libtorrent::torrent_status::seeding:
					TorrentState oldState = Handles_ [i].State_;
					Handles_ [i].State_ = TSSeeding;
					if (oldState == TSDownloading)
					{
						HandleSingleFinished (i);
						ScheduleSave ();
					}
					break;
			}
		}
	}

	struct SimpleDispatcher
	{
		mutable bool NeedToLog_ = true;

		const ICoreProxy_ptr Proxy_;
		IEntityManager * const IEM_;

		SimpleDispatcher (const ICoreProxy_ptr& proxy)
		: Proxy_ { proxy }
		, IEM_ { Proxy_->GetEntityManager () }
		{
		}

		void operator() (const libtorrent::external_ip_alert& a) const
		{
			const auto& extAddrStr = QString::fromStdString (a.external_address.to_string ());
			Core::Instance ()->SetExternalAddress (extAddrStr);
		}

		void operator() (const libtorrent::save_resume_data_alert& a) const
		{
			Core::Instance ()->SaveResumeData (a);
		}

		void operator() (const libtorrent::save_resume_data_failed_alert& a) const
		{
			const auto& text = QObject::tr ("Saving resume data failed for torrent:<br />%1<br />%2")
					.arg (QString::fromUtf8 (a.handle.name ().c_str ()))
					.arg (QString::fromUtf8 (a.error.message ().c_str ()));
			IEM_->HandleEntity (Util::MakeNotification ("BitTorrent", text, PWarning_));
		}

		void operator() (const libtorrent::storage_moved_alert& a) const
		{
			const auto& text = QObject::tr ("Storage for torrent:<br />%1"
						"<br />moved successfully to:<br />%2")
					.arg (QString::fromUtf8 (a.handle.name ().c_str ()))
					.arg (QString::fromUtf8 (a.path.c_str ()));
			IEM_->HandleEntity (Util::MakeNotification ("BitTorrent", text, PInfo_));
		}

		void operator() (const libtorrent::storage_moved_failed_alert& a) const
		{
			const auto& text = QObject::tr ("Storage move failure:<br />%2<br />for torrent:<br />%1")
					.arg (QString::fromUtf8 (a.handle.name ().c_str ()))
					.arg (QString::fromUtf8 (a.error.message ().c_str ()));
			IEM_->HandleEntity (Util::MakeNotification ("BitTorrent", text, PCritical_));
		}

		void operator() (const libtorrent::metadata_received_alert& a) const
		{
			Core::Instance ()->HandleMetadata (a);
		}

		void operator() (const libtorrent::file_renamed_alert& a) const
		{
			Core::Instance ()->HandleFileRenamed (a);
		}

		void operator() (const libtorrent::file_rename_failed_alert& a) const
		{
			const auto& text = QObject::tr ("File rename failed for torrent:<br />%1<br />"
						"file %2, error:<br />%3")
					.arg (QString::fromUtf8 (a.handle.name ().c_str ()))
					.arg (QString::number (a.index))
					.arg (QString::fromUtf8 (a.error.message ().c_str ()));
			IEM_->HandleEntity (Util::MakeNotification ("BitTorrent", text, PCritical_));
		}

		void operator() (const libtorrent::torrent_delete_failed_alert& a) const
		{
			const auto& text = QObject::tr ("Failed to delete torrent:<br />%1<br />error:<br />%2")
					.arg (QString::fromUtf8 (a.handle.name ().c_str ()))
					.arg (QString::fromUtf8 (a.error.message ().c_str ()));
			IEM_->HandleEntity (Util::MakeNotification ("BitTorrent", text, PCritical_));
		}

		void operator() (const libtorrent::read_piece_alert& a) const
		{
			Core::Instance ()->PieceRead (a);
		}

		void operator() (const libtorrent::state_update_alert& a) const
		{
			Core::Instance ()->UpdateStatus (a.status);
			NeedToLog_ = false;
		}

		void operator() (const libtorrent::torrent_paused_alert& a) const
		{
			Core::Instance ()->UpdateStatus ({ a.handle.status () });
		}

		void operator() (const libtorrent::torrent_resumed_alert& a) const
		{
			Core::Instance ()->UpdateStatus ({ a.handle.status () });
		}

		void operator() (const libtorrent::torrent_checked_alert& a) const
		{
			Core::Instance ()->HandleTorrentChecked (a.handle);
			Core::Instance ()->UpdateStatus ({ a.handle.status () });
		}

		void operator() (const libtorrent::dht_announce_alert& a) const
		{
			qDebug () << "<libtorrent> <DHT>"
					<< "got announce from"
					<< a.ip.to_string ().c_str ()
					<< ":"
					<< a.port
					<< "; the SHA1 hash is"
					<< libtorrent::to_hex (a.info_hash.to_string ()).c_str ();
			NeedToLog_ = false;
		}

		void operator() (const libtorrent::dht_reply_alert& a) const
		{
			qDebug () << "<libtorrent> <DHT>"
					<< "got reply with"
					<< a.num_peers
					<< "peers";
			NeedToLog_ = false;
		}

		void operator() (const libtorrent::dht_bootstrap_alert& a) const
		{
			qDebug () << "<libtorrent> <DHT>"
					<< "bootstrapped; "
					<< a.message ().c_str ();
			NeedToLog_ = false;
		}

		void operator() (const libtorrent::dht_get_peers_alert& a) const
		{
			qDebug () << "<libtorrent> <DHT>"
					<< "got peers for"
					<< libtorrent::to_hex (a.info_hash.to_string ()).c_str ();
			NeedToLog_ = false;
		}

		void operator() (const libtorrent::file_error_alert& a) const
		{
			const auto& text = QObject::tr ("File error for torrent:<br />%1<br />"
						"file:<br />%2<br />error:<br />%3")
					.arg (QString::fromUtf8 (a.handle.name ().c_str ()))
					.arg (QString::fromUtf8 (a.file.c_str ()))
					.arg (QString::fromUtf8 (a.error.message ().c_str ()));
			IEM_->HandleEntity (Util::MakeNotification ("BitTorrent", text, PCritical_));
		}

		void operator() (const libtorrent::torrent_error_alert& a) const
		{
			Core::Instance ()->UpdateStatus ({ a.handle.status () });
		}
	};

	namespace
	{
		template<typename Dispatcher, typename... Types>
		struct HandleAlertImpl;

		template<typename Dispatcher>
		struct HandleAlertImpl<Dispatcher>
		{
			const std::type_info& Info_;
			Dispatcher D_;

			void operator() (libtorrent::alert*) const
			{
			}
		};

		template<typename Dispatcher, typename Head, typename... Tail>
		struct HandleAlertImpl<Dispatcher, Head, Tail...>
		{
			const std::type_info& Info_;
			Dispatcher D_;

			void operator() (libtorrent::alert *alert) const
			{
				if (Info_ == typeid (Head))
					D_ (*static_cast<Head*> (alert));
				else
					HandleAlertImpl<Dispatcher, Tail...> { Info_, D_ } (alert);
			}
		};

		template<typename... Types, typename Dispatcher>
		void HandleAlert (libtorrent::alert *alert, const Dispatcher& dispatcher)
		{
			HandleAlertImpl<Dispatcher, Types...> { typeid (*alert), dispatcher } (alert);
		}
	}

	void Core::queryLibtorrentForWarnings ()
	{
		std::deque<libtorrent::alert*> alerts;
		const auto guard = Util::MakeScopeGuard ([&alerts]
				{
					for (const auto elem : alerts)
						delete elem;
				});
		Session_->pop_alerts (&alerts);

		for (const auto& alert : alerts)
		{
			SimpleDispatcher sd { Proxy_ };
			try
			{
				HandleAlert<
					libtorrent::external_ip_alert
					, libtorrent::save_resume_data_alert
					, libtorrent::save_resume_data_failed_alert
					, libtorrent::storage_moved_alert
					, libtorrent::storage_moved_failed_alert
					, libtorrent::metadata_received_alert
					, libtorrent::file_error_alert
					, libtorrent::file_renamed_alert
					, libtorrent::file_rename_failed_alert
					, libtorrent::read_piece_alert
					, libtorrent::state_update_alert
					, libtorrent::torrent_paused_alert
					, libtorrent::torrent_resumed_alert
					, libtorrent::torrent_checked_alert
					, libtorrent::dht_announce_alert
					, libtorrent::dht_reply_alert
					, libtorrent::dht_bootstrap_alert
					, libtorrent::dht_get_peers_alert
					, libtorrent::torrent_error_alert
					> (alert, sd);
			}
			catch (const libtorrent::libtorrent_exception&)
			{
			}
			catch (const std::exception&)
			{
			}

			try
			{
				if (sd.NeedToLog_)
				{
					const auto& logmsg = QString::fromUtf8 (alert->message ().c_str ());
					qDebug () << "<libtorrent>" << logmsg;
				}
			}
			catch (const std::exception& e)
			{
				qWarning () << Q_FUNC_INFO << typeid (e).name ();
			}
		}
	}

	void Core::scrape ()
	{
		for (HandleDict_t::iterator i = Handles_.begin (),
				end = Handles_.end (); i != end; ++i)
			i->Handle_.scrape_tracker ();
	}

	bool Core::CheckValidity (int pos) const
	{
		if (pos >= Handles_.size () || pos < 0)
			return false;
		if (!Handles_.at (pos).Handle_.is_valid ())
		{
			qWarning () << QString ("Torrent with position %1 found in The List, but is invalid").arg (pos);
			return false;
		}
		return true;
	}

	void Core::updateRows ()
	{
		if (!rowCount ())
			return;

		Session_->post_torrent_updates ();
		QTimer::singleShot (200,
				this,
				SLOT (queryLibtorrentForWarnings ()));
	}
}
}
