/*
 * Copyright © 2004-2009 Jens Oknelid, paskharen@gmail.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * In addition, as a special exception, compiling, linking, and/or
 * using OpenSSL with this program is allowed.
 */

#include <sstream>
#include <iostream>
#include <iomanip>
#include <iterator>
#include "transfers.hh"
#include "WulforUtil.hh"
#include "wulformanager.hh"
#include "settingsmanager.hh"
#include "func.hh"
#include <dcpp/Download.h>
#include <dcpp/Upload.h>
#include <dcpp/ClientManager.h>
#include <dcpp/TimerManager.h>
#include <dcpp/FavoriteManager.h>

using namespace std;
using namespace dcpp;

Transfers::Transfers() :
	Entry(Entry::TRANSFERS, "transfers.glade")
{
	// Initialize the user command menu
	userCommandMenu = new UserCommandMenu(getWidget("userCommandMenu"), ::UserCommand::CONTEXT_CHAT);
	addChild(userCommandMenu);

	// Initialize transfer treeview
	transferView.setView(GTK_TREE_VIEW(getWidget("transfers")), TRUE, "transfers");
	transferView.insertColumn(N_("User"), G_TYPE_STRING, TreeView::ICON_STRING, 150, "Icon");
	transferView.insertColumn(N_("Hub Name"), G_TYPE_STRING, TreeView::STRING, 100);
	transferView.insertColumn(N_("Status"), G_TYPE_STRING, TreeView::PROGRESS, 250, "Progress");
	transferView.insertColumn(N_("Time Left"), G_TYPE_INT64, TreeView::TIME_LEFT, 90);
	transferView.insertColumn(N_("Speed"), G_TYPE_INT64, TreeView::SPEED, 125);
	transferView.insertColumn(N_("Filename"), G_TYPE_STRING, TreeView::STRING, 200);
	transferView.insertColumn(N_("Size"), G_TYPE_INT64, TreeView::SIZE, 125);
	transferView.insertColumn(N_("Path"), G_TYPE_STRING, TreeView::STRING, 200);
	transferView.insertColumn(N_("IP"), G_TYPE_STRING, TreeView::STRING, 175);
	transferView.insertHiddenColumn("Icon", G_TYPE_STRING);
	transferView.insertHiddenColumn("Progress", G_TYPE_INT);
	transferView.insertHiddenColumn("Sort Order", G_TYPE_STRING);
	transferView.insertHiddenColumn("CID", G_TYPE_STRING);
	transferView.insertHiddenColumn("Download Position", G_TYPE_INT64);	// For keeping track of and calculating parent pos
	transferView.insertHiddenColumn("Failed", G_TYPE_BOOLEAN);
	transferView.insertHiddenColumn("Target", G_TYPE_STRING);
	transferView.insertHiddenColumn("Download", G_TYPE_BOOLEAN);
	transferView.insertHiddenColumn("Hub URL", G_TYPE_STRING);
	transferView.finalize();

	transferStore = gtk_tree_store_newv(transferView.getColCount(), transferView.getGTypes());
	gtk_tree_view_set_model(transferView.get(), GTK_TREE_MODEL(transferStore));
	g_object_unref(transferStore);
	transferSelection = gtk_tree_view_get_selection(transferView.get());
	gtk_tree_selection_set_mode(transferSelection, GTK_SELECTION_MULTIPLE);
	transferView.setSortColumn_gui("User", "Sort Order");
	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(transferStore), transferView.col("Sort Order"), GTK_SORT_ASCENDING);
	gtk_tree_view_column_set_sort_indicator(gtk_tree_view_get_column(transferView.get(), transferView.col("User")), TRUE);
	gtk_tree_view_set_fixed_height_mode(transferView.get(), TRUE);

	g_signal_connect(transferView.get(), "button-press-event", G_CALLBACK(onTransferButtonPressed_gui), (gpointer)this);
	g_signal_connect(transferView.get(), "button-release-event", G_CALLBACK(onTransferButtonReleased_gui), (gpointer)this);
	g_signal_connect(getWidget("getFileListItem"), "activate", G_CALLBACK(onGetFileListClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("matchQueueItem"), "activate", G_CALLBACK(onMatchQueueClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("sendPrivateMessageItem"), "activate", G_CALLBACK(onPrivateMessageClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("addToFavoritesItem"), "activate", G_CALLBACK(onAddFavoriteUserClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("grantExtraSlotItem"), "activate", G_CALLBACK(onGrantExtraSlotClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("removeUserItem"), "activate", G_CALLBACK(onRemoveUserFromQueueClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("forceAttemptItem"), "activate", G_CALLBACK(onForceAttemptClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("closeConnectionItem"), "activate", G_CALLBACK(onCloseConnectionClicked_gui), (gpointer)this);
}

Transfers::~Transfers()
{
	QueueManager::getInstance()->removeListener(this);
	DownloadManager::getInstance()->removeListener(this);
	UploadManager::getInstance()->removeListener(this);
	ConnectionManager::getInstance()->removeListener(this);
}	

void Transfers::show()
{
	QueueManager::getInstance()->addListener(this);
	DownloadManager::getInstance()->addListener(this);
	UploadManager::getInstance()->addListener(this);
	ConnectionManager::getInstance()->addListener(this);
}

void Transfers::popupTransferMenu_gui()
{
	// Build user command menu
	userCommandMenu->cleanMenu_gui();

	GtkTreePath *path;
	GtkTreeIter iter;
	GList *list = gtk_tree_selection_get_selected_rows(transferSelection, NULL);

	for (GList *i = list; i; i = i->next)
	{
		path = (GtkTreePath *)i->data;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(transferStore), &iter, path))
		{
			bool parent = gtk_tree_model_iter_has_child(GTK_TREE_MODEL(transferStore), &iter);

			do
			{
				string cid = transferView.getString(&iter, "CID");
				userCommandMenu->addUser(cid);
				userCommandMenu->addHub(WulforUtil::getHubAddress(CID(cid)));
			}
			while (parent && WulforUtil::getNextIter_gui(GTK_TREE_MODEL(transferStore), &iter, TRUE, FALSE));
		}
		gtk_tree_path_free(path);
	}
	g_list_free(list);

	userCommandMenu->buildMenu_gui();

	gtk_menu_popup(GTK_MENU(getWidget("transferMenu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
	gtk_widget_show_all(getWidget("transferMenu"));
}

void Transfers::onGetFileListClicked_gui(GtkMenuItem *item, gpointer data)
{
	Transfers *tr = (Transfers *)data;
	GtkTreeIter iter;
	GtkTreePath *path;
	GList *list = gtk_tree_selection_get_selected_rows(tr->transferSelection, NULL);
	typedef Func2<Transfers, string, string> F2;

	for (GList *i = list; i; i = i->next)
	{
		path = (GtkTreePath *)i->data;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(tr->transferStore), &iter, path))
		{
			bool parent = gtk_tree_model_iter_has_child(GTK_TREE_MODEL(tr->transferStore), &iter);
			
			do 
			{
				string cid = tr->transferView.getString(&iter, "CID");
				string hubUrl = tr->transferView.getString(&iter, "Hub URL");
				if (!cid.empty())
				{
					F2 *func = new F2(tr, &Transfers::getFileList_client, cid, hubUrl);
					WulforManager::get()->dispatchClientFunc(func);
				}
			} 
			while (parent && WulforUtil::getNextIter_gui(GTK_TREE_MODEL(tr->transferStore), &iter, TRUE, FALSE));
		}
		gtk_tree_path_free(path);
	}
	g_list_free(list);
}

void Transfers::onMatchQueueClicked_gui(GtkMenuItem *item, gpointer data)
{
	Transfers *tr = (Transfers *)data;
	GtkTreeIter iter;
	GtkTreePath *path;
	GList *list = gtk_tree_selection_get_selected_rows(tr->transferSelection, NULL);
	typedef Func2<Transfers, string, string> F2;

	for (GList *i = list; i; i = i->next)
	{
		path = (GtkTreePath *)i->data;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(tr->transferStore), &iter, path))
		{
			bool parent = gtk_tree_model_iter_has_child(GTK_TREE_MODEL(tr->transferStore), &iter);

			do
			{
				string cid = tr->transferView.getString(&iter, "CID");
				string hubUrl = tr->transferView.getString(&iter, "Hub URL");
				if (!cid.empty())
				{
					F2 *func = new F2(tr, &Transfers::matchQueue_client, cid, hubUrl);
					WulforManager::get()->dispatchClientFunc(func);
				}
			}
			while (parent && WulforUtil::getNextIter_gui(GTK_TREE_MODEL(tr->transferStore), &iter, TRUE, FALSE));
		}
		gtk_tree_path_free(path);
	}
	g_list_free(list);
}

void Transfers::onPrivateMessageClicked_gui(GtkMenuItem *item, gpointer data)
{
	Transfers *tr = (Transfers *)data;
	string cid;
	GtkTreeIter iter;
	GtkTreePath *path;
	GList *list = gtk_tree_selection_get_selected_rows(tr->transferSelection, NULL);

	for (GList *i = list; i; i = i->next)
	{
		path = (GtkTreePath *)i->data;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(tr->transferStore), &iter, path))
		{
			bool parent = gtk_tree_model_iter_has_child(GTK_TREE_MODEL(tr->transferStore), &iter);

			do 
			{
				cid = tr->transferView.getString(&iter, "CID");
				if (!cid.empty())
					WulforManager::get()->getMainWindow()->addPrivateMessage_gui(cid);
			}
			while (parent && WulforUtil::getNextIter_gui(GTK_TREE_MODEL(tr->transferStore), &iter, TRUE, FALSE));
		}
		gtk_tree_path_free(path);
	}
	g_list_free(list);
}

void Transfers::onAddFavoriteUserClicked_gui(GtkMenuItem *item, gpointer data)
{
	Transfers *tr = (Transfers *)data;
	string cid;
	GtkTreeIter iter;
	GtkTreePath *path;
	GList *list = gtk_tree_selection_get_selected_rows(tr->transferSelection, NULL);
	typedef Func1<Transfers, string > F1;
	F1 *func;

	for (GList *i = list; i; i = i->next)
	{
		path = (GtkTreePath *)i->data;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(tr->transferStore), &iter, path))
		{
			bool parent = gtk_tree_model_iter_has_child(GTK_TREE_MODEL(tr->transferStore), &iter);

			do
			{
				cid = tr->transferView.getString(&iter, "CID");
				if (!cid.empty())
				{
					func = new F1(tr, &Transfers::addFavoriteUser_client, cid);
					WulforManager::get()->dispatchClientFunc(func);
				}
			}
			while (parent && WulforUtil::getNextIter_gui(GTK_TREE_MODEL(tr->transferStore), &iter, TRUE, FALSE));
		}
		gtk_tree_path_free(path);
	}
	g_list_free(list);
}

void Transfers::onGrantExtraSlotClicked_gui(GtkMenuItem *item, gpointer data)
{
	Transfers *tr = (Transfers *)data;
	GtkTreeIter iter;
	GtkTreePath *path;
	GList *list = gtk_tree_selection_get_selected_rows(tr->transferSelection, NULL);
	typedef Func2<Transfers, string, string> F2;

	for (GList *i = list; i; i = i->next)
	{
		path = (GtkTreePath *)i->data;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(tr->transferStore), &iter, path))
		{
			bool parent = gtk_tree_model_iter_has_child(GTK_TREE_MODEL(tr->transferStore), &iter);

			do
			{
				string cid = tr->transferView.getString(&iter, "CID");
				string hubUrl = tr->transferView.getString(&iter, "Hub URL");
				if (!cid.empty())
				{
					F2 *func = new F2(tr, &Transfers::grantExtraSlot_client, cid, hubUrl);
					WulforManager::get()->dispatchClientFunc(func);
				}
			}
			while (parent && WulforUtil::getNextIter_gui(GTK_TREE_MODEL(tr->transferStore), &iter, TRUE, FALSE));
		}
		gtk_tree_path_free(path);
	}
	g_list_free(list);
}

void Transfers::onRemoveUserFromQueueClicked_gui(GtkMenuItem *item, gpointer data)
{
	Transfers *tr = (Transfers *)data;
	string cid;
	GtkTreeIter iter;
	GtkTreePath *path;
	GList *list = gtk_tree_selection_get_selected_rows(tr->transferSelection, NULL);
	typedef Func1<Transfers, string > F1;
	F1 *func;

	for (GList *i = list; i; i = i->next)
	{
		path = (GtkTreePath *)i->data;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(tr->transferStore), &iter, path))
		{
			bool parent = gtk_tree_model_iter_has_child(GTK_TREE_MODEL(tr->transferStore), &iter);

			do
			{
				cid = tr->transferView.getString(&iter, "CID");
				if (!cid.empty())
				{
					func = new F1(tr, &Transfers::removeUserFromQueue_client, cid);
					WulforManager::get()->dispatchClientFunc(func);
				}
			}
			while (parent && WulforUtil::getNextIter_gui(GTK_TREE_MODEL(tr->transferStore), &iter, TRUE, FALSE));
		}
		gtk_tree_path_free(path);
	}
	g_list_free(list);
}

void Transfers::onForceAttemptClicked_gui(GtkMenuItem *menuItem, gpointer data)
{
	Transfers *tr = (Transfers *)data;
	string cid;
	GtkTreeIter iter;
	GtkTreePath *path;
	GList *list = gtk_tree_selection_get_selected_rows(tr->transferSelection, NULL);
	typedef Func1<Transfers, string> F1;
	F1 *func;

	for (GList *i = list; i; i = i->next)
	{
		path = (GtkTreePath *)i->data;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(tr->transferStore), &iter, path))
		{
			cid = tr->transferView.getString(&iter, "CID");
			gtk_tree_store_set(tr->transferStore, &iter, tr->transferView.col("Status"), _("Connecting (forced)..."), -1);

			func = new F1(tr, &Transfers::forceAttempt_client, cid);
			WulforManager::get()->dispatchClientFunc(func);
		}
		gtk_tree_path_free(path);
	}
	g_list_free(list);
}

void Transfers::onCloseConnectionClicked_gui(GtkMenuItem *menuItem, gpointer data)
{
	Transfers *tr = (Transfers *)data;
	string cid;
	GtkTreeIter iter;
	GtkTreePath *path;
	bool download;
	GList *list = gtk_tree_selection_get_selected_rows(tr->transferSelection, NULL);
	typedef Func2<Transfers, string, bool> F2;
	F2 *func;

	for (GList *i = list; i; i = i->next)
	{
		path = (GtkTreePath *)i->data;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(tr->transferStore), &iter, path))
		{
			bool parent = gtk_tree_model_iter_has_child(GTK_TREE_MODEL(tr->transferStore), &iter);

			do
			{
				cid = tr->transferView.getString(&iter, "CID");
				if (!cid.empty())
				{
					gtk_tree_store_set(tr->transferStore, &iter, tr->transferView.col("Status"), _("Closing connection..."), -1);
					download = tr->transferView.getValue<gboolean>(&iter, "Download");

					func = new F2(tr, &Transfers::closeConnection_client, cid, download);
					WulforManager::get()->dispatchClientFunc(func);
				}
			}
			while (parent && WulforUtil::getNextIter_gui(GTK_TREE_MODEL(tr->transferStore), &iter, TRUE, FALSE));
		}
		gtk_tree_path_free(path);
	}
	g_list_free(list);
}

gboolean Transfers::onTransferButtonPressed_gui(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	Transfers *tr = (Transfers *)data;

	if (event->button == 3)
	{
		GtkTreePath *path;
		if (gtk_tree_view_get_path_at_pos(tr->transferView.get(), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
		{
			bool selected = gtk_tree_selection_path_is_selected(tr->transferSelection, path);
			gtk_tree_path_free(path);

			if (selected)
				return TRUE;
		}
	}

	return FALSE;
}

gboolean Transfers::onTransferButtonReleased_gui(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	Transfers *tr = (Transfers *)data;
	int count = gtk_tree_selection_count_selected_rows(tr->transferSelection);

	if (count > 0 && event->type == GDK_BUTTON_RELEASE && event->button == 3)
		tr->popupTransferMenu_gui();

	return FALSE;
}

bool Transfers::findParent_gui(const string& target, GtkTreeIter* iter)
{
	dcassert(!target.empty());
	GtkTreeModel *m = GTK_TREE_MODEL(transferStore);
	bool valid = gtk_tree_model_get_iter_first(m, iter);

	while (valid)
	{
		if (transferView.getValue<gboolean>(iter, "Download") &&
				target == transferView.getString(iter, "Target") && 
				transferView.getString(iter, "CID").empty())
			return TRUE;

		valid = WulforUtil::getNextIter_gui(m, iter, FALSE, FALSE);
	}

	return FALSE;
}

bool Transfers::findTransfer_gui(const string& cid, bool download, GtkTreeIter* iter)
{
	GtkTreeModel *m = GTK_TREE_MODEL(transferStore);
	bool valid = gtk_tree_model_get_iter_first(m, iter);

	while (valid)
	{
		if (cid == transferView.getString(iter, "CID") && !cid.empty())
		{
			if (download && transferView.getValue<gboolean>(iter, "Download"))
				return TRUE;
			if (!download && !transferView.getValue<gboolean>(iter, "Download"))
				return TRUE;
		}
		valid = WulforUtil::getNextIter_gui(m, iter, TRUE, TRUE);
	}

	return FALSE;
}

void Transfers::addConnection_gui(StringMap params, bool download)
{
	GtkTreeIter iter;
	dcassert(params.find("CID") != params.end());
	dcassert(findTransfer_gui(params["CID"], download, &iter) == FALSE);	// shouldn't fail, if it's already there we've forgot to remove it or dcpp core sends more than one Connection::Added 

	gtk_tree_store_append(transferStore, &iter, NULL);
	gtk_tree_store_set(transferStore, &iter, 
		transferView.col("User"), params["User"].c_str(),
		transferView.col("Hub Name"), params["Hub Name"].c_str(),
		transferView.col("Status"), params["Status"].c_str(),
		transferView.col("CID"), params["CID"].c_str(),
		transferView.col("Icon"), download ? "linuxdcpp-download" : "linuxdcpp-upload",
		transferView.col("Download"), download,
		transferView.col("Hub URL"), params["Hub URL"].c_str(),
		-1);
}

void Transfers::removeConnection_gui(const std::string cid, bool download)
{
	GtkTreeIter iter;
	GtkTreeIter parent;
	bool valid = findTransfer_gui(cid, download, &iter);

	if (valid)
	{
		if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(transferStore), &parent, &iter))
		{
			gtk_tree_store_remove(transferStore, &iter);

			if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(transferStore), &parent))
				gtk_tree_store_remove(transferStore, &parent);
			else
				updateParent_gui(&parent);
		}
		else
		{
			gtk_tree_store_remove(transferStore, &iter);
		}
	}
}

void Transfers::updateParent_gui(GtkTreeIter* iter)
{
	int active = 0;
	int total = 0;
	GtkTreeIter child;
	string users;
	string status;
	std::set<string> hubs;
	bool valid;
	int64_t speed = 0;
	int64_t position = 0;
	int64_t totalSize = 0;
	int64_t timeLeft = 0;
	double progress = 0.0;
	ostringstream tmpHubs;

	position = transferView.getValue<int64_t>(iter, "Download Position");
	totalSize = transferView.getValue<int64_t>(iter, "Size");

	// Get Totals 
	if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(transferStore), iter))
	{
		child = *iter;
		valid = WulforUtil::getNextIter_gui(GTK_TREE_MODEL(transferStore), &child, TRUE, FALSE);
		while (valid)
		{
			if (transferView.getValue<int>(&child, "Failed") == 0 && 
				transferView.getString(&child, "Sort Order").substr(0,1) == "d")
			{
				active++;
				speed += transferView.getValue<int64_t>(&child, "Speed");
				position += transferView.getValue<int64_t>(&child, "Download Position");	
			}
			++total;
			users += transferView.getString(&child, "User") + string(", ");
			hubs.insert(transferView.getString(&child, "Hub Name"));
			valid = WulforUtil::getNextIter_gui(GTK_TREE_MODEL(transferStore), &child, TRUE, FALSE);
		}
	}

	if (totalSize > 0)
		progress = (double)(position * 100.0) / totalSize;
	if (speed > 0)
		timeLeft = (totalSize - position) / speed;

	if (transferView.getValue<gboolean>(iter, "Failed") == 0)
	{
		if (active)
			status = F_("Downloaded %1% (%2$.1f%%)", % Util::formatBytes(position) % progress);
		else
			status = _("Waiting for slot");
	}
	else
	{
		status = transferView.getString(iter, "Status");
	}

	string user = P_("%1% of %2% User", "%1% of %2% Users", % active % total, total);
	std::copy(hubs.begin(), hubs.end(), std::ostream_iterator<string>(tmpHubs, ", "));

	gtk_tree_store_set(transferStore, iter, 
		transferView.col("User"), user.c_str(),
		transferView.col("Hub Name"), tmpHubs.str().substr(0, tmpHubs.str().length()-2).c_str(),
		transferView.col("Speed"), speed, 
		transferView.col("Time Left"), timeLeft,
		transferView.col("Status"), status.c_str(), 
		transferView.col("Progress"), static_cast<int>(progress),
		transferView.col("Sort Order"), active ? (string("d").append(users)).c_str() : (string("w").append(users)).c_str(), 
		-1);
}

void Transfers::updateTransfer_gui(StringMap params, bool download)
{
	dcassert(!params["CID"].empty());

	GtkTreeIter iter, parent;
	if (!findTransfer_gui(params["CID"], download, &iter))
	{
		dcdebug("Transfers::updateTransfer, CID not found %s\n", params["CID"].c_str());
		// Transfer not found. Usually this *shouldn't* happen, but I guess it's possible since tick updates are sent by TimerManager
		// and removing is handled by dl manager. 
		return;
	}

	int failed = transferView.getValue<int>(&iter, "Failed");
	if (failed && params.find("Failed") != params.end())
		failed = Util::toInt(params["Failed"]);

	if (failed)	// Transfer had failed already. We won't update the transfer before the fail status changes.
		return;

	for (StringMap::const_iterator it = params.begin(); it != params.end(); ++it)
	{
		if (it->first == "Size" || it->first == "Speed" || it->first == "Download Position" || it->first == "Time Left")
			gtk_tree_store_set(transferStore, &iter, transferView.col(it->first), Util::toInt64(it->second), -1);
		else if (it->first == "Progress" || it->first == "Failed")
			gtk_tree_store_set(transferStore, &iter, transferView.col(it->first), Util::toInt(it->second), -1);
		else if (!it->second.empty())
			gtk_tree_store_set(transferStore, &iter, transferView.col(it->first), it->second.c_str(), -1);
	}

	if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(transferStore), &parent, &iter))
		updateParent_gui(&parent);
}

void Transfers::updateFilePosition_gui(const string cid, int64_t filePosition)
{
	GtkTreeIter iter;
	GtkTreeIter parent;

	if (!findTransfer_gui(cid, TRUE, &iter))
		return;

	if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(transferStore), &parent, &iter))
	{
		gtk_tree_store_set(transferStore, &parent,
			transferView.col("Download Position"), filePosition,
			-1);	
		updateParent_gui(&parent);
	}	
}

void Transfers::initTransfer_gui(StringMap params)
{
	dcassert(!params["CID"].empty() && !params["Target"].empty());

	bool oldParentValid = FALSE;
	bool newParentValid = FALSE;
	bool needParent;
	GtkTreeIter iter;
	GtkTreeIter oldParent;
	GtkTreeIter newParent;
	GtkTreeIter newIter;

	// We could use && BOOLSETTING(SEGMENTED_DL) here to group only when segmented is enabled,
	// but then the transfer should be worked out to display the whole size of the file. As
	// it currently only shows the size of a transfer (and always starts from 0)
	needParent = params["Filename"] != string(_("File list"));	

	if (!findTransfer_gui(params["CID"], TRUE, &iter))
	{
		int connection_not_found = 0;
		dcassert(connection_not_found);	// not really fatal only annoying as the dl can't be seen, can be ignored in release build
		return;
	}

	oldParentValid = gtk_tree_model_iter_parent(GTK_TREE_MODEL(transferStore), &oldParent, &iter);
	if (needParent)
	{	
		newParentValid = findParent_gui(params["Target"], &newParent);
		
		if (newParentValid)
		{
			string target = oldParentValid ? transferView.getString(&oldParent, "Target") : transferView.getString(&iter, "Target");
			if (target != transferView.getString(&newParent, "Target")) // is the file changing
			{
				newIter = WulforUtil::copyRow_gui(transferStore, &iter, &newParent);
				gtk_tree_store_remove(transferStore, &iter);
				iter = newIter;
			}
			else
			{
				oldParentValid = FALSE;	// Don't update the parentRow twice, since old and new are the same (and definately don't remove twice)
			}
		}
		else
		{
			string filename = params["Filename"];
			if (filename.find(_("TTH: ")) != string::npos)
				filename = filename.substr((string(_("TTH: "))).length());
			gtk_tree_store_append(transferStore, &newParent, NULL);
			newParentValid = TRUE;
			gtk_tree_store_set(transferStore, &newParent,
				transferView.col("Filename"), filename.c_str(),
				transferView.col("Path"), params["Path"].c_str(),
				transferView.col("Size"), Util::toInt64(params["File Size"]),
				transferView.col("Icon"), "linuxdcpp-download",
				transferView.col("Download"), TRUE,
				transferView.col("Target"), params["Target"].c_str(),
				-1);

			newIter = WulforUtil::copyRow_gui(transferStore, &iter, &newParent);
			gtk_tree_store_remove(transferStore, &iter);
			iter = newIter;
		}

		gtk_tree_store_set(transferStore, &newParent,
				transferView.col("Size"), Util::toInt64(params["File Size"]),
				transferView.col("Download Position"), Util::toInt64(params["File Position"]),
				-1);	
	}
	else if (oldParentValid) // No need for parent, but we have one anyway => move row to top-level
	{
		newIter = WulforUtil::copyRow_gui(transferStore, &iter, NULL);
		gtk_tree_store_remove(transferStore, &iter);
		iter = newIter;
	}

	if (oldParentValid)
	{
		if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(transferStore), &oldParent) == 0)	
			gtk_tree_store_remove(transferStore, &oldParent);
		else
			updateParent_gui(&oldParent);
	}
	if (newParentValid)
	{
		updateParent_gui(&newParent);
	}
}

void Transfers::finishParent_gui(const string target, const string status)
{
	GtkTreeIter iter;
	if (findParent_gui(target, &iter))
	{
		if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(transferStore), &iter))
			return;

		if (transferView.getValue<gboolean>(&iter, "Failed") == 0)
		{
			gtk_tree_store_set(transferStore, &iter,
				transferView.col("Status"), status.c_str(),
				transferView.col("Failed"), (gboolean)1, 
				transferView.col("Speed"), (gint64)-1,
				-1);
		}
	}
}

void Transfers::getFileList_client(string cid, string hubUrl)
{
	try
	{
		if (!cid.empty() && !hubUrl.empty())
		{
			UserPtr user = ClientManager::getInstance()->getUser(CID(cid));
			QueueManager::getInstance()->addList(user, hubUrl, QueueItem::FLAG_CLIENT_VIEW);
		}
	}
	catch (const Exception&)
	{
	}
}

void Transfers::matchQueue_client(string cid, string hubUrl)
{
	try
	{
		if (!cid.empty() && !hubUrl.empty())
		{
			UserPtr user = ClientManager::getInstance()->getUser(CID(cid));
			QueueManager::getInstance()->addList(user, hubUrl, QueueItem::FLAG_MATCH_QUEUE);
		}
	}
	catch (const Exception&)
	{
	}
}

void Transfers::addFavoriteUser_client(string cid)
{
	if (!cid.empty())
	{
		UserPtr user = ClientManager::getInstance()->getUser(CID(cid));
		FavoriteManager::getInstance()->addFavoriteUser(user);
	}
}

void Transfers::grantExtraSlot_client(string cid, string hubUrl)
{
	if (!cid.empty() && !hubUrl.empty())
	{
		UserPtr user = ClientManager::getInstance()->getUser(CID(cid));
		UploadManager::getInstance()->reserveSlot(user, hubUrl);
	}
}

void Transfers::removeUserFromQueue_client(string cid)
{
	if (!cid.empty())
	{
		UserPtr user = ClientManager::getInstance()->getUser(CID(cid));
		QueueManager::getInstance()->removeSource(user, QueueItem::Source::FLAG_REMOVED);
	}
}

void Transfers::forceAttempt_client(string cid)
{
	if (!cid.empty())
	{
		UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
		ConnectionManager::getInstance()->force(user);
	}
}

void Transfers::closeConnection_client(string cid, bool download)
{
	if (!cid.empty())
	{
		UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
		ConnectionManager::getInstance()->disconnect(user, download);
	}
}

void Transfers::getParams_client(StringMap& params, ConnectionQueueItem* cqi)
{
	const UserPtr& user = cqi->getUser();

	params["CID"] = user->getCID().toBase32();
	params["User"] = WulforUtil::getNicks(user);
	params["Hub Name"] = WulforUtil::getHubNames(user); //TODO Get specific hub name
	params["Failed"] = "0";
	params["Hub URL"] = cqi->getHubHint();
}

void Transfers::getParams_client(StringMap& params, Transfer* tr)
{
	const UserPtr& user = tr->getUser();
	double percent = 0.0; 

	params["CID"] = user->getCID().toBase32();
	params["Filename"] = getFileName_client(tr);
	params["User"] = WulforUtil::getNicks(user);
	params["Hub Name"] = WulforUtil::getHubNames(user); //TODO Get specific hub name
	params["Path"] = Util::getFilePath(tr->getPath());
	params["Size"] = Util::toString(tr->getSize());
	params["Download Position"] = Util::toString(tr->getPos());
	params["Speed"] = Util::toString(tr->getAverageSpeed()); 
	if (tr->getSize() > 0)
		percent = static_cast<double>(tr->getPos() * 100.0)/ tr->getSize();
	params["Progress"] = Util::toString(static_cast<int>(percent));
	params["IP"] = tr->getUserConnection().getRemoteIp();
	params["Time Left"] = tr->getSecondsLeft() > 0 ? Util::toString(tr->getSecondsLeft()) : "-1";
	params["Target"] = tr->getPath();
	params["Hub URL"] = tr->getUserConnection().getHubUrl();
}

string Transfers::getFileName_client(Transfer *t)
{
 	if (t->getType() == Transfer::TYPE_FULL_LIST || t->getType() == Transfer::TYPE_PARTIAL_LIST)
		return _("File list");
	else if (t->getType() == Transfer::TYPE_TREE)
		return _("TTH: ") + Util::getFileName(t->getPath());
	else 
		return Util::getFileName(t->getPath());
}

void Transfers::on(DownloadManagerListener::Requesting, Download* dl) throw()
{
	StringMap params;

	getParams_client(params, dl);

	params["File Size"] = Util::toString(QueueManager::getInstance()->getSize(dl->getPath()));
	params["File Position"] = Util::toString(QueueManager::getInstance()->getPos(dl->getPath()));
	params["Sort Order"] = "w" + params["User"];
	params["Status"] = _("Requesting file");
	params["Failed"] = "0";

	typedef Func1<Transfers, StringMap> F1;
	F1* f1 = new F1(this, &Transfers::initTransfer_gui, params);
	WulforManager::get()->dispatchGuiFunc(f1);
}

void Transfers::on(DownloadManagerListener::Starting, Download* dl) throw()
{
	StringMap params;

	getParams_client(params, dl);
	params["Status"] = _("Download starting");
	params["Sort Order"] = "d" + params["User"];
	params["Failed"] = "0";

	typedef Func2<Transfers, StringMap, bool> F2;
	F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, TRUE);
	WulforManager::get()->dispatchGuiFunc(f2);
}

void Transfers::on(DownloadManagerListener::Tick, const DownloadList& dls) throw()
{
	for (DownloadList::const_iterator it = dls.begin(); it != dls.end(); ++it)
	{
		Download* dl = *it;
		StringMap params;
		ostringstream stream;
		
		getParams_client(params, *it);

		// TODO: These flags in the status are a hack and should be redesigned.
		if (dl->getUserConnection().isSecure())
		{
			if (dl->getUserConnection().isTrusted())
				// TRANSLATORS: Status flag to indicate transfer is secure and trusted.
				stream << _("[S]");
			else
				// TRANSLATORS: Status flag to indicate transfer is secure but untrusted.
				stream << _("[U]");
		}
		if (dl->isSet(Download::FLAG_TTH_CHECK))
			// TRANSLATORS: Status flag to indicate a TTH check will be performed on the transfer.
			stream << _("[T]");
		if (dl->isSet(Download::FLAG_ZDOWNLOAD))
			// TRANSLATORS: Status flag to indicate transfer is compressed.
			stream << _("[Z]");

		string bytes = Util::formatBytes(dl->getPos());
		double progress = dl->getSize() <= 0 ? 0.0 : static_cast<double>(dl->getPos() * 100.0) / dl->getSize();

		// TRANSLATORS: Shows the bytes downloaded and the percentage completed.
		string status = F_("Downloaded %1% (%2$.1f%%)", % bytes % progress);
		params["Status"] = stream.tellp() > 0 ? stream.str() + " " + status : status;

		typedef Func2<Transfers, StringMap, bool> F2;
		F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, TRUE);
		WulforManager::get()->dispatchGuiFunc(f2);
	}	
}

void Transfers::on(DownloadManagerListener::Complete, Download* dl) throw()
{
	StringMap params;

	getParams_client(params, dl);
	params["Status"] = _("Download finished");
	params["Sort Order"] = "w" + params["User"];
	params["Speed"] = "-1";

	int64_t pos = QueueManager::getInstance()->getPos(dl->getPath()) + dl->getPos();

	typedef Func2<Transfers, StringMap, bool> F2;
	F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, TRUE);
	WulforManager::get()->dispatchGuiFunc(f2);

	typedef Func2<Transfers, const string, int64_t> F2b;
	F2b* f2b = new F2b(this, &Transfers::updateFilePosition_gui, params["CID"], pos);
	WulforManager::get()->dispatchGuiFunc(f2b);
}

void Transfers::on(DownloadManagerListener::Failed, Download* dl, const std::string& reason) throw()
{
	StringMap params;
	getParams_client(params, dl);
	params["Status"] = reason;
	params["Sort Order"] = "w" + params["User"];
	params["Failed"] = "1";
	params["Speed"] = "-1";
	params["Time Left"] = "-1";

	int64_t pos = QueueManager::getInstance()->getPos(dl->getPath()) + dl->getPos();

	typedef Func2<Transfers, StringMap, bool> F2;
	F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, TRUE);
	WulforManager::get()->dispatchGuiFunc(f2);

	typedef Func2<Transfers, const string, int64_t> F2b;
	F2b* f2b = new F2b(this, &Transfers::updateFilePosition_gui, params["CID"], pos);
	WulforManager::get()->dispatchGuiFunc(f2b);
}

void Transfers::on(ConnectionManagerListener::Added, ConnectionQueueItem* cqi) throw()
{
	StringMap params;
	getParams_client(params, cqi);
	params["Status"] = _("Connecting");

	typedef Func2<Transfers, StringMap, bool> F2;
	F2* f2 = new F2(this, &Transfers::addConnection_gui, params, cqi->getDownload());
	WulforManager::get()->dispatchGuiFunc(f2);
}

void Transfers::on(ConnectionManagerListener::Connected, ConnectionQueueItem* cqi) throw()
{
	StringMap params;
	getParams_client(params, cqi);
	params["Status"] = _("Connected");

	typedef Func2<Transfers, StringMap, bool> F2;
	F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, cqi->getDownload());
	WulforManager::get()->dispatchGuiFunc(f2);
}

void Transfers::on(ConnectionManagerListener::Removed, ConnectionQueueItem* cqi) throw()
{
	typedef Func2<Transfers, const std::string, bool> F2;
	F2* f2 = new F2(this, &Transfers::removeConnection_gui, cqi->getUser()->getCID().toBase32(), cqi->getDownload());
	WulforManager::get()->dispatchGuiFunc(f2);
}

void Transfers::on(ConnectionManagerListener::Failed, ConnectionQueueItem* cqi, const std::string& reason) throw()
{
	StringMap params;
	getParams_client(params, cqi);
	params["Status"] = reason;
	params["Failed"] = "1";
	params["Sort Order"] = "w" + params["User"];
	params["Speed"] = "-1";
	params["Time Left"] = "-1";

	typedef Func2<Transfers, StringMap, bool> F2;
	F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, cqi->getDownload());
	WulforManager::get()->dispatchGuiFunc(f2);
}

void Transfers::on(ConnectionManagerListener::StatusChanged, ConnectionQueueItem* cqi) throw()
{
	StringMap params;
	getParams_client(params, cqi);

	if (cqi->getState() == ConnectionQueueItem::CONNECTING)
		params["Status"] = _("Connecting");
	else
		params["Status"] = _("Waiting to retry");
	params["Sort Order"] = "w" + params["User"];

	typedef Func2<Transfers, StringMap, bool> F2;
	F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, cqi->getDownload());
	WulforManager::get()->dispatchGuiFunc(f2);
}

void Transfers::on(QueueManagerListener::Finished, QueueItem* qi, const std::string& dir, int64_t size) throw()
{
	string target = qi->getTarget();

	typedef Func2<Transfers, const string, const string> F2;
	F2* f2 = new F2(this, &Transfers::finishParent_gui, target, _("Download finished"));
	WulforManager::get()->dispatchGuiFunc(f2);
}

void Transfers::on(QueueManagerListener::Removed, QueueItem* qi) throw()
{
	string target = qi->getTarget();

	typedef Func2<Transfers, const string, const string> F2;
	F2* f2 = new F2(this, &Transfers::finishParent_gui, target, _("Download removed"));
	WulforManager::get()->dispatchGuiFunc(f2);
}

void Transfers::on(UploadManagerListener::Starting, Upload* ul) throw()
{
	StringMap params;

	getParams_client(params, ul);
	params["Status"] = _("Upload starting");
	params["Sort Order"] = "u" + params["User"];
	params["Failed"] = "0";

	typedef Func2<Transfers, StringMap, bool> F2;
	F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, FALSE);
	WulforManager::get()->dispatchGuiFunc(f2);
}

void Transfers::on(UploadManagerListener::Tick, const UploadList& uls) throw()
{
	for (UploadList::const_iterator it = uls.begin(); it != uls.end(); ++it)
	{
		Upload* ul = *it;
		StringMap params;
		ostringstream stream;

		getParams_client(params, ul);

		if (ul->getUserConnection().isSecure())
		{
			if (ul->getUserConnection().isTrusted())
				stream << _("[S]");
			else
				stream << _("[U]");
		}
		if (ul->isSet(Upload::FLAG_ZUPLOAD))
			stream << _("[Z]");

		string bytes = Util::formatBytes(ul->getPos());
		double progress = ul->getSize() <= 0 ? 0.0 : static_cast<double>(ul->getPos() * 100.0) / ul->getSize();

		// TRANSLATORS: Shows the bytes uploaded and the percentage completed.
		string status = F_("Uploaded %1% (%2$.1f%%)", % bytes % progress);
		params["Status"] = stream.tellp() > 0 ? stream.str() + " " + status : status;

		typedef Func2<Transfers, StringMap, bool> F2;
		F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, FALSE);
		WulforManager::get()->dispatchGuiFunc(f2);
	}	
}

void Transfers::on(UploadManagerListener::Complete, Upload* ul) throw()
{
	StringMap params;

	getParams_client(params, ul);
	params["Status"] = _("Upload finished");
	params["Sort Order"] = "w" + params["User"];
	params["Speed"] = "-1";

	typedef Func2<Transfers, StringMap, bool> F2;
	F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, FALSE);
	WulforManager::get()->dispatchGuiFunc(f2);
}

void Transfers::on(UploadManagerListener::Failed, Upload* ul, const std::string& reason) throw()
{
	StringMap params;
	getParams_client(params, ul);
	params["Status"] = reason;
	params["Sort Order"] = "w" + params["User"];
	params["Failed"] = "1";
	params["Speed"] = "-1";
	params["Time Left"] = "-1";

	typedef Func2<Transfers, StringMap, bool> F2;
	F2* f2 = new F2(this, &Transfers::updateTransfer_gui, params, FALSE);
	WulforManager::get()->dispatchGuiFunc(f2);
}

