#pragma once

#include <queue>
#include <mutex>
#include <string>

#include <QtCore>
#include <QMenuBar>
#include <QMainWindow>
#include <QActionGroup>

#include "Common/System/System.h"
#include "Common/System/NativeApp.h"
#include "ConsoleListener.h"
#include "Core/Core.h"
#include "Core/Config.h"
#include "Core/System.h"
#include "Qt/QtMain.h"

extern bool g_TakeScreenshot;

class MenuAction;
class MenuTree;

enum {
	FB_NON_BUFFERED_MODE = 0,
	FB_BUFFERED_MODE = 1,
};

// hacky, should probably use qt signals or something, but whatever..
enum class MainWindowMsg {
	BOOT_DONE,
	WINDOW_TITLE_CHANGED,
};

class MainWindow : public QMainWindow
{
	Q_OBJECT

public:
	explicit MainWindow(QWidget *parent = nullptr, bool fullscreen = false);
	~MainWindow() { };

	CoreState GetNextState() { return nextState; }

	void updateMenuGroupInt(QActionGroup *group, int value);

	void updateMenus();

	void Notify(MainWindowMsg msg) {
		std::unique_lock<std::mutex> lock(msgMutex_);
		msgQueue_.push(msg);
	}

	void SetWindowTitleAsync(std::string title) {
		std::unique_lock<std::mutex> lock(titleMutex_);
		newWindowTitle_ = title;
		Notify(MainWindowMsg::WINDOW_TITLE_CHANGED);
	}

protected:
	void changeEvent(QEvent *e)
	{
		QMainWindow::changeEvent(e);
		// Does not work on Linux for Qt5.2 or Qt5.3 (Qt bug)
		if(e->type() == QEvent::WindowStateChange)
			Core_NotifyWindowHidden(isMinimized());
	}

	void closeEvent(QCloseEvent *) { exitAct(); }

signals:
	void retranslate();
	void updateMenu();

public slots:
	void newFrame();

private slots:
	// File
	void loadAct();
	void closeAct();
	void openmsAct();
	void saveStateGroup_triggered(QAction *action) { g_Config.iCurrentStateSlot = action->data().toInt(); }
	void qlstateAct();
	void qsstateAct();
	void lstateAct();
	void sstateAct();
	void recordDisplayAct();
	void useLosslessVideoCodecAct();
	void useOutputBufferAct();
	void recordAudioAct();
	void exitAct();

	// Emulation
	void runAct();
	void pauseAct();
	void stopAct();
	void resetAct();
	void switchUMDAct();
	void displayRotationGroup_triggered(QAction *action) { g_Config.iInternalScreenRotation = action->data().toInt(); }

	// Debug
	void breakonloadAct();
	void ignoreIllegalAct() { g_Config.bIgnoreBadMemAccess = !g_Config.bIgnoreBadMemAccess; }
	void lmapAct();
	void smapAct();
	void lsymAct();
	void ssymAct();
	void resetTableAct();
	void dumpNextAct();
	void takeScreen() { g_TakeScreenshot = true; }
	void consoleAct();

	// Game settings
	void languageAct() { System_PostUIMessage(UIMessage::SHOW_LANGUAGE_SCREEN); }
	void controlMappingAct() { System_PostUIMessage(UIMessage::SHOW_CONTROL_MAPPING); }
	void displayLayoutEditorAct() { System_PostUIMessage(UIMessage::SHOW_DISPLAY_LAYOUT_EDITOR); }
	void moreSettingsAct() { System_PostUIMessage(UIMessage::SHOW_SETTINGS); }

	void bufferRenderAct() {
		System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
		System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
	}
	void linearAct() { g_Config.iTexFiltering = (g_Config.iTexFiltering != 0) ? 0 : 3; }

	void renderingResolutionGroup_triggered(QAction *action) {
		g_Config.iInternalResolution = action->data().toInt();
		System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
	}
	void windowGroup_triggered(QAction *action) { SetWindowScale(action->data().toInt()); }

	void autoframeskipAct() {
		g_Config.bAutoFrameSkip = !g_Config.bAutoFrameSkip;
		if (g_Config.bSkipBufferEffects) {
			g_Config.bSkipBufferEffects = false;
			System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
		}
	}
	void frameSkippingGroup_triggered(QAction *action) { g_Config.iFrameSkip = action->data().toInt(); }
	void frameSkippingTypeGroup_triggered(QAction *action) { g_Config.iFrameSkipType = action->data().toInt(); }
	void textureFilteringGroup_triggered(QAction *action) { g_Config.iTexFiltering = action->data().toInt(); }
	void screenScalingFilterGroup_triggered(QAction *action) { g_Config.iDisplayFilter = action->data().toInt(); }
	void textureScalingLevelGroup_triggered(QAction *action) {
		g_Config.iTexScalingLevel = action->data().toInt();
		System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
	}
	void textureScalingTypeGroup_triggered(QAction *action) {
		g_Config.iTexScalingType = action->data().toInt();
		System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
	}
	void deposterizeAct() {
		g_Config.bTexDeposterize = !g_Config.bTexDeposterize;
		System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
	}
	void transformAct() {
		g_Config.bHardwareTransform = !g_Config.bHardwareTransform;
		System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
	}
	void frameskipAct() { g_Config.iFrameSkip = !g_Config.iFrameSkip; }
	void frameskipTypeAct() { g_Config.iFrameSkipType = !g_Config.iFrameSkipType; }

	// Sound
	void audioAct() {
		g_Config.bEnableSound = !g_Config.bEnableSound;
	}

	// Cheats
	void cheatsAct() { g_Config.bEnableCheats = !g_Config.bEnableCheats; }

	// Chat
	void chatAct() {
		if (GetUIState() == UISTATE_INGAME) {
			System_PostUIMessage(UIMessage::SHOW_CHAT_SCREEN);
		}
	}

	void fullscrAct();
	void raiseTopMost();

	// Help
	void websiteAct();
	void forumAct();
	void goldAct();
	void gitAct();
	void discordAct();
	void aboutAct();

	// Others
	void langChanged(QAction *action) { loadLanguage(action->data().toString(), true); }

private:
	void bootDone();
	void SetWindowScale(int zoom);
	void SetGameTitle(QString text);
	void SetFullScreen(bool fullscreen);
	void loadLanguage(const QString &language, bool retranslate);
	void createMenus();

	QTranslator translator;
	QString currentLanguage;

	CoreState nextState;
	GlobalUIState lastUIState;

	QActionGroup *windowGroup,
	             *textureScalingLevelGroup, *textureScalingTypeGroup,
	             *screenScalingFilterGroup, *textureFilteringGroup,
	             *frameSkippingTypeGroup, *frameSkippingGroup,
	             *renderingResolutionGroup,
	             *displayRotationGroup, *saveStateGroup;

	std::queue<MainWindowMsg> msgQueue_;
	std::mutex msgMutex_;

	std::string newWindowTitle_;
	std::mutex titleMutex_;
};

class MenuAction : public QAction
{
	Q_OBJECT

public:
	// Add to QMenu
	MenuAction(QWidget* parent, const char *callback, const char *text, QKeySequence key = 0) :
		QAction(parent), _text(text)
	{
		if (key != (QKeySequence)0) {
			this->setShortcut(key);
			parent->addAction(this); // So we don't lose the shortcut when menubar is hidden
		}
		connect(this, SIGNAL(triggered()), parent, callback);
		connect(parent, SIGNAL(retranslate()), this, SLOT(retranslate()));
		connect(parent, SIGNAL(updateMenu()), this, SLOT(update()));
	}
	// Add to QActionGroup
	MenuAction(QWidget* parent, QActionGroup* group, QVariant data, QString text, QKeySequence key = 0) :
		QAction(parent)
	{
		this->setCheckable(true);
		this->setData(data);
		this->setText(text); // Not translatable, yet
		if (key != (QKeySequence)0) {
			this->setShortcut(key);
			parent->addAction(this); // So we don't lose the shortcut when menubar is hidden
		}
		group->addAction(this);
	}
	// Event which causes it to be checked
	void addEventChecked(bool* event) {
		this->setCheckable(true);
		_eventCheck = event;
	}
	// TODO: Possibly handle compares
	void addEventChecked(int* event) {
		this->setCheckable(true);
		_eventCheck = (bool*)event;
	}
	// Event which causes it to be unchecked
	void addEventUnchecked(bool* event) {
		this->setCheckable(true);
		_eventUncheck = event;
	}
	// UI State which causes it to be enabled
	void addEnableState(int state) {
		_enabledFunc = nullptr;
		_stateEnable = state;
		_stateDisable = -1;
	}
	void addDisableState(int state) {
		_enabledFunc = nullptr;
		_stateEnable = -1;
		_stateDisable = state;
	}
	void SetEnabledFunc(std::function<bool()> func) {
		_enabledFunc = func;
		_stateEnable = -1;
		_stateDisable = -1;
	}
public slots:
	void retranslate() {
		setText(qApp->translate("MainWindow", _text));
	}
	void update() {
		if (_eventCheck)
			setChecked(*_eventCheck);
		if (_eventUncheck)
			setChecked(!*_eventUncheck);
		if (_stateEnable >= 0)
			setEnabled(GetUIState() == _stateEnable);
		if (_stateDisable >= 0)
			setEnabled(GetUIState() != _stateDisable);
		if (_enabledFunc)
			setEnabled(_enabledFunc());
	}
private:
	const char *_text;
	bool *_eventCheck = nullptr;
	bool *_eventUncheck = nullptr;
	int _stateEnable = -1;
	int _stateDisable = -1;
	std::function<bool()> _enabledFunc;
};

class MenuActionGroup : public QActionGroup
{
	Q_OBJECT
public:
	MenuActionGroup(QWidget* parent, QMenu* menu, const char* callback, QStringList nameList,
		QList<int> valueList, QList<int> keyList = QList<int>()) :
		QActionGroup(parent)
	{
		QListIterator<int> i(valueList);
		QListIterator<int> k(keyList);
		foreach(QString name, nameList) {
			new MenuAction(parent, this, i.next(), name, keyList.size() ? k.next() : 0);
		}
		connect(this, SIGNAL(triggered(QAction *)), parent, callback);
		menu->addActions(this->actions());
	}
};

class MenuTree : public QMenu
{
	Q_OBJECT
public:
	MenuTree(QWidget* parent, QMenuBar* menu, const char *text) :
		QMenu(parent), _text(text)
	{
		menu->addMenu(this);
		connect(parent, SIGNAL(retranslate()), this, SLOT(retranslate()));
	}
	MenuTree(QWidget* parent, QMenu* menu, const char *text) :
		QMenu(parent), _text(text)
	{
		menu->addMenu(this);
		connect(parent, SIGNAL(retranslate()), this, SLOT(retranslate()));
	}
	MenuAction* add(MenuAction* action)
	{
		addAction(action);
		return action;
	}
public slots:
	void retranslate() {
		setTitle(qApp->translate("MainWindow", _text));
	}
private:
	const char *_text;
};