#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "interface/WindowSystem.h"
#include "wxCemuConfig.h"
#include "wxgui/wxgui.h"
#include "wxgui/MainWindow.h"

#include <wx/mstream.h>
#include <wx/clipbrd.h>

#include "wxgui/GameUpdateWindow.h"
#include "wxgui/PadViewFrame.h"
#include "wxgui/windows/TextureRelationViewer/TextureRelationWindow.h"
#include "wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h"
#include "AudioDebuggerWindow.h"
#include "wxgui/canvas/OpenGLCanvas.h"
#include "wxgui/canvas/VulkanCanvas.h"
#include "Cafe/OS/libs/nfc/nfc.h"
#include "Cafe/OS/libs/swkbd/swkbd.h"
#include "wxgui/debugger/DebuggerWindow2.h"
#include "util/helpers/helpers.h"
#include "config/CemuConfig.h"
#include "Cemu/DiscordPresence/DiscordPresence.h"
#include "util/ScreenSaver/ScreenSaver.h"
#include "wxgui/GeneralSettings2.h"
#include "wxgui/GraphicPacksWindow2.h"
#include "wxgui/CemuApp.h"
#include "wxgui/CemuUpdateWindow.h"
#include "wxgui/LoggingWindow.h"
#include "config/ActiveSettings.h"
#include "config/LaunchSettings.h"

#include "Cafe/Filesystem/FST/FST.h"

#include "wxgui/TitleManager.h"
#include "wxgui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h"

#include "Cafe/CafeSystem.h"

#include "util/helpers/SystemException.h"
#include "wxgui/DownloadGraphicPacksWindow.h"
#include "wxgui/GettingStartedDialog.h"
#include "wxgui/helpers/wxHelpers.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h"
#include "wxgui/input/InputSettings2.h"
#include "wxgui/input/HotkeySettings.h"
#include "input/InputManager.h"

#if BOOST_OS_WINDOWS
#define exit(__c) ExitProcess(__c)
#else
#define exit(__c) _Exit(__c)
#endif

#if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD
#include "resource/embedded/resources.h"
#endif

#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
#include "wxgui/helpers/wxWayland.h"
#endif

//GameMode support
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
#include "gamemode_client.h"
#endif

#include "Cafe/TitleList/TitleInfo.h"
#include "Cafe/TitleList/TitleList.h"
#include "wxHelper.h"

extern WindowSystem::WindowInfo g_window_info;
extern std::shared_mutex g_mutex;

enum
{
	// ui elements
	MAINFRAME_GAMELIST_ID = 20000, //wxID_HIGHEST + 1,
	// file
	MAINFRAME_MENU_ID_FILE_LOAD = 20100,
	MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE,
	MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER,
	MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER,
	MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER,
	MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE,
	MAINFRAME_MENU_ID_FILE_EXIT,
	MAINFRAME_MENU_ID_FILE_END_EMULATION,
	MAINFRAME_MENU_ID_FILE_RECENT_0,
	MAINFRAME_MENU_ID_FILE_RECENT_LAST = MAINFRAME_MENU_ID_FILE_RECENT_0 + 15,
	// options
	MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN = 20200,
	MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW,
	MAINFRAME_MENU_ID_OPTIONS_GRAPHIC,
	MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2,
	MAINFRAME_MENU_ID_OPTIONS_GENERAL,
	MAINFRAME_MENU_ID_OPTIONS_GENERAL2,
	MAINFRAME_MENU_ID_OPTIONS_AUDIO,
	MAINFRAME_MENU_ID_OPTIONS_INPUT,
	MAINFRAME_MENU_ID_OPTIONS_HOTKEY,
	MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS,
	// options -> account
	MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 = 20350,
	MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12 = 20350 + 11,
	
	// options -> system language
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE = 20500,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_FRENCH,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_GERMAN,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ITALIAN,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_SPANISH,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_CHINESE,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_KOREAN,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_DUTCH,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN,
	MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE,
	// tools
	MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER = 20600,
	MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER,
	MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER,
	MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES,
	// cpu
	// cpu->timer speed
	MAINFRAME_MENU_ID_TIMER_SPEED_1X = 20700,
	MAINFRAME_MENU_ID_TIMER_SPEED_2X = 20701,
	MAINFRAME_MENU_ID_TIMER_SPEED_4X = 20702,
	MAINFRAME_MENU_ID_TIMER_SPEED_8X = 20703,
	MAINFRAME_MENU_ID_TIMER_SPEED_05X = 20704,
	MAINFRAME_MENU_ID_TIMER_SPEED_025X = 20705,
	MAINFRAME_MENU_ID_TIMER_SPEED_0125X = 20706,

	// nfc->Touch NFC file
	MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE = 21000,
	MAINFRAME_MENU_ID_NFC_RECENT_0,
	MAINFRAME_MENU_ID_NFC_RECENT_LAST = MAINFRAME_MENU_ID_NFC_RECENT_0 + 15,
	// debug
	MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN = 21100,
	MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW,
	MAINFRAME_MENU_ID_DEBUG_TOGGLE_GDB_STUB,
	MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS,
	MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER,
	MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER,
	MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS,
	MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY,
	MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS,

	// debug->logging
	MAINFRAME_MENU_ID_DEBUG_LOGGING_MESSAGE = 21499,
	MAINFRAME_MENU_ID_DEBUG_LOGGING0 = 21500,
	MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO = 21599,
	// debug->dump
	MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES = 21600,
	MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS,
	MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS,
	MAINFRAME_MENU_ID_DEBUG_DUMP_RAM,
	MAINFRAME_MENU_ID_DEBUG_DUMP_FST,
	MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS,
	// help
	MAINFRAME_MENU_ID_HELP_ABOUT = 21700,
	MAINFRAME_MENU_ID_HELP_UPDATE,
	// custom
	MAINFRAME_ID_TIMER1 = 21800,
};

wxDEFINE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_REQUEST_GAMELIST_REFRESH, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent);
wxDEFINE_EVENT(wxEVT_REQUEST_RECREATE_CANVAS, wxCommandEvent);

wxBEGIN_EVENT_TABLE(MainWindow, wxFrame)
EVT_TIMER(MAINFRAME_ID_TIMER1, MainWindow::OnTimer)
EVT_CLOSE(MainWindow::OnClose)
EVT_SIZE(MainWindow::OnSizeEvent)
EVT_DPI_CHANGED(MainWindow::OnDPIChangedEvent)
EVT_MOVE(MainWindow::OnMove)
// file menu
EVT_MENU(MAINFRAME_MENU_ID_FILE_LOAD, MainWindow::OnFileMenu)
EVT_MENU(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MainWindow::OnInstallUpdate)
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenFolder)
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MainWindow::OnOpenFolder)
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, MainWindow::OnOpenFolder)
EVT_MENU(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, MainWindow::OnClearSpotPassCache)
EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit)
EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu)
EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu)
// options -> region menu
EVT_MENU_RANGE(MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1, MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12, MainWindow::OnAccountSelect)
EVT_MENU_RANGE(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, MainWindow::OnConsoleLanguage)
// options menu
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_AUDIO, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_HOTKEY, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, MainWindow::OnOptionsInput)
// tools menu
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, MainWindow::OnToolsInput)
// cpu menu
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_4X, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_2X, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_1X, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_05X, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_025X, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_0125X, MainWindow::OnDebugSetting)
// nfc menu
EVT_MENU(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, MainWindow::OnNFCMenu)
EVT_MENU_RANGE(MAINFRAME_MENU_ID_NFC_RECENT_0 + 0, MAINFRAME_MENU_ID_NFC_RECENT_LAST, MainWindow::OnNFCMenu)
// debug -> logging menu
EVT_MENU_RANGE(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 0, MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 98, MainWindow::OnDebugLoggingToggleFlagGeneric)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO, MainWindow::OnPPCInfoToggle)
// debug -> dump menu
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, MainWindow::OnDebugDumpGeneric)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, MainWindow::OnDebugDumpGeneric)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, MainWindow::OnDebugDumpGeneric)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, MainWindow::OnDebugSetting)
// debug -> Other options
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_FST, MainWindow::OnDebugSetting)
// debug -> View ...
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW, MainWindow::OnLoggingWindow)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_TOGGLE_GDB_STUB, MainWindow::OnGDBStubToggle)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, MainWindow::OnDebugViewPPCThreads)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, MainWindow::OnDebugViewPPCDebugger)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, MainWindow::OnDebugViewAudioDebugger)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, MainWindow::OnDebugViewTextureRelations)
// help menu
EVT_MENU(MAINFRAME_MENU_ID_HELP_ABOUT, MainWindow::OnHelpAbout)
EVT_MENU(MAINFRAME_MENU_ID_HELP_UPDATE, MainWindow::OnHelpUpdate)
// misc
EVT_COMMAND(wxID_ANY, wxEVT_REQUEST_GAMELIST_REFRESH, MainWindow::OnRequestGameListRefresh)

EVT_COMMAND(wxID_ANY, wxEVT_GAMELIST_BEGIN_UPDATE, MainWindow::OnGameListBeginUpdate)
EVT_COMMAND(wxID_ANY, wxEVT_GAMELIST_END_UPDATE, MainWindow::OnGameListEndUpdate)
EVT_COMMAND(wxID_ANY, wxEVT_ACCOUNTLIST_REFRESH, MainWindow::OnAccountListRefresh)
EVT_COMMAND(wxID_ANY, wxEVT_SET_WINDOW_TITLE, MainWindow::OnSetWindowTitle)

EVT_COMMAND(wxID_ANY, wxEVT_REQUEST_RECREATE_CANVAS, MainWindow::OnRequestRecreateCanvas)

wxEND_EVENT_TABLE()

class wxGameDropTarget : public wxFileDropTarget
{
public:
	wxGameDropTarget(MainWindow* window) : m_window(window) {}
	bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override
	{
		if(!m_window->IsGameLaunched() && filenames.GetCount() == 1)
			return m_window->FileLoad(_utf8ToPath(filenames[0].utf8_string()), wxLaunchGameEvent::INITIATED_BY::DRAG_AND_DROP);
		
		return false;
	}

private:
	MainWindow* m_window;
};

class wxAmiiboDropTarget : public wxFileDropTarget
{
public:
	wxAmiiboDropTarget(MainWindow* window) : m_window(window) {}
	bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override
	{
		if (!m_window->IsGameLaunched() || filenames.GetCount() != 1)
			return false;
		uint32 nfcError;
		std::string path = filenames[0].utf8_string();
		if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError))
		{
			GetWxGUIConfig().AddRecentNfcFile(path);
			m_window->UpdateNFCMenu();
			return true;
		}
		else
		{
			if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS)
				wxMessageBox(_("Cannot open file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
			else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT)
				wxMessageBox(_("Not a valid NFC file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
			return false;
		}
	}

private:
	MainWindow* m_window;
};

MainWindow::MainWindow()
	: wxFrame(nullptr, wxID_ANY, GetInitialWindowTitle(), wxDefaultPosition, wxSize(1280, 720), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN | wxRESIZE_BORDER)
{
#ifdef __WXMAC__
	// Not necessary to set wxApp::s_macExitMenuItemId as automatically handled
	wxApp::s_macAboutMenuItemId = MAINFRAME_MENU_ID_HELP_ABOUT;
	wxApp::s_macPreferencesMenuItemId = MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS;
#endif
	g_window_info.window_main = initHandleContextFromWxWidgetsWindow(this);
	g_mainFrame = this;
	CafeSystem::SetImplementation(this);

	RecreateMenu();
	SetClientSize(1280, 720);
	SetIcon(wxICON(M_WND_ICON128));

#if BOOST_OS_MACOS
	this->EnableFullScreenView(true);
#endif

#if BOOST_OS_WINDOWS
	HICON hWindowIcon = (HICON)LoadImageA(NULL, "M_WND_ICON16", IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
	SendMessage(this->GetHWND(), WM_SETICON, ICON_SMALL, (LPARAM)hWindowIcon);
#endif

	auto* main_sizer = new wxBoxSizer(wxVERTICAL);
    auto load_file = LaunchSettings::GetLoadFile();
    auto load_title_id = LaunchSettings::GetLoadTitleID();
    bool quick_launch = false;

    if (load_file)
    {
        MainWindow::RequestLaunchGame(load_file.value(), wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE);
        quick_launch = true;
    }
    else if (load_title_id)
    {
        TitleInfo info;
        TitleId baseId;
        if (CafeTitleList::FindBaseTitleId(load_title_id.value(), baseId) && CafeTitleList::GetFirstByTitleId(baseId, info))
        {
            MainWindow::RequestLaunchGame(info.GetPath(), wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE);
            quick_launch = true;
        }
        else
        {
            wxString errorMsg = fmt::format("Title ID {:016x} not found", load_title_id.value());
            wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);

        }
    }
    SetSizer(main_sizer);
    if (!quick_launch)
    {
        CreateGameListAndStatusBar();
    }
    else
    {
      // launching game via -g or -t option. Don't set up or load game list
      m_game_list = nullptr;
      m_info_bar = nullptr;
    }
    SetSizer(main_sizer);

    m_last_mouse_move_time = std::chrono::steady_clock::now();

    m_timer = new wxTimer(this, MAINFRAME_ID_TIMER1);
    m_timer->Start(500);

    LoadSettings();

    #ifdef ENABLE_DISCORD_RPC
    if (GetWxGUIConfig().use_discord_presence)
            m_discord = std::make_unique<DiscordPresence>();
    #endif

    Bind(wxEVT_OPEN_GRAPHIC_PACK, &MainWindow::OnGraphicWindowOpen, this);
    Bind(wxEVT_LAUNCH_GAME, &MainWindow::OnLaunchFromFile, this);

    if (LaunchSettings::GDBStubEnabled())
    {
            g_gdbstub = std::make_unique<GDBServer>(GetConfig().gdb_port);
    }
}

MainWindow::~MainWindow()
{
	if (m_padView)
	{
		m_padView->Destroy();
		m_padView = nullptr;
	}

	m_timer->Stop();

	std::unique_lock lock(g_mutex);
	g_mainFrame = nullptr;
}

void MainWindow::CreateGameListAndStatusBar()
{
    if(m_main_panel)
        return; // already displayed
    m_main_panel = new wxPanel(this);
    auto* sizer = new wxBoxSizer(wxVERTICAL);
    // game list
    m_game_list = new wxGameList(m_main_panel, MAINFRAME_GAMELIST_ID);
    m_game_list->Bind(wxEVT_OPEN_SETTINGS, [this](auto&) {OpenSettings(); });
    m_game_list->SetDropTarget(new wxGameDropTarget(this));
    sizer->Add(m_game_list, 1, wxEXPAND);

    // info, warning bar
    m_info_bar = new wxInfoBar(m_main_panel);
    m_info_bar->SetShowHideEffects(wxSHOW_EFFECT_BLEND, wxSHOW_EFFECT_BLEND);
    m_info_bar->SetEffectDuration(500);
    sizer->Add(m_info_bar, 0, wxALL | wxEXPAND, 5);

    m_main_panel->SetSizer(sizer);

    auto* main_sizer = this->GetSizer();
    main_sizer->Add(m_main_panel, 1, wxEXPAND, 0, nullptr);
}

void MainWindow::DestroyGameListAndStatusBar()
{
    if(!m_main_panel)
        return;
    m_main_panel->Destroy();
    m_main_panel = nullptr;
    m_game_list = nullptr;
    m_info_bar = nullptr;
}

wxString MainWindow::GetInitialWindowTitle()
{
	return BUILD_VERSION_WITH_NAME_STRING;
}

void MainWindow::OnClose(wxCloseEvent& event)
{
	wxTheClipboard->Flush();

	if(m_game_list)
		m_game_list->OnClose(event);

	if (!IsMaximized() && !WindowSystem::IsFullScreen())
		m_restored_size = GetSize();

	SaveSettings();
	m_timer->Stop();

	event.Skip();

    CafeSystem::Shutdown();
	DestroyCanvas();
}

bool MainWindow::InstallUpdate(const fs::path& metaFilePath)
{
	try
	{
		GameUpdateWindow frame(*this, metaFilePath);
		const int updateResult = frame.ShowModal();

		if (updateResult == wxID_OK)
		{
			CafeTitleList::AddTitleFromPath(frame.GetTargetPath()); // this will also send a notification to the game list which will update the entry
			wxMessageBox(_("Title installed!"), _("Success"));
			return true;
		}
		else
		{
			if (frame.GetExceptionMessage().empty())
				wxMessageBox(_("Title installation has been canceled!"));
			else
			{
				throw std::runtime_error(frame.GetExceptionMessage());
			}
		}		
	}
	catch(const AbortException&)
	{
		// ignored
	}
	catch (const std::exception& ex)
	{
		wxMessageBox(ex.what(), _("Update error"));
	}
	return false;
}

bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATED_BY initiatedBy)
{
	TitleInfo launchTitle{ launchPath };
	if (launchTitle.IsValid())
	{
		// the title might not be in the TitleList, so we add it as a temporary entry
		CafeTitleList::AddTitleFromPath(launchPath);
		// title is valid, launch from TitleId
		TitleId baseTitleId;
		if (!CafeTitleList::FindBaseTitleId(launchTitle.GetAppTitleId(), baseTitleId))
		{
			wxString t = _("Unable to launch game because the base files were not found.");
			wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
			return false;
		}
		CafeSystem::PREPARE_STATUS_CODE r = CafeSystem::PrepareForegroundTitle(baseTitleId);
		if (r == CafeSystem::PREPARE_STATUS_CODE::INVALID_RPX)
		{
			cemu_assert_debug(false);
			return false;
		}
		else if (r == CafeSystem::PREPARE_STATUS_CODE::UNABLE_TO_MOUNT)
		{
			wxString t = _("Unable to mount title.\nMake sure the configured game paths are still valid and refresh the game list.\n\nFile which failed to load:\n");
			t.append(_pathToUtf8(launchPath));
			wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
			return false;
		}
		else if (r != CafeSystem::PREPARE_STATUS_CODE::SUCCESS)
		{
			wxString t = _("Failed to launch game.");
			t.append(_pathToUtf8(launchPath));
			wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
			return false;
		}
	}
	else //if (launchTitle.GetFormat() == TitleInfo::TitleDataFormat::INVALID_STRUCTURE )
	{
		// title is invalid, if it's an RPX/ELF we can launch it directly
		// otherwise it's an error
		CafeTitleFileType fileType = DetermineCafeSystemFileType(launchPath);
		if (fileType == CafeTitleFileType::RPX || fileType == CafeTitleFileType::ELF)
		{
			CafeSystem::PREPARE_STATUS_CODE r = CafeSystem::PrepareForegroundTitleFromStandaloneRPX(launchPath);
			if (r != CafeSystem::PREPARE_STATUS_CODE::SUCCESS)
			{
				cemu_assert_debug(false); // todo
				wxString t = _("Failed to launch executable. Path: ");
				t.append(_pathToUtf8(launchPath));
				wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
				return false;
			}
		}
		else if (initiatedBy == wxLaunchGameEvent::INITIATED_BY::GAME_LIST)
		{
			wxString t = _("Unable to launch title.\nMake sure the configured game paths are still valid and refresh the game list.\n\nPath which failed to load:\n");
			t.append(_pathToUtf8(launchPath));
			wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
			return false;
		}
		else if (initiatedBy == wxLaunchGameEvent::INITIATED_BY::MENU ||
			initiatedBy == wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE)
		{
			wxString t = _("Unable to launch game\nPath:\n");
			t.append(_pathToUtf8(launchPath));
			if(launchTitle.GetInvalidReason() == TitleInfo::InvalidReason::NO_DISC_KEY)
			{
				t.append("\n\n");
				t.append(_("Could not decrypt title. Make sure that keys.txt contains the correct disc key for this title."));
			}
			if(launchTitle.GetInvalidReason() == TitleInfo::InvalidReason::NO_TITLE_TIK)
			{
				t.append("\n\n");
				t.append(_("Could not decrypt title because title.tik is missing."));
			}
			wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
			return false;
		}
		else
		{
			wxString t = _("Unable to launch game\nPath:\n");
			t.append(_pathToUtf8(launchPath));
			wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
			return false;
		}
	}

	if(launchTitle.IsValid())
		GetWxGUIConfig().AddRecentlyLaunchedFile(_pathToUtf8(launchTitle.GetPath()));
	else
		GetWxGUIConfig().AddRecentlyLaunchedFile(_pathToUtf8(launchPath));

	wxWindowUpdateLocker lock(this);

    DestroyGameListAndStatusBar();

	m_game_launched = true;
	m_loadMenuItem->Enable(false);
	m_installUpdateMenuItem->Enable(false);
	m_memorySearcherMenuItem->Enable(true);

	m_launched_game_name = CafeSystem::GetForegroundTitleName();
	#ifdef ENABLE_DISCORD_RPC
	if (m_discord)
		m_discord->UpdatePresence(DiscordPresence::Playing, m_launched_game_name);
	#endif

	if (GetConfig().disable_screensaver)
	{
		ScreenSaver::SetInhibit(true);
		// TODO: disable when only the game, not Cemu, is closed (a feature not yet implemented)
		// currently unnecessary because this will happen automatically when Cemu closes
		// ScreenSaver::SetInhibit(false);
	}

	if (FullscreenEnabled())
		SetFullScreen(true);

    //GameMode support
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
    if(GetWxGUIConfig().feral_gamemode)
    {
        // attempt to start gamemode
        if(gamemode_request_start() < 0)
        {
            // GameMode failed to start
            cemuLog_log(LogType::Force, "Could not start GameMode");
        }
        else
        {
            cemuLog_log(LogType::Force, "GameMode has been started.");
        }
    }
#endif

	CreateCanvas();
	CafeSystem::LaunchForegroundTitle();
	RecreateMenu();
	UpdateChildWindowTitleRunningState();

	return true;
}

void MainWindow::OnLaunchFromFile(wxLaunchGameEvent& event)
{
	if (event.GetPath().empty())
		return;
	FileLoad(event.GetPath(), event.GetInitiatedBy());
}

void MainWindow::OnFileMenu(wxCommandEvent& event)
{
	const auto menuId = event.GetId();
	if (menuId == MAINFRAME_MENU_ID_FILE_LOAD)
	{
		const auto wildcard = formatWxString(
			"{}|*.wud;*.wux;*.wua;*.wuhb;*.iso;*.rpx;*.elf;title.tmd"
			"|{}|*.wud;*.wux;*.iso"
			"|{}|title.tmd"
			"|{}|*.wua"
			"|{}|*.wuhb"
			"|{}|*.rpx;*.elf"
			"|{}|*",
			_("All Wii U files (*.wud, *.wux, *.wua, *.wuhb, *.iso, *.rpx, *.elf)"),
			_("Wii U image (*.wud, *.wux, *.iso, *.wad)"),
			_("Wii U NUS content"),
			_("Wii U archive (*.wua)"),
			_("Wii U homebrew bundle (*.wuhb)"),
			_("Wii U executable (*.rpx, *.elf)"),
			_("All files (*.*)")
		);
		
		wxFileDialog openFileDialog(this, _("Open file to launch"), wxEmptyString, wxEmptyString, wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST);

		if (openFileDialog.ShowModal() == wxID_CANCEL || openFileDialog.GetPath().IsEmpty())
			return;

		const wxString wxStrFilePath = openFileDialog.GetPath();	
		FileLoad(_utf8ToPath(wxStrFilePath.utf8_string()), wxLaunchGameEvent::INITIATED_BY::MENU);
	}
	else if (menuId >= MAINFRAME_MENU_ID_FILE_RECENT_0 && menuId <= MAINFRAME_MENU_ID_FILE_RECENT_LAST)
	{
		const auto& config = GetWxGUIConfig();
		const size_t index = menuId - MAINFRAME_MENU_ID_FILE_RECENT_0;
		if (index < config.recent_launch_files.size())
		{
			fs::path path = _utf8ToPath(config.recent_launch_files[index]);
			if (!path.empty())
				FileLoad(path, wxLaunchGameEvent::INITIATED_BY::MENU);
		}
	}
	else if (menuId == MAINFRAME_MENU_ID_FILE_END_EMULATION)
	{
        CafeSystem::ShutdownTitle();
		DestroyCanvas();
		m_game_launched = false;
		RecreateMenu();
        CreateGameListAndStatusBar();
        DoLayout();
		UpdateChildWindowTitleRunningState();
	}
}

void MainWindow::OnOpenFolder(wxCommandEvent& event)
{
	const auto id = event.GetId();
	if(id == MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER)
		wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetUserDataPath()));
	else if(id == MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER)
		wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetMlcPath()));
	else if (id == MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER)
		wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetCachePath("shaderCache")));
}

void MainWindow::OnClearSpotPassCache(wxCommandEvent& event)
{
	fs::path bossPath = ActiveSettings::GetMlcPath("usr/boss");
	fs::remove_all(bossPath);
	// recreate usr/boss/
	fs::create_directory(bossPath);
	wxMessageBox(_("SpotPass cache cleared"));
}

void MainWindow::OnInstallUpdate(wxCommandEvent& event)
{
	while (true)
	{
		wxDirDialog openDirDialog(nullptr, _("Select folder of title to install"), "", wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST, wxDefaultPosition, wxDefaultSize, _("Select the folder that stores your update, DLC or base game files"));
		int modalChoice = openDirDialog.ShowModal();
		if (modalChoice == wxID_CANCEL || openDirDialog.GetPath().IsEmpty())
			break;
		if (modalChoice == wxID_OK)
		{
			#if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD
			fs::path dirPath((const char*)(openDirDialog.GetPath().fn_str()));
			#else
			fs::path dirPath(openDirDialog.GetPath().fn_str());
			#endif

			if ((dirPath.filename() == "code" || dirPath.filename() == "content" || dirPath.filename() == "meta") && dirPath.has_parent_path())
			{
				if (!fs::exists(dirPath.parent_path() / "code") || !fs::exists(dirPath.parent_path() / "content") || !fs::exists(dirPath.parent_path() / "meta"))
				{
					wxMessageBox(formatWxString(_("The (parent) folder of the title you selected is missing at least one of the required subfolders (\"code\", \"content\" and \"meta\")\nMake sure that the files are complete."), dirPath.filename().string()));
					continue;
				}
				else
					dirPath = dirPath.parent_path();
			}

			if (!fs::exists(dirPath))
				wxMessageBox(_("The folder you have selected cannot be found on your system."));
			else if (!fs::exists(dirPath / "meta" / "meta.xml"))
				wxMessageBox(_("Unable to find the /meta/meta.xml file inside the selected folder."));
			else
			{
				InstallUpdate(dirPath);
				return;
			}
		}
	}
}

void MainWindow::OnNFCMenu(wxCommandEvent& event)
{
	if (event.GetId() == MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE)
	{
		wxFileDialog
			openFileDialog(this, _("Open file to load"), "", "",
				"All NFC files (bin, dat, nfc)|*.bin;*.dat;*.nfc|All files (*.*)|*", wxFD_OPEN | wxFD_FILE_MUST_EXIST); // TRANSLATE
		if (openFileDialog.ShowModal() == wxID_CANCEL || openFileDialog.GetPath().IsEmpty())
			return;
		wxString wxStrFilePath = openFileDialog.GetPath();
		uint32 nfcError;
		if (nfc::TouchTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false)
		{
			if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS)
				wxMessageBox(_("Cannot open file"));
			else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT)
				wxMessageBox(_("Not a valid NFC file"));
		}
		else
		{
			GetWxGUIConfig().AddRecentNfcFile(wxStrFilePath.utf8_string());
			UpdateNFCMenu();
		}
	}
	else if (event.GetId() >= MAINFRAME_MENU_ID_NFC_RECENT_0 && event.GetId() <= MAINFRAME_MENU_ID_NFC_RECENT_LAST)
	{
		const size_t index = event.GetId() - MAINFRAME_MENU_ID_NFC_RECENT_0;
		auto& config = GetWxGUIConfig();
		if (index < config.recent_nfc_files.size())
		{
			const auto& path = config.recent_nfc_files[index];
			if (!path.empty())
			{
				uint32 nfcError = 0;
				if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError) == false)
				{
					if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS)
						wxMessageBox(_("Cannot open file"));
					else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT)
						wxMessageBox(_("Not a valid NFC file"));
				}
				else
				{
					config.AddRecentNfcFile(path);
					UpdateNFCMenu();
				}
			}
		}
	}
}

void MainWindow::OnFileExit(wxCommandEvent& event)
{
	// todo: Safely clean up everything
	SaveSettings();
	exit(0);
}

void MainWindow::TogglePadView()
{
	const auto& config = GetWxGUIConfig();
	if (config.pad_open)
	{
		if (m_padView)
			return;
		
		m_padView = new PadViewFrame(this);

		m_padView->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnPadClose, this);

		m_padView->Show(true);

#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
		if (wxWlIsWaylandWindow(m_padView))
			wxWlSetAppId(m_padView, "info.cemu.Cemu");
#endif

		m_padView->Initialize();
		if (m_game_launched)
			m_padView->InitializeRenderCanvas();
	}
	else if (m_padView)
	{
		m_padView->Destroy();
		m_padView = nullptr;
	}
}

#if BOOST_OS_WINDOWS

#ifndef DBT_DEVNODES_CHANGED
#define DBT_DEVNODES_CHANGED (0x0007)
#endif
WXLRESULT MainWindow::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
	if (nMsg == WM_DEVICECHANGE)
	{
		if (wParam == DBT_DEVNODES_CHANGED)
		{
			InputManager::instance().on_device_changed();
		}
	}

	return wxFrame::MSWWindowProc(nMsg, wParam, lParam);
}
#endif

void MainWindow::OpenSettings()
{
	auto& config = GetWxGUIConfig();
	const auto language = config.language;

	GeneralSettings2 frame(this, m_game_launched);
	frame.ShowModal();
	const bool paths_modified = frame.ShouldReloadGamelist();
	const bool mlc_modified = frame.MLCModified();

	if (paths_modified)
		m_game_list->ReloadGameEntries(false);
	else
		SaveSettings();

	#ifdef ENABLE_DISCORD_RPC
	if (config.use_discord_presence)
	{
		if (!m_discord)
		{
			m_discord = std::make_unique<DiscordPresence>();
			if (!m_launched_game_name.empty())
				m_discord->UpdatePresence(DiscordPresence::Playing, m_launched_game_name);
		}
	}
	else
		m_discord.reset();
	#endif

	if(config.check_update && !m_game_launched)
		m_update_available = CemuUpdateWindow::IsUpdateAvailableAsync();

	if (mlc_modified)
		RecreateMenu();

	if (!config.fullscreen_menubar && IsFullScreen())
		SetMenuVisible(false);

	if (language != config.language)
		wxMessageBox(_("Cemu must be restarted to apply the selected UI language."), _("Information"), wxOK | wxCENTRE, this); // TODO: change language to newly selected one
}

void MainWindow::OnOptionsInput(wxCommandEvent& event)
{
	switch (event.GetId())
	{
	case MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN:
	{
		const bool state = m_fullscreenMenuItem->IsChecked();
		SetFullScreen(state);
		break;
	}
	case MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW:
	{
		GetWxGUIConfig().pad_open = !GetWxGUIConfig().pad_open;
		g_wxConfig.Save();

		TogglePadView();
		break;
	}
	case MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2:
	{
		if (m_graphic_pack_window)
			return;

		uint64 titleId = 0;
		if (CafeSystem::IsTitleRunning())
			titleId = CafeSystem::GetForegroundTitleId();

		m_graphic_pack_window = new GraphicPacksWindow2(this, titleId);
		m_graphic_pack_window->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnGraphicWindowClose, this);
		m_graphic_pack_window->Show(true);

		break;
	}

	case MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS:
	case MAINFRAME_MENU_ID_OPTIONS_GENERAL2:
	{
		OpenSettings();
		break;
	}
	case MAINFRAME_MENU_ID_OPTIONS_INPUT:
	{
		auto* frame = new InputSettings2(this);
		frame->ShowModal();
		frame->Destroy();
		break;
	}

	case MAINFRAME_MENU_ID_OPTIONS_HOTKEY:
	{
		auto* frame = new HotkeySettings(this);
		frame->Show();
		break;
	}
	}
}

void MainWindow::OnAccountSelect(wxCommandEvent& event)
{
	const int index = event.GetId() - MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1;
	const auto& accounts = Account::GetAccounts();
	wxASSERT(index >= 0 && index < (int)accounts.size());
	auto& config = GetConfig();
	config.account.m_persistent_id = accounts[index].GetPersistentId();
	// config.account.online_enabled.value = false; // reset online for safety
	GetConfigHandle().Save();
}

void MainWindow::OnConsoleLanguage(wxCommandEvent& event)
{
	switch (event.GetId())
	{
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE:
		GetConfig().console_language = CafeConsoleLanguage::JA;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH:
		GetConfig().console_language = CafeConsoleLanguage::EN;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_FRENCH:
		GetConfig().console_language = CafeConsoleLanguage::FR;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_GERMAN:
		GetConfig().console_language = CafeConsoleLanguage::DE;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ITALIAN:
		GetConfig().console_language = CafeConsoleLanguage::IT;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_SPANISH:
		GetConfig().console_language = CafeConsoleLanguage::ES;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_CHINESE:
		GetConfig().console_language = CafeConsoleLanguage::ZH;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_KOREAN:
		GetConfig().console_language = CafeConsoleLanguage::KO;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_DUTCH:
		GetConfig().console_language = CafeConsoleLanguage::NL;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE:
		GetConfig().console_language = CafeConsoleLanguage::PT;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN:
		GetConfig().console_language = CafeConsoleLanguage::RU;
		break;
	case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE:
		GetConfig().console_language = CafeConsoleLanguage::TW;
		break;
	default:
		cemu_assert_debug(false);
	}
	if (m_game_list)
	{
		m_game_list->DeleteCachedStrings();
		m_game_list->ReloadGameEntries(false);
	}
	GetConfigHandle().Save();
}

//void MainWindow::OnCPUMode(wxCommandEvent& event)
//{
//	if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_SINGLECORE_INTERPRETER)
//		GetConfig().cpu_mode = CPUMode::SinglecoreInterpreter;
//	else if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_SINGLECORE_RECOMPILER)
//		GetConfig().cpu_mode = CPUMode::SinglecoreRecompiler;
//	else if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_DUALCORE_RECOMPILER)
//		GetConfig().cpu_mode = CPUMode::DualcoreRecompiler;
//	else if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_TRIPLECORE_RECOMPILER)
//		GetConfig().cpu_mode = CPUMode::TriplecoreRecompiler;
//	else
//		cemu_assert_debug(false);
//	
//	GetConfigHandle().Save();
//}

void MainWindow::OnDebugSetting(wxCommandEvent& event)
{
	if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN)
		GetConfig().render_upside_down = event.IsChecked();
	else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS)
	{
		GetConfig().vk_accurate_barriers = event.IsChecked();
		if(!GetConfig().vk_accurate_barriers)
			wxMessageBox(_("Warning: Disabling the accurate barriers option will lead to flickering graphics but may improve performance. It is highly recommended to leave it turned on."), _("Accurate barriers are off"), wxOK);
	}
	else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY)
		ActiveSettings::EnableAudioOnlyAux(event.IsChecked());
	else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_RAM)
		memory_createDump();
	else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_FST)
	{
		/*	int msgBoxAnswer = wxMessageBox(_("All files from the currently running game will be dumped to /dump/<gamefolder>. This process can take a few minutes."),
				_("Dump WUD"), wxOK | wxCANCEL | wxICON_WARNING);
			if (msgBoxAnswer == wxOK)
			{
				volumeFST_dump(bootGame_getMountedWUD());
				wxMessageBox(_("Dump complete"));
			}*/
	}
	else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS)
	{
		// toggle debug -> dump -> curl requests
		const bool state = event.IsChecked();
		ActiveSettings::EnableDumpLibcurlRequests(state);
		if (state)
		{
			try
			{
				const fs::path path(ActiveSettings::GetUserDataPath());
				fs::create_directories(path / "dump" / "curl");
			}
			catch (const std::exception& ex)
			{
				SystemException sys(ex);
				cemuLog_log(LogType::Force, "error when creating dump curl folder: {}", sys.what());
				ActiveSettings::EnableDumpLibcurlRequests(false);
			}
		}
	}
	else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_8X)
		ActiveSettings::SetTimerShiftFactor(0);
	else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_4X)
		ActiveSettings::SetTimerShiftFactor(1);
	else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_2X)
		ActiveSettings::SetTimerShiftFactor(2);
	else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_1X)
		ActiveSettings::SetTimerShiftFactor(3);
	else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_05X)
		ActiveSettings::SetTimerShiftFactor(4);
	else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_025X)
		ActiveSettings::SetTimerShiftFactor(5);
	else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_0125X)
		ActiveSettings::SetTimerShiftFactor(6);
	else
		cemu_assert_debug(false);
	
	GetConfigHandle().Save();
}

void MainWindow::OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event)
{
	sint32 loggingIdBase = MAINFRAME_MENU_ID_DEBUG_LOGGING0;

	sint32 id = event.GetId();
	if (id >= loggingIdBase && id < (MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 64))
	{
		bool isEnable = event.IsChecked();
		LogType loggingType = static_cast<LogType>(id - loggingIdBase);
		if (isEnable)
			GetConfig().log_flag = GetConfig().log_flag.GetValue() | cemuLog_getFlag(loggingType);
		else
			GetConfig().log_flag = GetConfig().log_flag.GetValue() & ~cemuLog_getFlag(loggingType);
		cemuLog_setActiveLoggingFlags(GetConfig().log_flag.GetValue());
		GetConfigHandle().Save();
	}
}

void MainWindow::OnPPCInfoToggle(wxCommandEvent& event)
{
	GetConfig().advanced_ppc_logging = !GetConfig().advanced_ppc_logging.GetValue();
	GetConfigHandle().Save();
}

void MainWindow::OnDebugDumpGeneric(wxCommandEvent& event)
{
	std::string dumpSubpath;
	std::function<void(bool)> setDumpState;
	switch(event.GetId())
	{
	case MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES:
		dumpSubpath = "dump/textures";
		setDumpState = ActiveSettings::EnableDumpTextures;
		break;
	case MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS:
		dumpSubpath = "dump/shaders";
		setDumpState = ActiveSettings::EnableDumpShaders;
		break;
	case MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS:
		dumpSubpath = "dump/recompiler";
		setDumpState = ActiveSettings::EnableDumpRecompilerFunctions;
		break;
	default:
		UNREACHABLE;
	}
	const bool value = event.IsChecked();
	setDumpState(value);
	if (value)
	{
		try
		{
			fs::create_directories(ActiveSettings::GetUserDataPath("dump/shaders"));
		}
		catch (const std::exception & ex)
		{
			SystemException sys(ex);
			cemuLog_log(LogType::Force, "can't create shaders dump folder: {}", ex.what());
			ActiveSettings::EnableDumpShaders(false);
		}
	}
}

void MainWindow::OnLoggingWindow(wxCommandEvent& event)
{
	if(m_logging_window)
		return;

	m_logging_window = new LoggingWindow(this);
	m_logging_window->Bind(wxEVT_CLOSE_WINDOW, 
		[this](wxCloseEvent& event) {
		m_logging_window = nullptr;
		event.Skip();
	});
	m_logging_window->Show(true);
}

void MainWindow::OnGDBStubToggle(wxCommandEvent& event)
{
	if (g_gdbstub)
	{
		g_gdbstub.release();
		return;
	}

	const auto& config = GetConfig();
	g_gdbstub = std::make_unique<GDBServer>(config.gdb_port);
}

void MainWindow::OnDebugViewPPCThreads(wxCommandEvent& event)
{
	auto frame = new DebugPPCThreadsWindow(*this);
	frame->Show(true);
}

void MainWindow::OnDebugViewPPCDebugger(wxCommandEvent& event)
{
	if (m_debugger_window && m_debugger_window->IsShown())
	{
		m_debugger_window->Close();
		m_debugger_window = nullptr;
		return;
	}

	auto rect = GetDesktopRect();
	/*
	sint32 new_width = max(rect.GetWidth() * 0.70, rect.GetWidth() - 850);
	this->SetSize(new_width, 480);*/

	this->SetSize(800, 450 + 50);
	this->CenterOnScreen();

	auto pos = this->GetPosition();
	pos.y = std::min(pos.y + 200, rect.GetHeight() - 400);
	this->SetPosition(pos);

	m_debugger_window = new DebuggerWindow2(*this, rect);
	m_debugger_window->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnDebuggerClose, this);
	m_debugger_window->Show(true);
}

void MainWindow::OnDebugViewAudioDebugger(wxCommandEvent& event)
{
	auto frame = new AudioDebuggerWindow(*this);
	frame->Show(true);
}

void MainWindow::OnDebugViewTextureRelations(wxCommandEvent& event)
{
	openTextureViewer(*this);
}

void MainWindow::ShowCursor(bool state)
{
	#if BOOST_OS_WINDOWS
	CURSORINFO info{};
	info.cbSize = sizeof(CURSORINFO);
	GetCursorInfo(&info);
	const bool visible = info.flags == CURSOR_SHOWING;

	if (state == visible)
		return;

	int counter = 0;
	if(state)
	{
		do
		{
			counter = ::ShowCursor(TRUE);
		} while (counter < 0);
	}
	else
	{
		do
		{
			counter = ::ShowCursor(FALSE);
		} while (counter >= 0);
	}
	#else
	if (state)
	{
		wxSetCursor(wxNullCursor); // restore system default cursor
	}
	else
	{
		wxSetCursor(wxCursor(wxCURSOR_BLANK));
	}
	#endif
}

uintptr_t MainWindow::GetRenderCanvasHWND()
{
	// deprecated. We can use the global cross-platform window info structs now
	#if BOOST_OS_WINDOWS
	if (!m_render_canvas)
		return 0;
	return (uintptr_t)m_render_canvas->GetHWND();
	#else
	return 0;
	#endif
}

wxRect MainWindow::GetDesktopRect()
{
	const auto pos = GetPosition();
	const auto middle = pos.x + GetSize().GetWidth() / 2;

	const auto displayCount = wxDisplay::GetCount();
	for (uint32 i = 0; i < displayCount; ++i)
	{
		wxDisplay display(i);
		if (!display.IsOk())
			continue;

		const auto geo = display.GetGeometry();
		if (geo.x <= middle && middle <= geo.x + geo.width)
			return geo;
	}
	return { 0,0,800,600 };
}

void MainWindow::LoadSettings()
{
	GetConfigHandle().Load();
	const auto& config = GetWxGUIConfig();

	if(config.check_update)
		m_update_available = CemuUpdateWindow::IsUpdateAvailableAsync();

	if (config.window_position != Vector2i{ -1,-1 })
		this->SetPosition({ config.window_position.x, config.window_position.y });

	if (config.window_size.x > 0 && config.window_size.y > 0)
	{
		this->SetSize({ config.window_size.x, config.window_size.y });

		if (config.window_maximized)
			this->Maximize();
	}

	if (config.pad_position != Vector2i{ -1,-1 })
	{
		g_window_info.restored_pad_x = config.pad_position.x;
		g_window_info.restored_pad_y = config.pad_position.y;
	}

	if (config.pad_size != Vector2i{ -1,-1 })
	{
		g_window_info.restored_pad_width = config.pad_size.x;
		g_window_info.restored_pad_height = config.pad_size.y;

		g_window_info.pad_maximized = config.pad_maximized;
	}

	this->TogglePadView();

	if(m_game_list)
		m_game_list->LoadConfig();
}

void MainWindow::SaveSettings()
{
	auto lock = GetConfigHandle().Lock();
	auto& config = GetWxGUIConfig();
	
	if (config.window_position != Vector2i{ -1,-1 })
	{
		config.window_position.x = m_restored_position.x;
		config.window_position.y = m_restored_position.y;
	}
	if (config.window_size != Vector2i{ -1,-1 })
	{
		config.window_size.x = m_restored_size.x;
		config.window_size.y = m_restored_size.y;
		config.window_maximized = IsMaximized();
	}
	else
	{
		config.window_maximized = false;
	}

	config.pad_open = m_padView != nullptr;

	if (config.pad_position != Vector2i{ -1,-1 } && g_window_info.restored_pad_x != -1)
	{
		config.pad_position.x = g_window_info.restored_pad_x;
		config.pad_position.y = g_window_info.restored_pad_y;
	}
	if (config.pad_size != Vector2i{ -1,-1 } && g_window_info.restored_pad_width != -1)
	{
		config.pad_size.x = g_window_info.restored_pad_width;
		config.pad_size.y = g_window_info.restored_pad_height;
		config.pad_maximized = g_window_info.pad_maximized;
	}
	else
	{
		config.pad_maximized = false;
	}

	if(m_game_list)
		m_game_list->SaveConfig();
	
	g_wxConfig.Save();
}

void MainWindow::OnMouseMove(wxMouseEvent& event)
{
	event.Skip();

	m_last_mouse_move_time = std::chrono::steady_clock::now();
	m_mouse_position = wxGetMousePosition();
	ShowCursor(true);

	auto& instance = InputManager::instance();
	std::unique_lock lock(instance.m_main_mouse.m_mutex);
	auto physPos = ToPhys(event.GetPosition());
	instance.m_main_mouse.position = { physPos.x, physPos.y };
	lock.unlock();

	if (!IsFullScreen())
		return;

	const auto& config = GetWxGUIConfig();
	// if mouse goes to upper screen then show our menu in fullscreen mode
	if (config.fullscreen_menubar)
		SetMenuVisible(event.GetPosition().y < 50);
}

void MainWindow::OnMouseLeft(wxMouseEvent& event)
{
	auto& instance = InputManager::instance();
	
	std::scoped_lock lock(instance.m_main_mouse.m_mutex);
	instance.m_main_mouse.left_down = event.ButtonDown(wxMOUSE_BTN_LEFT);
	auto physPos = ToPhys(event.GetPosition());
	instance.m_main_mouse.position = { physPos.x, physPos.y };
	if (event.ButtonDown(wxMOUSE_BTN_LEFT))
		instance.m_main_mouse.left_down_toggle = true;
	
	event.Skip();
}

void MainWindow::OnMouseRight(wxMouseEvent& event)
{
	auto& instance = InputManager::instance();

	std::scoped_lock lock(instance.m_main_mouse.m_mutex);
	instance.m_main_mouse.right_down = event.ButtonDown(wxMOUSE_BTN_RIGHT);
	auto physPos = ToPhys(event.GetPosition());
	instance.m_main_mouse.position = { physPos.x, physPos.y };
	if(event.ButtonDown(wxMOUSE_BTN_RIGHT))
		instance.m_main_mouse.right_down_toggle = true;
	
	event.Skip();
}

void MainWindow::OnGameListBeginUpdate(wxCommandEvent& event)
{
	if (m_game_list->IsShown())
		m_info_bar->ShowMessage(_("Updating game list..."));
}

void MainWindow::OnGameListEndUpdate(wxCommandEvent& event)
{
	m_info_bar->Dismiss();
}

void MainWindow::OnAccountListRefresh(wxCommandEvent& event)
{
	RecreateMenu();
}

void MainWindow::OnRequestGameListRefresh(wxCommandEvent& event)
{
	m_game_list->ReloadGameEntries(false);
}

void MainWindow::OnSetWindowTitle(wxCommandEvent& event)
{
	this->SetTitle(event.GetString());
}

void MainWindow::OnKeyUp(wxKeyEvent& event)
{
	event.Skip();

	if (swkbd_hasKeyboardInputHook())
		return;

	HotkeySettings::CaptureInput(event);
}

void MainWindow::OnKeyDown(wxKeyEvent& event)
{
#if defined(__APPLE__)
       // On macOS, allow Cmd+Q to quit the application
    if (event.CmdDown() && event.GetKeyCode() == 'Q')
    {
        Close(true);
    }
#else
     // On Windows/Linux, only Alt+F4 is allowed for quitting
    if (event.AltDown() && event.GetKeyCode() == WXK_F4)
    {
        Close(true);
    }
#endif
    else
    {
        event.Skip(); 
    }
}

void MainWindow::OnChar(wxKeyEvent& event)
{
	if (swkbd_hasKeyboardInputHook())
		swkbd_keyInput(event.GetUnicodeKey());
	
	// event.Skip();
}

void MainWindow::OnToolsInput(wxCommandEvent& event)
{
	const auto id = event.GetId();
	switch (id)
	{
	case MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER:
	{
		if (m_toolWindow)
			m_toolWindow->SetFocus();
		else
		{
			m_toolWindow = new MemorySearcherTool(this);
			m_toolWindow->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnMemorySearcherClose, this);
			m_toolWindow->Show(true);
		}
		break;
	}
	case MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER:
	case MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER:
	{
		const auto default_tab = id == MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER ? TitleManagerPage::TitleManager : TitleManagerPage::DownloadManager;
			
		if (m_title_manager)
			m_title_manager->SetFocusAndTab(default_tab);
		else
		{
			m_title_manager = new TitleManager(this, default_tab);
			m_title_manager->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event)
				{
					m_title_manager = nullptr;
					event.Skip();
				});
			m_title_manager->Show();
		}
		break;
	}
	case MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES:
	{
		if (m_usb_devices)
		{
			m_usb_devices->Show(true);
			m_usb_devices->Raise();
			m_usb_devices->SetFocus();
		}
		else
		{
			m_usb_devices = new EmulatedUSBDeviceFrame(this);
			m_usb_devices->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event)
				{
					if (event.CanVeto()) {
						m_usb_devices->Show(false);
						event.Veto();
					}
				});
			m_usb_devices->Show(true);
		}
		break;
	}
	break;
	}
}

void MainWindow::OnGesturePan(wxPanGestureEvent& event)
{
	auto& instance = InputManager::instance();
	std::scoped_lock lock(instance.m_main_touch.m_mutex);
	auto physPos = ToPhys(event.GetPosition());
	instance.m_main_touch.position = { physPos.x, physPos.y };
	instance.m_main_touch.left_down = event.IsGestureStart() || !event.IsGestureEnd();
	if (event.IsGestureStart() || !event.IsGestureEnd())
		instance.m_main_touch.left_down_toggle = true;
	

	event.Skip();
}

void MainWindow::OnGameLoaded()
{
	if (m_debugger_window)
		m_debugger_window->OnGameLoaded();
}

void MainWindow::AsyncSetTitle(std::string_view windowTitle)
{
	wxCommandEvent set_title_event(wxEVT_SET_WINDOW_TITLE);
	set_title_event.SetString(wxHelper::FromUtf8(windowTitle));
	g_mainFrame->QueueEvent(set_title_event.Clone());
}

void MainWindow::CreateCanvas()
{
    // create panel for canvas
    m_game_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxWANTS_CHARS);
    auto* sizer = new wxBoxSizer(wxVERTICAL);

    // shouldn't be needed, but who knows
    m_game_panel->Bind(wxEVT_KEY_UP, &MainWindow::OnKeyUp, this);
    m_game_panel->Bind(wxEVT_CHAR, &MainWindow::OnChar, this);

    m_game_panel->SetSizer(sizer);
    this->GetSizer()->Add(m_game_panel, 1, wxEXPAND, 0, nullptr);

    // create canvas
    if (ActiveSettings::GetGraphicsAPI() == kVulkan)
		m_render_canvas = new VulkanCanvas(m_game_panel, wxSize(1280, 720), true);
	else
		m_render_canvas = GLCanvas_Create(m_game_panel, wxSize(1280, 720), true);

	// mouse events
	m_render_canvas->Bind(wxEVT_MOTION, &MainWindow::OnMouseMove, this);
	m_render_canvas->Bind(wxEVT_MOUSEWHEEL, &MainWindow::OnMouseWheel, this);
	m_render_canvas->Bind(wxEVT_LEFT_DOWN, &MainWindow::OnMouseLeft, this);
	m_render_canvas->Bind(wxEVT_LEFT_UP, &MainWindow::OnMouseLeft, this);
	m_render_canvas->Bind(wxEVT_RIGHT_DOWN, &MainWindow::OnMouseRight, this);
	m_render_canvas->Bind(wxEVT_RIGHT_UP, &MainWindow::OnMouseRight, this);

	m_render_canvas->Bind(wxEVT_GESTURE_PAN, &MainWindow::OnGesturePan, this);

	// key events
	m_render_canvas->Bind(wxEVT_KEY_UP, &MainWindow::OnKeyUp, this);
	m_render_canvas->Bind(wxEVT_KEY_DOWN, &MainWindow::OnKeyDown, this);
	m_render_canvas->Bind(wxEVT_CHAR, &MainWindow::OnChar, this);

	m_render_canvas->SetDropTarget(new wxAmiiboDropTarget(this));
	m_game_panel->GetSizer()->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr);

	GetSizer()->Layout();
	m_render_canvas->SetFocus();

	if (m_padView)
		m_padView->InitializeRenderCanvas();
}

void MainWindow::DestroyCanvas()
{
	if (m_padView)
	{
		m_padView->DestroyCanvas();
	}
	if (m_render_canvas)
	{
		m_render_canvas->Destroy();
		m_render_canvas = nullptr;
	}
    if(m_game_panel)
    {
        m_game_panel->Destroy();
        m_game_panel = nullptr;
    }
}

void MainWindow::OnSizeEvent(wxSizeEvent& event)
{
	if (!IsMaximized() && !WindowSystem::IsFullScreen())
		m_restored_size = GetSize();

	const wxSize client_size = GetClientSize();
	g_window_info.width = client_size.GetWidth();
	g_window_info.height = client_size.GetHeight();
	g_window_info.phys_width = ToPhys(client_size.GetWidth());
	g_window_info.phys_height = ToPhys(client_size.GetHeight());
	g_window_info.dpi_scale = GetDPIScaleFactor();

	if (m_debugger_window && m_debugger_window->IsShown())
		m_debugger_window->OnParentMove(GetPosition(), event.GetSize());

	event.Skip();

	VsyncDriver_notifyWindowPosChanged();
}

void MainWindow::OnDPIChangedEvent(wxDPIChangedEvent& event)
{
	event.Skip();
	const wxSize client_size = GetClientSize();
	g_window_info.width = client_size.GetWidth();
	g_window_info.height = client_size.GetHeight();
	g_window_info.phys_width = ToPhys(client_size.GetWidth());
	g_window_info.phys_height = ToPhys(client_size.GetHeight());
	g_window_info.dpi_scale = GetDPIScaleFactor();
}

void MainWindow::OnMove(wxMoveEvent& event)
{
	if (!IsMaximized() && !WindowSystem::IsFullScreen())
		m_restored_position = GetPosition();

	if (m_debugger_window && m_debugger_window->IsShown())
		m_debugger_window->OnParentMove(GetPosition(), GetSize());
	VsyncDriver_notifyWindowPosChanged();
}

void MainWindow::OnDebuggerClose(wxCloseEvent& event)
{
	m_debugger_window = nullptr;
	event.Skip();
}

void MainWindow::OnPadClose(wxCloseEvent& event)
{
	auto& config = GetWxGUIConfig();
	config.pad_open = false;
	if (config.pad_position != Vector2i{ -1,-1 })
		m_padView->GetPosition(&config.pad_position.x, &config.pad_position.y);

	if (config.pad_size != Vector2i{ -1,-1 })
		m_padView->GetSize(&config.pad_size.x, &config.pad_size.y);

	g_wxConfig.Save();

	// already deleted by wxwidget
	m_padView = nullptr;

	if (m_padViewMenuItem)
		m_padViewMenuItem->Check(false);

	event.Skip();
}

void MainWindow::OnMemorySearcherClose(wxCloseEvent& event)
{
	m_toolWindow = nullptr;
	event.Skip();
}

void MainWindow::OnMouseWheel(wxMouseEvent& event)
{
	const float delta = event.GetWheelRotation(); // in 120 steps -> max reached ~480 (?)
	auto& instance = InputManager::instance();
	instance.m_mouse_wheel = (delta / 120.0f);

	event.Skip();
}

void MainWindow::SetFullScreen(bool state)
{
	// only update config entry if we dont't have launch parameters
	if (!LaunchSettings::FullscreenEnabled().has_value())
	{
		GetWxGUIConfig().fullscreen = state;
		g_wxConfig.Save();
	}
	if (state && !m_game_launched)
		return;
	g_window_info.is_fullscreen = state;
	m_fullscreenMenuItem->Check(state);

	this->ShowFullScreen(state);

	if (state)
		m_menu_visible = false; // menu gets always disabled by wxFULLSCREEN_NOMENUBAR
	else
		SetMenuVisible(true);
}

void MainWindow::SetMenuVisible(bool state)
{
	if (m_menu_visible == state)
		return;

	SetMenuBar(state ? m_menuBar : nullptr);
	m_menu_visible = state;
}

void MainWindow::UpdateNFCMenu()
{
	if (m_nfcMenuSeparator0)
	{
		m_nfcMenu->Remove(m_nfcMenuSeparator0->GetId());
		m_nfcMenuSeparator0 = nullptr;
	}
	// remove recent files list
	for (sint32 i = 0; i < wxCemuConfig::kMaxRecentEntries; i++)
	{
		if (m_nfcMenu->FindChildItem(MAINFRAME_MENU_ID_NFC_RECENT_0 + i) == nullptr)
			continue;
		m_nfcMenu->Remove(MAINFRAME_MENU_ID_NFC_RECENT_0 + i);
	}
	// add entries
	const auto& config = GetWxGUIConfig();
	sint32 recentFileIndex = 0;
	for (size_t i = 0; i < config.recent_nfc_files.size(); i++)
	{
		const auto& entry = config.recent_nfc_files[i];
		if (entry.empty())
			continue;
		
		if (!fs::exists(_utf8ToPath(entry)))
			continue;
		
		if (recentFileIndex == 0)
			m_nfcMenuSeparator0 = m_nfcMenu->AppendSeparator();

		m_nfcMenu->Append(MAINFRAME_MENU_ID_NFC_RECENT_0 + i, to_wxString(fmt::format("{}. {}", recentFileIndex, entry)));

		recentFileIndex++;
		if (recentFileIndex >= 12)
			break;
	}
}

bool MainWindow::IsMenuHidden() const
{
	return m_menu_visible;
}

void MainWindow::OnTimer(wxTimerEvent& event)
{
	if(m_update_available.valid() && future_is_ready(m_update_available))
	{
		if(m_update_available.get())
		{
			wxMessageDialog dialog(this, _("There's a new update available.\nDo you want to update?"), _("Update notification"), wxCENTRE | wxYES_NO);
			if(dialog.ShowModal() == wxID_YES)
			{
				CemuUpdateWindow update_window(this);
				update_window.ShowModal();
			}
		}

		m_update_available = {};
	}

	if (!IsFullScreen() || m_menu_visible)
		return;

	const auto mouse_position = wxGetMousePosition();
	if(m_mouse_position != mouse_position)
	{
		m_last_mouse_move_time = std::chrono::steady_clock::now();
		m_mouse_position = mouse_position;
		ShowCursor(true);
		return;
	}

	auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_last_mouse_move_time);
	if (diff.count() > 3000)
	{
		ShowCursor(false);
	}
		
}

#define BUILD_DATE __DATE__ " " __TIME__

class CemuAboutDialog : public wxDialog
{
public:
	CemuAboutDialog(wxWindow* parent = NULL)
		: wxDialog(NULL, wxID_ANY, _("About Cemu"), wxDefaultPosition, wxSize(500, 700))
	{
		Create(parent);
	}

	void Create(wxWindow* parent = NULL)
	{
		SetIcon(wxICON(M_WND_ICON128));

		wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this);

		wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

		m_scrolledSizer = new wxBoxSizer(wxVERTICAL);

		AddHeaderInfo(scrolledWindow, m_scrolledSizer);
		m_scrolledSizer->AddSpacer(5);
		AddLibInfo(scrolledWindow, m_scrolledSizer);
		m_scrolledSizer->AddSpacer(5);
		AddThanks(scrolledWindow, m_scrolledSizer);

		scrolledWindow->SetSizer(m_scrolledSizer);
		scrolledWindow->FitInside();
		scrolledWindow->SetScrollRate(25, 25);
		mainSizer->Add(scrolledWindow, wxSizerFlags(1).Expand().Border(wxLEFT, 10));

		SetSizer(mainSizer);
		CentreOnParent();
	}

	void AddHeaderInfo(wxWindow* parent, wxSizer* sizer)
	{
		auto versionString = formatWxString(_("Cemu\nVersion {0}\nCompiled on {1}\nOriginal authors: {2}"), BUILD_VERSION_STRING, BUILD_DATE, "Exzap, Petergov");

		sizer->Add(new wxStaticText(parent, wxID_ANY, versionString), wxSizerFlags().Border(wxALL, 3).Border(wxTOP, 10));
		sizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://cemu.info", "https://cemu.info", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 3));

		sizer->AddSpacer(3);
		sizer->Add(new wxStaticLine(parent), wxSizerFlags().Expand().Border(wxRIGHT, 4));
		sizer->AddSpacer(5);

		wxString extraInfo(_("Cemu is a Wii U emulator.\n\nWii and Wii U are trademarks of Nintendo.\nCemu is not affiliated with Nintendo."));
		sizer->Add(new wxStaticText(parent, wxID_ANY, extraInfo), wxSizerFlags());
	}

	void AddLibInfo(wxWindow* parent, wxSizer* sizer)
	{
		sizer->AddSpacer(3);
		sizer->Add(new wxStaticLine(parent), wxSizerFlags().Expand().Border(wxRIGHT, 4));
		sizer->AddSpacer(3);

		sizer->Add(new wxStaticText(parent, wxID_ANY, _("Used libraries and utilities:")), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 2));
		// zLib
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "zLib ("), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.zlib.net", "https://www.zlib.net", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0);
			sizer->Add(lineSizer);
		}
		// wxWidgets
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "wxWidgets ("), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.wxwidgets.org/", "https://www.wxwidgets.org/", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0);
			sizer->Add(lineSizer);
		}
		// OpenSSL
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "OpenSSL ("), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.openssl.org/", "https://www.openssl.org/", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0);
			sizer->Add(lineSizer);
		}
		// libcurl
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "libcurl ("), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://curl.haxx.se/libcurl/", "https://curl.haxx.se/libcurl/", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0);
			sizer->Add(lineSizer);
		}
		// imgui
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "imgui ("), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://github.com/ocornut/imgui", "https://github.com/ocornut/imgui", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0);
			sizer->Add(lineSizer);
		}
		// fontawesome
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "fontawesome ("), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://github.com/FortAwesome/Font-Awesome", "https://github.com/FortAwesome/Font-Awesome", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0);
			sizer->Add(lineSizer);
		}
		// boost
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "boost ("), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.boost.org", "https://www.boost.org", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0);
			sizer->Add(lineSizer);
		}
		// libusb
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "libusb ("), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://libusb.info", "https://libusb.info", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0);
			sizer->Add(lineSizer);
		}
#if BOOST_OS_MACOS
		// MoltenVK
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, -1, "MoltenVK ("), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://github.com/KhronosGroup/MoltenVK", "https://github.com/KhronosGroup/MoltenVK", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, -1, ")"), 0);
			sizer->Add(lineSizer);
		}
#endif
		// icons
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "icons from "), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://icons8.com", "https://icons8.com", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			sizer->Add(lineSizer);
		}
		// Lato font (are we still using it?)
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "\"Lato\" font by tyPoland Lukasz Dziedzic (OFL, V1.1)", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			sizer->Add(lineSizer);
		}
		// SDL
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "SDL ("), 0, wxALIGN_NOT, 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://github.com/libsdl-org/SDL", "https://github.com/libsdl-org/SDL", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0);
			sizer->Add(lineSizer);
		}
		// IH264
		{
			wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, "Modified ih264 from Android project ("), 0);
			lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "Source", "https://cemu.info/oss/ih264d.zip", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, " "), 0);
			wxHyperlinkCtrl* noticeLink = new wxHyperlinkCtrl(parent, wxID_ANY, "NOTICE", "", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT));
			noticeLink->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& event)
				{
					fs::path tempPath = fs::temp_directory_path();
					tempPath.append("NOTICE_IH264.txt");
					FileStream* fs = FileStream::createFile2(tempPath);
					if (!fs)
						return;
					fs->writeString(
						"/******************************************************************************\r\n"
						" *\r\n"
						" * Copyright (C) 2015 The Android Open Source Project\r\n"
						" *\r\n"
						" * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n"
						" * you may not use this file except in compliance with the License.\r\n"
						" * You may obtain a copy of the License at:"
						" *\r\n"
						" * http://www.apache.org/licenses/LICENSE-2.0\r\n"
						" *\r\n"
						" * Unless required by applicable law or agreed to in writing, software\r\n"
						" * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n"
						" * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n"
						" * See the License for the specific language governing permissions and\r\n"
						" * limitations under the License.\r\n"
						" *\r\n"
						" *****************************************************************************\r\n"
						" * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore\r\n"
						"*/\r\n"
						"/*****************************************************************************/\r\n"
					);
					delete fs;
					wxLaunchDefaultBrowser(wxHelper::FromUtf8(fmt::format("file:{}", _pathToUtf8(tempPath))));
				});
			lineSizer->Add(noticeLink, 0);
			lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0);
			sizer->Add(lineSizer);
		}
	}

	void AddThanks(wxWindow* parent, wxSizer* sizer)
	{
		sizer->AddSpacer(3);
		sizer->Add(new wxStaticLine(parent), wxSizerFlags().Expand().Border(wxRIGHT, 4));
		sizer->AddSpacer(3);

		wxGridSizer* gridSizer = new wxGridSizer(1, 2, 0, 0);

		sizer->AddSpacer(2);

		sizer->Add(new wxStaticText(parent, wxID_ANY, _("Thanks to our Patreon supporters:")), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 2));

		std::vector<const char*> patreonSupporterNames{ "Maufeat", "lvlv", "F34R", "John Godgames", "Jameel Lewis", "skooks", "Cheesy", "Barrowsx", "Mored1984", "madmat007"
			, "Kuhnnl", "Owen M", "lucianobugalu", "KimoMaka", "nick palma aka renaissance18", "TheGiantBros", "SpiGAndromeda"
			, "Chimech0", "Nicolás Pino", "Pezzatti", "Barry Wallace", "REGNR8 Productions", "Lagia", "Freestyler316", "Dentora"
			, "tactics", "Merola.C", "Ceigyx", "Mata", "BobSchneeder45", "fenixDG", "jjalapeno55", "FissionMetroid101", "Jetta88"
			, "nesxdie", "Mikah", "PornfoxVR.com", "Hunter4everosa", "Bbzx", "Salim Sanehi", "FalloutpunkX", "NashOH-CL", "RaheemWala"
			, "Faris Leonhart", "MahvZero", "PlaguedGuardian", "Stuffie", "CaptainLester", "Qtech", "Zaurexus", "Leonidas", "Artifesto"
			, "Alca259", "SirWestofAsh", "Loli Co.", "The Technical Revolutionary", "MegaYama", "mitori", "Seymordius", "Adrian Josh Cruz", "Manuel Hoenings", "Just A Jabb"
			, "pgantonio", "CannonXIII", "Lonewolf00708", "AlexsDesign.com", "NoskLo", "MrSirHaku", "xElite_V AKA William H. Johnson", "Zalnor", "Pig", "James \"SE4LS\"", "DairyOrange", "Horoko Lawrence", "bloodmc", "Officer Jenny", "Quasar", "Postposterous", "Jake Jackson", "Kaydax", "CthePredatorG"
			, "Hengi", "Pyrochaser", "luma.x3"};

		wxString nameListLeft, nameListRight;
		for (size_t i = 0; i < patreonSupporterNames.size(); i++)
		{
			const char* name = patreonSupporterNames[i];
			wxString& nameList = ((i % 2) == 0) ? nameListLeft : nameListRight;
			if (i >= 2)
				nameList.append("\n");
			nameList.append(wxString::FromUTF8(name));
		}

		gridSizer->Add(new wxStaticText(parent, wxID_ANY, nameListLeft), wxSizerFlags());
		gridSizer->Add(new wxStaticText(parent, wxID_ANY, nameListRight), wxSizerFlags());

		sizer->AddSpacer(4);

		sizer->Add(gridSizer, 1, wxEXPAND);

		sizer->AddSpacer(2);
		sizer->Add(new wxStaticText(parent, wxID_ANY, _("Special thanks:")), wxSizerFlags().Expand().Border(wxTOP, 2));
		sizer->Add(new wxStaticText(parent, wxID_ANY, "espes - Also try XQEMU!\nWaltzz92"), wxSizerFlags().Expand().Border(wxTOP, 1));
	}

protected:
	wxSizer* m_scrolledSizer;
};

void MainWindow::OnHelpAbout(wxCommandEvent& event)
{
	CemuAboutDialog dlgAbout(this);
	dlgAbout.ShowModal();
}

void MainWindow::OnHelpUpdate(wxCommandEvent& event)
{
	CemuUpdateWindow test(this);
	test.ShowModal();
}

void MainWindow::RecreateMenu()
{
	if (m_menuBar)
	{
		SetMenuBar(nullptr);
		m_menuBar->Destroy();
		m_menuBar = nullptr;
	}
	
	auto& guiConfig = GetWxGUIConfig();
	
	m_menuBar = new wxMenuBar();
	// file submenu
	m_fileMenu = new wxMenu();

	if (!m_game_launched)
	{
		m_loadMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_LOAD, _("&Load..."));
		m_installUpdateMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, _("&Install game title, update or DLC..."));

		wxMenu* recentMenu = new wxMenu();
		sint32 recentFileIndex = 1;
		m_fileMenuSeparator0 = nullptr;
		m_fileMenuSeparator1 = nullptr;
		for (size_t i = 0; i < guiConfig.recent_launch_files.size(); i++)
		{
			const std::string& pathStr = guiConfig.recent_launch_files[i];
			if (pathStr.empty())
				continue;
			recentMenu->Append(MAINFRAME_MENU_ID_FILE_RECENT_0 + i, to_wxString(fmt::format("{}. {}", recentFileIndex, pathStr)));
			recentFileIndex++;

			if (recentFileIndex >= 10)
				break;
		}
		if (recentFileIndex == 0)
		{
			wxMenuItem* placeholder = recentMenu->Append(wxID_NONE, _("(No recent files)"));
			placeholder->Enable(false);
		}

		m_fileMenu->AppendSeparator();
		m_fileMenu->AppendSubMenu(recentMenu, _("Recent files"));
		m_fileMenu->AppendSeparator();
	}
	else
	{
		// add 'Stop emulation' menu entry to file menu
#ifdef CEMU_DEBUG_ASSERT
		m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_END_EMULATION, _("Stop emulation"));
#endif
	}

	m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("Open Cemu folder"));
	m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("Open MLC folder"));
	m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, _("Open &shader cache folder"));
	m_fileMenu->AppendSeparator();
	m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, _("Clear Spot&Pass cache"), wxEmptyString);
	if (m_game_launched)
		m_fileMenu->Enable(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, false);
	m_fileMenu->AppendSeparator();
	m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit"));
	m_menuBar->Append(m_fileMenu, _("&File"));
	// options->account submenu
	m_optionsAccountMenu = new wxMenu();
	const auto account_id = ActiveSettings::GetPersistentId();
	int index = 0;
	for(const auto& account : Account::GetAccounts())
	{
		wxMenuItem* item = m_optionsAccountMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 + index, account.ToString());
		item->Check(account_id == account.GetPersistentId());
		if (m_game_launched || LaunchSettings::GetPersistentId().has_value())
			item->Enable(false);
		
		++index;
	}

	auto& config = GetConfig();
	auto& wxConfig = GetWxGUIConfig();
	// options->console language submenu
	wxMenu* optionsConsoleLanguageMenu = new wxMenu();
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH, _("&English"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::EN);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE, _("&Japanese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::JA);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_FRENCH, _("&French"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::FR);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_GERMAN, _("&German"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::DE);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ITALIAN, _("&Italian"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::IT);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_SPANISH, _("&Spanish"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::ES);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_CHINESE, _("&Chinese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::ZH);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_KOREAN, _("&Korean"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::KO);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_DUTCH, _("&Dutch"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::NL);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE, _("&Portuguese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::PT);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN, _("&Russian"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::RU);
	optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, _("&Taiwanese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::TW);
	if(IsGameLaunched())
	{
		auto items = optionsConsoleLanguageMenu->GetMenuItems();
		for (auto& item : items)
		{
			item->Enable(false);
		}
	}

	// options submenu
	wxMenu* optionsMenu = new wxMenu();
	m_fullscreenMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN, _("&Fullscreen"), wxEmptyString);
	m_fullscreenMenuItem->Check(FullscreenEnabled());		
	
	optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2, _("&Graphic packs"));
	m_padViewMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, _("&Separate GamePad view"), wxEmptyString);
	m_padViewMenuItem->Check(wxConfig.pad_open);
	optionsMenu->AppendSeparator();
	#if BOOST_OS_MACOS
	optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, _("&Settings..." "\tCtrl-,"));
	#endif
	optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, _("&General settings"));
	optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_INPUT, _("&Input settings"));
	optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_HOTKEY, _("&Hotkey settings"));

	optionsMenu->AppendSeparator();
	optionsMenu->AppendSubMenu(m_optionsAccountMenu, _("&Active account"));
	optionsMenu->AppendSubMenu(optionsConsoleLanguageMenu, _("&Console language"));
	m_menuBar->Append(optionsMenu, _("&Options"));

	// tools submenu
	wxMenu* toolsMenu = new wxMenu();
	m_memorySearcherMenuItem = toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, _("&Memory searcher"));
	m_memorySearcherMenuItem->Enable(false);
	toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager"));
	toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, _("&Download Manager"));
	toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, _("&Emulated USB Devices"));

	m_menuBar->Append(toolsMenu, _("&Tools"));

	// cpu timer speed menu
	wxMenu* timerSpeedMenu = new wxMenu();
	timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_1X, _("&1x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 3);
	timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_2X, _("&2x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 2);
	timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_4X, _("&4x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 1);
	timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_8X, _("&8x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 0);
	timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_05X, _("&0.5x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 4);
	timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_025X, _("&0.25x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 5);
	timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_0125X, _("&0.125x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 6);

	// cpu submenu
	wxMenu* cpuMenu = new wxMenu();
	cpuMenu->AppendSubMenu(timerSpeedMenu, _("&Timer speed"));
	m_menuBar->Append(cpuMenu, _("&CPU"));

	// nfc submenu
	wxMenu* nfcMenu = new wxMenu();
	m_nfcMenu = nfcMenu;
	nfcMenu->Append(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, _("&Scan NFC tag from file"))->Enable(false);
	m_menuBar->Append(nfcMenu, _("&NFC"));
	m_nfcMenuSeparator0 = nullptr;
	// debug->logging submenu
	wxMenu* debugLoggingMenu = new wxMenu();

	debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::UnsupportedAPI), _("&Unsupported API calls"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::UnsupportedAPI));
	debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::APIErrors), _("&Invalid API usage"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::APIErrors));
	debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitLogging), _("&Coreinit Logging (OSReport/OSConsole)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitLogging));
	debugLoggingMenu->AppendSeparator();

	wxMenu* logCosModulesMenu = new wxMenu();
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING_MESSAGE, _("&Options below are for experts. Leave off if unsure"), wxEmptyString)->Enable(false);
	logCosModulesMenu->AppendSeparator();
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitFile), _("coreinit File-Access API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitFile));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThreadSync), _("coreinit Thread-Synchronization API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThreadSync));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMem), _("coreinit Memory API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMem));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMP), _("coreinit MP API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMP));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("nn_save API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Save));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("nn_nfp API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("nn_fp API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::PRUDP), _("nn_fp PRUDP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::PRUDP));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("nn_boss API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NFC), _("nfc API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NFC));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NTAG), _("ntag API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NTAG));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("nsysnet API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Socket));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("h264 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::H264));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("gx2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI));
	logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("Input API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::InputAPI));

	debugLoggingMenu->AppendSubMenu(logCosModulesMenu, _("&CafeOS modules logging"));
	debugLoggingMenu->AppendSeparator();
	debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Patches), _("&Graphic pack patches"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Patches));
	debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureCache), _("&Texture cache warnings"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::TextureCache));
	debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureReadback), _("&Texture readback"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::TextureReadback));
	debugLoggingMenu->AppendSeparator();
	debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::OpenGLLogging), _("&OpenGL debug output"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::OpenGLLogging));
	debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::VulkanValidation), _("&Vulkan validation layer (slow)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::VulkanValidation));
	debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO, _("&Log PPC context for API"), wxEmptyString)->Check(cemuLog_advancedPPCLoggingEnabled());
	m_loggingSubmenu = debugLoggingMenu;
	// debug->dump submenu
	wxMenu* debugDumpMenu = new wxMenu;
	debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, _("&Textures"), wxEmptyString)->Check(ActiveSettings::DumpTexturesEnabled());
	debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, _("&Shaders"), wxEmptyString)->Check(ActiveSettings::DumpShadersEnabled());
	debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, _("&Recompiled functions"), wxEmptyString)->Check(ActiveSettings::DumpRecompilerFunctionsEnabled());
	debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, _("&nlibcurl HTTP/HTTPS requests"), wxEmptyString);
	// debug submenu
	wxMenu* debugMenu = new wxMenu();
	m_debugMenu = debugMenu;
	debugMenu->AppendSubMenu(debugLoggingMenu, _("&Logging"));
	debugMenu->AppendSubMenu(debugDumpMenu, _("&Dump"));
	debugMenu->AppendSeparator();
	
	auto upsidedownItem = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, _("&Render upside-down"), wxEmptyString);
	upsidedownItem->Check(ActiveSettings::RenderUpsideDownEnabled());
	if(LaunchSettings::RenderUpsideDownEnabled().has_value())
		upsidedownItem->Enable(false);

	auto accurateBarriers = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, _("&Accurate barriers (Vulkan)"), wxEmptyString);
	accurateBarriers->Check(GetConfig().vk_accurate_barriers);

	debugMenu->AppendSeparator();

#ifdef CEMU_DEBUG_ASSERT
	auto audioAuxOnly = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY, _("&Audio AUX only"), wxEmptyString);
	audioAuxOnly->Check(ActiveSettings::AudioOutputOnlyAux());
#endif

	debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW, _("&Open logging window"));
	m_gdbstub_toggle = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_TOGGLE_GDB_STUB, _("&Launch with GDB stub"), wxEmptyString);
	m_gdbstub_toggle->Check(g_gdbstub != nullptr);
	m_gdbstub_toggle->Enable(!m_game_launched);

	debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, _("&View PPC threads"));
	debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, _("&View PPC debugger"));
	debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, _("&View audio debugger"));
	debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, _("&View texture cache info"));
	debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, _("&Dump current RAM"));
	// debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_DUMP_FST, _("&Dump WUD filesystem"))->Enable(false);

	m_menuBar->Append(debugMenu, _("&Debug"));
	// help menu
	wxMenu* helpMenu = new wxMenu();
	m_check_update_menu = helpMenu->Append(MAINFRAME_MENU_ID_HELP_UPDATE, _("&Check for updates"));
#if BOOST_OS_LINUX
	if (!std::getenv("APPIMAGE")) {
		m_check_update_menu->Enable(false);
	}
#elif BOOST_OS_BSD // BSD users must update from source so disable update checks
	m_check_update_menu->Enable(false);
#endif
	helpMenu->AppendSeparator();
	helpMenu->Append(MAINFRAME_MENU_ID_HELP_ABOUT, _("&About Cemu"));

	m_menuBar->Append(helpMenu, _("&Help"));

	SetMenuBar(m_menuBar);
	m_menu_visible = true;

	if (m_game_launched)
	{
		if (m_check_update_menu)
			m_check_update_menu->Enable(false);

		m_memorySearcherMenuItem->Enable(true);
		m_nfcMenu->Enable(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, true);

		// these options cant be toggled after the renderer backend is initialized:
		m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::OpenGLLogging), false);
		m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::VulkanValidation), false);

		UpdateNFCMenu();
	}

	// hide new menu in fullscreen
	if (IsFullScreen())
		SetMenuVisible(false);
}

void MainWindow::UpdateChildWindowTitleRunningState()
{
	const bool running = CafeSystem::IsTitleRunning();

	if(m_graphic_pack_window)
		m_graphic_pack_window->UpdateTitleRunning(running);
}

void MainWindow::RestoreSettingsAfterGameExited()
{
	RecreateMenu();
}

void MainWindow::UpdateSettingsAfterGameLaunch()
{
	m_update_available = {};
	RecreateMenu();
}

void MainWindow::OnGraphicWindowClose(wxCloseEvent& event)
{
	m_graphic_pack_window->Destroy();
	m_graphic_pack_window = nullptr;
}

void MainWindow::OnGraphicWindowOpen(wxTitleIdEvent& event)
{
	if (m_graphic_pack_window)
		return;
	m_graphic_pack_window = new GraphicPacksWindow2(this, event.GetTitleId());
	m_graphic_pack_window->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnGraphicWindowClose, this);
	m_graphic_pack_window->Show(true);
}

void MainWindow::RequestGameListRefresh()
{
	auto* evt = new wxCommandEvent(wxEVT_REQUEST_GAMELIST_REFRESH);
	wxQueueEvent(g_mainFrame, evt);
}

void MainWindow::RequestLaunchGame(fs::path filePath, wxLaunchGameEvent::INITIATED_BY initiatedBy)
{
	wxLaunchGameEvent evt(filePath, initiatedBy);
	wxPostEvent(g_mainFrame, evt);
}

void MainWindow::OnRequestRecreateCanvas(wxCommandEvent& event)
{
	CounterSemaphore* sem = (CounterSemaphore*)event.GetClientData();
	DestroyCanvas();
	CreateCanvas();
	sem->increment();
}

void MainWindow::CafeRecreateCanvas()
{
	CounterSemaphore sem;
	auto* evt = new wxCommandEvent(wxEVT_REQUEST_RECREATE_CANVAS);
	evt->SetClientData((void*)&sem);
	wxQueueEvent(g_mainFrame, evt);
	sem.decrementWithWait();
}

bool MainWindow::FullscreenEnabled() const
{
	return LaunchSettings::FullscreenEnabled().value_or(GetWxGUIConfig().fullscreen);
}
