/* Copyright (C) 2025 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "precompiled.h"

#include "QuickFileCtrl.h"

#include "tools/atlas/AtlasUI/General/Datafile.h"

#include <wx/anybutton.h>
#include <wx/button.h>
#include <wx/chartype.h>
#include <wx/debug.h>
#include <wx/event.h>
#include <wx/filedlg.h>
#include <wx/filename.h>
#include <wx/gdicmn.h>
#include <wx/log.h>
#include <wx/sizer.h>
#include <wx/string.h>
#include <wx/textctrl.h>
#include <wx/toolbar.h>
#include <wx/translation.h>
#include <wx/window.h>

const int verticalPadding = 2;

//////////////////////////////////////////////////////////////////////////

// Unpleasant hack: It's necessary to keep focus inside the QuickFileCtrl;
// but since that's made of lots of controls, they each need to detect focus
// changes, and compare the focus-recipient to every control inside the group,
// to tell whether focus is leaving.
//
// (This solution doesn't work very well, and I'd like to find a better
// way of doing it...)
//
// So, create some unexciting classes:

class FileCtrl_TextCtrl : public wxTextCtrl
{
public:
	FileCtrl_TextCtrl(wxWindow* pParent, wxWindowID vId, const wxString& rsValue = wxEmptyString, const wxPoint& rPos = wxDefaultPosition, const wxSize& rSize = wxDefaultSize, long lStyle = 0, const wxValidator& rValidator = wxDefaultValidator, const wxString& rsName = wxTextCtrlNameStr)
		: wxTextCtrl(pParent, vId, rsValue, rPos, rSize, lStyle, rValidator, rsName)
	{}

	void OnKillFocus(wxFocusEvent& WXUNUSED(event))
	{
		wxWindow* focused = wxWindow::FindFocus();
		QuickFileCtrl* parent = wxDynamicCast(GetParent(), QuickFileCtrl);
		wxASSERT(parent);
		if (! (focused == parent->m_TextCtrl || focused == parent->m_ButtonBrowse))
			parent->OnKillFocus();
	}

	void OnChar(wxKeyEvent& event)
	{
		QuickFileCtrl* parent = wxDynamicCast(GetParent(), QuickFileCtrl);
		wxASSERT(parent);
		if (event.GetKeyCode() == WXK_RETURN)
			parent->OnKillFocus();
		else if (event.GetKeyCode() == WXK_ESCAPE)
			parent->Destroy();
		else
			event.Skip();
	}

	DECLARE_EVENT_TABLE();
};

BEGIN_EVENT_TABLE(FileCtrl_TextCtrl, wxTextCtrl)
	EVT_KILL_FOCUS(FileCtrl_TextCtrl::OnKillFocus)
	EVT_CHAR(FileCtrl_TextCtrl::OnChar)
END_EVENT_TABLE()

class FileCtrl_Button : public wxButton
{
public:
	FileCtrl_Button(wxWindow* pParent, wxWindowID vId, const wxString& rsValue = wxEmptyString, const wxPoint& rPos = wxDefaultPosition, const wxSize& rSize = wxDefaultSize, long lStyle = 0, const wxValidator& rValidator = wxDefaultValidator, const wxString& rsName = wxTextCtrlNameStr)
		: wxButton(pParent, vId, rsValue, rPos, rSize, lStyle, rValidator, rsName)
	{}

	void OnKillFocus(wxFocusEvent& WXUNUSED(event))
	{
		wxWindow* focused = wxWindow::FindFocus();
		QuickFileCtrl* parent = wxDynamicCast(GetParent(), QuickFileCtrl);
		wxASSERT(parent);
		if (! (focused == parent->m_TextCtrl || focused == parent->m_ButtonBrowse))
			parent->OnKillFocus();
	}

	virtual void OnPress(wxCommandEvent& event)=0;

	DECLARE_EVENT_TABLE();
};

BEGIN_EVENT_TABLE(FileCtrl_Button, wxButton)
	EVT_KILL_FOCUS(FileCtrl_Button::OnKillFocus)
	EVT_BUTTON(wxID_ANY, FileCtrl_Button::OnPress)
END_EVENT_TABLE()

//////////////////////////////////////////////////////////////////////////

class FileCtrl_Button_Browse : public FileCtrl_Button
{
public:
	FileCtrl_Button_Browse(wxWindow* parent, const wxPoint& pos, const wxSize& size,
		long style, const wxString& rootDir, const wxString& fileMask)
	: FileCtrl_Button(parent, wxID_ANY, _("&Browse..."), pos, size, style),
		m_RootDir(rootDir), m_FileMask(fileMask)
	{
	}

	void OnPress(wxCommandEvent& WXUNUSED(event))
	{
		QuickFileCtrl* parent = wxDynamicCast(GetParent(), QuickFileCtrl);
		wxASSERT(parent);

		wxString defaultDir, defaultFile;

		wxFileName oldFilename (parent->m_TextCtrl->GetValue()); // TODO: use wxPATH_UNIX?
		if (oldFilename.IsOk())
		{
			// XXX: this assumes the file was in the 'public' mod, which is
			// often untrue and will quite annoy users
			wxFileName path (_T("mods/public/") + m_RootDir);
			wxASSERT(path.IsOk());
			path.MakeAbsolute(Datafile::GetDataDirectory());
			wxASSERT(path.IsOk());

			oldFilename.MakeAbsolute(path.GetPath());
			defaultDir = oldFilename.GetPath();
			defaultFile = oldFilename.GetFullName();
		}
		else
		{
			defaultDir = *parent->m_RememberedDir;
			defaultFile = wxEmptyString;
		}

		wxFileDialog dlg(this, _("Choose a file"), defaultDir, defaultFile, m_FileMask, wxFD_OPEN);

		parent->m_DisableKillFocus = true;
		int ret = dlg.ShowModal();
		parent->m_DisableKillFocus = false;

		if (ret != wxID_OK)
			return;

		wxFileName filename (dlg.GetPath());
		*parent->m_RememberedDir = filename.GetPath();

		// The file should be in ".../mods/*/$m_RootDir/..." for some unknown '*'
		// So assume it is, and just find the substring after the m_RootDir
		// (XXX: This is pretty bogus because of case-insensitive filenames etc)
		wxString filenameStr (filename.GetFullPath(wxPATH_UNIX));
		int idx = filenameStr.Find(m_RootDir);
		if (idx < 0)
		{
			wxLogError(_T("Selected file (%s) must be in a \"%s\" directory"), filenameStr.c_str(), m_RootDir.c_str());
			return;
		}
		wxString filenameRel (filenameStr.Mid(idx + m_RootDir.Len()));
		parent->m_TextCtrl->SetValue(filenameRel);
	}

private:
	wxString m_RootDir;
	wxString m_FileMask;
};

//////////////////////////////////////////////////////////////////////////


IMPLEMENT_DYNAMIC_CLASS(QuickFileCtrl, wxPanel);

QuickFileCtrl::QuickFileCtrl(wxWindow* parent,
							 wxRect& location,
							 const wxString& rootDir,
							 const wxString& fileMask,
							 wxString& rememberedDir,
							 const wxValidator& validator)
 : wxPanel(parent, wxID_ANY,
	location.GetPosition()-wxPoint(0,verticalPadding), wxDefaultSize,
	wxNO_BORDER),
	m_DisableKillFocus(false), m_RememberedDir(&rememberedDir)
{
	wxBoxSizer* s = new wxBoxSizer(wxVERTICAL);

	m_TextCtrl = new FileCtrl_TextCtrl(this, wxID_ANY, wxEmptyString,
		wxDefaultPosition,
		location.GetSize()+wxSize(0,verticalPadding*2),
		wxSUNKEN_BORDER,
		validator);

	m_ButtonBrowse = new FileCtrl_Button_Browse(this,
		// Position the button just below the text control
		wxPoint(0, location.GetSize().GetHeight()+verticalPadding),
		wxDefaultSize, wxBU_EXACTFIT,
		rootDir, fileMask);

	s->Add(m_TextCtrl);
	s->Add(m_ButtonBrowse);
	SetSizer(s);
	s->SetSizeHints(this);

	m_DisableKillFocus = true; // yucky
	m_TextCtrl->GetValidator()->TransferToWindow();
	m_TextCtrl->SetFocus();
	m_TextCtrl->SetSelection(-1, -1);
	m_DisableKillFocus = false;
	m_TextCtrl->SetFocus();
}

void QuickFileCtrl::OnKillFocus()
{
	// (This is always called explicitly, never by the wx event system)

	// Another unpleasant hack: TransferFromWindow happens to shift focus,
	// which ends up with OnKillFocus being called again, so it ends up
	// trying to destroy the window twice. So, only allow that to happen once:
	if (m_DisableKillFocus) return;
	m_DisableKillFocus = true;

	m_TextCtrl->GetValidator()->TransferFromWindow();
	Destroy();
}
