/* ScummVM Tools
 *
 * ScummVM Tools is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/* Main entry point for the tool GUI */

#include "wx/wxprec.h"

#ifdef __BORLANDC__
	#pragma hdrstop
#endif

#ifndef WX_PRECOMP
	#include "wx/wx.h"
#endif

#include <wx/statline.h>
#include <wx/aboutdlg.h>
#include <wx/stdpaths.h>
#include <wx/hyperlink.h>
#include <wx/notebook.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include "gui/main.h"
#include "gui/pages.h"
#include "gui/gui_tools.h"

#include "version.h"


/**
 * The application class, main entry point for wxWidgets applications.
 */
class ScummVMToolsApp : public wxApp {
	virtual bool OnInit();

public:
	/**
	 * Essentially a global function to display the about dialog
	 * We keep it here since there is no good place to stick it since it's called
	 * from many different classes.
	 */
	void OnAbout();
};

IMPLEMENT_APP(ScummVMToolsApp)

bool ScummVMToolsApp::OnInit() {
	// Init tools
	g_tools.init();

	SetAppName(wxT("ScummVM Tools"));

	// Create window & display
	ScummToolsFrame *frame = new ScummToolsFrame(
			GetAppName() + wxT(" ") + wxString(gScummVMToolsVersionDate, wxConvISO8859_1),
			wxDefaultPosition, wxSize(600,420)
		);
#ifdef __WXMAC__ // Menu bar looks ugly when it's part of the window, on OSX it's not
	frame->CreateMenuBar();
	frame->CentreOnScreen();
#endif
	frame->SetMinSize(wxSize(600, 420));
	SetTopWindow(frame);

	// Create and load configuration
	Configuration &configuration = frame->_configuration;

	if (argc == 2) {
		Common::Filename fn((const char *)wxString(argv[1]).mb_str());

		wxArrayString ls = g_tools.getToolList(fn);
		if (ls.size() == 1)
			frame->switchPage(new ChooseOutPage(configuration));
		else
			frame->switchPage(new ChooseToolPage(configuration, ls));
	} else {
		frame->switchPage(new IntroPage(configuration));
	}

	frame->Show(true);

	return true;
}

void ScummVMToolsApp::OnAbout() {
	wxDialog *dialog = new wxDialog(NULL, wxID_ANY, wxT("About"), wxDefaultPosition, wxSize(500, 380));

	wxSizer *topsizer = new wxBoxSizer(wxVERTICAL);

	topsizer->Add(new Header(dialog, wxT("detaillogo.jpg"), wxT("tile.gif"), wxT("")), wxSizerFlags().Expand());

	wxSizer *sizer = new wxBoxSizer(wxVERTICAL);

	// Create text content

	wxStaticText *titletext = new wxStaticText(dialog, wxID_ANY, wxT("ScummTools GUI"));
	titletext->SetFont(wxFont(22, wxSWISS, wxNORMAL, wxBOLD, false, wxT("Arial")));
	sizer->Add(titletext, wxSizerFlags());

	wxStaticText *versiontext = new wxStaticText(dialog, wxID_ANY, wxString(gScummVMToolsVersionDate, wxConvISO8859_1));
	versiontext->SetForegroundColour(wxColor(128, 128, 128));
	versiontext->SetFont(wxFont(10, wxSWISS, wxNORMAL, wxNORMAL, false, wxT("Arial")));
	sizer->Add(versiontext, wxSizerFlags());

	wxHyperlinkCtrl *websitetext = new wxHyperlinkCtrl(dialog, wxID_ANY, wxT("https://www.scummvm.org"), wxT("https://www.scummvm.org"));
	sizer->Add(websitetext, wxSizerFlags().Border(wxTOP, 5));

	wxStaticText *copyrighttext = new wxStaticText(dialog, wxID_ANY, wxT("Copyright ScummVM Team 2009-2022"));
	copyrighttext->SetFont(wxFont(8, wxSWISS, wxNORMAL, wxNORMAL, false, wxT("Arial")));
	sizer->Add(copyrighttext, wxSizerFlags());

	wxStaticText *descriptiontext = new wxStaticText(dialog, wxID_ANY,
		wxT("This tool allows you to extract data files from several different games \n")
		wxT("to be used by ScummVM, it can also compress audio data files into a more \n")
		wxT("compact format than the original."));
	descriptiontext->SetFont(wxFont(8, wxSWISS, wxNORMAL, wxNORMAL, false, wxT("Arial")));
	sizer->Add(descriptiontext, wxSizerFlags().Border(wxTOP, 6));

	wxStaticText *licensetext = new wxStaticText(dialog, wxID_ANY,
		wxT("Published under the GNU General Public License\n")
		wxT("This program comes with ABSOLUTELY NO WARRANTY\n")
		wxT("This is free software, and you are welcome to redistribute it ")
		wxT("under certain conditions"));
	licensetext->SetFont(wxFont(8, wxSWISS, wxNORMAL, wxNORMAL, false, wxT("Arial")));
	sizer->Add(licensetext, wxSizerFlags().Border(wxTOP, 10));


	// Apply layout
	topsizer->Add(sizer, wxSizerFlags().Expand().Border(wxALL, 10));

	// Add the standard buttons (only one)
	topsizer->Add(dialog->CreateStdDialogButtonSizer(wxOK), wxSizerFlags().Center().Border());

	// Add the standard buttons
	dialog->SetSizerAndFit(topsizer);

	// Display it
	dialog->ShowModal();

	// Destroy it
	dialog->Destroy();
}


// Event table for the top frame
BEGIN_EVENT_TABLE(ScummToolsFrame, wxFrame)
	//EVT_MENU(wxID_PREFERENCES, ScummToolsFrame::onMenuPreferences)
	EVT_BUTTON(ID_HELP, ScummToolsFrame::onMenuHelp)
	EVT_MENU(wxID_HELP, ScummToolsFrame::onMenuHelp)
	EVT_BUTTON(ID_ADVANCED, ScummToolsFrame::onMenuAdvanced)
	EVT_MENU(wxID_PREFERENCES, ScummToolsFrame::onMenuAdvanced)
	EVT_MENU(ID_MANUAL, ScummToolsFrame::onMenuManual)
	EVT_MENU(ID_WEBSITE, ScummToolsFrame::onMenuWebsite)
	EVT_MENU(wxID_ABOUT, ScummToolsFrame::onMenuAbout)
	EVT_BUTTON(ID_ABOUT, ScummToolsFrame::onMenuAbout)
	EVT_MENU(wxID_EXIT, ScummToolsFrame::onMenuExit)

	EVT_IDLE(ScummToolsFrame::onIdle)
	EVT_CLOSE(ScummToolsFrame::onClose)
END_EVENT_TABLE()

ScummToolsFrame::ScummToolsFrame(const wxString &title, const wxPoint &pos, const wxSize& size)
		: wxFrame((wxFrame *)NULL, -1, title, pos, size)
{
	// Load the default configuration
	_configuration.load();


	// We need a parent frame for correct background color (default frame looks 'disabled' in the background)
	wxPanel *main = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE, wxT("Wizard Main Panel"));

	wxSizer *sizer = new wxBoxSizer(wxVERTICAL);

	// Add the top header, it's sweet!
	sizer->Add(
		new Header(main, wxT("logo.jpg"), wxT("tile.gif"), wxT("Extraction & Compression Wizard")),
		wxSizerFlags(0).Expand());

	// Pane that holds the wizard window
	_wizardpane = new wxPanel(main);
	_wizardpane->SetSizer(new wxBoxSizer(wxVERTICAL));
	sizer->Add(_wizardpane, wxSizerFlags(1).Expand().Border());

	// Add a spacer line
	// We split it in two parts over a panel to have a small text there
	wxPanel *linepanel = new wxPanel(main, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE, wxT("Wizard Line Panel"));
	wxSizer *linesizer = new wxBoxSizer(wxHORIZONTAL);

	wxStaticText *linetext = new wxStaticText(linepanel, wxID_ANY, wxGetApp().GetAppName());
	linesizer->Add(linetext, wxSizerFlags());
	linetext->Disable();

	wxStaticLine *line = new wxStaticLine(
			linepanel, wxID_ANY,
			wxDefaultPosition, wxSize(300, 1),
			wxBORDER_SIMPLE | wxLI_HORIZONTAL, wxT("Line Spacer")
		);
	line->Disable();
	linesizer->Add(line, wxSizerFlags(1).Center());

	linepanel->SetSizer(linesizer);

	// Add the line to the main panel
	sizer->Add(linepanel, wxSizerFlags().Expand().Border());

	// Buttons on the bottom
	_buttons = new WizardButtons(main, linetext, _configuration);
	sizer->Add(_buttons, wxSizerFlags().Border().Expand());

	main->SetSizer(sizer);
}

ScummToolsFrame::~ScummToolsFrame() {
	// Save the current configuration
	// false means we don't save audio settings
	_configuration.save(false);

	for (std::vector<WizardPage *>::iterator iter = _pages.begin(); iter != _pages.end(); ++iter)
		delete *iter;

	for (std::vector<wxWindow *>::iterator iter = _oldPanels.begin(); iter != _oldPanels.end(); ++iter)
		delete *iter;
}

void ScummToolsFrame::CreateMenuBar() {
	wxMenuBar *menubar = new wxMenuBar();

#ifdef __WXMAC__
	wxApp::s_macHelpMenuTitleName = wxT("Help");
#endif

	// Name of this seems really inappropriate
	wxMenu *helpmenu = new wxMenu();
	helpmenu->Append(wxID_HELP, wxT("Help"));
	// Might be under the wrong menu...
	helpmenu->Append(wxID_PREFERENCES, wxT("&Default Settings"));
	helpmenu->Append(ID_MANUAL, wxT("&Manual Page"));
	helpmenu->Append(ID_WEBSITE, wxT("Visit ScummVM &Website"));
	helpmenu->Append(wxID_ABOUT, wxT("&About ") + wxGetApp().GetAppName());
	menubar->Append(helpmenu, wxT("Help"));

	SetMenuBar(menubar);
}

void ScummToolsFrame::switchPage(WizardPage *next, SwitchToPage page) {
	// Associate us with the new page
	if (next)
		next->SetScummFrame(this);

	// Find the old page
	wxPanel *oldPanel = dynamic_cast<wxPanel *>(_wizardpane->FindWindow(wxT("Wizard Page")));

	if (oldPanel)
		_pages.back()->save(oldPanel);

	switch (page) {
	case FirstPage:
		while (_pages.size() > 1) {
			delete _pages.back();
			_pages.pop_back();

		}
		break;
	case PreviousPage:
		if (_pages.size() > 1) {
			delete _pages.back();
			_pages.pop_back();
		}
		break;
	case NextPage:
		_pages.push_back(next);
		break;
	}

	if (oldPanel) {
		// Destroy the old page
		// oldPanel->Destroy();
		// We have an issue here for the intro page. This is called from the event handler of buttons on the panel
		// (the Compress, Extract or Advanced button). So we cannot delete the oldPanel now (that would cause a crash).
		// Instead we hide the panel and it will be deleted later (on the next Idel Event).
		oldPanel->Hide();
		_oldPanels.push_back(oldPanel);
	}

	wxWindow *newPanel = _pages.back()->CreatePanel(_wizardpane);

	// Add the new page!
	_wizardpane->GetSizer()->Add(newPanel, wxSizerFlags(1).Expand());

	// Make sure it fits
	_wizardpane->Layout();

	// And reset the buttons to a standard state
	_buttons->reset();
	_buttons->showPrevious(_pages.size() > 1);
	_buttons->setPage(_pages.back(), newPanel);
}

void ScummToolsFrame::destroyOldPanels() {
	while (!_oldPanels.empty()) {
		delete _oldPanels.back();
		_oldPanels.pop_back();
	}
}

wxString ScummToolsFrame::GetHelpText() {
	return _pages.back()->getHelp();
}

void ScummToolsFrame::onMenuHelp(wxCommandEvent &evt) {
	wxString help = _pages.back()->getHelp();
	wxMessageDialog dlg(this, help, wxT("Help"));
	dlg.ShowModal();
}

void ScummToolsFrame::onMenuAdvanced(wxCommandEvent &evt) {
	// We fill the temporary object with the current standard settings
	Configuration defaults;
	defaults.load();

	// Display the dialog with options
	int ok;
	{
		AdvancedSettingsDialog dlg(this, defaults);
		ok = dlg.ShowModal();
		// Settings are saved once the window is closed
	}

	// Save the settings
	if (ok == wxID_OK) {
		defaults.save();
		// Fill in values from the defaults, note that this overrides
		// current settings!
		_configuration.load();
	}
}

void ScummToolsFrame::onMenuManual(wxCommandEvent &evt) {
	// Wiki page
	::wxLaunchDefaultBrowser(wxT("https://wiki.scummvm.org/index.php?title=ScummVM_Tools"));
}

void ScummToolsFrame::onMenuWebsite(wxCommandEvent &evt) {
	::wxLaunchDefaultBrowser(wxT("https://www.scummvm.org"));
}

void ScummToolsFrame::onMenuAbout(wxCommandEvent &evt) {
	wxGetApp().OnAbout();
}

void ScummToolsFrame::onMenuExit(wxCommandEvent &evt) {
	Close();
}

void ScummToolsFrame::onIdle(wxIdleEvent &evt) {
	// Delete old panels on idle event
	destroyOldPanels();
	if (_pages.back()->onIdle(dynamic_cast<wxPanel *>(_wizardpane->FindWindow(wxT("Wizard Page"))))) {
		// We want more!
		evt.RequestMore(true);
		// This way we don't freeze the OS with continous events
		wxMilliSleep(10);
	}
}

void ScummToolsFrame::onClose(wxCloseEvent &evt) {
	if (!evt.CanVeto() || _pages.back()->onCancel(dynamic_cast<wxPanel *>(_wizardpane->FindWindow(wxT("Wizard Page")))))
		wxFrame::OnCloseWindow(evt);
}


// Event table for the WizardButtons window
BEGIN_EVENT_TABLE(WizardButtons, wxPanel)
	EVT_BUTTON(ID_NEXT, WizardButtons::onClickNext)
	EVT_BUTTON(ID_PREV, WizardButtons::onClickPrevious)
	EVT_BUTTON(ID_CANCEL, WizardButtons::onClickCancel)
END_EVENT_TABLE()

WizardButtons::WizardButtons(wxWindow *parent, wxStaticText *linetext, Configuration &conf)
	: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE, wxT("WizardButtonPanel")),
	  _configuration(conf),
	  _linetext(linetext),
	  _currentPage(NULL)
{
	wxSizer *topsizer = new wxBoxSizer(wxHORIZONTAL);

	wxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);

	_prev = new wxButton(this, ID_ABOUT, wxT("About"));
	_prev->SetSize(80, -1);
	sizer->Add(_prev, wxSizerFlags().Left().ReserveSpaceEvenIfHidden());

	sizer->AddSpacer(10);

	_help = new wxButton(this, ID_HELP, wxT("Help"));
	_help->SetSize(80, -1);
	sizer->Add(_help, wxSizerFlags().Left().ReserveSpaceEvenIfHidden());

	sizer->AddSpacer(10);

	_prefs= new wxButton(this, ID_ADVANCED, wxT("Settings"));
	_prefs->SetSize(80, -1);
	sizer->Add(_prefs, wxSizerFlags().Left().ReserveSpaceEvenIfHidden());

#ifdef __WXMAC__
	_help->Hide();
	_prefs->Hide();
#endif

	// Insert space between the buttons
	topsizer->Add(sizer, wxSizerFlags().Left().Border());
	topsizer->Add(new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize), wxSizerFlags(1).Expand());
	sizer = new wxBoxSizer(wxHORIZONTAL);


	_prev = new wxButton(this, ID_PREV, wxT("< Back"));
	_prev->SetSize(80, -1);
	sizer->Add(_prev, wxSizerFlags().ReserveSpaceEvenIfHidden());

	_next = new wxButton(this, ID_NEXT, wxT("Next >"));
	_next->SetSize(80, -1);
	sizer->Add(_next, wxSizerFlags().ReserveSpaceEvenIfHidden());

	sizer->AddSpacer(10);

	_cancel = new wxButton(this, ID_CANCEL, wxT("Cancel"));
	_cancel->SetSize(80, -1);
	sizer->Add(_cancel, wxSizerFlags().ReserveSpaceEvenIfHidden());

	topsizer->Add(sizer, wxSizerFlags().Border());

	SetSizerAndFit(topsizer);

	reset();
}

void WizardButtons::reset() {
	enableNext(true);
	enablePrevious(true);
	showFinish(false);
	showAbort(false);
	showNavigation(true);

	wxString label(wxGetApp().GetAppName());
	if (_configuration.selectedTool)
		label << wxT(" - ") << _configuration.selectedTool->getName();
	setLineLabel(label);
}

void WizardButtons::setPage(WizardPage *current, wxWindow *panel) {
	_currentPage = current;
	_currentPanel = panel;

	// We call onUpdateButtons, which sets the _buttons member of WizardPage
	// to this, and in turn calls updateButtons on itself and sets up the buttons
	// We cannot set this up in the constructor of the WizardPage, since it's impossible
	// to call reset *before* the page is created from SwicthPage
	_currentPage->updateButtons(_currentPanel, this);
}

void WizardButtons::setLineLabel(wxString label) {
	_linetext->SetLabel(label);
}

void WizardButtons::enableNext(bool enable) {
	_next->Enable(enable);
}

void WizardButtons::enablePrevious(bool enable) {
	_prev->Enable(enable);
}

void WizardButtons::showFinish(bool show) {
	if (show)
		_cancel->SetLabel(wxT("Finish!"));
	else
		_cancel->SetLabel(wxT("Cancel"));
}

void WizardButtons::showAbort(bool show) {
	if (show)
		_cancel->SetLabel(wxT("Abort"));
	else
		_cancel->SetLabel(wxT("Cancel"));
}

void WizardButtons::showNavigation(bool show) {
	if (show) {
		_next->Show();
		_prev->Show();
	} else {
		_next->Hide();
		_prev->Hide();
	}
}

void WizardButtons::showPrevious(bool show) {
	if (show) {
		_prev->Show();
	} else {
		_prev->Hide();
	}
}

// wx event handlers

void WizardButtons::onClickNext(wxCommandEvent &e) {
	wxASSERT(_currentPage);
	_currentPage->onNext(_currentPanel);
}

void WizardButtons::onClickPrevious(wxCommandEvent &e) {
	wxASSERT(_currentPage);
	_currentPage->onPrevious(_currentPanel);
}

void WizardButtons::onClickCancel(wxCommandEvent &e) {
	wxASSERT(_currentPage);
	_currentPage->onCancel(_currentPanel);
}

// Window header

BEGIN_EVENT_TABLE(Header, wxPanel)
	EVT_PAINT(Header::onPaint)
END_EVENT_TABLE()

Header::Header(wxWindow *parent, const wxString &logo, const wxString &tile, const wxString &title)
	: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(400, 118), wxBORDER_NONE, wxT("Wizard Splash"))
{
	// Disable warnings in this function
	wxLogNull nulllog;

	// Add support for loading .jpg images
	if (wxImage::FindHandler(wxT("jpg")) == NULL)
		wxImage::AddHandler(new wxJPEGHandler);
	if (wxImage::FindHandler(wxT("gif")) == NULL)
		wxImage::AddHandler(new wxGIFHandler);

	// Load image files
	_logo = loadBitmapFile(logo, wxBITMAP_TYPE_JPEG);
	_tile = loadBitmapFile(tile, wxBITMAP_TYPE_GIF);

	// Load font
	_font = wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false);

	// Set the text title
	_title = title;
}

void Header::onPaint(wxPaintEvent &evt) {
	wxPaintDC dc(this);

	int w, h;
	this->GetSize(&w, &h);

	if (_logo.IsOk() == false || _tile.IsOk() == false) {
		// If we couldn't load the images, use orange instead!
		dc.SetBackground(wxBrush(wxColor(213, 114, 0)));
		dc.Clear();

		// Draw lighter stripe below, looks good
		dc.SetPen(wxPen(*wxBLACK, 0, wxTRANSPARENT));
		dc.SetBrush(wxBrush(wxColor(245, 228, 156)));
		dc.DrawRectangle(0, 90, w, h - 90);
	} else {
		// We got some good-looking images! Draw them!
		int x = 0;
		dc.DrawBitmap(_logo, x, 0);
		x += _logo.GetWidth();

		while (x < w) {
			dc.DrawBitmap(_tile, x, 0);
			x += _tile.GetWidth();
		}
	}

	dc.SetFont(_font);
	dc.DrawText(_title, 290, 70);
}

wxBitmap Header::loadBitmapFile(const wxString& file, wxBitmapType type) {
	wxBitmap bitmap;

	// Tries the following locations (in that order):
	// - wxStandardPaths::Get().GetResourcesDir()
	//   On all platfforms. It should be the default place where the files are located.
	// - APP_MEDIA_PATH/scummvm-tools
	//   When APP_MEDIA_PATH is defined (i.e. on platform where configure is used).
	//   With wxWidgets 2.8 wxStandardPaths::Get().GetResourcesDir() does not always return the expected path.
	// - CURRENT_DIR/media
	//   On all platforms. This is a fallback if the above fails.
	// - CURRENT_DIR/gui/media
	//   On all platforms. This in last resort and is needed when running from the source tree.
	bitmap.LoadFile(wxStandardPaths::Get().GetResourcesDir() + wxT("/") + file, type);
#ifdef APP_MEDIA_PATH
	if (!bitmap.IsOk())
		bitmap.LoadFile(wxString(wxT(APP_MEDIA_PATH)) + wxT("/scummvm-tools/") + file, type);
#endif
	if (!bitmap.IsOk())
		bitmap.LoadFile(wxT("media/") + file, type);
	if (!bitmap.IsOk())
		bitmap.LoadFile(wxT("gui/media/") + file, type);

	return bitmap;
}

AdvancedSettingsDialog::AdvancedSettingsDialog(wxWindow *parent, Configuration &defaults) :
	wxDialog(parent, wxID_ANY, wxT("Default Settings"), wxDefaultPosition, wxDefaultSize),
	_defaults(defaults)
{
	wxNotebook *notebook = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);

	_mp3 = new ChooseAudioOptionsMp3Page(defaults);
	_flac = new ChooseAudioOptionsFlacPage(defaults);
	_vorbis = new ChooseAudioOptionsVorbisPage(defaults);

	notebook->AddPage(_mp3panel = _mp3->CreatePanel(notebook), wxT("MP3"));
	notebook->AddPage(_flacpanel = _flac->CreatePanel(notebook), wxT("Flac"));
	notebook->AddPage(_vorbispanel = _vorbis->CreatePanel(notebook), wxT("Vorbis"));

	wxSizer *topsizer = new wxBoxSizer(wxVERTICAL);

	topsizer->Add(notebook, wxSizerFlags(1).Expand().Border());
	topsizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), wxSizerFlags(0).Center().Border());

	SetSizerAndFit(topsizer);
}

AdvancedSettingsDialog::~AdvancedSettingsDialog() {
	_mp3->save(_mp3panel);
	_flac->save(_flacpanel);
	_vorbis->save(_vorbispanel);
	delete _mp3;
	delete _flac;
	delete _vorbis;
}
