// Super basic screen manager. Let's you, well, switch between screens. Can also be used
// to pop one screen in front for a bit while keeping another one running, it's basically
// a native "activity stack". Well actually that part is still a TODO.
//
// Semantics
//
// switchScreen: When you call this, on a newed screen, the ScreenManager takes ownership.
// On the next update, it switches to the new screen and deletes the previous screen.
//
// TODO: A way to do smooth transitions between screens. Will probably involve screenshotting
// the previous screen and then animating it on top of the current screen with transparency
// and/or other similar effects.

#pragma once

#include <cstdint>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>

#include "Common/Common.h"
#include "Common/Input/InputState.h"
#include "Common/System/System.h"

namespace UI {
	class View;
}

enum DialogResult {
	DR_NONE,
	DR_OK,
	DR_CANCEL,
	DR_YES,
	DR_NO,
	DR_BACK,
};

class ScreenManager;
class UIContext;

namespace Draw {
	class DrawContext;
}

enum class ScreenFocusChange {
	FOCUS_LOST_TOP,  // Another screen was pushed on top
	FOCUS_BECAME_TOP,  // Became the top screen again
};

enum class ScreenRenderMode {
	DEFAULT = 0,
	FIRST = 1,
	BEHIND = 4,
	TOP = 8,
};
ENUM_CLASS_BITOPS(ScreenRenderMode);

enum class ScreenRenderFlags {
	NONE = 0,
	HANDLED_THROTTLING = 1,
};
ENUM_CLASS_BITOPS(ScreenRenderFlags);


enum class ScreenRenderRole {
	NONE = 0,
	CAN_BE_BACKGROUND = 1,
	MUST_BE_FIRST = 2,
};
ENUM_CLASS_BITOPS(ScreenRenderRole);

class Screen {
public:
	Screen() = default;
	virtual ~Screen();

	virtual void onFinish(DialogResult reason) {}
	virtual void update() {}
	virtual ScreenRenderFlags PreRender(ScreenRenderMode mode) { return ScreenRenderFlags::NONE; }
	virtual ScreenRenderFlags render(ScreenRenderMode mode) = 0;
	virtual void resized() {}
	virtual void dialogFinished(const Screen *dialog, DialogResult result) {}
	virtual void sendMessage(UIMessage message, const char *value) {}
	virtual void deviceLost() {}
	virtual void deviceRestored(Draw::DrawContext *draw) {}
	virtual ScreenRenderRole renderRole(bool isTop) const { return ScreenRenderRole::NONE; }
	virtual bool wantBrightBackground() const { return false; }  // special hack for DisplayLayoutScreen.

	virtual void focusChanged(ScreenFocusChange focusChange);

	// Return value of UnsyncTouch is only used to let the overlay screen block touches.
	virtual bool UnsyncTouch(const TouchInput &touch) = 0;
	// Return value of UnsyncKey is used to not block certain system keys like volume when unhandled, on Android.
	virtual bool UnsyncKey(const KeyInput &touch) = 0;
	virtual void UnsyncAxis(const AxisInput *axes, size_t count) = 0;

	virtual void RecreateViews() {}

	ScreenManager *screenManager() { return screenManager_; }
	const ScreenManager *screenManager() const { return screenManager_; }
	void setScreenManager(ScreenManager *sm) { screenManager_ = sm; }

	virtual const char *tag() const = 0;

	virtual bool isTransparent() const { return false; }
	virtual bool isTopLevel() const { return false; }

	virtual TouchInput transformTouch(const TouchInput &touch) { return touch; }

protected:
	int GetRequesterToken();
	void WipeRequesterToken() {
		token_ = -1;
	}

private:
	ScreenManager *screenManager_ = nullptr;
	int token_ = -1;

	DISALLOW_COPY_AND_ASSIGN(Screen);
};

class Transition {
public:
	Transition() = default;
};

enum {
	LAYER_TRANSPARENT = 2,
};

typedef void(*PostRenderCallback)(UIContext *ui, void *userdata);

class ScreenManager {
public:
	virtual ~ScreenManager();

	void switchScreen(Screen *screen);
	void update();

	void setUIContext(UIContext *context) { uiContext_ = context; }
	UIContext *getUIContext() { return uiContext_; }
	Draw::DrawContext *getDrawContext() { return draw_; }

	void setPostRenderCallback(PostRenderCallback cb, void *userdata) {
		postRenderCb_ = cb;
		postRenderUserdata_ = userdata;
	}

	ScreenRenderFlags render();
	void resized();
	void shutdown();

	void deviceLost();
	void deviceRestored(Draw::DrawContext *draw);

	// Push a dialog box in front. Currently 1-level only.
	void push(Screen *screen, int layerFlags = 0);

	// Recreate all views
	void RecreateAllViews();

	// Pops the dialog away.
	void finishDialog(Screen *dialog, DialogResult result = DR_OK);
	Screen *dialogParent(const Screen *dialog) const;

	// Instant touch, separate from the update() mechanism.
	void touch(const TouchInput &touch);
	bool key(const KeyInput &key);
	void axis(const AxisInput *axes, size_t count);

	void sendMessage(UIMessage message, const char *value);

	const Screen *topScreen() const {
		return stack_.empty() ? nullptr : stack_.back().screen;
	}
	Screen *topScreen() {
		return stack_.empty() ? nullptr : stack_.back().screen;
	}

	void cancelScreensAbove(Screen *screen);

	void getFocusPosition(float &x, float &y, float &z);

	// Will delete any existing overlay screen.
	void SetBackgroundOverlayScreens(Screen *backgroundScreen, Screen *overlayScreen);

	std::recursive_mutex inputLock_;

private:
	void pop();
	void switchToNext();
	void processFinishDialog();

	UIContext *uiContext_ = nullptr;
	Draw::DrawContext *draw_ = nullptr;

	PostRenderCallback postRenderCb_ = nullptr;
	void *postRenderUserdata_ = nullptr;

	const Screen *dialogFinished_ = nullptr;
	DialogResult dialogResult_{};

	Screen *backgroundScreen_ = nullptr;
	Screen *overlayScreen_ = nullptr;

	Screen *cancelScreensAbove_ = nullptr;

	struct Layer {
		Screen *screen;
		int flags;  // From LAYER_ enum above
		UI::View *focusedView;  // TODO: save focus here. Going for quick solution now to reset focus.
	};

	// Dialog stack. These are shown "on top" of base screens and the Android back button works as expected.
	// Used for options, in-game menus and other things you expect to be able to back out from onto something.
	std::vector<Layer> stack_;
	std::vector<Layer> nextStack_;

	std::unordered_map<int64_t, int> lastAxis_;
};
