#include "FileZilla.h"
#include "asyncrequestqueue.h"
#include "fileexistsdlg.h"
#include "Mainfrm.h"
#include "defaultfileexistsdlg.h"
#include "Options.h"
#include "queue.h"
#include "verifycertdialog.h"
#include "loginmanager.h"

DECLARE_EVENT_TYPE(fzEVT_PROCESSASYNCREQUESTQUEUE, -1)
DEFINE_EVENT_TYPE(fzEVT_PROCESSASYNCREQUESTQUEUE)

BEGIN_EVENT_TABLE(CAsyncRequestQueue, wxEvtHandler)
EVT_COMMAND(wxID_ANY, fzEVT_PROCESSASYNCREQUESTQUEUE, CAsyncRequestQueue::OnProcessQueue)
END_EVENT_TABLE()

CAsyncRequestQueue::CAsyncRequestQueue(CMainFrame *pMainFrame)
{
	m_pMainFrame = pMainFrame;
	m_pQueueView = 0;
	m_pVerifyCertDlg = new CVerifyCertDialog;
}

CAsyncRequestQueue::~CAsyncRequestQueue()
{
	delete m_pVerifyCertDlg;
}

bool CAsyncRequestQueue::ProcessDefaults(CFileZillaEngine *pEngine, CAsyncRequestNotification *pNotification)
{
	// Process notifications, see if we have defaults not requirering user interaction.
	switch (pNotification->GetRequestID())
	{
	case reqId_fileexists:
		{
			CFileExistsNotification *pFileExistsNotification = reinterpret_cast<CFileExistsNotification *>(pNotification);

			// Get the action, go up the hierarchy till one is found
			int action = pFileExistsNotification->overwriteAction;
			if (action == -1)
				action = CDefaultFileExistsDlg::GetDefault(pFileExistsNotification->download);
			if (action == -1)
				action = COptions::Get()->GetOptionVal(pFileExistsNotification->download ? OPTION_FILEEXISTS_DOWNLOAD : OPTION_FILEEXISTS_UPLOAD);

			// Ask and rename options require user interaction
			if (!action || action == 4)
				break;

			if (action == 3 && pFileExistsNotification->ascii)
			{
				// Check if resuming ascii files is allowed
				if (!COptions::Get()->GetOptionVal(OPTION_ASCIIRESUME))
					// Overwrite instead
					action = 1;
			}

			pFileExistsNotification->overwriteAction = (enum CFileExistsNotification::OverwriteAction)action;
			
			pEngine->SetAsyncRequestReply(pNotification);
			delete pNotification;

			return true;
		}
	case reqId_certificate:
		{
			CCertificateNotification* pCertNotification = reinterpret_cast<CCertificateNotification *>(pNotification);

			if (!m_pVerifyCertDlg->IsTrusted(pCertNotification))
				break;

			pCertNotification->m_trusted = true;
			pEngine->SetAsyncRequestReply(pNotification);
			delete pNotification;

			return true;			
		}
		break;
	default:
		break;
	}

	return false;
}

bool CAsyncRequestQueue::AddRequest(CFileZillaEngine *pEngine, CAsyncRequestNotification *pNotification)
{
	ClearPending(pEngine);

	if (ProcessDefaults(pEngine, pNotification))
		return false;

	t_queueEntry entry;
	entry.pEngine = pEngine;
	entry.pNotification = pNotification;

	m_requestList.push_back(entry);

	if (m_requestList.size() == 1)
	{
		wxCommandEvent evt(fzEVT_PROCESSASYNCREQUESTQUEUE);
		wxPostEvent(this, evt);
	}

	return true;
}

void CAsyncRequestQueue::ProcessNextRequest()
{
	if (m_requestList.empty())
		return;

	t_queueEntry &entry = m_requestList.front();

	if (!entry.pEngine->IsPendingAsyncRequestReply(entry.pNotification))
	{
		delete entry.pNotification;
		m_requestList.pop_front();
		return;
	}
		
	if (entry.pNotification->GetRequestID() == reqId_fileexists)
	{
		CFileExistsNotification *pNotification = reinterpret_cast<CFileExistsNotification *>(entry.pNotification);

		// Get the action, go up the hierarchy till one is found
		int action = pNotification->overwriteAction;
		if (action == -1)
			action = CDefaultFileExistsDlg::GetDefault(pNotification->download);
		if (action == -1)
			action = COptions::Get()->GetOptionVal(pNotification->download ? OPTION_FILEEXISTS_DOWNLOAD : OPTION_FILEEXISTS_UPLOAD);

		if (!action)
		{
			CFileExistsDlg dlg(pNotification);
			dlg.Create(m_pMainFrame);
			int res = dlg.ShowModal();

			if (res == wxID_OK)
			{
				action = dlg.GetAction() + 1;

				bool directionOnly, queueOnly;
				if (dlg.Always(directionOnly, queueOnly))
				{
					if (!queueOnly)
					{
						if (pNotification->download || !directionOnly)
							CDefaultFileExistsDlg::SetDefault(true, action);

						if (!pNotification->download || !directionOnly)
							CDefaultFileExistsDlg::SetDefault(false, action);
					}
					else
					{
						// For the notifications already in the request queue, we have to set the queue action directly
						for (std::list<t_queueEntry>::iterator iter = ++m_requestList.begin(); iter != m_requestList.end(); iter++)
						{
							if (pNotification->GetRequestID() != reqId_fileexists)
								continue;

							reinterpret_cast<CFileExistsNotification *>(entry.pNotification)->overwriteAction = CFileExistsNotification::OverwriteAction(action - 1);
						}

						enum TransferDirection direction;
						if (directionOnly)
						{
							if (pNotification->download)
								direction = download;
							else
								direction = upload;
						}
						else
							direction = both;

						if (m_pQueueView)
							m_pQueueView->SetDefaultFileExistsAction(action, direction);
					}
				}
			}
			else
				action = 5;
		}

		if (action < 1 || action > 5)
			action = 5;

		if (action == 3 && pNotification->ascii)
		{
			// Check if resuming ascii files is allowed
			if (!COptions::Get()->GetOptionVal(OPTION_ASCIIRESUME))
				// Overwrite instead
				action = 1;
		}

		switch (action)
		{
		case 4:
			{
				wxString msg;
				wxString defaultName;
				if (pNotification->download)
				{
					msg.Printf(_("The file %s already exists.\nPlease enter a new name:"), pNotification->localFile.c_str());
					wxFileName fn = pNotification->localFile;
					defaultName = fn.GetFullName();
				}
				else
				{
					wxString fullName = pNotification->remotePath.GetPath() + pNotification->remoteFile;
					msg.Printf(_("The file %s already exists.\nPlease enter a new name:"), fullName.c_str());
					defaultName = pNotification->remoteFile;
				}
				wxTextEntryDialog dlg(m_pMainFrame, msg, _("Rename file"), defaultName);

				// Repeat until user cancels or enters a new name
				while (1)
				{
					int res = dlg.ShowModal();
					if (res == wxID_OK)
					{
						if (dlg.GetValue() == _T(""))
							continue; // Disallow empty names
						if (dlg.GetValue() == defaultName)
						{
							wxMessageDialog dlg2(m_pMainFrame, _("You did not enter a new name for the file. Overwrite the file instead?"), _("Filename unchanged"), 
								wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION | wxCANCEL);
							int res = dlg2.ShowModal();

							if (res == wxID_CANCEL)
								pNotification->overwriteAction = CFileExistsNotification::skip;
							else if (res == wxID_NO)
								continue;
							else
								pNotification->overwriteAction = CFileExistsNotification::skip;
						}
						else
						{
							pNotification->overwriteAction = CFileExistsNotification::rename;
							pNotification->newName = dlg.GetValue();
						}
					}
					else
						pNotification->overwriteAction = CFileExistsNotification::skip;
					break;
				}
			}
			break;
		default:
			pNotification->overwriteAction = (enum CFileExistsNotification::OverwriteAction)action;
			break;
		}

		entry.pEngine->SetAsyncRequestReply(entry.pNotification);
		delete pNotification;
	}
	else if (entry.pNotification->GetRequestID() == reqId_interactiveLogin)
	{
		CInteractiveLoginNotification* pNotification = reinterpret_cast<CInteractiveLoginNotification*>(entry.pNotification);

		if (CLoginManager::Get().GetPassword(pNotification->server, false, _T(""), pNotification->GetChallenge()))
			pNotification->passwordSet = true;

		entry.pEngine->SetAsyncRequestReply(pNotification);
		delete pNotification;
	}
	else if (entry.pNotification->GetRequestID() == reqId_hostkey || entry.pNotification->GetRequestID() == reqId_hostkeyChanged)
	{
		CHostKeyNotification *pNotification = reinterpret_cast<CHostKeyNotification *>(entry.pNotification);

		wxDialogEx* pDlg = new wxDialogEx;
		if (pNotification->GetRequestID() == reqId_hostkey)
			pDlg->Load(m_pMainFrame, _T("ID_HOSTKEY"));
		else
			pDlg->Load(m_pMainFrame, _T("ID_HOSTKEYCHANGED"));

		pDlg->WrapText(pDlg, XRCID("ID_DESC"), 400);

		pDlg->SetLabel(XRCID("ID_HOST"), wxString::Format(_T("%s:%d"), pNotification->GetHost().c_str(), pNotification->GetPort()));
		pDlg->SetLabel(XRCID("ID_FINGERPRINT"), pNotification->GetFingerprint());

		pDlg->GetSizer()->Fit(pDlg);
		pDlg->GetSizer()->SetSizeHints(pDlg);

		int res = pDlg->ShowModal();

		if (res == wxID_OK)
		{
			pNotification->m_trust = true;
			pNotification->m_alwaysTrust = XRCCTRL(*pDlg, "ID_ALWAYS", wxCheckBox)->GetValue();
		}

		entry.pEngine->SetAsyncRequestReply(pNotification);
		delete pNotification;
	}
	else if (entry.pNotification->GetRequestID() == reqId_certificate)
	{
		CCertificateNotification* pNotification = reinterpret_cast<CCertificateNotification *>(entry.pNotification);

		m_pVerifyCertDlg->ShowVerificationDialog(pNotification);

		entry.pEngine->SetAsyncRequestReply(entry.pNotification);
		delete entry.pNotification;
	}
	else
	{
		entry.pEngine->SetAsyncRequestReply(entry.pNotification);
		delete entry.pNotification;
	}

	RecheckDefaults();
	m_requestList.pop_front();
}

void CAsyncRequestQueue::ClearPending(const CFileZillaEngine *pEngine)
{
	if (m_requestList.empty())
		return;
	
	// Remove older requests coming from the same engine, but never the first
	// entry in the list as that one displays a dialog at this moment.
	for (std::list<t_queueEntry>::iterator iter = ++m_requestList.begin(); iter != m_requestList.end(); iter++)
	{
		if (iter->pEngine == pEngine)
		{
			m_requestList.erase(iter);

			// At most one pending request per engine possible, 
			// so we can stop here
			break;
		}
	}
}

void CAsyncRequestQueue::RecheckDefaults()
{
	if (m_requestList.size() <= 1)
		return;

	std::list<t_queueEntry>::iterator cur, next;
	cur = ++m_requestList.begin();
	while (cur != m_requestList.end())
	{
		next = cur;
		next++;

		if (ProcessDefaults(cur->pEngine, cur->pNotification))
			m_requestList.erase(cur);
		cur = next;
	}
}

void CAsyncRequestQueue::SetQueue(CQueueView *pQueue)
{
	m_pQueueView = pQueue;
}

void CAsyncRequestQueue::OnProcessQueue(wxCommandEvent &event)
{
	ProcessNextRequest();

	if (!m_requestList.empty())
	{
		wxCommandEvent evt(fzEVT_PROCESSASYNCREQUESTQUEUE);
		wxPostEvent(this, evt);
	}
}


syntax highlighted by Code2HTML, v. 0.9.1