#include "FileBrowserUI.h"

#include "DialogManager.h"
#include "PLButtonWidget.h"
#include "FileManager.h"
#include "FontFamily.h"
#include "GpApplicationName.h"
#include "GpBuildVersion.h"
#include "GpIOStream.h"
#include "GpRenderedFontMetrics.h"
#include "IGpDirectoryCursor.h"
#include "IGpFileSystem.h"
#include "IGpFont.h"
#include "IGpSystemServices.h"
#include "WindowManager.h"
#include "MacFileInfo.h"
#include "MemoryManager.h"
#include "PLStandardColors.h"
#include "RenderedFont.h"
#include "ResolveCachingColor.h"
#include "WindowDef.h"
#include "MacRomanConversion.h"

#include "PLApplication.h"
#include "PLArrayView.h"
#include "PLControlDefinitions.h"
#include "PLCore.h"
#include "PLDialogs.h"
#include "PLDrivers.h"
#include "PLEditboxWidget.h"
#include "PLKeyEncoding.h"
#include "PLQDraw.h"
#include "PLScrollBarWidget.h"
#include "PLSysCalls.h"
#include "PLTimeTaggedVOSEvent.h"

#include <algorithm>

static const int kOkayButton = 1;
static const int kCancelButton = 2;
static const int kFileList = 3;
static const int kFileListScrollBar = 4;
static const int kFileNameEditBox = 5;
static const int kOpenDeleteButton = 5;
static const int kSaveDeleteButton = 7;
static const int kFileBrowserUIOpenDialogTemplateID = 2001;
static const int kFileBrowserUISaveDialogTemplateID = 2002;
static const int kFileBrowserUIOverwriteDialogTemplateID = 2003;
static const int kFileBrowserUIBadNameDialogTemplateID = 2004;
static const int kFileBrowserUISaveDialogUnobstructiveTemplateID = 2007;
static const int kFileBrowserUIDeleteDialogTemplateID = 2008;
static const int kFileBrowserUISaveDialogWithDeleteButtonTemplateID = 2009;


static const int kOverwriteNoButton = 1;
static const int kOverwriteYesButton = 2;

namespace PortabilityLayer
{
	class FileBrowserUIImpl
	{
	public:
		FileBrowserUIImpl(VirtualDirectory_t dir, const FileBrowserUI_DetailsCallbackAPI &callbackAPI);
		~FileBrowserUIImpl();

		static void PubScrollBarCallback(void *captureContext, Widget *control, int part);
		static bool PubEditBoxCharFilter(void *context, uint8_t ch);

		bool AppendName(const char *name, size_t nameLength, void *details);
		void SortNames();
		void DrawHeaders();
		void DrawFileList();

		void CaptureFileListDrag();
		void SetScrollOffset(int32_t offset);
		uint16_t GetScrollCapacity() const;

		void SetUIComponents(Window *window, DrawSurface *surface, const Rect &fileListRect, EditboxWidget *editBox);

		PLPasStr GetSelectedFileName() const;

		void RemoveSelectedFile();

		static int16_t PopUpAlert(const Rect &rect, int dialogResID, const DialogTextSubstitutions *substitutions);

		int16_t FileBrowserUIFilter(Dialog *dialog, const TimeTaggedVOSEvent *evt);
		int16_t PopUpAlertUIFilter(Dialog *dialog, const TimeTaggedVOSEvent *evt);

	private:
		typedef PascalStr<255> NameStr_t;

		struct FileEntry
		{
			NameStr_t m_nameStr;
			void *m_fileDetails;
		};

		void ScrollBarCallback(Widget *control, int part);

		static bool FileEntrySortPred(const FileEntry &a, const FileEntry &b);

		int m_offset;
		int m_selectedIndex;
		int32_t m_scrollOffset;
		int32_t m_fontSpacing;
		THandle<FileEntry> m_entries;
		size_t m_numEntries;
		DrawSurface *m_surface;
		Window *m_window;
		EditboxWidget *m_editBox;
		Rect m_rect;

		Point m_doubleClickPos;
		uint32_t m_doubleClickTime;
		bool m_haveFirstClick;

		VirtualDirectory_t m_dir;

		const FileBrowserUI_DetailsCallbackAPI m_api;
	};
}


static int16_t FileBrowserUIImpl_FileBrowserUIFilter(void *context, Dialog *dialog, const TimeTaggedVOSEvent *evt)
{
	return static_cast<PortabilityLayer::FileBrowserUIImpl*>(context)->FileBrowserUIFilter(dialog, evt);
}

static int16_t FileBrowserUIImpl_PopUpAlertUIFilter(void *context, Dialog *dialog, const TimeTaggedVOSEvent *evt)
{
	return static_cast<PortabilityLayer::FileBrowserUIImpl*>(context)->PopUpAlertUIFilter(dialog, evt);
}

namespace PortabilityLayer
{
	FileBrowserUIImpl::FileBrowserUIImpl(VirtualDirectory_t dir, const FileBrowserUI_DetailsCallbackAPI &callbackAPI)
		: m_offset(0)
		, m_surface(nullptr)
		, m_window(nullptr)
		, m_editBox(nullptr)
		, m_rect(Rect::Create(0, 0, 0, 0))
		, m_selectedIndex(-1)
		, m_scrollOffset(0)
		, m_fontSpacing(1)
		, m_numEntries(0)
		, m_doubleClickPos(Point::Create(0, 0))
		, m_doubleClickTime(0)
		, m_haveFirstClick(false)
		, m_api(callbackAPI)
		, m_dir(dir)
	{
	}

	FileBrowserUIImpl::~FileBrowserUIImpl()
	{
		if (m_entries)
		{
			FileEntry *entries = *m_entries;
			for (size_t i = 0; i < m_numEntries; i++)
				m_api.m_freeFileDetailsCallback(m_api.m_context, entries[i].m_fileDetails);
		}
		m_entries.Dispose();
	}

	void FileBrowserUIImpl::PubScrollBarCallback(void *captureContext, Widget *control, int part)
	{
		static_cast<FileBrowserUIImpl*>(captureContext)->ScrollBarCallback(control, part);
	}

	bool FileBrowserUIImpl::PubEditBoxCharFilter(void *context, uint8_t ch)
	{
		uint16_t unicodeChar = MacRoman::ToUnicode(ch);

		return PLDrivers::GetFileSystem()->ValidateFilePathUnicodeChar(unicodeChar);
	}

	bool FileBrowserUIImpl::AppendName(const char *name, size_t nameLen, void *details)
	{
		MemoryManager *mm = MemoryManager::GetInstance();
		if (!m_entries)
		{
			m_entries = THandle<FileEntry>(mm->AllocHandle(0));
			if (!m_entries)
				return false;
		}

		size_t oldSize = m_entries.MMBlock()->m_size;

		if (!mm->ResizeHandle(m_entries.MMBlock(), oldSize + sizeof(FileEntry)))
			return false;

		FileEntry &entry = (*m_entries)[m_numEntries++];
		entry.m_nameStr = NameStr_t(nameLen, name);
		entry.m_fileDetails = details;

		return true;
	}

	void FileBrowserUIImpl::SortNames()
	{
		if (!m_entries)
			return;

		FileEntry *entries = *m_entries;

		std::sort(entries, entries + m_numEntries, FileEntrySortPred);
	}

	void FileBrowserUIImpl::DrawHeaders()
	{
		PortabilityLayer::RenderedFont *font = GetFont(FontPresets::kApplication12Bold);

		ResolveCachingColor blackColor = StdColors::Black();

		const Point basePoint = Point::Create(16, 16 + font->GetMetrics().m_ascent);
		m_api.m_drawLabelsCallback(m_api.m_context, m_surface, basePoint);
	}

	void FileBrowserUIImpl::DrawFileList()
	{
		if (!m_entries.MMBlock())
			return;

		PortabilityLayer::RenderedFont *font = GetFont(FontPresets::kApplication12Bold);

		GpRenderedFontMetrics metrics = font->GetMetrics();
		int32_t spacing = metrics.m_linegap;
		int32_t glyphOffset = (metrics.m_linegap + metrics.m_ascent) / 2;

		Rect itemRect = Rect::Create(m_rect.top, m_rect.left, m_rect.top + spacing, m_rect.right);
		itemRect.top -= m_scrollOffset;
		itemRect.bottom -= m_scrollOffset;

		ResolveCachingColor blackColor = StdColors::Black();
		ResolveCachingColor whiteColor = StdColors::White();
		ResolveCachingColor focusColor = RGBAColor::Create(153, 153, 255, 255);

		m_surface->FillRect(m_rect, whiteColor);

		for (size_t i = 0; i < m_numEntries; i++)
		{
			if (m_selectedIndex >= 0 && static_cast<size_t>(m_selectedIndex) == i)
			{
				Rect focusRect = itemRect.Intersect(m_rect);
				if (focusRect.IsValid())
					m_surface->FillRect(focusRect, focusColor);
			}

			Point itemStringPoint = Point::Create(itemRect.left + 2, itemRect.top + glyphOffset);
			m_surface->DrawStringConstrained(itemStringPoint, (*m_entries)[i].m_nameStr.ToShortStr(), m_rect, blackColor, font);

			m_api.m_drawFileDetailsCallback(m_api.m_context, m_surface, itemStringPoint, m_rect, (*m_entries)[i].m_fileDetails);

			itemRect.top += spacing;
			itemRect.bottom += spacing;
		}

		m_fontSpacing = spacing;
	}

	void FileBrowserUIImpl::CaptureFileListDrag()
	{
	}

	void FileBrowserUIImpl::SetScrollOffset(int32_t offset)
	{
		m_scrollOffset = offset;
		DrawFileList();
	}

	uint16_t FileBrowserUIImpl::GetScrollCapacity() const
	{
		int32_t boxHeight = m_rect.Height();
		int32_t overCapacity = (static_cast<int32_t>(m_numEntries) * m_fontSpacing - boxHeight);

		if (overCapacity < 0)
			return 0;
		else
			return static_cast<uint16_t>(overCapacity);
	}

	void FileBrowserUIImpl::SetUIComponents(Window *window, DrawSurface *surface, const Rect &rect, EditboxWidget *editbox)
	{
		m_surface = surface;
		m_rect = rect;
		m_window = window;
		m_editBox = editbox;
	}

	PLPasStr FileBrowserUIImpl::GetSelectedFileName() const
	{
		if (m_selectedIndex < 0)
			return PSTR("");
		else
			return (*m_entries)[m_selectedIndex].m_nameStr.ToShortStr();
	}

	void FileBrowserUIImpl::RemoveSelectedFile()
	{
		if (m_selectedIndex < 0)
			return;

		FileEntry *entries = *m_entries;

		FileEntry &removedEntry = entries[m_selectedIndex];
		m_api.m_freeFileDetailsCallback(m_api.m_context, removedEntry.m_fileDetails);

		for (size_t i = m_selectedIndex; i < m_numEntries - 1; i++)
			entries[i] = entries[i + 1];

		m_numEntries--;
		PortabilityLayer::MemoryManager::GetInstance()->ResizeHandle(m_entries.MMBlock(), sizeof(FileEntry) * m_numEntries);

		m_selectedIndex = -1;
		DrawFileList();
	}

	void FileBrowserUIImpl::ScrollBarCallback(Widget *control, int part)
	{
		const int pageStepping = 5;

		switch (part)
		{
		case kControlUpButtonPart:
			control->SetState(control->GetState() - m_fontSpacing);
			break;
		case kControlDownButtonPart:
			control->SetState(control->GetState() + m_fontSpacing);
			break;
		case kControlPageUpPart:
			control->SetState(control->GetState() - pageStepping * m_fontSpacing);
			break;
		case kControlPageDownPart:
			control->SetState(control->GetState() + pageStepping * m_fontSpacing);
			break;
		default:
			break;
		};

		SetScrollOffset(control->GetState());
	}

	static FileBrowserUI::Mode gs_currentFileBrowserUIMode;

	int16_t FileBrowserUIImpl::FileBrowserUIFilter(Dialog *dialog, const TimeTaggedVOSEvent *evt)
	{
		bool		handledIt = false;
		int16_t		hit = -1;

		if (!evt)
			return -1;

		Window *window = dialog->GetWindow();
		DrawSurface *surface = window->GetDrawSurface();

		if (evt->IsKeyDownEvent())
		{
			switch (PackVOSKeyCode(evt->m_vosEvent.m_event.m_keyboardInputEvent))
			{
			case PL_KEY_SPECIAL(kEnter):
			case PL_KEY_NUMPAD_SPECIAL(kEnter):
				{
					Widget *okayButton = dialog->GetItems()[kOkayButton - 1].GetWidget();

					if (okayButton->IsEnabled())
					{
						PL_ASYNCIFY_PARANOID_DISARM_FOR_SCOPE();
						okayButton->SetHighlightStyle(kControlButtonPart, true);
						PLSysCalls::Sleep(8);
						okayButton->SetHighlightStyle(kControlButtonPart, false);

						hit = kOkayButton;
						handledIt = true;
					}
				}
				break;
				
			case PL_KEY_SPECIAL(kEscape):
				{
					PL_ASYNCIFY_PARANOID_DISARM_FOR_SCOPE();
					Widget *cancelButton = dialog->GetItems()[kCancelButton - 1].GetWidget();

					cancelButton->SetHighlightStyle(kControlButtonPart, true);
					PLSysCalls::Sleep(8);
					cancelButton->SetHighlightStyle(kControlButtonPart, false);

					hit = kCancelButton;
					handledIt = true;
				}
				break;

			default:
				handledIt = false;
				break;
			}
		}

		if (evt->IsLMouseDownEvent())
		{
			Point mousePt = m_window->MouseToLocal(evt->m_vosEvent.m_event.m_mouseInputEvent);
			bool haveDoubleClick = false;

			if (m_rect.Contains(mousePt))
			{
				if (m_haveFirstClick)
				{
					const uint32_t doubleTime = 30;	// PL_NotYetImplemented_TODO: Get this from the system settings

					if (mousePt == m_doubleClickPos && evt->m_timestamp - m_doubleClickTime < doubleTime)
						haveDoubleClick = true;
				}

				if (!haveDoubleClick)
				{
					m_haveFirstClick = true;
					m_doubleClickPos = mousePt;
					m_doubleClickTime = evt->m_timestamp;

					const TimeTaggedVOSEvent *rcvEvt = evt;

					TimeTaggedVOSEvent evtHolder;
					for (;;)
					{
						if (rcvEvt)
						{
							if (rcvEvt->m_vosEvent.m_eventType == GpVOSEventTypes::kMouseInput)
							{
								mousePt = m_window->MouseToLocal(rcvEvt->m_vosEvent.m_event.m_mouseInputEvent);
								if (mousePt != m_doubleClickPos)
									m_haveFirstClick = false;

								if (m_rect.Contains(mousePt))
								{
									int32_t selection = (mousePt.v - m_rect.top + m_scrollOffset) / m_fontSpacing;

									if (selection < 0 || static_cast<size_t>(selection) >= m_numEntries)
										selection = -1;

									if (selection >= 0)
									{
										if (selection != m_selectedIndex)
										{
											m_selectedIndex = selection;

											dialog->GetItems()[kOkayButton - 1].GetWidget()->SetEnabled(selection >= 0);

											bool isDeleteValid = (selection >= 0);

											if (isDeleteValid)
											{
												const FileBrowserUIImpl::FileEntry &entry = (*m_entries)[selection];
												isDeleteValid = m_api.m_isDeleteValidCallback(m_api.m_context, m_dir, entry.m_nameStr.ToShortStr());
											}

											if (gs_currentFileBrowserUIMode == FileBrowserUI::Mode_Open)
												dialog->GetItems()[kOpenDeleteButton - 1].GetWidget()->SetEnabled(isDeleteValid);
											else if (gs_currentFileBrowserUIMode == FileBrowserUI::Mode_SaveWithDelete)
												dialog->GetItems()[kSaveDeleteButton - 1].GetWidget()->SetEnabled(isDeleteValid);


											DrawFileList();
										}

										if (m_editBox)
										{
											PLPasStr nameStr = (*m_entries)[m_selectedIndex].m_nameStr.ToShortStr();
											m_editBox->SetString(nameStr);
											m_editBox->SetSelection(0, nameStr.Length());
										}
									}
								}
							}

							if (rcvEvt->IsLMouseUpEvent())
								break;
						}

						bool haveEvent = false;
						{
							PL_ASYNCIFY_PARANOID_DISARM_FOR_SCOPE();
							haveEvent = WaitForEvent(&evtHolder, 1);
						}

						if (haveEvent)
							rcvEvt = &evtHolder;
						else
							rcvEvt = nullptr;
					}
				}
			}

			if (haveDoubleClick && m_selectedIndex >= 0)
			{
				handledIt = true;
				hit = kOkayButton;
			}
		}

		if (!handledIt)
			return -1;

		return hit;
	}

	int16_t FileBrowserUIImpl::PopUpAlertUIFilter(Dialog *dialog, const TimeTaggedVOSEvent *evt)
	{
		bool		handledIt = false;
		int16_t		hit = -1;

		if (!evt)
			return -1;

		Window *window = dialog->GetWindow();
		DrawSurface *surface = window->GetDrawSurface();

		if (evt->IsKeyDownEvent())
		{
			switch (PackVOSKeyCode(evt->m_vosEvent.m_event.m_keyboardInputEvent))
			{
				case PL_KEY_SPECIAL(kEnter):
				case PL_KEY_NUMPAD_SPECIAL(kEnter):
				{
					Widget *okayButton = dialog->GetItems()[kOkayButton - 1].GetWidget();

					if (okayButton->IsEnabled())
					{
						okayButton->SetHighlightStyle(kControlButtonPart, true);
						PLSysCalls::Sleep(8);
						okayButton->SetHighlightStyle(kControlButtonPart, false);

						hit = kOkayButton;
						handledIt = true;
					}
				}
				break;

			default:
				handledIt = false;
				break;
			}
		}

		if (!handledIt)
			return -1;

		return hit;
	}

	bool FileBrowserUIImpl::FileEntrySortPred(const FileEntry &a, const FileEntry &b)
	{
		const size_t lenA = a.m_nameStr.Length();
		const size_t lenB = b.m_nameStr.Length();

		const size_t shorterLength = std::min(lenA, lenB);

		int comparison = memcmp(a.m_nameStr.UnsafeCharPtr(), b.m_nameStr.UnsafeCharPtr(), shorterLength);
		if (comparison > 0)
			return false;

		if (comparison < 0)
			return true;

		return lenA < lenB;
	}

	int16_t FileBrowserUIImpl::PopUpAlert(const Rect &rect, int dialogResID, const DialogTextSubstitutions *substitutions)
	{
		IGpSystemServices *sysServices = PLDrivers::GetSystemServices();

		// Disable text input temporarily and restore it after
		const bool wasTextInput = sysServices->IsTextInputEnabled();
		sysServices->SetTextInputEnabled(false);

		PortabilityLayer::DialogManager *dialogManager = PortabilityLayer::DialogManager::GetInstance();
		Dialog *dialog = dialogManager->LoadDialogFromTemplate(dialogResID, rect, true, false, 0, 0, PL_GetPutInFrontWindowPtr(), PSTR(""), substitutions);

		const PortabilityLayer::DialogItem	&firstItem = *dialog->GetItems().begin();
		Rect								itemRect = firstItem.GetWidget()->GetRect();

		PortabilityLayer::ButtonWidget::DrawDefaultButtonChrome(itemRect, dialog->GetWindow()->GetDrawSurface());

		int16_t hit = 0;
		do 
		{
			hit = dialog->ExecuteModal(nullptr, PL_FILTER_FUNC(FileBrowserUIImpl_PopUpAlertUIFilter));
		} while (hit != kOkayButton && hit != kCancelButton);

		dialog->Destroy();

		sysServices->SetTextInputEnabled(wasTextInput);

		return hit;
	}

	bool FileBrowserUI::Prompt(Mode mode, VirtualDirectory_t dirID, const char *extension, char *path, size_t &outPathLength, size_t pathCapacity, const PLPasStr &initialFileName, const PLPasStr &promptText, bool composites, const FileBrowserUI_DetailsCallbackAPI &callbackAPI)
	{
		size_t extensionLength = strlen(extension);

		int dialogID = 0;
		bool isObstructive = false;
		int windowHeight = 272;
		if (mode == Mode_Open)
			dialogID = kFileBrowserUIOpenDialogTemplateID;
		else if (mode == Mode_SaveWithDelete || mode == Mode_SaveNoDelete)
		{
			if (PLDrivers::GetSystemServices()->IsTextInputObstructive())
			{
				dialogID = kFileBrowserUISaveDialogUnobstructiveTemplateID;
				windowHeight = 208;
				isObstructive = true;
				mode = Mode_SaveNoDelete;	// HACK HACK HACK
			}
			else
			{
				if (mode == Mode_SaveWithDelete)
					dialogID = kFileBrowserUISaveDialogWithDeleteButtonTemplateID;
				else
					dialogID = kFileBrowserUISaveDialogTemplateID;
			}
		}
		else
		{
			assert(false);
			return false;
		}

		FileBrowserUIImpl uiImpl(dirID, callbackAPI);

		// Enumerate files
		IGpFileSystem *fs = PLDrivers::GetFileSystem();
		IGpDirectoryCursor *dirCursor = fs->ScanDirectory(dirID);

		if (!dirCursor)
			return false;

		const char *fileName;
		while (dirCursor->GetNext(fileName))
		{
			size_t nameLength = strlen(fileName);

			if (nameLength < extensionLength)
				continue;

			const char *nameExt = fileName + (nameLength - extensionLength);

			if (!memcmp(nameExt, extension, extensionLength))
			{
				PLPasStr fnamePStr = PLPasStr(nameLength - extensionLength, fileName);
				if (!callbackAPI.m_filterFileCallback(callbackAPI.m_context, dirID, fnamePStr))
					continue;

				if (!uiImpl.AppendName(fileName, nameLength - extensionLength, callbackAPI.m_loadFileDetailsCallback(callbackAPI.m_context, dirID, fnamePStr)))
				{
					dirCursor->Destroy();
					return false;
				}
			}
		}

		uiImpl.SortNames();

		dirCursor->Destroy();

		const int scrollBarWidth = 16;
		const Rect windowRect = Rect::Create(0, 0, windowHeight, 450);

		PortabilityLayer::WindowDef wdef = PortabilityLayer::WindowDef::Create(windowRect, PortabilityLayer::WindowStyleFlags::kAlert, true, 0, 0, PSTR(""));

		PortabilityLayer::ResolveCachingColor blackColor = StdColors::Black();
		PortabilityLayer::RenderedFont *font = GetFont(FontPresets::kApplication12Bold);
		PortabilityLayer::RenderedFont *fontLight = GetFont(FontPresets::kApplication8);

		int16_t verticalPoint = 16 + font->GetMetrics().m_ascent;
		int16_t horizontalOffset = 16;
		const int16_t spacing = 12;

		PortabilityLayer::DialogManager *dialogManager = PortabilityLayer::DialogManager::GetInstance();

		DialogTextSubstitutions substitutions(promptText);
		Dialog *dialog = dialogManager->LoadDialogFromTemplate(dialogID, windowRect, true, false, 0, 0, PL_GetPutInFrontWindowPtr(), PSTR(""), &substitutions);

		Window *window = dialog->GetWindow();

		if (isObstructive)
			window->SetPosition(PortabilityLayer::Vec2i(window->GetPosition().m_x, 6));

		DrawSurface *surface = window->GetDrawSurface();

		const PortabilityLayer::DialogItem	&firstItem = *dialog->GetItems().begin();
		Rect								itemRect = firstItem.GetWidget()->GetRect();

		PortabilityLayer::ButtonWidget::DrawDefaultButtonChrome(itemRect, surface);

		// Get item rects
		const Rect fileListRect = dialog->GetItems()[kFileList - 1].GetWidget()->GetRect();
		const Rect scrollBarRect = dialog->GetItems()[kFileListScrollBar - 1].GetWidget()->GetRect();

		EditboxWidget *editbox = nullptr;
		if (mode == Mode_SaveWithDelete || mode == Mode_SaveNoDelete)
		{
			editbox = static_cast<EditboxWidget*>(dialog->GetItems()[kFileNameEditBox - 1].GetWidget());
			editbox->SetCharacterFilter(&uiImpl, FileBrowserUIImpl::PubEditBoxCharFilter);
			editbox->SetCapacity(31);
			editbox->SetString(initialFileName);

			dialog->GetWindow()->FocusWidget(editbox);
		}

		// Draw file list frame
		surface->FrameRect(fileListRect.Inset(-1, -1), blackColor);

		// Draw initial stuff
		uiImpl.SetUIComponents(dialog->GetWindow(), surface, fileListRect, editbox);
		uiImpl.DrawFileList();

		PortabilityLayer::ScrollBarWidget *scrollBar = nullptr;

		{
			PortabilityLayer::WidgetBasicState state;
			state.m_rect = scrollBarRect;
			state.m_refConstant = 0;
			state.m_window = nullptr;
			state.m_max = uiImpl.GetScrollCapacity();
			state.m_state = 0;
			state.m_defaultCallback = FileBrowserUIImpl::PubScrollBarCallback;
			scrollBar = PortabilityLayer::ScrollBarWidget::Create(state, nullptr);
		}

		dialog->ReplaceWidget(kFileListScrollBar - 1, scrollBar);

		window->DrawControls();

		uiImpl.DrawHeaders();

		int16_t hit = 0;

		Window *exclWindow = dialog->GetWindow();

		WindowManager::GetInstance()->SwapExclusiveWindow(exclWindow);

		gs_currentFileBrowserUIMode = mode;

		do
		{
			hit = dialog->ExecuteModal(&uiImpl, PL_FILTER_FUNC(FileBrowserUIImpl_FileBrowserUIFilter));

			if (hit == kFileListScrollBar)
				uiImpl.SetScrollOffset(scrollBar->GetState());

			if (hit == kOkayButton && (mode == Mode_SaveWithDelete || mode == Mode_SaveNoDelete))
			{
				IGpFileSystem *fs = PLDrivers::GetFileSystem();

				EditboxWidget *editBox = static_cast<EditboxWidget*>(dialog->GetItems()[kFileNameEditBox - 1].GetWidget());

				PLPasStr nameStr = editBox->GetString();
				if (nameStr.Length() == 0 || !fs->ValidateFilePath(nameStr.Chars(), nameStr.Length()))
				{
					SysBeep(1);
					FileBrowserUIImpl::PopUpAlert(Rect::Create(0, 0, 135, 327), kFileBrowserUIBadNameDialogTemplateID, nullptr);
					hit = -1;
				}
				else
				{
					bool fileExists = false;
					if (composites)
						fileExists = PortabilityLayer::FileManager::GetInstance()->CompositeFileExists(dirID, nameStr);
					else
						fileExists = PortabilityLayer::FileManager::GetInstance()->NonCompositeFileExists(dirID, nameStr, extension);

					if (fileExists)
					{
						DialogTextSubstitutions substitutions(nameStr);

						SysBeep(1);
						int16_t subHit = FileBrowserUIImpl::PopUpAlert(Rect::Create(0, 0, 135, 327), kFileBrowserUIOverwriteDialogTemplateID, &substitutions);

						if (subHit == kOverwriteNoButton)
							hit = -1;
					}
				}
			}

			if ((mode == Mode_Open && hit == kOpenDeleteButton) || (mode == Mode_SaveWithDelete && hit == kSaveDeleteButton))
			{
				SysBeep(1);
				int16_t subHit = FileBrowserUIImpl::PopUpAlert(Rect::Create(0, 0, 135, 327), kFileBrowserUIDeleteDialogTemplateID, &substitutions);

				if (subHit == kOverwriteYesButton)
				{
					PLPasStr uiFileName = uiImpl.GetSelectedFileName();

					bool deleted = false;
					if (composites)
						deleted = PortabilityLayer::FileManager::GetInstance()->DeleteCompositeFile(dirID, uiFileName);
					else
						deleted = PortabilityLayer::FileManager::GetInstance()->DeleteNonCompositeFile(dirID, uiFileName, extension);

					if (deleted)
					{
						callbackAPI.m_onDeletedCallback(callbackAPI.m_context, dirID, uiFileName);

						uiImpl.RemoveSelectedFile();
						dialog->GetItems()[kOkayButton - 1].GetWidget()->SetEnabled(false);
						if (mode == Mode_Open)
							dialog->GetItems()[kOpenDeleteButton - 1].GetWidget()->SetEnabled(false);
						else if (mode == Mode_SaveWithDelete)
							dialog->GetItems()[kSaveDeleteButton - 1].GetWidget()->SetEnabled(false);
					}
				}
				hit = -1;
			}
		} while (hit != kOkayButton && hit != kCancelButton);

		WindowManager::GetInstance()->SwapExclusiveWindow(exclWindow);

		bool confirmed = false;
		PLPasStr uiFileName;

		if (hit == kOkayButton)
		{
			if (mode == Mode_Open)
			{
				uiFileName = uiImpl.GetSelectedFileName();
				confirmed = true;
			}
			else if (mode == Mode_SaveWithDelete || mode == Mode_SaveNoDelete)
			{
				uiFileName = editbox->GetString();
				confirmed = true;
			}
		}

		if (confirmed)
		{
			if (uiFileName.Length() > pathCapacity)
				confirmed = false;
		}

		if (confirmed)
		{
			memcpy(path, uiFileName.Chars(), uiFileName.Length());
			outPathLength = uiFileName.Length();
		}

		dialog->Destroy();

		return confirmed;
	}
}

PL_IMPLEMENT_FILTER_FUNCTION(FileBrowserUIImpl_FileBrowserUIFilter)
PL_IMPLEMENT_FILTER_FUNCTION(FileBrowserUIImpl_PopUpAlertUIFilter)
