#include "FileZilla.h"
#include "viewheader.h"
#include "commandqueue.h"

#ifdef __WXMSW__
#include "wx/msw/uxtheme.h"
#endif //__WXMSW__

// wxComboBox derived class which captures WM_CANCELMODE under Windows
class CComboBoxEx : public wxComboBox
{
public:
	CComboBoxEx(CViewHeader* parent)
		: wxComboBox(parent, wxID_ANY, _T(""), wxDefaultPosition, wxDefaultSize, wxArrayString(), wxCB_DROPDOWN | wxTE_PROCESS_ENTER | wxCB_SORT)
	{
		m_parent = parent;
	}
#ifdef __WXMSW__
protected:
	CViewHeader* m_parent;
	virtual WXLRESULT MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
	{
		if (nMsg == WM_CANCELMODE)
		{
			m_parent->m_bLeftMousePressed = false;
			Refresh();
		}
		else if (nMsg == WM_CAPTURECHANGED && !lParam)
		{
			WXLRESULT res = wxComboBox::MSWDefWindowProc(nMsg, wParam, lParam);

			if (!SendMessage((HWND)GetHandle(), CB_GETDROPPEDSTATE, 0, 0))
			{
				m_parent->m_bLeftMousePressed = false;
				Refresh();
			}
			return res;
		}
		return wxComboBox::MSWDefWindowProc(nMsg, wParam, lParam);
	}
#endif //__WXMSW__

	DECLARE_EVENT_TABLE();
	void OnKeyDown(wxKeyEvent& event)
	{
		if (event.GetKeyCode() != WXK_TAB)
		{
			event.Skip();
			return;
		}

		wxNavigationKeyEvent navEvent;
		navEvent.SetEventObject(m_parent);
		navEvent.SetDirection(!event.ShiftDown());
		navEvent.SetFromTab(true);
		navEvent.ResumePropagation(1);
		m_parent->ProcessEvent(navEvent);
	}

	void OnChar(wxKeyEvent& event)
	{
		if (event.GetKeyCode() == WXK_TAB)
			return;

		event.Skip();
	}
};

BEGIN_EVENT_TABLE(CComboBoxEx, wxComboBox)
EVT_KEY_DOWN(CComboBoxEx::OnKeyDown)
EVT_CHAR(CComboBoxEx::OnChar)
END_EVENT_TABLE()

BEGIN_EVENT_TABLE(CViewHeader, wxWindow)
EVT_SIZE(CViewHeader::OnSize)
EVT_PAINT(CViewHeader::OnPaint)
END_EVENT_TABLE()

CViewHeader::CViewHeader(wxWindow* pParent, const wxString& label)
	: wxWindow(pParent, wxID_ANY)
{
	m_alreadyInPaint = false;
	m_pComboBox = new CComboBoxEx(this);
	wxSize size = GetSize();
#ifdef __WXMSW__
	size.SetHeight(m_pComboBox->GetBestSize().GetHeight());
#else
	size.SetHeight(m_pComboBox->GetBestSize().GetHeight() + 10);
#endif
	SetSize(size);

#ifdef __WXMSW__
	m_pComboBox->Connect(wxID_ANY, wxEVT_PAINT, (wxObjectEventFunction)(wxEventFunction)(wxPaintEventFunction)&CViewHeader::OnComboPaint, 0, this);
	m_pComboBox->Connect(wxID_ANY, wxEVT_LEFT_DOWN, (wxObjectEventFunction)(wxEventFunction)(wxMouseEventFunction)&CViewHeader::OnComboMouseEvent, 0, this);
	m_pComboBox->Connect(wxID_ANY, wxEVT_LEFT_UP, (wxObjectEventFunction)(wxEventFunction)(wxMouseEventFunction)&CViewHeader::OnComboMouseEvent, 0, this);
	m_bLeftMousePressed = false;
#endif //__WXMSW__
	SetLabel(label);
}

void CViewHeader::OnSize(wxSizeEvent& event)
{
	wxRect rect = GetClientRect();

	rect.SetWidth(rect.GetWidth() - m_cbOffset + 2);
	rect.SetX(m_cbOffset);
#ifndef __WXMSW__
	rect.Deflate(0, 5);
	rect.SetWidth(rect.GetWidth() - 5);
#endif
	m_pComboBox->SetSize(rect);
	Refresh();
}

#ifdef __WXMSW__

void CViewHeader::OnComboPaint(wxPaintEvent& event)
{
	// We do a small trick to let the control handle the event before we can paint
	if (m_alreadyInPaint)
	{
		event.Skip();
		return;
	}

	wxComboBox* box = m_pComboBox;

	m_alreadyInPaint = true;
	box->Refresh();
	box->Update();
	m_alreadyInPaint = false;

	wxClientDC dc(box);
	dc.SetBrush(*wxTRANSPARENT_BRUSH);

	int thumbWidth = ::GetSystemMetrics(SM_CXHTHUMB);

	if (m_bLeftMousePressed)
	{
		if (!SendMessage((HWND)box->GetHandle(), CB_GETDROPPEDSTATE, 0, 0))
			m_bLeftMousePressed = false;
	}

#if wxUSE_UXTHEME
	wxUxThemeEngine *p = wxUxThemeEngine::Get();
	if (p && p->IsThemeActive())
	{
	}
	else
#endif //wxUSE_UXTHEME
	{
		dc.SetPen(wxPen(wxSystemSettings::GetColour(box->IsEnabled() ? wxSYS_COLOUR_WINDOW : wxSYS_COLOUR_BTNFACE)));
		wxRect rect = box->GetClientRect();
		rect.Deflate(1);
		wxRect rect2 = rect;
		rect2.SetWidth(rect.GetWidth() - thumbWidth);
		dc.DrawRectangle(rect2);

		if (!m_bLeftMousePressed || !box->IsEnabled())
		{
			wxPoint topLeft = rect.GetTopLeft();
			wxPoint bottomRight = rect.GetBottomRight();
			bottomRight.x--;
			topLeft.x = bottomRight.x - thumbWidth + 1;

			dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)));
			dc.DrawLine(topLeft.x, topLeft.y, bottomRight.x + 1, topLeft.y);
			dc.DrawLine(topLeft.x, topLeft.y + 1, topLeft.x, bottomRight.y);
			dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DDKSHADOW)));
			dc.DrawLine(bottomRight.x, topLeft.y + 1, bottomRight.x, bottomRight.y + 1);
			dc.DrawLine(topLeft.x, bottomRight.y, bottomRight.x, bottomRight.y);

			topLeft.x++;
			topLeft.y++;
			bottomRight.x--;
			bottomRight.y--;
			dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNHIGHLIGHT)));
			dc.DrawLine(topLeft.x, topLeft.y, bottomRight.x + 1, topLeft.y);
			dc.DrawLine(topLeft.x, topLeft.y + 1, topLeft.x, bottomRight.y);
			dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW)));
			dc.DrawLine(bottomRight.x, topLeft.y + 1, bottomRight.x, bottomRight.y + 1);
			dc.DrawLine(topLeft.x, bottomRight.y, bottomRight.x, bottomRight.y);

			topLeft.x++;
			topLeft.y++;
			bottomRight.x--;
			bottomRight.y--;
			dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)));
			dc.DrawRectangle(wxRect(topLeft, bottomRight));
		}
		else
		{
			wxPoint topLeft = rect.GetTopLeft();
			wxPoint bottomRight = rect.GetBottomRight();
			bottomRight.x--;
			topLeft.x = bottomRight.x - thumbWidth + 1;

			dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW)));
			dc.DrawRectangle(wxRect(topLeft, bottomRight));

			topLeft.x++;
			topLeft.y++;
			bottomRight.x--;
			bottomRight.y--;
			dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)));
			dc.DrawRectangle(wxRect(topLeft, bottomRight));
		}
	}

	// Cover up dark 3D shadow.
	wxRect rect = box->GetClientRect();
	dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DDKSHADOW)));
	dc.DrawRectangle(rect);

}

void CViewHeader::OnComboMouseEvent(wxMouseEvent& event)
{
	if (event.GetEventType() == wxEVT_LEFT_UP)
		m_bLeftMousePressed = false;
	else if (event.GetEventType() == wxEVT_LEFT_DOWN)
		m_bLeftMousePressed = true;

	event.Skip();
}

#endif //__WXMSW__

void CViewHeader::OnPaint(wxPaintEvent& event)
{
	wxRect rect = GetClientRect();
	wxPaintDC dc(this);
	dc.SetPen(*wxBLACK_PEN);

#ifdef __WXMSW__
	dc.DrawLine(rect.GetLeft(), rect.GetBottom(), m_cbOffset, rect.GetBottom());
#else
	dc.DrawLine(rect.GetLeft(), rect.GetBottom(), rect.GetRight(), rect.GetBottom());
#endif

	dc.SetFont(GetFont());
	dc.DrawText(m_label, 5, (rect.GetHeight() - m_labelHeight) / 2 - 1);
}

void CViewHeader::SetLabel(const wxString& label)
{
	m_label = label;
	int w;
	GetTextExtent(label, &w, &m_labelHeight);
	m_cbOffset = w + 10;
}

void CViewHeader::Reparent(CViewHeader** pViewHeader, wxWindow* parent)
{
#if defined __WXMSW__ || defined __WXGTK__ || \
	(defined __WXMAC__ && !(defined __WXMAC_CLASSIC__))
	((wxWindow*)*pViewHeader)->Reparent(parent);
#else
#error CViewHeader::Reparent unimplemented
#endif
}

wxString CViewHeader::GetLabel() const
{
	return m_label;
}

void CViewHeader::AddRecentDirectory(const wxString &directory)
{
	// Check if directory is already in the list
	for (std::list<wxString>::const_iterator iter = m_recentDirectories.begin(); iter != m_recentDirectories.end(); iter++)
	{
		if (*iter == directory)
			return;
	}

	if (m_recentDirectories.size() == 20)
	{
		wxString dirToRemove = m_recentDirectories.front();
		m_recentDirectories.pop_front();
		if (dirToRemove == directory)
		{
			m_recentDirectories.push_back(directory);
			return;
		}
		
		int item = m_pComboBox->FindString(dirToRemove, true);
		if (item != wxNOT_FOUND)
			m_pComboBox->Delete(item);
	}

	m_recentDirectories.push_back(directory);
	m_pComboBox->Append(directory);
}

void CViewHeader::SetFocus()
{
	m_pComboBox->SetFocus();
}

bool CViewHeader::IsEnabled() const
{
	return m_pComboBox->IsEnabled();
}

#ifdef __WXGTK__
DECLARE_EVENT_TYPE(fzEVT_LOCALVIEWHEADERSETTEXTSEL, -1)
DEFINE_EVENT_TYPE(fzEVT_LOCALVIEWHEADERSETTEXTSEL)
#endif

BEGIN_EVENT_TABLE(CLocalViewHeader, CViewHeader)
EVT_TEXT(wxID_ANY, CLocalViewHeader::OnTextChanged)
EVT_TEXT_ENTER(wxID_ANY, CLocalViewHeader::OnTextEnter)
EVT_COMBOBOX(wxID_ANY, CLocalViewHeader::OnSelectionChanged)
#ifdef __WXGTK__
EVT_COMMAND(wxID_ANY, fzEVT_LOCALVIEWHEADERSETTEXTSEL, CLocalViewHeader::OnSelectTextEvent)
#endif
END_EVENT_TABLE()

CLocalViewHeader::CLocalViewHeader(wxWindow* pParent, CState* pState)
	: CViewHeader(pParent, _("Local site:")), CStateEventHandler(pState, STATECHANGE_LOCAL_DIR)
{
}

void CLocalViewHeader::OnTextChanged(wxCommandEvent& event)
{
	// This function handles auto-completion

#ifdef __WXGTK__
	m_autoCompletionText = _T("");
#endif

	wxString str = m_pComboBox->GetValue();
	if (str == _T("") || str.Right(1) == _T("/"))
	{
		m_oldValue = str;
		return;
	}
#ifdef __WXMSW__
	if (str.Right(1) == _T("\\"))
	{
		m_oldValue = str;
		return;
	}
#endif

	if (str == m_oldValue)
		return;

	if (str.Left(m_oldValue.Length()) != m_oldValue)
	{
		m_oldValue = str;
		return;
	}

	if (str.Left(2) == _T("\\\\"))
	{
		int pos = str.Mid(2).Find('\\');
		if (pos == -1)
		{
			// Partial UNC path, no full server yet, skip further processing
			return;
		}

		pos = str.Mid(pos + 3).Find('\\');
		if (pos == -1)
		{
			// Partial UNC path, no full share yet, skip further processing
			return;
		}
	}

	wxFileName fn(str);
	if (!fn.IsOk())
	{
		m_oldValue = str;
		return;
	}

	wxString name = fn.GetFullName();
	const wxString path = fn.GetPath();

	wxDir dir;
	if (name == _T("") || path == _T("") || !dir.Open(path) || !dir.IsOpened())
	{
		m_oldValue = str;
		return;
	}
	wxString found;

	{
		wxLogNull noLog;
		if (!dir.GetFirst(&found, name + _T("*"), wxDIR_DIRS))
		{
			m_oldValue = str;
			return;
		}
	}

	wxString tmp;
	if (dir.GetNext(&tmp))
	{
		m_oldValue = str;
		return;
	}

#ifdef __WXMSW__
	if (found.Left(name.Length()).CmpNoCase(name))
#else
	if (found.Left(name.Length()) != name)
#endif
	{
		m_oldValue = str;
		return;
	}

#ifdef __WXGTK__
	m_autoCompletionText = found.Mid(name.Length()) + wxFileName::GetPathSeparator();

	wxCommandEvent evt(fzEVT_LOCALVIEWHEADERSETTEXTSEL);
	wxPostEvent(this, evt);
#else
	m_pComboBox->SetValue(str + found.Mid(name.Length()) + wxFileName::GetPathSeparator());
	m_pComboBox->SetSelection(str.Length(), m_pComboBox->GetValue().Length() + 1);
#endif

	m_oldValue = str;
}

#ifdef __WXGTK__
void CLocalViewHeader::OnSelectTextEvent(wxCommandEvent& event)
{
	if (m_autoCompletionText == _T(""))
		return;

	const wxString& oldValue = m_pComboBox->GetValue();
	const wxString completionText = m_autoCompletionText;
	m_autoCompletionText = _T("");

	if (m_pComboBox->GetInsertionPoint() != (int)oldValue.Len())
		return;

	m_pComboBox->SetValue(oldValue + completionText);
	m_pComboBox->SetSelection(oldValue.Len(), oldValue.Len() + completionText.Len());
}
#endif

void CLocalViewHeader::OnSelectionChanged(wxCommandEvent& event)
{
#ifdef __WXGTK__
	m_autoCompletionText = _T("");
#endif

	wxString dir = event.GetString();
	if (dir == _T(""))
		return;

	if (!wxDir::Exists(dir))
	{
		const wxString& current = m_pState->GetLocalDir();
		int item = m_pComboBox->FindString(current, true);
		if (item != wxNOT_FOUND)
			m_pComboBox->SetSelection(item);

		wxBell();
		return;
	}

	m_pState->SetLocalDir(dir);
}

void CLocalViewHeader::OnTextEnter(wxCommandEvent& event)
{
#ifdef __WXGTK__
	m_autoCompletionText = _T("");
#endif

	wxString dir = m_pComboBox->GetValue();

	wxString error;
	if (!m_pState->SetLocalDir(dir, &error))
	{
		if (error != _T(""))
			wxMessageBox(error, _("Failed to change directory"), wxICON_INFORMATION);
		else
			wxBell();
		m_pComboBox->SetValue(m_pState->GetLocalDir());
	}
}

void CLocalViewHeader::OnStateChange(unsigned int event, const wxString& data)
{
	if (event != STATECHANGE_LOCAL_DIR)
		return;

#ifdef __WXGTK__
	m_autoCompletionText = _T("");
#endif

	wxString dir = m_pState->GetLocalDir();
	AddRecentDirectory(dir);
	m_pComboBox->SetValue(dir);
	m_pComboBox->SetSelection(m_pComboBox->GetValue().Length(), m_pComboBox->GetValue().Length());
}

BEGIN_EVENT_TABLE(CRemoteViewHeader, CViewHeader)
EVT_TEXT_ENTER(wxID_ANY, CRemoteViewHeader::OnTextEnter)
EVT_COMBOBOX(wxID_ANY, CRemoteViewHeader::OnSelectionChanged)
END_EVENT_TABLE()

CRemoteViewHeader::CRemoteViewHeader(wxWindow* pParent, CState* pState)
	: CViewHeader(pParent, _("Remote site:")), CStateEventHandler(pState, STATECHANGE_REMOTE_DIR)
{
	m_pComboBox->Disable();
}

void CRemoteViewHeader::OnStateChange(unsigned int event, const wxString& data)
{
	if (event != STATECHANGE_REMOTE_DIR)
		return;

	m_path = m_pState->GetRemotePath();
	if (m_path.IsEmpty())
	{
		m_pComboBox->SetValue(_T(""));
		m_pComboBox->Clear();
		m_pComboBox->Disable();
	}
	else
	{
		AddRecentDirectory(m_path.GetPath());
		m_pComboBox->Enable();
		m_pComboBox->SetValue(m_path.GetPath());
		wxString path = m_path.GetPath();
		wxString v = m_pComboBox->GetValue();
		wxASSERT(v == path);
		m_pComboBox->SetSelection(m_pComboBox->GetValue().Length(), m_pComboBox->GetValue().Length());

	}
}

void CRemoteViewHeader::OnTextEnter(wxCommandEvent& event)
{
	CServerPath path = m_path;
	wxString value = m_pComboBox->GetValue();
	if (value == _T("") || !path.ChangePath(value))
	{
		wxBell();
		return;
	}

	if (!m_pState->m_pCommandQueue->Idle())
	{
		wxBell();
		return;
	}

	m_pState->m_pCommandQueue->ProcessCommand(new CListCommand(path));
}

void CRemoteViewHeader::OnSelectionChanged(wxCommandEvent& event)
{
	const wxString& dir = event.GetString();
	if (dir == _T(""))
		return;

	CServerPath path = m_path;
	if (!path.SetPath(dir))
	{
		wxBell();
		return;
	}

	if (!m_pState->m_pCommandQueue->Idle())
	{
		wxBell();
		return;
	}

	m_pState->m_pCommandQueue->ProcessCommand(new CListCommand(path));
}


syntax highlighted by Code2HTML, v. 0.9.1