#include "FileZilla.h"
#include "queue.h"
#include "Mainfrm.h"
#include "Options.h"
#include "StatusView.h"
#include "statuslinectrl.h"
#include "../tinyxml/tinyxml.h"
#include "xmlfunctions.h"
#include "filezillaapp.h"
#include "ipcmutex.h"
#include "state.h"
#include "asyncrequestqueue.h"
#include "defaultfileexistsdlg.h"
#include "filter.h"
#include <wx/dnd.h>
#include "dndobjects.h"
#include "loginmanager.h"
#include "aui_notebook_ex.h"
#include "queueview_failed.h"
#include "queueview_successful.h"
#include "commandqueue.h"
#include <wx/utils.h>
#include <wx/progdlg.h>
#include <wx/sound.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

class CQueueViewDropTarget : public wxDropTarget
{
public:
	CQueueViewDropTarget(CQueueView* pQueueView)
		: m_pQueueView(pQueueView), m_pFileDataObject(new wxFileDataObject()),
		m_pRemoteDataObject(new CRemoteDataObject())
	{
		m_pDataObject = new wxDataObjectComposite;
		m_pDataObject->Add(m_pRemoteDataObject, true);
		m_pDataObject->Add(m_pFileDataObject, false);
		SetDataObject(m_pDataObject);
	}

	virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def)
	{
		if (def == wxDragError ||
			def == wxDragNone ||
			def == wxDragCancel)
			return def;

		if (!GetData())
			return wxDragError;

		if (m_pDataObject->GetReceivedFormat() == m_pFileDataObject->GetFormat())
		{
			CState* const pState = m_pQueueView->m_pMainFrame->GetState();
			const CServer* const pServer = pState->GetServer();
			if (!pServer)
				return wxDragNone;

			const CServerPath& path = pState->GetRemotePath();
			if (path.IsEmpty())
				return wxDragNone;

			pState->UploadDroppedFiles(m_pFileDataObject, path, true);
		}
		else
		{
			if (m_pRemoteDataObject->GetProcessId() != (int)wxGetProcessId())
			{
				wxMessageBox(_("Drag&drop between different instances of FileZilla has not been implemented yet."));
				return wxDragNone;
			}

			CState* const pState = m_pQueueView->m_pMainFrame->GetState();
			const CServer* const pServer = pState->GetServer();
			if (!pServer)
				return wxDragNone;

			if (*pServer != m_pRemoteDataObject->GetServer())
			{
				wxMessageBox(_("Drag&drop between different servers has not been implemented yet."));
				return wxDragNone;
			}

			const wxString& target = pState->GetLocalDir();
#ifdef __WXMSW__
			if (target == _T("\\"))
			{
				wxBell();
				return wxDragNone;
			}
#endif

			if (!pState->DownloadDroppedFiles(m_pRemoteDataObject, target, true))
				return wxDragNone;
		}

		return def;
	}

	virtual bool OnDrop(wxCoord x, wxCoord y)
	{
		return true;
	}

	virtual wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult def)
	{
		if (def == wxDragError ||
			def == wxDragNone ||
			def == wxDragCancel)
		{
			return def;
		}

		def = wxDragCopy;

		return def;
	}

	virtual void OnLeave()
	{
	}

	virtual wxDragResult OnEnter(wxCoord x, wxCoord y, wxDragResult def)
	{
		return OnDragOver(x, y, def);
	}

protected:
	CQueueView *m_pQueueView;
	wxFileDataObject* m_pFileDataObject;
	CRemoteDataObject* m_pRemoteDataObject;
	wxDataObjectComposite* m_pDataObject;
};

DECLARE_EVENT_TYPE(fzEVT_FOLDERTHREAD_COMPLETE, -1)
DEFINE_EVENT_TYPE(fzEVT_FOLDERTHREAD_COMPLETE)

DECLARE_EVENT_TYPE(fzEVT_FOLDERTHREAD_FILES, -1)
DEFINE_EVENT_TYPE(fzEVT_FOLDERTHREAD_FILES)

DECLARE_EVENT_TYPE(fzEVT_UPDATE_STATUSLINES, -1)
DEFINE_EVENT_TYPE(fzEVT_UPDATE_STATUSLINES)

DECLARE_EVENT_TYPE(fzEVT_ASKFORPASSWORD, -1)
DEFINE_EVENT_TYPE(fzEVT_ASKFORPASSWORD)

BEGIN_EVENT_TABLE(CQueueView, CQueueViewBase)
EVT_FZ_NOTIFICATION(wxID_ANY, CQueueView::OnEngineEvent)
EVT_COMMAND(wxID_ANY, fzEVT_FOLDERTHREAD_COMPLETE, CQueueView::OnFolderThreadComplete)
EVT_COMMAND(wxID_ANY, fzEVT_FOLDERTHREAD_FILES, CQueueView::OnFolderThreadFiles)
EVT_SCROLLWIN(CQueueView::OnScrollEvent)
EVT_COMMAND(wxID_ANY, fzEVT_UPDATE_STATUSLINES, CQueueView::OnUpdateStatusLines)
EVT_MOUSEWHEEL(CQueueView::OnMouseWheel)

EVT_CONTEXT_MENU(CQueueView::OnContextMenu)
EVT_MENU(XRCID("ID_PROCESSQUEUE"), CQueueView::OnProcessQueue)
EVT_MENU(XRCID("ID_REMOVEALL"), CQueueView::OnStopAndClear)
EVT_MENU(XRCID("ID_REMOVE"), CQueueView::OnRemoveSelected)
EVT_MENU(XRCID("ID_DEFAULT_FILEEXISTSACTION"), CQueueView::OnSetDefaultFileExistsAction)
EVT_MENU(XRCID("ID_ACTIONAFTER_DISABLE"), CQueueView::OnActionAfter)
EVT_MENU(XRCID("ID_ACTIONAFTER_CLOSE"), CQueueView::OnActionAfter)
EVT_MENU(XRCID("ID_ACTIONAFTER_DISCONNECT"), CQueueView::OnActionAfter)
EVT_MENU(XRCID("ID_ACTIONAFTER_RUNCOMMAND"), CQueueView::OnActionAfter)
EVT_MENU(XRCID("ID_ACTIONAFTER_SHOWMESSAGE"), CQueueView::OnActionAfter)
EVT_MENU(XRCID("ID_ACTIONAFTER_PLAYSOUND"), CQueueView::OnActionAfter)
EVT_MENU(XRCID("ID_ACTIONAFTER_REBOOT"), CQueueView::OnActionAfter)
EVT_MENU(XRCID("ID_ACTIONAFTER_SHUTDOWN"), CQueueView::OnActionAfter)

EVT_COMMAND(wxID_ANY, fzEVT_ASKFORPASSWORD, CQueueView::OnAskPassword)

EVT_LIST_ITEM_FOCUSED(wxID_ANY, CQueueView::OnFocusItemChanged)

EVT_TIMER(wxID_ANY, CQueueView::OnTimer)

EVT_MENU(XRCID("ID_PRIORITY_HIGHEST"), CQueueView::OnSetPriority)
EVT_MENU(XRCID("ID_PRIORITY_HIGH"), CQueueView::OnSetPriority)
EVT_MENU(XRCID("ID_PRIORITY_NORMAL"), CQueueView::OnSetPriority)
EVT_MENU(XRCID("ID_PRIORITY_LOW"), CQueueView::OnSetPriority)
EVT_MENU(XRCID("ID_PRIORITY_LOWEST"), CQueueView::OnSetPriority)

EVT_COMMAND(wxID_ANY, fzEVT_GRANTEXCLUSIVEENGINEACCESS, CQueueView::OnExclusiveEngineRequestGranted)

END_EVENT_TABLE()

class CFolderProcessingThread : public wxThread
{
public:
	CFolderProcessingThread(CQueueView* pOwner, CFolderScanItem* pFolderItem)
		: wxThread(wxTHREAD_JOINABLE), m_condition(m_sync) {
		m_pOwner = pOwner;
		m_pFolderItem = pFolderItem;

		m_didSendEvent = false;
		m_threadWaiting = false;
	}
	~CFolderProcessingThread() { }

	void GetFiles(std::list<t_newEntry> &entryList)
	{
		wxASSERT(entryList.empty());
		wxMutexLocker locker(m_sync);
		entryList.swap(m_entryList);

		m_didSendEvent = false;

		if (m_threadWaiting)
		{
			m_threadWaiting = false;
			m_condition.Signal();
		}
	}

protected:

	void AddEntry(const t_newEntry& entry)
	{
		m_sync.Lock();
		m_entryList.push_back(entry);

		// Wait if there are more than 100 items to queue,
		// don't send notification if there are less than 10.
		// This reduces overhead
		bool send;

		if (m_didSendEvent)
		{
			send = false;
			if (m_entryList.size() >= 100)
			{
				m_threadWaiting = true;
				m_condition.Wait();
			}
		}
		else if (m_entryList.size() < 10)
			send = false;
		else
			send = true;

		m_sync.Unlock();

		if (send)
		{
			// We send the notification after leaving the critical section, else we
			// could get into a deadlock. wxWidgets event system does internal
			// locking.
			wxCommandEvent evt(fzEVT_FOLDERTHREAD_FILES, wxID_ANY);
			wxPostEvent(m_pOwner, evt);
		}
	}

	ExitCode Entry()
	{
		wxMutexGuiEnter();
		wxASSERT(m_pFolderItem->GetTopLevelItem() && m_pFolderItem->GetTopLevelItem()->GetType() == QueueItemType_Server);
		wxMutexGuiLeave();

		wxASSERT(!m_pFolderItem->Download());

		wxString currentLocalPath;
		CServerPath currentRemotePath;

		wxDir* pDir = 0;

		while (!TestDestroy())
		{
			if (m_pFolderItem->m_remove)
			{
				wxCommandEvent evt(fzEVT_FOLDERTHREAD_COMPLETE, wxID_ANY);
				wxPostEvent(m_pOwner, evt);

				delete pDir;

				return 0;
			}
			bool found;
			wxString file;
			if (!pDir)
			{
				if (!m_pFolderItem->m_dirsToCheck.empty())
				{
					const CFolderScanItem::t_dirPair& pair = m_pFolderItem->m_dirsToCheck.front();

					wxLogNull nullLog;

					pDir = new wxDir(pair.localPath);

					if (pDir->IsOpened())
					{
						currentLocalPath = pair.localPath;
						currentRemotePath = pair.remotePath;

						found = pDir->GetFirst(&file);

						if (!found)
						{
							// Empty directory

							t_newEntry entry;
							entry.remotePath.SetSafePath(pair.remotePath.GetSafePath().c_str());

							AddEntry(entry);
						}
					}
					else
						found = false;
					m_pFolderItem->m_dirsToCheck.pop_front();
				}
				else
				{
					wxCommandEvent evt(fzEVT_FOLDERTHREAD_COMPLETE, wxID_ANY);
					wxPostEvent(m_pOwner, evt);

					if (pDir)
						delete pDir;
					return 0;
				}
			}
			else
				found = pDir->GetNext(&file);

			while (found && !TestDestroy() && !m_pFolderItem->m_remove)
			{
				const wxString& fullName = currentLocalPath + wxFileName::GetPathSeparator() + file;

				if (wxDir::Exists(fullName))
				{
#ifdef S_ISLNK
					wxStructStat buf;
					const int result = wxLstat(fullName, &buf);
					if (!result && S_ISLNK(buf.st_mode))
					{
						found = pDir->GetNext(&file);
						continue;
					}
#endif

					if (!m_filters.FilenameFiltered(file, true, -1, true))
					{
						CFolderScanItem::t_dirPair pair;
						pair.localPath = fullName;
						pair.remotePath = currentRemotePath;
						pair.remotePath.AddSegment(file);
						m_pFolderItem->m_dirsToCheck.push_back(pair);
					}
				}
				else
				{
					wxStructStat buf;
					int result;
					result = wxStat(fullName, &buf);

					if (!m_filters.FilenameFiltered(file, false, result ? -1 : buf.st_size, true))
					{
						t_newEntry entry;
						entry.localFile = fullName.c_str();
						entry.remoteFile = file.c_str();
						entry.remotePath.SetSafePath(currentRemotePath.GetSafePath().c_str());
						entry.size = result ? -1 : buf.st_size;

						AddEntry(entry);
					}
				}

				found = pDir->GetNext(&file);
			}

			bool send;
			m_sync.Lock();
			if (!m_didSendEvent && m_entryList.size())
				send = true;
			else
				send = false;
			m_sync.Unlock();

			if (send)
			{
				// We send the notification after leaving the critical section, else we
				// could get into a deadlock. wxWidgets event system does internal
				// locking.
				wxCommandEvent evt(fzEVT_FOLDERTHREAD_FILES, wxID_ANY);
				wxPostEvent(m_pOwner, evt);
			}

			delete pDir;
			pDir = 0;
		}

		return 0;
	}

	// Access has to be guarded by m_sync
	std::list<t_newEntry> m_entryList;

	CQueueView* m_pOwner;
	CFolderScanItem* m_pFolderItem;

	CFilterDialog m_filters;

	wxMutex m_sync;
	wxCondition m_condition;
	bool m_threadWaiting;
	bool m_didSendEvent;
};

CQueueView::CQueueView(CQueue* parent, int index, CMainFrame* pMainFrame, CAsyncRequestQueue *pAsyncRequestQueue)
	: CQueueViewBase(parent, index, _("Queued files")),
	  m_pMainFrame(pMainFrame),
	  m_pAsyncRequestQueue(pAsyncRequestQueue)
{
	if (m_pAsyncRequestQueue)
		m_pAsyncRequestQueue->SetQueue(this);

	m_activeCount = 0;
	m_activeCountDown = 0;
	m_activeCountUp = 0;
	m_activeMode = 0;
	m_quit = false;
	m_waitStatusLineUpdate = false;
	m_lastTopItem = -1;
	m_pFolderProcessingThread = 0;

	m_totalQueueSize = 0;
	m_filesWithUnknownSize = 0;
	
	m_actionAfterState = ActionAfterState_Disabled;
#ifdef __WXMSW__
	m_actionAfterWarnDialog = 0;
	m_actionAfterTimer = 0;
	m_actionAfterTimerId = -1;
#endif

	CreateColumns(_("Status"));

	//Initialize the engine data
	t_EngineData *pData = new t_EngineData;
	pData->active = false;
	pData->pItem = 0;
	pData->pStatusLineCtrl = 0;
	pData->state = t_EngineData::none;
	pData->pEngine = 0; // TODO: Primary transfer engine data
	pData->m_idleDisconnectTimer = 0;
	m_engineData.push_back(pData);

	int engineCount = COptions::Get()->GetOptionVal(OPTION_NUMTRANSFERS);
	for (int i = 0; i < engineCount; i++)
	{
		t_EngineData *pData = new t_EngineData;
		pData->active = false;
		pData->pItem = 0;
		pData->pStatusLineCtrl = 0;
		pData->state = t_EngineData::none;
		pData->m_idleDisconnectTimer = 0;

		pData->pEngine = new CFileZillaEngine();
		pData->pEngine->Init(this, COptions::Get());

		m_engineData.push_back(pData);
	}

	SettingsChanged();

	SetDropTarget(new CQueueViewDropTarget(this));

#if (!defined(__WIN32__) && !defined(__WXMAC__)) || defined(__WXUNIVERSAL__)

	// The generic list control a scrolled child window. In order to receive
	// scroll events, we have to connect the event handler to it.
	((wxWindow*)m_mainWin)->Connect(-1, wxEVT_SCROLLWIN_TOP, wxScrollWinEventHandler(CQueueView::OnScrollEvent), 0, this);
	((wxWindow*)m_mainWin)->Connect(-1, wxEVT_SCROLLWIN_BOTTOM, wxScrollWinEventHandler(CQueueView::OnScrollEvent), 0, this);
	((wxWindow*)m_mainWin)->Connect(-1, wxEVT_SCROLLWIN_LINEUP, wxScrollWinEventHandler(CQueueView::OnScrollEvent), 0, this);
	((wxWindow*)m_mainWin)->Connect(-1, wxEVT_SCROLLWIN_LINEDOWN, wxScrollWinEventHandler(CQueueView::OnScrollEvent), 0, this);
	((wxWindow*)m_mainWin)->Connect(-1, wxEVT_SCROLLWIN_PAGEUP, wxScrollWinEventHandler(CQueueView::OnScrollEvent), 0, this);
	((wxWindow*)m_mainWin)->Connect(-1, wxEVT_SCROLLWIN_PAGEDOWN, wxScrollWinEventHandler(CQueueView::OnScrollEvent), 0, this);
#endif
}

CQueueView::~CQueueView()
{
	if (m_pFolderProcessingThread)
	{
		m_pFolderProcessingThread->Delete();
		m_pFolderProcessingThread->Wait();
		delete m_pFolderProcessingThread;
	}

	DeleteEngines();
}

bool CQueueView::QueueFile(const bool queueOnly, const bool download, const wxString& localFile,
						   const wxString& remoteFile, const CServerPath& remotePath,
						   const CServer& server, const wxLongLong size)
{
	CServerItem* pServerItem = CreateServerItem(server);

	CFileItem* fileItem;
	if (localFile == _T("") || remotePath.IsEmpty())
	{
		if (download)
			fileItem = new CFolderItem(pServerItem, queueOnly, localFile);
		else
			fileItem = new CFolderItem(pServerItem, queueOnly, remotePath, remoteFile);
	}
	else
	{
		fileItem = new CFileItem(pServerItem, queueOnly, download, localFile, remoteFile, remotePath, size);
		fileItem->m_transferSettings.binary = ShouldUseBinaryMode(download ? remoteFile : wxFileName(localFile).GetFullName());
	}

	InsertItem(pServerItem, fileItem);

	CommitChanges();

	if (!m_activeMode && !queueOnly)
		m_activeMode = 1;

	m_waitStatusLineUpdate = true;
	AdvanceQueue();
	m_waitStatusLineUpdate = false;
	UpdateStatusLinePositions();

	Refresh(false);

	return true;
}

bool CQueueView::QueueFiles(const bool queueOnly, const wxString& localPath, const CRemoteDataObject& dataObject)
{
	CServerItem* pServerItem = CreateServerItem(dataObject.GetServer());

	const std::list<CRemoteDataObject::t_fileInfo>& files = dataObject.GetFiles();

	for (std::list<CRemoteDataObject::t_fileInfo>::const_iterator iter = files.begin(); iter != files.end(); iter++)
	{
		if (iter->dir)
			continue;

		CFileItem* fileItem;
		fileItem = new CFileItem(pServerItem, queueOnly, true, localPath + iter->name, iter->name, dataObject.GetServerPath(), iter->size);
		fileItem->m_transferSettings.binary = ShouldUseBinaryMode(iter->name);

		InsertItem(pServerItem, fileItem);
	}

	CommitChanges();

	if (!m_activeMode && !queueOnly)
		m_activeMode = 1;

	m_waitStatusLineUpdate = true;
	AdvanceQueue();
	m_waitStatusLineUpdate = false;
	UpdateStatusLinePositions();

	Refresh(false);

	return true;
}

void CQueueView::OnEngineEvent(wxEvent &event)
{
	std::vector<t_EngineData*>::iterator iter;
	for (iter = m_engineData.begin(); iter != m_engineData.end(); iter++)
	{
		if ((wxObject *)(*iter)->pEngine == event.GetEventObject())
			break;
	}
	if (iter == m_engineData.end())
		return;

	t_EngineData* const pEngineData = *iter;

	CNotification *pNotification = pEngineData->pEngine->GetNextNotification();
	while (pNotification)
	{
		ProcessNotification(pEngineData, pNotification);

		if (m_engineData.empty() || !pEngineData->pEngine)
			break;

		pNotification = pEngineData->pEngine->GetNextNotification();
	}
}

void CQueueView::ProcessNotification(CNotification* pNotification)
{
	if (m_engineData.empty())
	{
		delete pNotification;
		return;
	}

	t_EngineData* pEngineData = m_engineData[0];
	if (!pEngineData->active)
	{
		delete pNotification;
		return;
	}

	ProcessNotification(pEngineData, pNotification);
}

void CQueueView::ProcessNotification(t_EngineData* pEngineData, CNotification* pNotification)
{
	switch (pNotification->GetID())
	{
	case nId_logmsg:
		m_pMainFrame->GetStatusView()->AddToLog(reinterpret_cast<CLogmsgNotification *>(pNotification));
		delete pNotification;
		break;
	case nId_operation:
		ProcessReply(*pEngineData, reinterpret_cast<COperationNotification*>(pNotification));
		delete pNotification;
		break;
	case nId_asyncrequest:
		if (!pEngineData->pItem)
		{
			delete pNotification;
			break;
		}
		switch (reinterpret_cast<CAsyncRequestNotification *>(pNotification)->GetRequestID())
		{
		case reqId_fileexists:
			{
				// Set default file exists action
				CFileExistsNotification *pFileExistsNotification = reinterpret_cast<CFileExistsNotification *>(pNotification);
				pFileExistsNotification->overwriteAction = (enum CFileExistsNotification::OverwriteAction)pEngineData->pItem->m_defaultFileExistsAction;
			}
			break;
		default:
			break;
		}

		m_pAsyncRequestQueue->AddRequest(pEngineData->pEngine, reinterpret_cast<CAsyncRequestNotification *>(pNotification));
		break;
	case nId_active:
		{
			CActiveNotification *pActiveNotification = reinterpret_cast<CActiveNotification *>(pNotification);
			if (pActiveNotification->IsRecv())
				m_pMainFrame->UpdateRecvLed();
			else
				m_pMainFrame->UpdateSendLed();
			delete pNotification;
		}
		break;
	case nId_transferstatus:
		if (pEngineData->pItem && pEngineData->pStatusLineCtrl)
		{
			CTransferStatusNotification *pTransferStatusNotification = reinterpret_cast<CTransferStatusNotification *>(pNotification);
			const CTransferStatus *pStatus = pTransferStatusNotification->GetStatus();

			if (pEngineData->active)
				pEngineData->pStatusLineCtrl->SetTransferStatus(pStatus);
		}
		delete pNotification;
		break;
	default:
		delete pNotification;
		break;
	}
}

bool CQueueView::TryStartNextTransfer()
{
	if (m_quit || !m_activeMode)
		return false;

	// Check transfer limit
	if (m_activeCount >= COptions::Get()->GetOptionVal(OPTION_NUMTRANSFERS))
		return false;

	// Check limits for concurrent up/downloads
	const int maxDownloads = COptions::Get()->GetOptionVal(OPTION_CONCURRENTDOWNLOADLIMIT);
	const int maxUploads = COptions::Get()->GetOptionVal(OPTION_CONCURRENTUPLOADLIMIT);
	enum TransferDirection wantedDirection;
	if (maxDownloads && m_activeCountDown >= maxDownloads)
	{
		if (maxUploads && m_activeCountUp >= maxUploads)
			return false;
		else
			wantedDirection = upload;
	}
	else if (maxUploads && m_activeCountUp >= maxUploads)
		wantedDirection = download;
	else
		wantedDirection = both;

	struct t_bestMatch
	{
		CFileItem* fileItem;
		CServerItem* serverItem;
		t_EngineData* pEngineData;
		bool browsingConnection;
	} bestMatch = {0};

	// Find inactive file. Check all servers for
	// the file with the highest priority
	for (std::vector<CServerItem*>::iterator iter = m_serverList.begin(); iter != m_serverList.end(); iter++)
	{
		t_EngineData* pEngineData = 0;
		CServerItem* currentServerItem = *iter;
		int maxCount = currentServerItem->GetServer().MaximumMultipleConnections();
		int activeCount = currentServerItem->m_activeCount;
		const CServer* pBrowsingServer = m_pMainFrame->GetState()->GetServer();
		bool browsingOnSame = false;
		if (pBrowsingServer && currentServerItem->GetServer() == *pBrowsingServer)
		{
			browsingOnSame = true;
			activeCount++;
		}
		if (maxCount && activeCount >= maxCount)
		{
			// If we got an idle engine connected to this very server, start the
			// transfer anyhow. Let's not get this connection go to waste.
			pEngineData = GetIdleEngine(&(*iter)->GetServer());
			if (pEngineData)
			{
				if (!pEngineData->pEngine->IsConnected() || pEngineData->lastServer != (*iter)->GetServer())
				{
					// If the browsing connection is connected to this server and idle, use it
					if (browsingOnSame && activeCount == 1)
					{
						pEngineData = m_engineData[0];
						if (pEngineData->active)
							continue;
					}
					else
						continue;
				}
			}
			else
			{
				// If the browsing connection is connected to this server and idle, use it
				if (browsingOnSame && activeCount == 1)
				{
					pEngineData = m_engineData[0];
					if (pEngineData->active)
						continue;
				}
				else
					continue;
			}
		}

		CFileItem* newFileItem = currentServerItem->GetIdleChild(m_activeMode == 1, wantedDirection);

		while (newFileItem && newFileItem->Download() && newFileItem->GetType() == QueueItemType_Folder)
		{
			wxFileName fn(newFileItem->GetLocalFile(), _T(""));
			wxFileName::Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL);
			CState* const pState = m_pMainFrame->GetState();
			pState->RefreshLocalFile(fn.GetFullPath());
			if (RemoveItem(newFileItem, true))
			{
				// Server got deleted. Unfortunately we have to start over now
				if (m_serverList.empty())
					return false;

				return true;
			}
			newFileItem = currentServerItem->GetIdleChild(m_activeMode == 1, wantedDirection);
		}

		if (!newFileItem)
			continue;

		if (!bestMatch.fileItem || newFileItem->GetPriority() > bestMatch.fileItem->GetPriority())
		{
			bestMatch.serverItem = currentServerItem;
			bestMatch.fileItem = newFileItem;
			bestMatch.pEngineData = pEngineData;
			if (newFileItem->GetPriority() == priority_highest)
				break;
		}
	}
	if (!bestMatch.fileItem)
		return false;

	// Find idle engine
	t_EngineData* pEngineData;
	if (bestMatch.pEngineData)
		pEngineData = bestMatch.pEngineData;
	else
	{
		pEngineData = GetIdleEngine(&bestMatch.serverItem->GetServer());
		if (!pEngineData)
			return false;
	}

	// Now we have both inactive engine and file.
	// Assign the file to the engine.

	bestMatch.fileItem->SetActive(true);

	pEngineData->pItem = bestMatch.fileItem;
	bestMatch.fileItem->m_pEngineData = pEngineData;
	pEngineData->active = true;
	delete pEngineData->m_idleDisconnectTimer;
	pEngineData->m_idleDisconnectTimer = 0;
	bestMatch.serverItem->m_activeCount++;
	m_activeCount++;
	if (bestMatch.fileItem->Download())
		m_activeCountDown++;
	else
		m_activeCountUp++;

	const CServer oldServer = pEngineData->lastServer;
	pEngineData->lastServer = bestMatch.serverItem->GetServer();

	if (!pEngineData->pEngine)
	{
		pEngineData->state = t_EngineData::waitprimary;
	}
	else if (!pEngineData->pEngine->IsConnected())
	{
		if (pEngineData->lastServer.GetLogonType() == ASK)
		{
			if (CLoginManager::Get().GetPassword(pEngineData->lastServer, true))
				pEngineData->state = t_EngineData::connect;
			else
				pEngineData->state = t_EngineData::askpassword;
		}
		else
			pEngineData->state = t_EngineData::connect;
	}
	else if (oldServer != bestMatch.serverItem->GetServer())
		pEngineData->state = t_EngineData::disconnect;
	else if (pEngineData->pItem->GetType() == QueueItemType_File)
		pEngineData->state = t_EngineData::transfer;
	else
		pEngineData->state = t_EngineData::mkdir;

	if (bestMatch.fileItem->GetType() == QueueItemType_File)
	{
		// Create status line

		m_itemCount++;
		SetItemCount(m_itemCount);
		int lineIndex = GetItemIndex(bestMatch.fileItem);
		UpdateSelections_ItemAdded(lineIndex + 1);

		wxRect rect;
		GetItemRect(lineIndex + 1, rect);
		m_allowBackgroundErase = false;
		if (!pEngineData->pStatusLineCtrl)
			pEngineData->pStatusLineCtrl = new CStatusLineCtrl(this, pEngineData, rect);
		else
		{
			pEngineData->pStatusLineCtrl->SetTransferStatus(0);
			pEngineData->pStatusLineCtrl->SetSize(rect);
			pEngineData->pStatusLineCtrl->Show();
		}
		m_allowBackgroundErase = true;
		m_statusLineList.push_back(pEngineData->pStatusLineCtrl);
	}

	SendNextCommand(*pEngineData);

	return true;
}

void CQueueView::ProcessReply(t_EngineData& engineData, COperationNotification* pNotification)
{
	if (pNotification->nReplyCode & FZ_REPLY_DISCONNECTED &&
		pNotification->commandId == cmd_none)
	{
		// Queue is not interested in disconnect notifications
		return;
	}
	wxASSERT(pNotification->commandId != cmd_none);

	// Cancel pending requests
	m_pAsyncRequestQueue->ClearPending(engineData.pEngine);

	// Process reply from the engine
	int replyCode = pNotification->nReplyCode;

	if ((replyCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
	{
		enum ResetReason reason;
		if (engineData.pItem && engineData.pItem->m_remove)
			reason = remove;
		else
			reason = reset;
		engineData.pItem->m_statusMessage = _("Interrupted by user");
		ResetEngine(engineData, reason);
		return;
	}

	// Cycle through queue states
	switch (engineData.state)
	{
	case t_EngineData::disconnect:
		if (engineData.active)
		{
			engineData.state = t_EngineData::connect;
			engineData.pStatusLineCtrl->SetTransferStatus(0);
		}
		else
			engineData.state = t_EngineData::none;
		break;
	case t_EngineData::connect:
		if (replyCode == FZ_REPLY_OK)
		{
			if (engineData.pItem->GetType() == QueueItemType_File)
				engineData.state = t_EngineData::transfer;
			else
				engineData.state = t_EngineData::mkdir;
			if (engineData.active && engineData.pStatusLineCtrl)
				engineData.pStatusLineCtrl->SetTransferStatus(0);
		}
		else
		{
			if (replyCode & FZ_REPLY_PASSWORDFAILED)
				CLoginManager::Get().CachedPasswordFailed(engineData.lastServer);

			if ((replyCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
				engineData.pItem->m_statusMessage = _T("");
			else if (replyCode & FZ_REPLY_PASSWORDFAILED)
				engineData.pItem->m_statusMessage = _("Incorrect password");
			else if ((replyCode & FZ_REPLY_TIMEOUT) == FZ_REPLY_TIMEOUT)
				engineData.pItem->m_statusMessage = _("Timeout");
			else if (replyCode & FZ_REPLY_DISCONNECTED)
				engineData.pItem->m_statusMessage = _("Disconnected from server");
			else
				engineData.pItem->m_statusMessage = _("Connection attempt failed");

			if (!IncreaseErrorCount(engineData))
				return;
		}
		break;
	case t_EngineData::transfer:
		if (replyCode == FZ_REPLY_OK)
		{
			ResetEngine(engineData, success);
			return;
		}
		// Increase error count only if item didn't make any progress. This keeps
		// user interaction at a minimum if connection is unstable.

		// FIXME: Disabled since detection isn't reliable (yet)
		//else if (!engineData.pStatusLineCtrl || !engineData.pStatusLineCtrl->MadeProgress())
		{
			if ((replyCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
				engineData.pItem->m_statusMessage = _T("");
			else if ((replyCode & FZ_REPLY_TIMEOUT) == FZ_REPLY_TIMEOUT)
				engineData.pItem->m_statusMessage = _("Timeout");
			else if (replyCode & FZ_REPLY_DISCONNECTED)
				engineData.pItem->m_statusMessage = _("Disconnected from server");
			else if ((replyCode & FZ_REPLY_CRITICALERROR) == FZ_REPLY_CRITICALERROR)
			{
				engineData.pItem->m_statusMessage = _("Could not start transfer");
				ResetEngine(engineData, failure);
				return;
			}
			else
				engineData.pItem->m_statusMessage = _("Could not start transfer");
			if (!IncreaseErrorCount(engineData))
				return;

			if (replyCode & FZ_REPLY_DISCONNECTED && &engineData == m_engineData[0])
			{
				ResetEngine(engineData, retry);
				return;
			}
		}
		if (replyCode & FZ_REPLY_DISCONNECTED)
			engineData.state = t_EngineData::connect;
		break;
	case t_EngineData::mkdir:
		if (replyCode == FZ_REPLY_OK)
		{
			ResetEngine(engineData, success);
			return;
		}
		if (replyCode & FZ_REPLY_DISCONNECTED)
		{
			if (!IncreaseErrorCount(engineData))
				return;

			if (&engineData == m_engineData[0])
			{
				ResetEngine(engineData, retry);
				return;
			}

			engineData.state = t_EngineData::connect;
		}
		else
		{
			// Cannot retry
			ResetEngine(engineData, failure);
			return;
		}

		break;
	case t_EngineData::list:
		ResetEngine(engineData, remove);
		return;
	default:
		return;
	}

	if (engineData.state == t_EngineData::connect && engineData.lastServer.GetLogonType() == ASK)
	{
		if (!CLoginManager::Get().GetPassword(engineData.lastServer, true))
			engineData.state = t_EngineData::askpassword;
	}

	if (!m_activeMode)
	{
		enum ResetReason reason;
		if (engineData.pItem && engineData.pItem->m_remove)
			reason = remove;
		else
			reason = reset;
		ResetEngine(engineData, reason);
		return;
	}

	SendNextCommand(engineData);
}

void CQueueView::ResetEngine(t_EngineData& data, const enum ResetReason reason)
{
	if (!data.active)
		return;

	m_waitStatusLineUpdate = true;

	if (data.pItem)
	{
		if (data.pItem->GetType() == QueueItemType_File)
		{
			wxASSERT(data.pStatusLineCtrl);
			for (std::list<CStatusLineCtrl*>::iterator iter = m_statusLineList.begin(); iter != m_statusLineList.end(); iter++)
			{
				if (*iter == data.pStatusLineCtrl)
				{
					m_statusLineList.erase(iter);
					break;
				}
			}
			m_allowBackgroundErase = false;
			data.pStatusLineCtrl->Hide();
			m_allowBackgroundErase = true;

			UpdateSelections_ItemRemoved(GetItemIndex(data.pItem) + 1);

			m_itemCount--;
			SetItemCount(m_itemCount);

			const CFileItem* const pFileItem = (CFileItem*)data.pItem;
			if (pFileItem->Download())
				m_pMainFrame->GetState()->RefreshLocalFile(pFileItem->GetLocalFile());
		}

		wxASSERT(data.pItem->IsActive());
		wxASSERT(data.pItem->m_pEngineData == &data);
		if (data.pItem->IsActive())
			data.pItem->SetActive(false);
		if (data.pItem->Download())
		{
			wxASSERT(m_activeCountDown > 0);
			if (m_activeCountDown > 0)
				m_activeCountDown--;
		}
		else
		{
			wxASSERT(m_activeCountUp > 0);
			if (m_activeCountUp > 0)
				m_activeCountUp--;
		}

		if (reason == reset)
		{
			if (!data.pItem->Queued())
				reinterpret_cast<CServerItem*>(data.pItem->GetTopLevelItem())->QueueImmediateFile(data.pItem);
		}
		else if (reason == failure)
		{
			if (data.pItem->GetType() == QueueItemType_File || data.pItem->GetType() == QueueItemType_Folder)
			{
				const CServer server = ((CServerItem*)data.pItem->GetTopLevelItem())->GetServer();

				RemoveItem(data.pItem, false);

				CQueueViewFailed* pQueueViewFailed = m_pQueue->GetQueueView_Failed();
				CServerItem* pServerItem = pQueueViewFailed->CreateServerItem(server);
				data.pItem->SetParent(pServerItem);
				pQueueViewFailed->InsertItem(pServerItem, data.pItem);
				pQueueViewFailed->CommitChanges();
			}
		}
		else if (reason == success)
		{
			if (data.pItem->GetType() == QueueItemType_File || data.pItem->GetType() == QueueItemType_Folder)
			{
				CQueueViewSuccessful* pQueueViewSuccessful = m_pQueue->GetQueueView_Successful();
				if (pQueueViewSuccessful->AutoClear())
					RemoveItem(data.pItem, true);
				else
				{
					const CServer server = ((CServerItem*)data.pItem->GetTopLevelItem())->GetServer();

					RemoveItem(data.pItem, false);

					CServerItem* pServerItem = pQueueViewSuccessful->CreateServerItem(server);
					data.pItem->SetParent(pServerItem);
					pQueueViewSuccessful->InsertItem(pServerItem, data.pItem);
					pQueueViewSuccessful->CommitChanges();
				}
			}
			else
				RemoveItem(data.pItem, true);
		}
		else if (reason == retry)
		{
		}
		else
			RemoveItem(data.pItem, true);
		data.pItem = 0;

		wxASSERT(m_activeCount > 0);
		if (m_activeCount > 0)
			m_activeCount--;
	}
	data.active = false;

	if (data.state == t_EngineData::waitprimary)
	{
		CCommandQueue* pCommandQueue = m_pMainFrame->GetState()->m_pCommandQueue;
		if (pCommandQueue)
			pCommandQueue->RequestExclusiveEngine(false);
	}

	data.state = t_EngineData::none;
	CServerItem* item = GetServerItem(data.lastServer);
	if (item)
	{
		wxASSERT(item->m_activeCount > 0);
		if (item->m_activeCount > 0)
			item->m_activeCount--;
	}

	AdvanceQueue();

	m_waitStatusLineUpdate = false;
	UpdateStatusLinePositions();
}

bool CQueueView::RemoveItem(CQueueItem* item, bool destroy, bool updateItemCount /*=true*/, bool updateSelections /*=true*/)
{
	// RemoveItem assumes that the item has already been removed from all engines

	if (item->GetType() == QueueItemType_File)
	{
		// Update size information
		const CFileItem* const pFileItem = (const CFileItem* const)item;
		const wxLongLong& size = pFileItem->GetSize();
		if (size < 0)
		{
			m_filesWithUnknownSize--;
			wxASSERT(m_filesWithUnknownSize >= 0);
			if (!m_filesWithUnknownSize && updateItemCount)
				DisplayQueueSize();
		}
		else if (size > 0)
		{
			m_totalQueueSize -= size;
			if (updateItemCount)
				DisplayQueueSize();
			wxASSERT(m_totalQueueSize >= 0);
		}
	}

	bool didRemoveParent = CQueueViewBase::RemoveItem(item, destroy, updateItemCount, updateSelections);

	UpdateStatusLinePositions();

	return didRemoveParent;
}

void CQueueView::SendNextCommand(t_EngineData& engineData)
{
	while (true)
	{
		if (engineData.state == t_EngineData::waitprimary)
		{
			engineData.pItem->m_statusMessage = _("Waiting for browsing connection");
			CState* const pState = m_pMainFrame->GetState();
			CCommandQueue* pCommandQueue = pState->m_pCommandQueue;
			pCommandQueue->RequestExclusiveEngine(true);
			return;
		}

		if (engineData.state == t_EngineData::disconnect)
		{
			engineData.pItem->m_statusMessage = _("Disconnecting from previous server");
			RefreshItem(engineData.pItem);
			if (engineData.pEngine->Command(CDisconnectCommand()) == FZ_REPLY_WOULDBLOCK)
				return;

			engineData.state = t_EngineData::connect;
			if (engineData.active && engineData.pStatusLineCtrl)
				engineData.pStatusLineCtrl->SetTransferStatus(0);
		}

		if (engineData.state == t_EngineData::askpassword)
		{
			engineData.pItem->m_statusMessage = _("Waiting for password");
			RefreshItem(engineData.pItem);
			if (m_waitingForPassword.empty())
			{
				wxCommandEvent evt(fzEVT_ASKFORPASSWORD);
				wxPostEvent(this, evt);
			}
			m_waitingForPassword.push_back(engineData.pEngine);
			return;
		}

		if (engineData.state == t_EngineData::connect)
		{
			engineData.pItem->m_statusMessage = _("Connecting");
			RefreshItem(engineData.pItem);

			int res = engineData.pEngine->Command(CConnectCommand(engineData.lastServer));

			wxASSERT((res & FZ_REPLY_BUSY) != FZ_REPLY_BUSY);
			if (res == FZ_REPLY_WOULDBLOCK)
				return;

			if (res == FZ_REPLY_ALREADYCONNECTED)
			{
				engineData.state = t_EngineData::disconnect;
				continue;
			}

			if (res == FZ_REPLY_OK)
			{
				if (engineData.pItem->GetType() == QueueItemType_File)
				{
					engineData.state = t_EngineData::transfer;
					if (engineData.active)
						engineData.pStatusLineCtrl->SetTransferStatus(0);
				}
				else
					engineData.state = t_EngineData::mkdir;
				break;
			}

			if (!IncreaseErrorCount(engineData))
				return;
			continue;
		}

		if (engineData.state == t_EngineData::transfer)
		{
			CFileItem* fileItem = engineData.pItem;

			fileItem->m_statusMessage = _("Transferring");
			RefreshItem(engineData.pItem);

			int res = engineData.pEngine->Command(CFileTransferCommand(fileItem->GetLocalFile(), fileItem->GetRemotePath(),
												fileItem->GetRemoteFile(), fileItem->Download(), fileItem->m_transferSettings));
			wxASSERT((res & FZ_REPLY_BUSY) != FZ_REPLY_BUSY);
			if (res == FZ_REPLY_WOULDBLOCK)
				return;

			if (res == FZ_REPLY_NOTCONNECTED)
			{
				if (&engineData == m_engineData[0])
				{
					ResetEngine(engineData, retry);
					return;
				}

				engineData.state = t_EngineData::connect;
				continue;
			}

			if (res == FZ_REPLY_OK)
			{
				ResetEngine(engineData, success);
				return;
			}

			if (!IncreaseErrorCount(engineData))
				return;
			continue;
		}

		if (engineData.state == t_EngineData::mkdir)
		{
			CFileItem* fileItem = engineData.pItem;

			fileItem->m_statusMessage = _("Creating directory");
			RefreshItem(engineData.pItem);

			int res = engineData.pEngine->Command(CMkdirCommand(fileItem->GetRemotePath()));

			wxASSERT((res & FZ_REPLY_BUSY) != FZ_REPLY_BUSY);
			if (res == FZ_REPLY_WOULDBLOCK)
				return;

			if (res == FZ_REPLY_NOTCONNECTED)
			{
				if (&engineData == m_engineData[0])
				{
					ResetEngine(engineData, retry);
					return;
				}

				engineData.state = t_EngineData::connect;
				continue;
			}

			if (res == FZ_REPLY_OK)
			{
				ResetEngine(engineData, success);
				return;
			}

			// Pointless to retry
			ResetEngine(engineData, failure);
			return;
		}
	}
}

bool CQueueView::SetActive(bool active /*=true*/)
{
	if (!active)
	{
		m_activeMode = 0;
		for (std::vector<CServerItem*>::iterator iter = m_serverList.begin(); iter != m_serverList.end(); iter++)
			(*iter)->QueueImmediateFiles();

		UpdateStatusLinePositions();

		// Send active engines the cancel command
		unsigned int engineIndex;
		for (engineIndex = 0; engineIndex < m_engineData.size(); engineIndex++)
		{
			t_EngineData* const pEngineData = m_engineData[engineIndex];
			if (!pEngineData->active)
				continue;

			if (pEngineData->state == t_EngineData::waitprimary)
			{
				if (pEngineData->pItem)
					pEngineData->pItem->m_statusMessage = _("Interrupted by user");
				ResetEngine(*pEngineData, reset);
			}
			else
			{
				wxASSERT(pEngineData->pEngine);
				if (!pEngineData->pEngine)
					continue;
				pEngineData->pEngine->Command(CCancelCommand());
			}
		}

		return m_activeCount == 0;
	}
	else
	{
		m_activeMode = 2;

		m_waitStatusLineUpdate = true;
		AdvanceQueue();
		m_waitStatusLineUpdate = false;
		UpdateStatusLinePositions();
	}

	return true;
}

bool CQueueView::Quit()
{
	m_quit = true;

#ifdef __WXMSW__
	if (m_actionAfterWarnDialog)
	{
		m_actionAfterWarnDialog->Destroy();
		m_actionAfterWarnDialog = 0;
	}
	delete m_actionAfterTimer;
	m_actionAfterTimer = 0;
#endif

	bool canQuit = true;
	if (!SetActive(false))
		canQuit = false;

	for (unsigned int i = 0; i < 2; i++)
	{
		if (!m_queuedFolders[i].empty())
		{
			canQuit = false;
			for (std::list<CFolderScanItem*>::iterator iter = m_queuedFolders[i].begin(); iter != m_queuedFolders[i].end(); iter++)
				(*iter)->m_remove = true;
		}
	}
	if (m_pFolderProcessingThread)
		canQuit = false;

	if (!canQuit)
		return false;

	DeleteEngines();

	SaveQueue();

	COptions::Get()->SaveColumnWidths(this, OPTION_QUEUE_COLUMN_WIDTHS);

	return true;
}

void CQueueView::CheckQueueState()
{
	if (!m_engineData.empty() && !m_engineData[0]->active && m_engineData[0]->pEngine)
	{
		CCommandQueue* pCommandQueue = m_pMainFrame->GetState()->m_pCommandQueue;
		if (pCommandQueue)
			pCommandQueue->ReleaseEngine();
		m_engineData[0]->pEngine = 0;
	}

	if (m_activeCount)
		return;

	if (m_activeMode)
	{
		m_activeMode = 0;
		/* Users don't seem to like this, so comment it out for now.
		 * maybe make it configureable in future?
		if (!m_pQueue->GetSelection())
		{
			CQueueViewBase* pFailed = m_pQueue->GetQueueView_Failed();
			CQueueViewBase* pSuccessful = m_pQueue->GetQueueView_Successful();
			if (pFailed->GetItemCount())
				m_pQueue->SetSelection(1);
			else if (pSuccessful->GetItemCount())
				m_pQueue->SetSelection(2);
		}
		*/
		if (!m_quit)
			ActionAfter();
	}

	if (m_quit)
		m_pMainFrame->Close();
}

bool CQueueView::IncreaseErrorCount(t_EngineData& engineData)
{
	engineData.pItem->m_errorCount++;
	if (engineData.pItem->m_errorCount <= COptions::Get()->GetOptionVal(OPTION_RECONNECTCOUNT))
		return true;

	ResetEngine(engineData, failure);

	return false;
}

void CQueueView::UpdateStatusLinePositions()
{
	if (m_waitStatusLineUpdate)
		return;

	m_lastTopItem = GetTopItem();
	int bottomItem = m_lastTopItem + GetCountPerPage();

	for (std::list<CStatusLineCtrl*>::iterator iter = m_statusLineList.begin(); iter != m_statusLineList.end(); iter++)
	{
		CStatusLineCtrl *pCtrl = *iter;
		int index = GetItemIndex(pCtrl->GetItem()) + 1;
		if (index < m_lastTopItem || index > bottomItem)
		{
			pCtrl->Show(false);
			continue;
		}

		wxRect rect;
		if (!GetItemRect(index, rect))
		{
			pCtrl->Show(false);
			continue;
		}
		m_allowBackgroundErase = bottomItem + 1 >= m_itemCount;
		pCtrl->SetSize(rect);
		m_allowBackgroundErase = false;
		pCtrl->Show();
		m_allowBackgroundErase = true;
	}
}

void CQueueView::CalculateQueueSize()
{
	// Collect total queue size
	m_totalQueueSize = 0;
	m_fileCount = 0;
	m_folderScanCount = 0;

	m_filesWithUnknownSize = 0;
	for (std::vector<CServerItem*>::const_iterator iter = m_serverList.begin(); iter != m_serverList.end(); iter++)
		m_totalQueueSize += (*iter)->GetTotalSize(m_filesWithUnknownSize, m_fileCount, m_folderScanCount);

	DisplayQueueSize();
	DisplayNumberQueuedFiles();
}

void CQueueView::DisplayQueueSize()
{
	wxStatusBar *pStatusBar = m_pMainFrame->GetStatusBar();
	if (!pStatusBar)
		return;

	wxString queueSize;
	wxLongLong totalSize = m_totalQueueSize;
	if (totalSize == 0 && m_filesWithUnknownSize == 0)
		queueSize = _("Queue: empty");
	if (totalSize > (1000 * 1000))
	{
		totalSize /= 1000 * 1000;
		queueSize.Printf(_("Queue: %s%d MB"), (m_filesWithUnknownSize > 0) ? _T(">") : _T(""), totalSize.GetLo());
	}
	else if (totalSize > 1000)
	{
		totalSize /= 1000;
		queueSize.Printf(_("Queue: %s%d KB"), (m_filesWithUnknownSize > 0) ? _T(">") : _T(""), totalSize.GetLo());
	}
	else
		queueSize.Printf(_("Queue: %s%d bytes"), (m_filesWithUnknownSize > 0) ? _T(">") : _T(""), totalSize.GetLo());
	pStatusBar->SetStatusText(queueSize, 4);
}

bool CQueueView::QueueFolder(bool queueOnly, bool download, const wxString& localPath, const CServerPath& remotePath, const CServer& server)
{
	CServerItem* pServerItem = CreateServerItem(server);

	CFolderScanItem* folderItem = new CFolderScanItem(pServerItem, queueOnly, download, localPath, remotePath);
	InsertItem(pServerItem, folderItem);

	folderItem->m_statusMessage = _("Waiting");

	CommitChanges();

	m_queuedFolders[download ? 0 : 1].push_back(folderItem);
	ProcessFolderItems();

	Refresh(false);

	return true;
}

bool CQueueView::ProcessFolderItems(int type /*=-1*/)
{
	if (type == -1)
	{
		while (ProcessFolderItems(0));
		ProcessUploadFolderItems();

		return true;
	}

	return false;
}

void CQueueView::ProcessUploadFolderItems()
{
	if (m_queuedFolders[1].empty())
	{
		if (m_quit)
			m_pMainFrame->Close();

		return;
	}

	if (m_pFolderProcessingThread)
		return;

	CFolderScanItem* pItem = m_queuedFolders[1].front();

	if (pItem->Queued())
		pItem->m_statusMessage = _("Scanning for files to add to queue");
	else
		pItem->m_statusMessage = _("Scanning for files to upload");
	RefreshItem(pItem);
	pItem->m_active = true;
	m_pFolderProcessingThread = new CFolderProcessingThread(this, pItem);
	m_pFolderProcessingThread->Create();
	m_pFolderProcessingThread->Run();

	Refresh(false);
}

void CQueueView::OnFolderThreadComplete(wxCommandEvent& event)
{
	if (!m_pFolderProcessingThread)
		return;

	wxASSERT(!m_queuedFolders[1].empty());
	CFolderScanItem* pItem = m_queuedFolders[1].front();
	m_queuedFolders[1].pop_front();

	RemoveItem(pItem, true);

	m_pFolderProcessingThread->Wait();
	delete m_pFolderProcessingThread;
	m_pFolderProcessingThread = 0;

	ProcessUploadFolderItems();
}

bool CQueueView::QueueFiles(const std::list<t_newEntry> &entryList, bool queueOnly, bool download, CServerItem* pServerItem, const int defaultFileExistsAction)
{
	wxASSERT(pServerItem);

	for (std::list<t_newEntry>::const_iterator iter = entryList.begin(); iter != entryList.end(); iter++)
	{
		const t_newEntry& entry = *iter;

		CFileItem* fileItem;
		if (entry.localFile != _T(""))
		{
			fileItem = new CFileItem(pServerItem, queueOnly, download, entry.localFile, entry.remoteFile, entry.remotePath, entry.size);
			fileItem->m_transferSettings.binary = ShouldUseBinaryMode(download ? entry.remoteFile : wxFileName(entry.localFile).GetFullName());
			fileItem->m_defaultFileExistsAction = defaultFileExistsAction;
		}
		else
			fileItem = new CFolderItem(pServerItem, queueOnly, entry.remotePath, _T(""));

		InsertItem(pServerItem, fileItem);
	}

	CommitChanges();

	if (!m_activeMode && !queueOnly)
		m_activeMode = 1;

	m_waitStatusLineUpdate = true;
	AdvanceQueue();
	m_waitStatusLineUpdate = false;
	UpdateStatusLinePositions();

	Refresh(false);

	return true;
}

void CQueueView::SaveQueue()
{
	// We have to synchronize access to queue.xml so that multiple processed don't write
	// to the same file or one is reading while the other one writes.
	CInterProcessMutex mutex(MUTEX_QUEUE);

	wxFileName file(wxGetApp().GetSettingsDir(), _T("queue.xml"));
	TiXmlElement* pDocument = GetXmlFile(file);
	if (!pDocument)
	{
		wxString msg = wxString::Format(_("Could not load \"%s\", please make sure the file is valid and can be accessed.\nThe queue will not be saved."), file.GetFullPath().c_str());
		wxMessageBox(msg, _("Error loading xml file"), wxICON_ERROR);

		return;
	}

	WriteToFile(pDocument);

	if (!pDocument->GetDocument()->SaveFile(file.GetFullPath().mb_str()))
	{
		wxString msg = wxString::Format(_("Could not write \"%s\", the queue could not be saved."), file.GetFullPath().c_str());
		wxMessageBox(msg, _("Error writing xml file"), wxICON_ERROR);
	}

	delete pDocument->GetDocument();
}

void CQueueView::LoadQueue()
{
	// We have to synchronize access to queue.xml so that multiple processed don't write
	// to the same file or one is reading while the other one writes.
	CInterProcessMutex mutex(MUTEX_QUEUE);

	wxFileName file(wxGetApp().GetSettingsDir(), _T("queue.xml"));
	TiXmlElement* pDocument = GetXmlFile(file);
	if (!pDocument)
	{
		wxString msg = wxString::Format(_("Could not load \"%s\", please make sure the file is valid and can be accessed.\nThe queue will not be saved."), file.GetFullPath().c_str());
		wxMessageBox(msg, _("Error loading xml file"), wxICON_ERROR);

		return;
	}

	TiXmlElement* pQueue = pDocument->FirstChildElement("Queue");
	if (!pQueue)
	{
		delete pDocument->GetDocument();
		return;
	}

	ImportQueue(pQueue, false);

	pDocument->RemoveChild(pQueue);

	if (!pDocument->GetDocument()->SaveFile(file.GetFullPath().mb_str()))
	{
		wxString msg = wxString::Format(_("Could not write \"%s\", the queue could not be saved."), file.GetFullPath().c_str());
		wxMessageBox(msg, _("Error writing xml file"), wxICON_ERROR);
	}

	delete pDocument->GetDocument();
}

void CQueueView::ImportQueue(TiXmlElement* pElement, bool updateSelections)
{
	TiXmlElement* pServer = pElement->FirstChildElement("Server");
	while (pServer)
	{
		CServer server;
		if (GetServer(pServer, server))
		{
			m_insertionStart = -1;
			m_insertionCount = 0;
			CServerItem *pServerItem = CreateServerItem(server);

			for (TiXmlElement* pFile = pServer->FirstChildElement("File"); pFile; pFile = pFile->NextSiblingElement("File"))
			{
				wxString localFile = GetTextElement(pFile, "LocalFile");
				wxString remoteFile = GetTextElement(pFile, "RemoteFile");
				wxString safeRemotePath = GetTextElement(pFile, "RemotePath");
				bool download = GetTextElementInt(pFile, "Download") != 0;
				wxLongLong size = GetTextElementLongLong(pFile, "Size", -1);
				unsigned int errorCount = GetTextElementInt(pFile, "ErrorCount");
				unsigned int priority = GetTextElementInt(pFile, "Priority", priority_normal);
				unsigned int itemState = GetTextElementInt(pFile, "ItemState", ItemState_Wait);
				bool binary = GetTextElementInt(pFile, "TransferMode", 1) != 0;

				CServerPath remotePath;
				if (localFile != _T("") && remoteFile != _T("") && remotePath.SetSafePath(safeRemotePath) &&
					size >= -1 && priority < PRIORITY_COUNT &&
					(itemState == ItemState_Wait || itemState == ItemState_Error))
				{
					CFileItem* fileItem = new CFileItem(pServerItem, true, download, localFile, remoteFile, remotePath, size);
					fileItem->m_transferSettings.binary = binary;
					fileItem->SetPriorityRaw((enum QueuePriority)priority);
					fileItem->SetItemState((enum ItemState)itemState);
					fileItem->m_errorCount = errorCount;
					InsertItem(pServerItem, fileItem);
				}
			}
			for (TiXmlElement* pFolder = pServer->FirstChildElement("Folder"); pFolder; pFolder = pFolder->NextSiblingElement("Folder"))
			{
				CFolderItem* folderItem;

				bool download = GetTextElementInt(pFolder, "Download") != 0;
				if (download)
				{
					wxString localFile = GetTextElement(pFolder, "LocalFile");
					if (localFile == _T(""))
						continue;
					folderItem = new CFolderItem(pServerItem, true, localFile);
				}
				else
				{
					wxString remoteFile = GetTextElement(pFolder, "RemoteFile");
					wxString safeRemotePath = GetTextElement(pFolder, "RemotePath");
					if (safeRemotePath == _T(""))
						continue;

					CServerPath remotePath;
					if (!remotePath.SetSafePath(safeRemotePath))
						continue;
					folderItem = new CFolderItem(pServerItem, true, remotePath, remoteFile);
				}

				unsigned int priority = GetTextElementInt(pFolder, "Priority", priority_normal);
				if (priority >= PRIORITY_COUNT)
				{
					delete folderItem;
					continue;
				}
				folderItem->SetPriority((enum QueuePriority)priority);

				InsertItem(pServerItem, folderItem);
			}

			if (!pServerItem->GetChild(0))
			{
				m_itemCount--;
				m_serverList.pop_back();
				delete pServerItem;
			}
			else if (updateSelections)
				CommitChanges();
		}

		pServer = pServer->NextSiblingElement("Server");
	}

	if (!updateSelections)
	{
		m_insertionStart = -1;
		m_insertionCount = 0;
		CommitChanges();
	}
	else
		Refresh();
}

void CQueueView::SettingsChanged()
{
	m_asciiFiles.clear();
	wxString extensions = COptions::Get()->GetOption(OPTION_ASCIIFILES);
	wxString ext;
	int pos = extensions.Find(_T("|"));
	while (pos != -1)
	{
		if (!pos)
		{
			if (ext != _T(""))
			{
				ext.Replace(_T("\\\\"), _T("\\"));
				m_asciiFiles.push_back(ext);
				ext = _T("");
			}
		}
		else if (extensions.c_str()[pos - 1] != '\\')
		{
			ext += extensions.Left(pos);
			ext.Replace(_T("\\\\"), _T("\\"));
			m_asciiFiles.push_back(ext);
			ext = _T("");
		}
		else
		{
			ext += extensions.Left(pos - 1) + _T("|");
		}
		extensions = extensions.Mid(pos + 1);
		pos = extensions.Find(_T("|"));
	}
	ext += extensions;
	ext.Replace(_T("\\\\"), _T("\\"));
	m_asciiFiles.push_back(ext);
}

bool CQueueView::ShouldUseBinaryMode(wxString filename)
{
	int mode = COptions::Get()->GetOptionVal(OPTION_ASCIIBINARY);
	if (mode == 1)
		return false;
	else if (mode == 2)
		return true;

	int pos = filename.Find('.');
	if (pos == -1)
		return COptions::Get()->GetOptionVal(OPTION_ASCIINOEXT) != 0;
	else if (!pos)
		return COptions::Get()->GetOptionVal(OPTION_ASCIIDOTFILE) != 0;

	wxString ext = filename;
	do
	{
		ext = ext.Mid(pos + 1);
	}
	while ((pos = ext.Find('.')) != -1);

	if (ext == _T(""))
		return true;

	for (std::list<wxString>::const_iterator iter = m_asciiFiles.begin(); iter != m_asciiFiles.end(); iter++)
		if (!ext.CmpNoCase(*iter))
			return false;

	return true;
}

void CQueueView::OnScrollEvent(wxScrollWinEvent& event)
{
	event.Skip();
	wxCommandEvent evt(fzEVT_UPDATE_STATUSLINES, wxID_ANY);
	AddPendingEvent(evt);
}

void CQueueView::OnUpdateStatusLines(wxCommandEvent& event)
{
	if (GetTopItem() != m_lastTopItem)
		UpdateStatusLinePositions();
}

void CQueueView::OnMouseWheel(wxMouseEvent& event)
{
	event.Skip();
	wxCommandEvent evt(fzEVT_UPDATE_STATUSLINES, wxID_ANY);
	AddPendingEvent(evt);
}

void CQueueView::OnContextMenu(wxContextMenuEvent& event)
{
	wxMenu* pMenu = wxXmlResource::Get()->LoadMenu(_T("ID_MENU_QUEUE"));
	if (!pMenu)
		return;

	pMenu->Check(XRCID("ID_PROCESSQUEUE"), IsActive() ? true : false);
	pMenu->Check(XRCID("ID_ACTIONAFTER_DISABLE"), IsActionAfter(ActionAfterState_Disabled));
	pMenu->Check(XRCID("ID_ACTIONAFTER_CLOSE"), IsActionAfter(ActionAfterState_Close));
	pMenu->Check(XRCID("ID_ACTIONAFTER_DISCONNECT"), IsActionAfter(ActionAfterState_Disconnect));
	pMenu->Check(XRCID("ID_ACTIONAFTER_RUNCOMMAND"), IsActionAfter(ActionAfterState_RunCommand));
	pMenu->Check(XRCID("ID_ACTIONAFTER_SHOWMESSAGE"), IsActionAfter(ActionAfterState_ShowMessage));
	pMenu->Check(XRCID("ID_ACTIONAFTER_PLAYSOUND"), IsActionAfter(ActionAfterState_PlaySound));
#ifdef __WXMSW__
	pMenu->Check(XRCID("ID_ACTIONAFTER_REBOOT"), IsActionAfter(ActionAfterState_Reboot));
	pMenu->Check(XRCID("ID_ACTIONAFTER_SHUTDOWN"), IsActionAfter(ActionAfterState_Shutdown));
#endif
	pMenu->Enable(XRCID("ID_REMOVE"), GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);

	const bool hasSelection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1;
	
	pMenu->Enable(XRCID("ID_PRIORITY"), hasSelection);
	pMenu->Enable(XRCID("ID_DEFAULT_FILEEXISTSACTION"), hasSelection);
#ifdef __WXMSW__
	pMenu->Enable(XRCID("ID_ACTIONAFTER"), m_actionAfterWarnDialog == NULL);
#endif

	PopupMenu(pMenu);
	delete pMenu;
}

void CQueueView::OnProcessQueue(wxCommandEvent& event)
{
	SetActive(event.IsChecked());
}

void CQueueView::OnStopAndClear(wxCommandEvent& event)
{
	SetActive(false);
	RemoveAll();
}

void CQueueView::OnActionAfter(wxCommandEvent& event)
{
	if (!event.IsChecked() || event.GetId() == XRCID("ID_ACTIONAFTER_DISABLE"))
	{ // Goes from checked to non-checked or disable is pressed
		m_actionAfterState = ActionAfterState_Disabled;
		m_actionAfterRunCommand = _T("");

#ifdef __WXMSW__
		if (m_actionAfterWarnDialog)
		{
			m_actionAfterWarnDialog->Destroy();
			m_actionAfterWarnDialog = 0;
		}
		delete m_actionAfterTimer;
		m_actionAfterTimer = 0;
#endif
	}
	else if (event.GetId() == XRCID("ID_ACTIONAFTER_DISABLE"))
		m_actionAfterState = ActionAfterState_Disabled;
	
	else if (event.GetId() == XRCID("ID_ACTIONAFTER_CLOSE"))
		m_actionAfterState = ActionAfterState_Close;
	
	else if (event.GetId() == XRCID("ID_ACTIONAFTER_DISCONNECT"))
		m_actionAfterState = ActionAfterState_Disconnect;
	
	else if (event.GetId() == XRCID("ID_ACTIONAFTER_SHOWMESSAGE"))
		m_actionAfterState = ActionAfterState_ShowMessage;
	
	else if (event.GetId() == XRCID("ID_ACTIONAFTER_PLAYSOUND"))
		m_actionAfterState = ActionAfterState_PlaySound;
		
	else if (event.GetId() == XRCID("ID_ACTIONAFTER_RUNCOMMAND"))
	{	
		m_actionAfterState = ActionAfterState_RunCommand;
		wxTextEntryDialog dlg(m_pMainFrame, _("Please enter a path and executable to run.\nE.g. c:\\somePath\\file.exe under MS Windows or /somePath/file under Unix.\nYou can also optionally specify program arguments."), _("Enter command"));
		
		if (dlg.ShowModal() != wxID_OK)
		{
			m_actionAfterState = ActionAfterState_Disabled;
			return;
		}
		const wxString &command = dlg.GetValue();
		
		if (command == _T(""))
		{
			wxMessageBox(_("No command given, aborting."), _("Empty command"), wxICON_ERROR, m_pMainFrame);
			m_actionAfterState = ActionAfterState_Disabled;
			return;
		}
		m_actionAfterRunCommand = command;
	}

#ifdef __WXMSW__
	
	else if (event.GetId() == XRCID("ID_ACTIONAFTER_REBOOT"))
		m_actionAfterState = ActionAfterState_Reboot;
		
	else if (event.GetId() == XRCID("ID_ACTIONAFTER_SHUTDOWN"))
		m_actionAfterState = ActionAfterState_Shutdown;

#endif

}

void CQueueView::RemoveAll()
{
	// This function removes all inactive items and queues active items
	// for removal

	// First, clear all selections
	int item;
	while ((item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1)
		SetItemState(item, 0, wxLIST_STATE_SELECTED);

	std::vector<CServerItem*> newServerList;
	m_itemCount = 0;
	for (std::vector<CServerItem*>::iterator iter = m_serverList.begin(); iter != m_serverList.end(); iter++)
	{
		if ((*iter)->TryRemoveAll())
			delete *iter;
		else
		{
			newServerList.push_back(*iter);
			m_itemCount += 1 + (*iter)->GetChildrenCount(true);
		}
	}
	SetItemCount(m_itemCount);
	m_actionAfterState = ActionAfterState_Disabled;

	m_serverList = newServerList;
	UpdateStatusLinePositions();

	CalculateQueueSize();

	CheckQueueState();
	Refresh();
}

void CQueueView::OnRemoveSelected(wxCommandEvent& event)
{
	std::list<CQueueItem*> selectedItems;
	long item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;

		selectedItems.push_front(GetQueueItem(item));
		SetItemState(item, 0, wxLIST_STATE_SELECTED);
	}

	m_waitStatusLineUpdate = true;

	while (!selectedItems.empty())
	{
		CQueueItem* pItem = selectedItems.front();
		selectedItems.pop_front();

		if (pItem->GetType() == QueueItemType_Status)
			continue;
		else if (pItem->GetType() == QueueItemType_FolderScan)
		{
			CFolderScanItem* pFolder = (CFolderScanItem*)pItem;
			if (pFolder->m_active)
			{
				pFolder->m_remove = true;
				continue;
			}
		}
		else if (pItem->GetType() == QueueItemType_Server)
		{
			CServerItem* pServer = (CServerItem*)pItem;
			StopItem(pServer);

			// Server items get deleted automatically if all children are gone
			continue;
		}
		else if (pItem->GetType() == QueueItemType_File ||
				 pItem->GetType() == QueueItemType_Folder)
		{
			CFileItem* pFile = (CFileItem*)pItem;
			if (pFile->IsActive())
			{
				pFile->m_remove = true;
				StopItem(pFile);
				continue;
			}
		}

		CQueueItem* pTopLevelItem = pItem->GetTopLevelItem();
		if (!pTopLevelItem->GetChild(1))
		{
			// Parent will get deleted
			// If next selected item is parent, remove it from list
			if (!selectedItems.empty() && selectedItems.front() == pTopLevelItem)
				selectedItems.pop_front();
		}
		RemoveItem(pItem, true, false, false);
	}
	DisplayNumberQueuedFiles();
	DisplayQueueSize();
	SetItemCount(m_itemCount);

	m_waitStatusLineUpdate = false;
	UpdateStatusLinePositions();

	Refresh();
}

bool CQueueView::StopItem(CFileItem* item)
{
	if (!item->IsActive())
		return true;

	((CServerItem*)item->GetTopLevelItem())->QueueImmediateFile(item);

	if (item->m_pEngineData->state == t_EngineData::waitprimary)
	{
		enum ResetReason reason;
		if (item->m_pEngineData->pItem && item->m_pEngineData->pItem->m_remove)
			reason = remove;
		else
			reason = reset;
		if (item->m_pEngineData->pItem)
			item->m_pEngineData->pItem->m_statusMessage = _T("");
		ResetEngine(*item->m_pEngineData, reason);
		return true;
	}
	else
	{
		item->m_pEngineData->pEngine->Command(CCancelCommand());
		return false;
	}
}

bool CQueueView::StopItem(CServerItem* pServerItem)
{
	std::list<CQueueItem*> items;
	for (unsigned int i = 0; i < pServerItem->GetChildrenCount(false); i++)
		items.push_back(pServerItem->GetChild(i, false));

	for (std::list<CQueueItem*>::reverse_iterator iter = items.rbegin(); iter != items.rend(); iter++)
	{
		CQueueItem* pItem = *iter;
		if (pItem->GetType() == QueueItemType_FolderScan)
		{
			CFolderScanItem* pFolder = (CFolderScanItem*)pItem;
			if (pFolder->m_active)
			{
				pFolder->m_remove = true;
				continue;
			}
		}
		else if (pItem->GetType() == QueueItemType_File ||
				 pItem->GetType() == QueueItemType_Folder)
		{
			CFileItem* pFile = (CFileItem*)pItem;
			if (pFile->IsActive())
			{
				pFile->m_remove = true;
				StopItem(pFile);
				continue;
			}
		}
		else
			// Unknown type, shouldn't be here.
			wxASSERT(false);

		if (RemoveItem(pItem, true, false))
		{
			DisplayNumberQueuedFiles();
			SetItemCount(m_itemCount);
			return true;
		}
	}
	DisplayNumberQueuedFiles();
	SetItemCount(m_itemCount);

	return false;
}

void CQueueView::OnFolderThreadFiles(wxCommandEvent& event)
{
	if (!m_pFolderProcessingThread)
		return;

	wxASSERT(!m_queuedFolders[1].empty());
	CFolderScanItem* pItem = m_queuedFolders[1].front();

	std::list<t_newEntry> entryList;
	m_pFolderProcessingThread->GetFiles(entryList);
	QueueFiles(entryList, pItem->Queued(), false, (CServerItem*)pItem->GetTopLevelItem(), pItem->m_defaultFileExistsAction);

	pItem->m_count += entryList.size();
	pItem->m_statusMessage = wxString::Format(_("%d files added to queue"), pItem->GetCount());
	RefreshItem(pItem);
}

void CQueueView::SetDefaultFileExistsAction(int action, const enum TransferDirection direction)
{
	for (std::vector<CServerItem*>::iterator iter = m_serverList.begin(); iter != m_serverList.end(); iter++)
		(*iter)->SetDefaultFileExistsAction(action, direction);
}

void CQueueView::OnSetDefaultFileExistsAction(wxCommandEvent &event)
{
	if (GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == -1)
		return;

	CDefaultFileExistsDlg dlg;
	if (!dlg.Load(this, true))
		return;

	int downloadAction, uploadAction;
	if (!dlg.Run(&downloadAction, &uploadAction))
		return;

	std::list<long> selectedItems;
	long item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;

		CQueueItem* pItem = GetQueueItem(item);
		if (!pItem)
			continue;

		switch (pItem->GetType())
		{
		case QueueItemType_FolderScan:
			((CFolderScanItem*)pItem)->m_defaultFileExistsAction = uploadAction;
			break;
		case QueueItemType_File:
			{
				CFileItem *pFileItem = (CFileItem*)pItem;
				if (pFileItem->Download())
					pFileItem->m_defaultFileExistsAction = downloadAction;
				else
					pFileItem->m_defaultFileExistsAction = uploadAction;
			}
			break;
		case QueueItemType_Server:
			{
				CServerItem *pServerItem = (CServerItem*)pItem;
				pServerItem->SetDefaultFileExistsAction(downloadAction, download);
				pServerItem->SetDefaultFileExistsAction(uploadAction, upload);
			}
			break;
		default:
			break;
		}
	}
}

t_EngineData* CQueueView::GetIdleEngine(const CServer* pServer /*=0*/)
{
	t_EngineData* pFirstIdle = 0;

	// We start with 1, since 0 is the primary connection
	for (unsigned int i = 1; i < m_engineData.size(); i++)
	{
		if (m_engineData[i]->active)
			continue;

		if (!pServer)
			return m_engineData[i];

		if (m_engineData[i]->pEngine->IsConnected() && m_engineData[i]->lastServer == *pServer)
			return m_engineData[i];

		if (!pFirstIdle)
			pFirstIdle = m_engineData[i];
	}

	return pFirstIdle;
}

void CQueueView::TryRefreshListings()
{
	// TODO: This function is currently unused

	if (m_quit)
		return;

	const CState* const pState = m_pMainFrame->GetState();
	const CServer* const pServer = pState->GetServer();
	if (!pServer)
		return;

	const CDirectoryListing* const pListing = pState->GetRemoteDir();
	if (!pListing || !pListing->m_hasUnsureEntries)
		return;

	// See if there's an engine with is already listing
	for (unsigned int i = 1; i < m_engineData.size(); i++)
	{
		if (!m_engineData[i]->active)
			continue;

		if (m_engineData[i]->pItem)
			continue;

		if (m_engineData[i]->pEngine->IsConnected() && m_engineData[i]->lastServer == *pServer)
		{
			// This engine is already listing a directory on the current server
			return;
		}
	}

	t_EngineData* pEngineData = GetIdleEngine(pServer);
	if (!pEngineData)
		return;

	if (!pEngineData->pEngine->IsConnected() || pEngineData->lastServer != *pServer)
		return;

	CListCommand command(pListing->path);
	int res = pEngineData->pEngine->Command(command);
	if (res != FZ_REPLY_WOULDBLOCK)
		return;

	pEngineData->active = true;
	pEngineData->state = t_EngineData::list;
}

void CQueueView::OnAskPassword(wxCommandEvent& event)
{
	while (!m_waitingForPassword.empty())
	{
		const CFileZillaEngine* const pEngine = m_waitingForPassword.front();

		std::vector<t_EngineData*>::iterator iter;
		for (iter = m_engineData.begin(); iter != m_engineData.end(); iter++)
		{
			if ((*iter)->pEngine == pEngine)
				break;
		}
		if (iter == m_engineData.end())
		{
			m_waitingForPassword.pop_front();
			continue;
		}

		t_EngineData* pEngineData = *iter;

		if (pEngineData->state != t_EngineData::askpassword)
		{
			m_waitingForPassword.pop_front();
			continue;
		}

		static std::list<void*> AskPasswordBusyList;
		if (CLoginManager::Get().GetPassword(pEngineData->lastServer, false))
		{
			pEngineData->state = t_EngineData::connect;
			SendNextCommand(*pEngineData);
		}
		else
			ResetEngine(*pEngineData, remove);

		m_waitingForPassword.pop_front();
	}
}

void CQueueView::OnFocusItemChanged(wxListEvent& event)
{
	event.Skip();
	wxCommandEvent evt(fzEVT_UPDATE_STATUSLINES, wxID_ANY);
	AddPendingEvent(evt);
}

void CQueueView::UpdateItemSize(CFileItem* pItem, wxLongLong size)
{
	wxASSERT(pItem);

	const wxLongLong oldSize = pItem->GetSize();
	if (size == oldSize)
		return;

	if (oldSize == -1)
	{
		wxASSERT(m_filesWithUnknownSize);
		if (m_filesWithUnknownSize)
			m_filesWithUnknownSize--;
	}
	else
	{
		wxASSERT(m_totalQueueSize > oldSize);
		if (m_totalQueueSize > oldSize)
			m_totalQueueSize -= oldSize;
		else
			m_totalQueueSize = 0;
	}

	if (size == -1)
		m_filesWithUnknownSize++;
	else
		m_totalQueueSize += size;

	pItem->SetSize(size);

	DisplayQueueSize();
}

void CQueueView::AdvanceQueue()
{
	static bool insideAdvanceQueue = false;
	if (insideAdvanceQueue)
		return;

	insideAdvanceQueue = true;
	while (TryStartNextTransfer())
	{
	}

	// Set timer for connected, idle engines
	for (unsigned int i = 1; i < m_engineData.size(); i++)
	{
		if (m_engineData[i]->active)
			continue;

		if (m_engineData[i]->m_idleDisconnectTimer)
		{
			if (m_engineData[i]->pEngine->IsConnected())
				continue;

			delete m_engineData[i]->m_idleDisconnectTimer;
			m_engineData[i]->m_idleDisconnectTimer = 0;
		}
		else
		{
			if (!m_engineData[i]->pEngine->IsConnected())
				continue;

			m_engineData[i]->m_idleDisconnectTimer = new wxTimer(this);
			m_engineData[i]->m_idleDisconnectTimer->Start(30000, true);
		}
	}

	insideAdvanceQueue = false;

	CheckQueueState();
}

void CQueueView::InsertItem(CServerItem* pServerItem, CQueueItem* pItem)
{
	CQueueViewBase::InsertItem(pServerItem, pItem);

	if (pItem->GetType() == QueueItemType_File)
	{
		CFileItem* pFileItem = (CFileItem*)pItem;

		const wxLongLong& size = pFileItem->GetSize();
		if (size < 0)
			m_filesWithUnknownSize++;
		else if (size > 0)
			m_totalQueueSize += size;
	}
}

void CQueueView::CommitChanges()
{
	CQueueViewBase::CommitChanges();

	DisplayQueueSize();
}

void CQueueView::OnTimer(wxTimerEvent& event)
{
	const int id = event.GetId();
	if (id == -1)
		return;
#ifdef __WXMSW__
	if (id == m_actionAfterTimerId)
	{
		OnActionAfterTimerTick();
		return;
	}
#endif

	for (unsigned int i = 1; i < m_engineData.size(); i++)
	{
		t_EngineData* pData = m_engineData[i];
		if (pData->m_idleDisconnectTimer && !pData->m_idleDisconnectTimer->IsRunning())
		{
			delete pData->m_idleDisconnectTimer;
			pData->m_idleDisconnectTimer = 0;

			if (pData->pEngine->IsConnected())
				pData->pEngine->Command(CDisconnectCommand());
		}
	}
}

void CQueueView::DeleteEngines()
{
	for (unsigned int engineIndex = 1; engineIndex < m_engineData.size(); engineIndex++)
	{
		t_EngineData* const data = m_engineData[engineIndex];
		delete data->pEngine;
		data->pEngine = 0;
		delete data->m_idleDisconnectTimer;
		data->m_idleDisconnectTimer = 0;
	}
	for (unsigned int engineIndex = 0; engineIndex < m_engineData.size(); engineIndex++)
		delete m_engineData[engineIndex];
	m_engineData.clear();
}

void CQueueView::WriteToFile(TiXmlElement* pElement) const
{
	TiXmlElement* pQueue = pElement->FirstChildElement("Queue");
	if (!pQueue)
	{
		pQueue = pElement->InsertEndChild(TiXmlElement("Queue"))->ToElement();
	}

	wxASSERT(pQueue);

	for (std::vector<CServerItem*>::const_iterator iter = m_serverList.begin(); iter != m_serverList.end(); iter++)
		(*iter)->SaveItem(pQueue);
}

void CQueueView::OnSetPriority(wxCommandEvent& event)
{
	enum QueuePriority priority;

	const int id = event.GetId();
	if (id == XRCID("ID_PRIORITY_LOWEST"))
		priority = priority_lowest;
	else if (id == XRCID("ID_PRIORITY_LOW"))
		priority = priority_low;
	else if (id == XRCID("ID_PRIORITY_HIGH"))
		priority = priority_high;
	else if (id == XRCID("ID_PRIORITY_HIGHEST"))
		priority = priority_highest;
	else
		priority = priority_normal;


	CQueueItem* pSkip = 0;
	long item = -1;
	while (-1 != (item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)))
	{
		CQueueItem* pItem = GetQueueItem(item);
		if (!pItem)
			continue;

		if (pItem->GetType() == QueueItemType_Server)
			pSkip = pItem;
		else if (pItem->GetTopLevelItem() == pSkip)
			continue;
		else
			pSkip = 0;

		pItem->SetPriority(priority);
	}

	Refresh();
}

void CQueueView::OnExclusiveEngineRequestGranted(wxCommandEvent& event)
{
	CCommandQueue* pCommandQueue = m_pMainFrame->GetState()->m_pCommandQueue;
	if (!pCommandQueue)
		return;

	CFileZillaEngine* pEngine = pCommandQueue->GetEngineExclusive(event.GetId());

	t_EngineData* pEngineData = m_engineData[0];

	if (!pEngineData->active)
	{
		wxASSERT(!pEngineData->pEngine);
		if (pEngine)
			pCommandQueue->ReleaseEngine();

		return;
	}

	if (!pEngine)
		return;

	wxASSERT(!pEngineData->pEngine);

	wxASSERT(pEngineData->state == t_EngineData::waitprimary);
	if (pEngineData->state != t_EngineData::waitprimary)
		return;

	const CServer* pCurrentServer = m_pMainFrame->GetState()->GetServer();
	CServerItem* pServerItem = (CServerItem*)pEngineData->pItem->GetParent();

	wxASSERT(pServerItem);

	if (!pCurrentServer || *pCurrentServer != pServerItem->GetServer())
	{
		pCommandQueue->ReleaseEngine();
		ResetEngine(*pEngineData, retry);
		return;
	}

	if (pEngineData->pItem->GetType() == QueueItemType_File)
		pEngineData->state = t_EngineData::transfer;
	else
		pEngineData->state = t_EngineData::mkdir;

	pEngineData->pEngine = pEngine;

	SendNextCommand(*pEngineData);
}

enum ActionAfterState CQueueView::GetActionAfterState() const
{
	return m_actionAfterState;
}

bool CQueueView::IsActionAfter(enum ActionAfterState state)
{
	return m_actionAfterState == state;
}

void CQueueView::ActionAfter(bool warned /*=false*/)
{
	switch (m_actionAfterState)
	{
		case ActionAfterState_Close:
		{
			m_pMainFrame->Close();
			break;
		}
		case ActionAfterState_Disconnect:
		{
			if (m_pMainFrame->GetState()->IsRemoteConnected() && m_pMainFrame->GetState()->IsRemoteIdle()) 
				m_pMainFrame->GetState()->m_pCommandQueue->ProcessCommand(new CDisconnectCommand());
			break;
		}
		case ActionAfterState_RunCommand:
		{
			wxExecute(m_actionAfterRunCommand);
			break;
		}
		case ActionAfterState_ShowMessage:
		{
			wxMessageDialog* dialog = new wxMessageDialog(m_pMainFrame, _T("No more files in the queue!"), _T("Queue completion"), wxOK | wxICON_INFORMATION);
			dialog->ShowModal();
			m_pMainFrame->RequestUserAttention(wxUSER_ATTENTION_ERROR);
			break;
		}
		case ActionAfterState_PlaySound:
		{
			wxSound(wxGetApp().GetResourceDir() + _T("finished.wav")).Play(wxSOUND_ASYNC);
			break;
		}
#ifdef __WXMSW__
		case ActionAfterState_Reboot:
		{
			if (!warned)
				ActionAfterWarnUser(_T("The system will soon reboot unless you press cancel."));
			else
				wxShutdown(wxSHUTDOWN_REBOOT);
			break;
		}

		case ActionAfterState_Shutdown:
		{
			if (!warned)
				ActionAfterWarnUser(_T("The system will soon shutdown unless you press cancel."));
			else
				wxShutdown(wxSHUTDOWN_POWEROFF);
			break;
		}
#endif

	}
	m_actionAfterState = ActionAfterState_Disabled; // Resetting the state.
}

#ifdef __WXMSW__
void CQueueView::ActionAfterWarnUser(wxString message)
{
	if (m_actionAfterWarnDialog != NULL)
		return;

	m_actionAfterWarnDialog = new wxProgressDialog(_T("Queue completion dialog"), message, 150, m_pMainFrame, wxPD_CAN_ABORT | wxPD_AUTO_HIDE | wxPD_CAN_SKIP | wxPD_APP_MODAL);
	wxSize dialogSize = m_actionAfterWarnDialog->GetSize();
	m_actionAfterWarnDialog->SetSize(dialogSize.GetWidth() / 2, dialogSize.GetHeight());
	m_actionAfterWarnDialog->CentreOnParent();
	m_actionAfterWarnDialog->SetFocus();
	m_pMainFrame->RequestUserAttention(wxUSER_ATTENTION_ERROR);

	wxASSERT(!m_actionAfterTimer);
	m_actionAfterTimer = new wxTimer(this, m_actionAfterTimerId);
	m_actionAfterTimerId = m_actionAfterTimer->GetId();
	m_actionAfterTimerCount = 0;
	m_actionAfterTimer->Start(100, wxTIMER_CONTINUOUS);
}

void CQueueView::OnActionAfterTimerTick()
{
	if (!m_actionAfterWarnDialog)
	{
		delete m_actionAfterTimer;
		m_actionAfterTimer = 0;
		return;
	}

	bool skipped = false;
	if (m_actionAfterTimerCount > 150)
	{
		m_actionAfterWarnDialog->Destroy();
		m_actionAfterWarnDialog = 0;
		delete m_actionAfterTimer;
		m_actionAfterTimer = 0;
		ActionAfter(true);
	}
	else if (!m_actionAfterWarnDialog->Update(m_actionAfterTimerCount++, _T(""), &skipped))
	{
		// User has pressed cancel!
		m_actionAfterState = ActionAfterState_Disabled; // resetting to disabled
		m_actionAfterWarnDialog->Destroy();
		m_actionAfterWarnDialog = 0;
		delete m_actionAfterTimer;
		m_actionAfterTimer = 0;
	}
	else if (skipped)
	{
		m_actionAfterWarnDialog->Destroy();
		m_actionAfterWarnDialog = 0;
		delete m_actionAfterTimer;
		m_actionAfterTimer = 0;
		ActionAfter(true);
	}
}
#endif //__WXMSW__


syntax highlighted by Code2HTML, v. 0.9.1