#include "FileZilla.h"
#include "LocalListView.h"
#include "queue.h"
#include "filezillaapp.h"
#include "filter.h"
#include "inputdialog.h"
#include <algorithm>
#include <wx/dnd.h>
#include "dndobjects.h"
#include "Options.h"
#ifdef __WXMSW__
#include "lm.h"
#include <wx/msw/registry.h>
#else
#include <langinfo.h>
#endif

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

class CLocalListViewDropTarget : public wxDropTarget
{
public:
	CLocalListViewDropTarget(CLocalListView* pLocalListView)
		: m_pLocalListView(pLocalListView), 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);
	}

	void ClearDropHighlight()
	{
		const int dropTarget = m_pLocalListView->m_dropTarget;
		if (dropTarget != -1)
		{
			m_pLocalListView->SetItemState(dropTarget, 0, wxLIST_STATE_DROPHILITED);
			m_pLocalListView->m_dropTarget = -1;
		}
	}

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

		if (m_pLocalListView->m_fileData.empty())
			return wxDragError;

		if (def != wxDragCopy && def != wxDragMove)
			return wxDragError;

		wxString subdir;
		int flags;
		int hit = m_pLocalListView->HitTest(wxPoint(x, y), flags, 0);
		if (hit != -1 && (flags & wxLIST_HITTEST_ONITEM))
		{
			const CLocalListView::t_fileData* const data = m_pLocalListView->GetData(hit);
			if (data && data->dir)
				subdir = data->name;
		}

		wxString dir;
		if (subdir != _T(""))
		{
			dir = CState::Canonicalize(m_pLocalListView->m_dir, subdir);
			if (dir == _T(""))
				return wxDragError;
		}
		else
			dir = m_pLocalListView->m_dir;

		if (!CState::LocalDirIsWriteable(dir))
			return wxDragError;

		if (!GetData())
			return wxDragError;

		if (m_pDataObject->GetReceivedFormat() == m_pFileDataObject->GetFormat())
			m_pLocalListView->m_pState->HandleDroppedFiles(m_pFileDataObject, dir, def == wxDragCopy);
		else
		{
			if (m_pRemoteDataObject->GetProcessId() != (int)wxGetProcessId())
			{
				wxMessageBox(_("Drag&drop between different instances of FileZilla has not been implemented yet."));
				return wxDragNone;
			}

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

			if (!m_pLocalListView->m_pState->DownloadDroppedFiles(m_pRemoteDataObject, dir))
				return wxDragNone;
		}

		return def;
	}

	virtual bool OnDrop(wxCoord x, wxCoord y)
	{
		ClearDropHighlight();

		if (m_pLocalListView->m_fileData.empty())
			return false;

		return true;
	}

	wxString DisplayDropHighlight(wxPoint point)
	{
		wxString subDir;

		int flags;
		int hit = m_pLocalListView->HitTest(point, flags, 0);
		if (!(flags & wxLIST_HITTEST_ONITEM))
			hit = -1;

		if (hit != -1)
		{
			const CLocalListView::t_fileData* const data = m_pLocalListView->GetData(hit);
			if (!data || !data->dir)
				hit = -1;
			else
				subDir = data->name;
		}
		if (hit != m_pLocalListView->m_dropTarget)
		{
			ClearDropHighlight();
			if (hit != -1)
			{
				m_pLocalListView->SetItemState(hit, wxLIST_STATE_DROPHILITED, wxLIST_STATE_DROPHILITED);
				m_pLocalListView->m_dropTarget = hit;
			}
		}

		return subDir;
	}

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

		if (m_pLocalListView->m_fileData.empty())
		{
			ClearDropHighlight();
			return wxDragNone;
		}

		const wxString& subdir = DisplayDropHighlight(wxPoint(x, y));

		if (subdir == _T(""))
		{
			if (!CState::LocalDirIsWriteable(m_pLocalListView->m_dir))
				return wxDragNone;
		}
		else
		{
			wxString dir = CState::Canonicalize(m_pLocalListView->m_dir, subdir);
			if (dir == _T("") || !CState::LocalDirIsWriteable(dir))
				return wxDragNone;
		}

		if (def == wxDragLink)
			def = wxDragCopy;

		return def;
	}

	virtual void OnLeave()
	{
		ClearDropHighlight();
	}

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

protected:
	CLocalListView *m_pLocalListView;
	wxFileDataObject* m_pFileDataObject;
	CRemoteDataObject* m_pRemoteDataObject;
	wxDataObjectComposite* m_pDataObject;
};

BEGIN_EVENT_TABLE(CLocalListView, wxListCtrl)
	EVT_LIST_ITEM_ACTIVATED(wxID_ANY, CLocalListView::OnItemActivated)
	EVT_LIST_COL_CLICK(wxID_ANY, CLocalListView::OnColumnClicked)
	EVT_CONTEXT_MENU(CLocalListView::OnContextMenu)
	// Map both ID_UPLOAD and ID_ADDTOQUEUE to OnMenuUpload, code is identical
	EVT_MENU(XRCID("ID_UPLOAD"), CLocalListView::OnMenuUpload)
	EVT_MENU(XRCID("ID_ADDTOQUEUE"), CLocalListView::OnMenuUpload)
	EVT_MENU(XRCID("ID_MKDIR"), CLocalListView::OnMenuMkdir)
	EVT_MENU(XRCID("ID_DELETE"), CLocalListView::OnMenuDelete)
	EVT_MENU(XRCID("ID_RENAME"), CLocalListView::OnMenuRename)
	EVT_KEY_DOWN(CLocalListView::OnKeyDown)
	EVT_LIST_BEGIN_LABEL_EDIT(wxID_ANY, CLocalListView::OnBeginLabelEdit)
	EVT_LIST_END_LABEL_EDIT(wxID_ANY, CLocalListView::OnEndLabelEdit)
	EVT_LIST_BEGIN_DRAG(wxID_ANY, CLocalListView::OnBeginDrag)
END_EVENT_TABLE()

CLocalListView::CLocalListView(wxWindow* parent, wxWindowID id, CState *pState, CQueueView *pQueue)
	: wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxLC_VIRTUAL | wxLC_REPORT | wxNO_BORDER | wxLC_EDIT_LABELS),
	CSystemImageList(16), CStateEventHandler(pState, STATECHANGE_LOCAL_DIR | STATECHANGE_APPLYFILTER | STATECHANGE_LOCAL_REFRESH_FILE)
{
	m_dropTarget = -1;

	m_hasParent = true;

	m_pQueue = pQueue;

	unsigned long widths[4] = { 120, 80, 100, 120 };

	if (!wxGetKeyState(WXK_SHIFT) || !wxGetKeyState(WXK_ALT) || !wxGetKeyState(WXK_CONTROL))
		COptions::Get()->ReadColumnWidths(OPTION_LOCALFILELIST_COLUMN_WIDTHS, 4, widths);

	InsertColumn(0, _("Filename"), wxLIST_FORMAT_LEFT, widths[0]);
	InsertColumn(1, _("Filesize"), wxLIST_FORMAT_RIGHT, widths[1]);
	InsertColumn(2, _("Filetype"), wxLIST_FORMAT_LEFT, widths[2]);
	InsertColumn(3, _("Last modified"), wxLIST_FORMAT_LEFT, widths[3]);

	wxString sortInfo = COptions::Get()->GetOption(OPTION_LOCALFILELIST_SORTORDER);
	m_sortDirection = sortInfo[0] - '0';
	if (m_sortDirection < 0 || m_sortDirection > 1)
		m_sortDirection = 0;

	if (sortInfo.Len() == 3)
	{
		m_sortColumn = sortInfo[2] - '0';
		if (m_sortColumn < 0 || m_sortColumn > 3)
			m_sortColumn = 0;
	}
	else
		m_sortColumn = 0;

	SetImageList(GetSystemImageList(), wxIMAGE_LIST_SMALL);

#ifdef __WXMSW__
	// Initialize imagelist for list header
	m_pHeaderImageList = new wxImageListEx(8, 8, true, 3);

	wxBitmap bmp;

	bmp.LoadFile(wxGetApp().GetResourceDir() + _T("empty.png"), wxBITMAP_TYPE_PNG);
	m_pHeaderImageList->Add(bmp);
	bmp.LoadFile(wxGetApp().GetResourceDir() + _T("up.png"), wxBITMAP_TYPE_PNG);
	m_pHeaderImageList->Add(bmp);
	bmp.LoadFile(wxGetApp().GetResourceDir() + _T("down.png"), wxBITMAP_TYPE_PNG);
	m_pHeaderImageList->Add(bmp);

	HWND hWnd = (HWND)GetHandle();
	if (!hWnd)
	{
		delete m_pHeaderImageList;
		m_pHeaderImageList = 0;
		return;
	}

	HWND header = (HWND)SendMessage(hWnd, LVM_GETHEADER, 0, 0);
	if (!header)
	{
		delete m_pHeaderImageList;
		m_pHeaderImageList = 0;
		return;
	}

	TCHAR buffer[1000] = {0};
	HDITEM item;
	item.mask = HDI_TEXT;
	item.pszText = buffer;
	item.cchTextMax = 999;
	SendMessage(header, HDM_GETITEM, 0, (LPARAM)&item);

	SendMessage(header, HDM_SETIMAGELIST, 0, (LPARAM)m_pHeaderImageList->GetHandle());
#endif

	SetDropTarget(new CLocalListViewDropTarget(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_KEY_DOWN, wxKeyEventHandler(CLocalListView::OnKeyDown), 0, this);
#endif

	InitDateFormat();
}

CLocalListView::~CLocalListView()
{
	wxString str = wxString::Format(_T("%d %d"), m_sortDirection, m_sortColumn);
	COptions::Get()->SetOption(OPTION_LOCALFILELIST_SORTORDER, str);
#ifdef __WXMSW__
	delete m_pHeaderImageList;
#endif
}

bool CLocalListView::DisplayDir(wxString dirname)
{
	wxString focused;
	std::list<wxString> selectedNames;
	if (m_dir != dirname)
	{
		// Clear selection
		int item = -1;
		while (true)
		{
			item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
			if (item == -1)
				break;
			SetItemState(item, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
		}
		focused = _T("..");

		if (GetItemCount())
			EnsureVisible(0);
		m_dir = dirname;
	}
	else
	{
		// Remember which items were selected
		selectedNames = RememberSelectedItems(focused);
	}

	const int oldItemCount = m_indexMapping.size();

	m_fileData.clear();
	m_indexMapping.clear();

	m_hasParent = CState::LocalDirHasParent(dirname);

	if (m_hasParent)
	{
		t_fileData data;
		data.dir = true;
		data.icon = -2;
		data.name = _T("..");
		data.size = -1;
		data.hasTime = 0;
		m_fileData.push_back(data);
		m_indexMapping.push_back(0);
	}

#ifdef __WXMSW__
	if (dirname == _T("\\"))
	{
		DisplayDrives();
	}
	else if (dirname.Left(2) == _T("\\\\"))
	{
		int pos = dirname.Mid(2).Find('\\');
		if (pos != -1 && pos + 3 != (int)dirname.Len())
			goto regular_dir;

		// UNC path without shares
		DisplayShares(dirname);
	}
	else
#endif
	{
#ifdef __WXMSW__
regular_dir:
#endif
		CFilterDialog filter;

		wxDir dir(dirname);
		if (!dir.IsOpened())
		{
			SetItemCount(1);
			return false;
		}
		wxString file;
		bool found = dir.GetFirst(&file);
		int num = m_fileData.size();
		while (found)
		{
			if (file == _T(""))
			{
				wxGetApp().DisplayEncodingWarning();
				found = dir.GetNext(&file);
				continue;
			}
			t_fileData data;

			data.dir = wxFileName::DirExists(dirname + file);
			data.icon = -2;
			data.name = file;

			wxStructStat buf;
			int result;
			result = wxStat(dirname + file, &buf);

			if (!result)
			{
				data.hasTime = true;
				data.lastModified = wxDateTime(buf.st_mtime);
			}
			else
				data.hasTime = false;

			if (data.dir)
				data.size = -1;
			else
				data.size = result ? -1 : buf.st_size;

			m_fileData.push_back(data);
			if (!filter.FilenameFiltered(data.name, data.dir, data.size, true))
				m_indexMapping.push_back(num);
			num++;

			found = dir.GetNext(&file);
		}
	}

	if (m_dropTarget != -1)
	{
		t_fileData* data = GetData(m_dropTarget);
		if (!data || !data->dir)
		{
			SetItemState(m_dropTarget, 0, wxLIST_STATE_DROPHILITED);
			m_dropTarget = -1;
		}
	}

	const int count = m_indexMapping.size();
	if (oldItemCount != count)
		SetItemCount(count);

	SortList();

	ReselectItems(selectedNames, focused);

	Refresh();

	return true;
}

wxString FormatSize(const wxLongLong& size)
{
	COptions* const pOptions = COptions::Get();
	const int format = pOptions->GetOptionVal(OPTION_SIZE_FORMAT);

	if (!format)
	{
		if (!pOptions->GetOptionVal(OPTION_SIZE_USETHOUSANDSEP))
			return size.ToString();

#ifdef __WXMSW__
		wxChar sep[5];
		int count = ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, sep, 5);
		if (!count)
			return size.ToString();
#else
		char* chr = nl_langinfo(THOUSEP);
		if (!chr || !*chr)
			return size.ToString();
		wxChar sep = chr[0];
#endif

		wxString tmp = size.ToString();
		const int len = tmp.Len();
		if (len <= 3)
			return tmp;

		wxString result;
		int i = (len - 1) % 3 + 1;
		result = tmp.Left(i);
		while (i < len)
		{
			result += sep + tmp.Mid(i, 3);
			i += 3;
		}

		return result;
	}

	int divider;
	if (format == 3)
		divider = 1000;
	else
		divider = 1024;

	bool r2 = false;
	int p = 0;
	wxLongLong r = size;
	while (r > divider || p == 6)
	{
		const wxLongLong rr = r / divider;
		if (rr * divider != r)
			r2 = true;
		r = rr;
		p++;
	}
	if (r2)
		r++;

	wxString result = r.ToString();
	result += ' ';
	if (!p)
		return result + _T("B");

	// We stop at Exa. If someone has files bigger than that, he can afford to 
	// make a donation to have this changed ;)
	wxChar prefix[] = { ' ', 'K', 'M', 'G', 'T', 'P', 'E' };

	result += prefix[p];
	if (format == 1)
		result += 'i';
	result += 'B';

	return result;
}

// Declared const due to design error in wxWidgets.
// Won't be fixed since a fix would break backwards compatibility
// Both functions use a const_cast<CLocalListView *>(this) and modify
// the instance.
wxString CLocalListView::OnGetItemText(long item, long column) const
{
	CLocalListView *pThis = const_cast<CLocalListView *>(this);
	t_fileData *data = pThis->GetData(item);
	if (!data)
		return _T("");

	if (!column)
		return data->name;
	else if (column == 1)
	{
		if (data->size < 0)
			return _T("");
		else
			return FormatSize(data->size);
	}
	else if (column == 2)
	{
		if (!item && m_hasParent)
			return _T("");

		if (data->fileType == _T(""))
			data->fileType = pThis->GetType(data->name, data->dir);

		return data->fileType;
	}
	else if (column == 3)
	{
		if (!data->hasTime)
			return _T("");

		return data->lastModified.Format(m_dateFormat);
	}
	return _T("");
}

// See comment to OnGetItemText
int CLocalListView::OnGetItemImage(long item) const
{
	CLocalListView *pThis = const_cast<CLocalListView *>(this);
	t_fileData *data = pThis->GetData(item);
	if (!data)
		return -1;
	int &icon = data->icon;

	if (icon == -2)
	{
		wxString path = _T("");
		if (data->name != _T(".."))
		{
#ifdef __WXMSW__
			if (m_dir == _T("\\"))
				path = data->name + _T("\\");
			else
#endif
				path = m_dir + data->name;
		}

		icon = pThis->GetIconIndex(data->dir ? dir : file, path);
	}
	return icon;
}

void CLocalListView::OnItemActivated(wxListEvent &event)
{
	int count = 0;
	bool back = false;

	int item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;

		count++;

		if (!item && m_hasParent)
			back = true;
	}
	if (count > 1)
	{
		if (back)
		{
			wxBell();
			return;
		}

		wxCommandEvent cmdEvent;
		OnMenuUpload(cmdEvent);
		return;
	}

	item = event.GetIndex();

	t_fileData *data = GetData(item);
	if (!data)
		return;

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

	const CServer* pServer = m_pState->GetServer();
	if (!pServer)
	{
		wxBell();
		return;
	}

	CServerPath path = m_pState->GetRemotePath();
	if (path.IsEmpty())
	{
		wxBell();
		return;
	}

	wxFileName fn(m_dir, data->name);

	m_pQueue->QueueFile(false, false, fn.GetFullPath(), data->name, path, *pServer, data->size);
}

#ifdef __WXMSW__
void CLocalListView::DisplayDrives()
{
	long drivesToHide = 0;
	// Adhere to the NODRIVES group policy
	wxRegKey key(_T("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer"));
	if (key.Exists())
	{
		if (!key.HasValue(_T("NoDrives")) || !key.QueryValue(_T("NoDrives"), &drivesToHide))
			drivesToHide = 0;
	}

	int len = GetLogicalDriveStrings(0, 0);
	if (!len)
		return;

	wxChar* drives = new wxChar[len + 1];

	if (!GetLogicalDriveStrings(len, drives))
	{
		delete [] drives;
		return;
	}

	const wxChar* pDrive = drives;

	int count = m_fileData.size();
	while(*pDrive)
	{
		// Check if drive should be hidden by default
		if (pDrive[0] != 0 && pDrive[1] == ':')
		{
			int bit = -1;
			char letter = pDrive[0];
			if (letter >= 'A' && letter <= 'Z')
				bit = 1 << (letter - 'A');
			if (letter >= 'a' && letter <= 'z')
				bit = 1 << (letter - 'a');

			if (bit != -1 && drivesToHide & bit)
			{
				pDrive += wxStrlen(pDrive) + 1;
				continue;
			}
		}

		wxString path = pDrive;
		if (path.Right(1) == _T("\\"))
			path.Truncate(path.Length() - 1);

		t_fileData data;
		data.name = path;
		data.dir = true;
		data.icon = -2;
		data.size = -1;
		data.hasTime = false;

		m_fileData.push_back(data);
		m_indexMapping.push_back(count);
		pDrive += wxStrlen(pDrive) + 1;
		count++;
	}

	delete [] drives;
}

void CLocalListView::DisplayShares(wxString computer)
{
	SHARE_INFO_1* pShareInfo = 0;

	DWORD read, total;
	DWORD resume_handle = 0;

	if (computer.Last() == '\\')
		computer.RemoveLast();

	int j = m_fileData.size();
	int res = 0;
	do
	{
		const wxWX2WCbuf buf = computer.wc_str(wxConvLocal);
		res = NetShareEnum((wchar_t*)(const wchar_t*)buf, 1, (LPBYTE*)&pShareInfo, MAX_PREFERRED_LENGTH, &read, &total, &resume_handle);

		if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA)
			break;

		SHARE_INFO_1* p = pShareInfo;
		for (unsigned int i = 0; i < read; i++, p++)
		{
			if (p->shi1_type != STYPE_DISKTREE)
				continue;

			t_fileData data;
			data.name = p->shi1_netname;
			data.dir = true;
			data.icon = -2;
			data.size = -1;
			data.hasTime = false;

			m_fileData.push_back(data);
			m_indexMapping.push_back(j++);
		}

		NetApiBufferFree(pShareInfo);
	}
	while (res == ERROR_MORE_DATA);
}

#endif

wxString CLocalListView::GetType(wxString name, bool dir)
{
#ifdef __WXMSW__
	wxString ext = wxFileName(name).GetExt();
	ext.MakeLower();
	std::map<wxString, wxString>::iterator typeIter = m_fileTypeMap.find(ext);
	if (typeIter != m_fileTypeMap.end())
		return typeIter->second;

	wxString type;

	wxString path;
	if (m_dir == _T("\\"))
		path = name + _T("\\");
	else
		path = m_dir + name;

	SHFILEINFO shFinfo;
	memset(&shFinfo, 0, sizeof(SHFILEINFO));
	if (SHGetFileInfo(path,
		dir ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL,
		&shFinfo,
		sizeof(shFinfo),
		SHGFI_TYPENAME))
	{
		type = shFinfo.szTypeName;
		if (type == _T(""))
		{
			type = ext;
			type.MakeUpper();
			if (!type.IsEmpty())
			{
				type += _T("-");
				type += _("file");
			}
			else
				type = _("File");
		}
		else
		{
			if (!dir && ext != _T(""))
				m_fileTypeMap[ext.MakeLower()] = type;
		}
	}
	else
	{
		type = ext;
		type.MakeUpper();
		if (!type.IsEmpty())
		{
			type += _T("-");
			type += _("file");
		}
		else
			type = _("File");
	}
	return type;
#else
	if (dir)
		return _("Folder");

	wxFileName fn(name);
	wxString ext = fn.GetExt();
	if (ext == _T(""))
		return _("File");

	std::map<wxString, wxString>::iterator typeIter = m_fileTypeMap.find(ext);
	if (typeIter != m_fileTypeMap.end())
		return typeIter->second;

	wxString desc;
	wxFileType *pType = wxTheMimeTypesManager->GetFileTypeFromExtension(ext);
	if (!pType)
	{
		m_fileTypeMap[ext] = desc;
		return desc;
	}

	if (pType->GetDescription(&desc) && desc != _T(""))
	{
		delete pType;
		m_fileTypeMap[ext] = desc;
		return desc;
	}
	delete pType;

	desc = _("File");
	m_fileTypeMap[ext.MakeLower()] = desc;
	return desc;
#endif
}

CLocalListView::t_fileData* CLocalListView::GetData(unsigned int item)
{
	if (!IsItemValid(item))
		return 0;

	return &m_fileData[m_indexMapping[item]];
}

bool CLocalListView::IsItemValid(unsigned int item) const
{
	if (item >= m_indexMapping.size())
		return false;

	unsigned int index = m_indexMapping[item];
	if (index >= m_fileData.size())
		return false;

	return true;
}

// Helper classes for fast sorting using std::sort
// -----------------------------------------------

class CLocalListViewSort //: public std::binary_function<int,int,bool>
{
public:
	enum DirSortMode
	{
		dirsort_ontop,
		dirsort_onbottom,
		dirsort_inline
	};

	CLocalListViewSort(std::vector<CLocalListView::t_fileData>& fileData, enum DirSortMode dirSortMode)
		: m_fileData(fileData), m_dirSortMode(dirSortMode)
	{
	}

	virtual ~CLocalListViewSort() { }

	virtual bool operator()(int a, int b) const = 0;

	#define CMP(f, data1, data2) \
		{\
			int res = f(data1, data2);\
			if (res == -1)\
				return true;\
			else if (res == 1)\
				return false;\
		}

	#define CMP_LESS(f, data1, data2) \
		{\
			int res = f(data1, data2);\
			if (res == -1)\
				return true;\
			else\
				return false;\
		}

	inline int CmpDir(const CLocalListView::t_fileData &data1, const CLocalListView::t_fileData &data2) const
	{
		switch (m_dirSortMode)
		{
		default:
		case dirsort_ontop:
			if (data1.dir)
			{
				if (!data2.dir)
					return -1;
				else
					return 0;
			}
			else
			{
				if (data2.dir)
					return 1;
				else
					return 0;
			}
		case dirsort_onbottom:
			if (data1.dir)
			{
				if (!data2.dir)
					return 1;
				else
					return 0;
			}
			else
			{
				if (data2.dir)
					return -1;
				else
					return 0;
			}
		case dirsort_inline:
			return 0;
		}
	}

	inline int CmpName(const CLocalListView::t_fileData &data1, const CLocalListView::t_fileData &data2) const
	{
#ifdef __WXMSW__
		return data1.name.CmpNoCase(data2.name);
#else
		return data1.name.Cmp(data2.name);
#endif
	}

	inline int CmpSize(const CLocalListView::t_fileData &data1, const CLocalListView::t_fileData &data2) const
	{
		const wxLongLong diff = data1.size - data2.size;
		if (diff < 0)
			return -1;
		else if (diff > 0)
			return 1;
		else
			return 0;
	}

	inline int CmpType(const CLocalListView::t_fileData &data1, const CLocalListView::t_fileData &data2) const
	{
		return data1.fileType.CmpNoCase(data2.fileType);
	}

	inline int CmpTime(const CLocalListView::t_fileData &data1, const CLocalListView::t_fileData &data2) const
	{
		if (!data1.hasTime)
		{
			if (data2.hasTime)
				return -1;
			else
				return 0;
		}
		else
		{
			if (!data2.hasTime)
				return 1;

			if (data1.lastModified < data2.lastModified)
				return -1;
			else if (data1.lastModified > data2.lastModified)
				return 1;
			else
				return 0;
		}
	}

protected:
	std::vector<CLocalListView::t_fileData>& m_fileData;

	DirSortMode m_dirSortMode;
};

class CLocalListViewSortObject : public std::binary_function<int,int,bool>
{
public:
	CLocalListViewSortObject(CLocalListViewSort* pObject)
		: m_pObject(pObject)
	{
	}

	void Destroy()
	{
		delete m_pObject;
	}

	inline bool operator()(int a, int b)
	{
		return m_pObject->operator ()(a, b);
	}
protected:
	CLocalListViewSort* m_pObject;
};

template<class T> class CReverseSort : public T
{
public:
	CReverseSort(std::vector<CLocalListView::t_fileData>& fileData, enum CLocalListViewSort::DirSortMode dirSortMode, CLocalListView* pListView)
		: T(fileData, dirSortMode, pListView)
	{
	}

	inline bool operator()(int a, int b) const
	{
		return T::operator()(b, a);
	}
};

class CLocalListViewSortName : public CLocalListViewSort
{
public:
	CLocalListViewSortName(std::vector<CLocalListView::t_fileData>& fileData, enum DirSortMode dirSortMode, CLocalListView* pListView)
		: CLocalListViewSort(fileData, dirSortMode)
	{
	}

	bool operator()(int a, int b) const
	{
		const CLocalListView::t_fileData &data1 = m_fileData[a];
		const CLocalListView::t_fileData &data2 = m_fileData[b];

		CMP(CmpDir, data1, data2);

		CMP_LESS(CmpName, data1, data2);
	}
};
typedef CReverseSort<CLocalListViewSortName> CLocalListViewSortName_Reverse;

class CLocalListViewSortSize : public CLocalListViewSort
{
public:
	CLocalListViewSortSize(std::vector<CLocalListView::t_fileData>& fileData, enum DirSortMode dirSortMode, CLocalListView* pListView)
		: CLocalListViewSort(fileData, dirSortMode)
	{
	}

	bool operator()(int a, int b) const
	{
		const CLocalListView::t_fileData &data1 = m_fileData[a];
		const CLocalListView::t_fileData &data2 = m_fileData[b];

		CMP(CmpDir, data1, data2);

		if (!data1.dir)
			CMP(CmpSize, data1, data2)

		CMP_LESS(CmpName, data1, data2);
	}
};
typedef CReverseSort<CLocalListViewSortSize> CLocalListViewSortSize_Reverse;

class CLocalListViewSortType : public CLocalListViewSort
{
public:
	CLocalListViewSortType(std::vector<CLocalListView::t_fileData>& fileData, enum DirSortMode dirSortMode, CLocalListView* pListView)
		: CLocalListViewSort(fileData, dirSortMode)
	{
		m_pListView = pListView;
	}

	bool operator()(int a, int b) const
	{
		CLocalListView::t_fileData &data1 = m_fileData[a];
		CLocalListView::t_fileData &data2 = m_fileData[b];

		if (data1.fileType == _T(""))
			data1.fileType = m_pListView->GetType(data1.name, data1.dir);
		if (data2.fileType == _T(""))
			data2.fileType = m_pListView->GetType(data2.name, data2.dir);

		CMP(CmpDir, data1, data2);

		CMP(CmpType, data1, data2)

		CMP_LESS(CmpName, data1, data2);
	}

protected:
	CLocalListView* m_pListView;
};
typedef CReverseSort<CLocalListViewSortType> CLocalListViewSortType_Reverse;

class CLocalListViewSortTime : public CLocalListViewSort
{
public:
	CLocalListViewSortTime(std::vector<CLocalListView::t_fileData>& fileData, enum DirSortMode dirSortMode, CLocalListView* pListView)
		: CLocalListViewSort(fileData, dirSortMode)
	{
	}

	bool operator()(int a, int b) const
	{
		const CLocalListView::t_fileData &data1 = m_fileData[a];
		const CLocalListView::t_fileData &data2 = m_fileData[b];

		CMP(CmpDir, data1, data2);

		CMP(CmpTime, data1, data2)

		CMP_LESS(CmpName, data1, data2);
	}
};
typedef CReverseSort<CLocalListViewSortTime> CLocalListViewSortTime_Reverse;

CLocalListViewSortObject CLocalListView::GetComparisonObject()
{
	const int dirSortOption = COptions::Get()->GetOptionVal(OPTION_FILELIST_DIRSORT);

	CLocalListViewSort::DirSortMode dirSortMode;
	switch (dirSortOption)
	{
	case 0:
	default:
		dirSortMode = CLocalListViewSort::dirsort_ontop;
		break;
	case 1:
		if (m_sortDirection)
			dirSortMode = CLocalListViewSort::dirsort_onbottom;
		else
			dirSortMode = CLocalListViewSort::dirsort_ontop;
		break;
	case 2:
		dirSortMode = CLocalListViewSort::dirsort_inline;
		break;
	}

	if (!m_sortDirection)
	{
		if (m_sortColumn == 1)
			return CLocalListViewSortObject(new CLocalListViewSortSize(m_fileData, dirSortMode, this));
		else if (m_sortColumn == 2)
			return CLocalListViewSortObject(new CLocalListViewSortType(m_fileData, dirSortMode, this));
		else if (m_sortColumn == 3)
			return CLocalListViewSortObject(new CLocalListViewSortTime(m_fileData, dirSortMode, this));
		else
			return CLocalListViewSortObject(new CLocalListViewSortName(m_fileData, dirSortMode, this));
	}
	else
	{
		if (m_sortColumn == 1)
			return CLocalListViewSortObject(new CLocalListViewSortSize_Reverse(m_fileData, dirSortMode, this));
		else if (m_sortColumn == 2)
			return CLocalListViewSortObject(new CLocalListViewSortType_Reverse(m_fileData, dirSortMode, this));
		else if (m_sortColumn == 3)
			return CLocalListViewSortObject(new CLocalListViewSortTime_Reverse(m_fileData, dirSortMode, this));
		else
			return CLocalListViewSortObject(new CLocalListViewSortName_Reverse(m_fileData, dirSortMode, this));
	}
}

void CLocalListView::SortList(int column /*=-1*/, int direction /*=-1*/)
{
	if (column != -1)
	{
#ifdef __WXMSW__
		if (column != m_sortColumn && m_pHeaderImageList)
		{
			HWND hWnd = (HWND)GetHandle();
			HWND header = (HWND)SendMessage(hWnd, LVM_GETHEADER, 0, 0);

			wxChar buffer[100];
			HDITEM item;
			item.mask = HDI_TEXT | HDI_FORMAT;
			item.pszText = buffer;
			item.cchTextMax = 99;
			SendMessage(header, HDM_GETITEM, m_sortColumn, (LPARAM)&item);
			item.mask |= HDI_IMAGE;
			item.fmt |= HDF_IMAGE | HDF_BITMAP_ON_RIGHT;
			item.iImage = 0;
			SendMessage(header, HDM_SETITEM, m_sortColumn, (LPARAM)&item);
		}
#endif
		m_sortColumn = column;
	}
	else
		column = m_sortColumn;

	if (direction == -1)
		direction = m_sortDirection;

#ifdef __WXMSW__
	if (m_pHeaderImageList)
	{
		HWND hWnd = (HWND)GetHandle();
		HWND header = (HWND)SendMessage(hWnd, LVM_GETHEADER, 0, 0);

		wxChar buffer[100];
		HDITEM item;
		item.mask = HDI_TEXT | HDI_FORMAT;
		item.pszText = buffer;
		item.cchTextMax = 99;
		SendMessage(header, HDM_GETITEM, column, (LPARAM)&item);
		item.mask |= HDI_IMAGE;
		item.fmt |= HDF_IMAGE | HDF_BITMAP_ON_RIGHT;
		item.iImage = direction ? 2 : 1;
		SendMessage(header, HDM_SETITEM, column, (LPARAM)&item);
	}
#endif

	// Remember which files are selected
	bool *selected = new bool[m_fileData.size()];
	memset(selected, 0, sizeof(bool) * m_fileData.size());

	int item = -1;
	while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1)
		selected[m_indexMapping[item]] = 1;
	int focused = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
	if (focused != -1)
		focused = m_indexMapping[focused];

	const int dirSortOption = COptions::Get()->GetOptionVal(OPTION_FILELIST_DIRSORT);

	if (column == m_sortColumn && direction != m_sortDirection && !m_indexMapping.empty() &&
		dirSortOption != 1)
	{
		// Simply reverse everything
		m_sortDirection = direction;
		m_sortColumn = column;
		std::vector<unsigned int>::iterator start = m_indexMapping.begin();
		if (m_hasParent)
			start++;
		std::reverse(start, m_indexMapping.end());

		SortList_UpdateSelections(selected, focused);
		delete [] selected;

		return;
	}

	m_sortDirection = direction;
	m_sortColumn = column;

	const unsigned int minsize = m_hasParent ? 3 : 2;
	if (m_indexMapping.size() < minsize)
	{
		delete [] selected;
		return;
	}

	std::vector<unsigned int>::iterator start = m_indexMapping.begin();
	if (m_hasParent)
		start++;
	CLocalListViewSortObject object = GetComparisonObject();
	std::sort(start, m_indexMapping.end(), object);
	object.Destroy();

	SortList_UpdateSelections(selected, focused);
	delete [] selected;
}

void CLocalListView::SortList_UpdateSelections(bool* selections, int focus)
{
	for (int i = m_hasParent ? 1 : 0; i < GetItemCount(); i++)
	{
		const int state = GetItemState(i, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
		const bool selected = (state & wxLIST_STATE_SELECTED) != 0;
		const bool focused = (state & wxLIST_STATE_FOCUSED) != 0;

		int item = m_indexMapping[i];
		if (selections[item] != selected)
			SetItemState(i, selections[item] ? wxLIST_STATE_SELECTED : 0, wxLIST_STATE_SELECTED);
		if (focused)
		{
			if (item != focus)
				SetItemState(i, 0, wxLIST_STATE_FOCUSED);
		}
		else
		{
			if (item == focus)
				SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
		}
	}
}

void CLocalListView::OnColumnClicked(wxListEvent &event)
{
	int col = event.GetColumn();
	if (col == -1)
		return;

	int dir;
	if (col == m_sortColumn)
		dir = m_sortDirection ? 0 : 1;
	else
		dir = m_sortDirection;

	SortList(col, dir);
	Refresh(false);
}

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

	int index = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
	int count = 0;
	while (index != -1)
	{
		count++;
		if (!index && m_hasParent)
		{
			pMenu->Enable(XRCID("ID_UPLOAD"), false);
			pMenu->Enable(XRCID("ID_ADDTOQUEUE"), false);
			pMenu->Enable(XRCID("ID_DELETE"), false);
			pMenu->Enable(XRCID("ID_RENAME"), false);
		}
		index = GetNextItem(index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
	}
	if (!count)
	{
		pMenu->Enable(XRCID("ID_UPLOAD"), false);
		pMenu->Enable(XRCID("ID_ADDTOQUEUE"), false);
		pMenu->Enable(XRCID("ID_DELETE"), false);
		pMenu->Enable(XRCID("ID_RENAME"), false);
	}
	else if (count > 1)
		pMenu->Enable(XRCID("ID_RENAME"), false);

	PopupMenu(pMenu);
	delete pMenu;
}

void CLocalListView::OnMenuUpload(wxCommandEvent& event)
{
	long item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;

		if (!item && m_hasParent)
			return;
	}

	item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;

		t_fileData *data = GetData(item);
		if (!data)
			return;

		if (!item && m_hasParent)
			m_pState->SetLocalDir(data->name);
		else
		{
			const CServer* pServer = m_pState->GetServer();
			if (!pServer)
			{
				wxBell();
				return;
			}

			CServerPath path = m_pState->GetRemotePath();
			if (path.IsEmpty())
			{
				wxBell();
				return;
			}

			if (data->dir)
			{
				path.ChangePath(data->name);

				wxFileName fn(m_dir, _T(""));
				fn.AppendDir(data->name);
				m_pQueue->QueueFolder(event.GetId() == XRCID("ID_ADDTOQUEUE"), false, fn.GetPath(), path, *pServer);
			}
			else
			{
				wxFileName fn(m_dir, data->name);

				m_pQueue->QueueFile(event.GetId() == XRCID("ID_ADDTOQUEUE"), false, fn.GetFullPath(), data->name, path, *pServer, data->size);
			}
		}
	}
}

void CLocalListView::OnMenuMkdir(wxCommandEvent& event)
{
	CInputDialog dlg;
	if (!dlg.Create(this, _("Create directory"), _("Please enter the name of the directory which should be created:")))
		return;

	if (dlg.ShowModal() != wxID_OK)
		return;

	if (dlg.GetValue() == _T(""))
	{
		wxBell();
		return;
	}

	wxFileName fn(dlg.GetValue(), _T(""));
	fn.Normalize(wxPATH_NORM_ALL, m_dir);

	bool res;

	{
		wxLogNull log;
		res = fn.Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL);
	}

	if (!res)
		wxBell();

	DisplayDir(m_dir);
}

void CLocalListView::OnMenuDelete(wxCommandEvent& event)
{
	// Under Windows use SHFileOperation to delete files and directories.
	// Under other systems, we have to recurse into subdirectories manually
	// to delete all contents.

#ifdef __WXMSW__
	// SHFileOperation accepts a list of null-terminated strings. Go through all
	// items to get the required buffer length

	wxString dir = m_dir;
	if (dir.Right(1) != _T("\\") && dir.Right(1) != _T("/"))
		dir += _T("\\");
	int dirLen = dir.Length();

	int len = 1; // String list terminated by empty string

	long item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;

		if (!item && m_hasParent)
			continue;

		t_fileData *data = GetData(item);
		if (!data)
			continue;

		len += dirLen + data->name.Length() + 1;
	}

	// Allocate memory
	wxChar* pBuffer = new wxChar[len];
	wxChar* p = pBuffer;

	// Loop through all selected items again and fill buffer
	item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;

		if (!item && m_hasParent)
			continue;

		t_fileData *data = GetData(item);
		if (!data)
			continue;

		_tcscpy(p, dir);
		p += dirLen;
		_tcscpy(p, data->name);
		p += data->name.Length() + 1;
	}
	*p = 0;

	// Now we can delete the files in the buffer
	SHFILEOPSTRUCT op;
	memset(&op, 0, sizeof(op));
	op.hwnd = (HWND)GetHandle();
	op.wFunc = FO_DELETE;
	op.pFrom = pBuffer;

	// Move to trash if shift is not pressed, else delete
	op.fFlags = wxGetKeyState(WXK_SHIFT) ? 0 : FOF_ALLOWUNDO;

	SHFileOperation(&op);
	delete [] pBuffer;
#else
	if (wxMessageBox(_("Really delete all selected files and/or directories?"), _("Confirmation needed"), wxICON_QUESTION | wxYES_NO, this) != wxYES)
		return;

	// Remember the directories to delete and the directories to visit
	std::list<wxString> dirsToDelete;
	std::list<wxString> dirsToVisit;

	wxString dir = m_dir;
	if (dir.Right(1) != _T("\\") && dir.Right(1) != _T("/"))
		dir += _T("/");

	// First process selected items
	long item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;

		if (!item && m_hasParent)
			continue;

		t_fileData *data = GetData(item);
		if (!data)
			continue;

		if (data->dir)
		{
			wxString subDir = dir + data->name + _T("/");
			dirsToVisit.push_back(subDir);
			dirsToDelete.push_front(subDir);
		}
		else
			wxRemoveFile(dir + data->name);
	}

	// Process any subdirs which still have to be visited
	while (!dirsToVisit.empty())
	{
		wxString filename = dirsToVisit.front();
		dirsToVisit.pop_front();
		wxDir dir;
		if (!dir.Open(filename))
			continue;

		wxString file;
		for (bool found = dir.GetFirst(&file); found; found = dir.GetNext(&file))
		{
			if (file == _T(""))
			{
				wxGetApp().DisplayEncodingWarning();
				continue;
			}
			if (wxFileName::DirExists(filename + file))
			{
				wxString subDir = filename + file + _T("/");
				dirsToVisit.push_back(subDir);
				dirsToDelete.push_front(subDir);
			}
			else
				wxRemoveFile(filename + file);
		}
	}

	// Delete the now empty directories
	for (std::list<wxString>::const_iterator iter = dirsToDelete.begin(); iter != dirsToDelete.end(); iter++)
		wxRmdir(*iter);

#endif
	m_pState->SetLocalDir(m_dir);
}

void CLocalListView::OnMenuRename(wxCommandEvent& event)
{
	int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
	if (item < 0 || (!item && m_hasParent))
	{
		wxBell();
		return;
	}

	if (GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1)
	{
		wxBell();
		return;
	}

	EditLabel(item);
}

void CLocalListView::OnChar(wxKeyEvent& event)
{
	int code = event.GetKeyCode();
	if (code == WXK_DELETE)
	{
		if (GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == -1)
		{
			wxBell();
			return;
		}

		wxCommandEvent tmp;
		OnMenuDelete(tmp);
	}
	else if (code == WXK_F2)
	{
		wxCommandEvent tmp;
		OnMenuRename(tmp);
	}
	else if (code == WXK_BACK)
	{
		if (!m_hasParent)
		{
			wxBell();
			return;
		}
		wxString error;
		if (!m_pState->SetLocalDir(_T(".."), &error))
		{
			if (error != _T(""))
				wxMessageBox(error, _("Failed to change directory"), wxICON_INFORMATION);
			else
				wxBell();
		}
	}
	else if (code > 32 && code < 300 && !event.HasModifiers())
	{
		// Keyboard navigation within items
		wxDateTime now = wxDateTime::UNow();
		if (m_lastKeyPress.IsValid())
		{
			wxTimeSpan span = now - m_lastKeyPress;
			if (span.GetSeconds() >= 1)
				m_prefix = _T("");
		}
		m_lastKeyPress = now;

		wxChar tmp[2];
#if wxUSE_UNICODE
		tmp[0] = event.GetUnicodeKey();
#else
		tmp[0] = code;
#endif
		tmp[1] = 0;
		wxString newPrefix = m_prefix + tmp;

		bool beep = false;
		int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item != -1)
		{
			wxString text = GetData(item)->name;
			if (text.Length() >= m_prefix.Length() && !m_prefix.CmpNoCase(text.Left(m_prefix.Length())))
				beep = true;
		}
		else if (m_prefix == _T(""))
			beep = true;

		int start = item;
		if (start < 0)
			start = 0;

		int newPos = FindItemWithPrefix(newPrefix, start);

		if (newPos == -1 && m_prefix == tmp && item != -1 && beep)
		{
			// Search the next item that starts with the same letter
			newPrefix = m_prefix;
			newPos = FindItemWithPrefix(newPrefix, item + 1);
		}

		m_prefix = newPrefix;
		if (newPos == -1)
		{
			if (beep)
				wxBell();
			return;
		}

		item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		while (item != -1)
		{
			SetItemState(item, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
			item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		}
		SetItemState(newPos, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
		EnsureVisible(newPos);
	}
	else
		event.Skip();
}

void CLocalListView::OnKeyDown(wxKeyEvent& event)
{
	const int code = event.GetKeyCode();
	if (code == 'A' && event.GetModifiers() == wxMOD_CMD)
	{
		for (int i = m_hasParent ? 1 : 0; i < GetItemCount(); i++)
		{
			SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
		}
	}
	else
		OnChar(event);
		//event.Skip();
}

int CLocalListView::FindItemWithPrefix(const wxString& prefix, int start)
{
	const int itemCount = m_fileData.size();
	for (int i = start; i < (itemCount + start); i++)
	{
		int item = i % itemCount;
		wxString fn;

		t_fileData* data = GetData(item);
		if (!data)
			continue;
		fn = data->name.Left(prefix.Length());

		if (!fn.CmpNoCase(prefix))
			return i % itemCount;
	}
	return -1;
}

void CLocalListView::OnBeginLabelEdit(wxListEvent& event)
{
	if (!m_hasParent)
		return;

	if (event.GetIndex() == 0)
	{
		event.Veto();
		return;
	}
}

void CLocalListView::OnEndLabelEdit(wxListEvent& event)
{
	if (event.IsEditCancelled())
		return;

	int index = event.GetIndex();
	if (!index && m_hasParent)
	{
		event.Veto();
		return;
	}

	if (event.GetLabel() == _T(""))
	{
		event.Veto();
		return;
	}

	wxString newname = event.GetLabel();
#ifdef __WXMSW__
	newname = newname.Left(255);

	if ((newname.Find('/') != -1) ||
		(newname.Find('\\') != -1) ||
		(newname.Find(':') != -1) ||
		(newname.Find('*') != -1) ||
		(newname.Find('?') != -1) ||
		(newname.Find('"') != -1) ||
		(newname.Find('<') != -1) ||
		(newname.Find('>') != -1) ||
		(newname.Find('|') != -1))
	{
		wxMessageBox(_("Filenames may not contain any of the following characters: / \\ : * ? \" < > |"), _("Invalid filename"), wxICON_EXCLAMATION);
		event.Veto();
		return;
	}

	SHFILEOPSTRUCT op;
	memset(&op, 0, sizeof(op));

	wxString dir = m_dir;
	if (dir.Right(1) != _T("\\") && dir.Right(1) != _T("/"))
		dir += _T("\\");
	wxString from = dir + m_fileData[m_indexMapping[index]].name + _T(" ");
	from.SetChar(from.Length() - 1, '\0');
	op.pFrom = from;
	wxString to = dir + newname + _T(" ");
	to.SetChar(to.Length()-1, '\0');
	op.pTo = to;
	op.hwnd = (HWND)GetHandle();
	op.wFunc = FO_RENAME;
	op.fFlags = FOF_ALLOWUNDO;
	if (SHFileOperation(&op))
		event.Veto();
	else
	{
		m_fileData[m_indexMapping[index]].name = newname;
		return;
	}
#else
	if ((newname.Find('/') != -1) ||
		(newname.Find('*') != -1) ||
		(newname.Find('?') != -1) ||
		(newname.Find('<') != -1) ||
		(newname.Find('>') != -1) ||
		(newname.Find('|') != -1))
	{
		wxMessageBox(_("Filenames may not contain any of the following characters: / * ? < > |"), _("Invalid filename"), wxICON_EXCLAMATION);
		event.Veto();
		return;
	}

	wxString dir = m_dir;
	if (dir.Right(1) != _T("\\") && dir.Right(1) != _T("/"))
		dir += _T("\\");
	if (wxRename(dir + m_fileData[m_indexMapping[index]].name, dir + newname))
		m_fileData[m_indexMapping[index]].name = newname;
	else
		event.Veto();
#endif
}

void CLocalListView::ApplyCurrentFilter()
{
	unsigned int min = m_hasParent ? 1 : 0;
	if (m_fileData.size() <= min)
		return;

	wxString focused;
	const std::list<wxString>& selectedNames = RememberSelectedItems(focused);

	CFilterDialog filter;
	m_indexMapping.clear();
	m_indexMapping.push_back(0);
	for (unsigned int i = min; i < m_fileData.size(); i++)
	{
		const t_fileData& data = m_fileData[i];
		if (!filter.FilenameFiltered(data.name, data.dir, data.size, true))
			m_indexMapping.push_back(i);
	}
	SetItemCount(m_indexMapping.size());

	SortList();

	ReselectItems(selectedNames, focused);
}

std::list<wxString> CLocalListView::RememberSelectedItems(wxString& focused)
{
	std::list<wxString> selectedNames;
	// Remember which items were selected
	int item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;
		const t_fileData &data = m_fileData[m_indexMapping[item]];
		if (data.dir)
			selectedNames.push_back(_T("d") + data.name);
		else
			selectedNames.push_back(_T("-") + data.name);
		SetItemState(item, 0, wxLIST_STATE_SELECTED);
	}

	item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
	if (item != -1)
	{
		const t_fileData &data = m_fileData[m_indexMapping[item]];
		focused = data.name;

		SetItemState(item, 0, wxLIST_STATE_FOCUSED);
	}

	return selectedNames;
}

void CLocalListView::ReselectItems(const std::list<wxString>& selectedNames, wxString focused)
{
	// Reselect previous items if neccessary.
	// Sorting direction did not change. We just have to scan through items once

	if (selectedNames.empty())
	{
		if (focused == _T(""))
			return;
		for (unsigned int i = 0; i < m_indexMapping.size(); i++)
		{
			const t_fileData &data = m_fileData[m_indexMapping[i]];
			if (data.name == focused)
			{
				SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
				return;
			}
		}
		return;
	}

	int firstSelected = -1;

	unsigned i = 0;
	for (std::list<wxString>::const_iterator iter = selectedNames.begin(); iter != selectedNames.end(); iter++)
	{
		while (i < m_indexMapping.size())
		{
			const t_fileData &data = m_fileData[m_indexMapping[i]];
			if (data.name == focused)
			{
				SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
				focused = _T("");
			}
			if (data.dir && *iter == (_T("d") + data.name))
			{
				if (firstSelected == -1)
					firstSelected = i;
				SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
				i++;
				break;
			}
			else if (*iter == (_T("-") + data.name))
			{
				if (firstSelected == -1)
					firstSelected = i;
				SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
				i++;
				break;
			}
			else
				i++;
		}
		if (i == m_indexMapping.size())
			break;
	}
	if (focused != _T(""))
	{
		if (firstSelected != -1)
			SetItemState(firstSelected, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
		else
			SetItemState(0, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
	}
}

void CLocalListView::OnStateChange(unsigned int event, const wxString& data)
{
	if (event == STATECHANGE_LOCAL_DIR)
		DisplayDir(m_pState->GetLocalDir());
	else if (event == STATECHANGE_APPLYFILTER)
		ApplyCurrentFilter();
	else if (event == STATECHANGE_LOCAL_REFRESH_FILE)
		RefreshFile(data);
}

void CLocalListView::OnBeginDrag(wxListEvent& event)
{
	long item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;

		if (!item && m_hasParent)
			return;
	}

	wxFileDataObject obj;

	item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == -1)
			break;

		t_fileData *data = GetData(item);
		if (!data)
			return;

		obj.AddFile(m_dir + data->name);
	}

	wxDropSource source(this);
	source.SetData(obj);
	int res = source.DoDragDrop(wxDrag_AllowMove);
	if (res == wxDragCopy || res == wxDragMove)
		m_pState->RefreshLocal();
}

void CLocalListView::RefreshFile(const wxString& file)
{
	bool fileExists;
	if (!(fileExists = wxFileName::FileExists(m_dir + file)) && !wxFileName::DirExists(m_dir + file))
		return;

	t_fileData data;

	data.dir = !fileExists;
	data.icon = -2;
	data.name = file;

	wxStructStat buf;
	int result = wxStat(m_dir + file, &buf);

	if (!result)
	{
		data.hasTime = true;
		data.lastModified = wxDateTime(buf.st_mtime);
	}
	else
		data.hasTime = false;

	if (data.dir)
		data.size = -1;
	else
		data.size = result ? -1 : buf.st_size;

	// Look if file data already exists
	for (std::vector<t_fileData>::iterator iter = m_fileData.begin(); iter != m_fileData.end(); iter++)
	{
		const t_fileData& oldData = *iter;
		if (oldData.name != file)
			continue;

		// It exists, update entry
		data.fileType = oldData.fileType;

		*iter = data;
		if (m_sortColumn)
			SortList();
		Refresh(false);
		return;
	}

	// Insert new entry
	int index = m_fileData.size();
	m_fileData.push_back(data);

	// Find correct position in index mapping
	std::vector<unsigned int>::iterator start = m_indexMapping.begin();
	if (m_hasParent)
		start++;
	CLocalListViewSortObject compare = GetComparisonObject();
	std::vector<unsigned int>::iterator insertPos = std::lower_bound(start, m_indexMapping.end(), index, compare);
	compare.Destroy();

	const int item = insertPos - m_indexMapping.begin();
	m_indexMapping.insert(insertPos, index);
	SetItemCount(m_indexMapping.size());

	// Move selections
	int prevState = 0;
	for (unsigned int i = item; i < m_indexMapping.size(); i++)
	{
		int state = GetItemState(i, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
		if (state != prevState)
		{
			SetItemState(i, prevState, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
			prevState = state;
		}
	}

	Refresh(false);
}

void CLocalListView::InitDateFormat()
{
	const wxString& dateFormat = COptions::Get()->GetOption(OPTION_DATE_FORMAT);
	const wxString& timeFormat = COptions::Get()->GetOption(OPTION_TIME_FORMAT);
	
	if (dateFormat == _T("1"))
		m_dateFormat = _T("%Y-%m-%d");
	else if (dateFormat[0] == '2')
		m_dateFormat = dateFormat.Mid(1);
	else
		m_dateFormat = _T("%x");

	m_dateFormat += ' ';

	if (timeFormat == _T("1"))
		m_dateFormat += _T("%H:%M");
	else if (timeFormat[0] == '2')
		m_dateFormat += timeFormat.Mid(1);
	else
		m_dateFormat += _T("%X");
}


syntax highlighted by Code2HTML, v. 0.9.1