// Copyright (c) 2023 Alex Diener. All rights reserved.

#include "gamemath/MouseCoordinateTransforms.h"
#include "PROJECT_NAME/Atoms.h"
#include "PROJECT_NAME/Events.h"
#include "PROJECT_NAME/GameScreen.h"
#include "PROJECT_NAME/Globals.h"
#include "PROJECT_NAME/SharedDefinitions.h"
#include "shadercollection/ShaderCollection.h"

#define stemobject_implementation GameScreen

v_begin();
v_func(dispose);
v_func(activate);
v_func(deactivate);
v_func(update);
v_func(resized);
v_func(mouseDown);
v_func(mouseUp);
v_func(mouseMoved);
v_func(mouseDragged);
v_func(mouseLeave);
v_func(scrollWheel);
v_func(keyDown);
v_func(keyUp);
v_func(keyModifiersChanged);
v_func(gamepadAxisMoved);
v_func(gamepadButtonDown);
v_func(gamepadButtonUp);
v_func(gamepadAttached);
v_func(gamepadDetached);
v_func(needsRedraw);
v_func(listRenderables);
v_end();

GameScreen * GameScreen_create(void) {
	stemobject_create_implementation(init)
}

static void listRenderables(RenderableIO * renderableIO, void * context) {
	GameScreen * self = context;
	call_virtual(listRenderables, self, renderableIO, 0, RECT4i_EMPTY);
}

bool GameScreen_init(GameScreen * self) {
	call_super(init, self);
	self->rootContainer = UIContainer_create(VECTOR2f(0.0f, 0.0f), VECTOR2f_ZERO, VECTOR2f_ZERO, VECTOR2f_ZERO, false, g_uiAppearance);
	self->renderLayer = RenderLayer_create(RENDER_LAYER_SORT_DRAW_ORDER_ONLY, listRenderables, self);
	self->active = false;
	self->lastMousePosition = g_lastMousePosition;
	self->backgrounded = false;
	return true;
}

void GameScreen_dispose(GameScreen * self) {
	UIContainer_dispose(self->rootContainer);
	RenderLayer_dispose(self->renderLayer);
	call_super(dispose, self);
}

static bool resized(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	call_virtual(resized, self);
	return true;
}

static bool backgrounded(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	self->backgrounded = true;
	return true;
}

static bool foregrounded(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	self->backgrounded = false;
	return true;
}

static bool mouseMoved(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct mouseEvent * event = eventData;
	self->lastMousePosition = event->position;
	Vector2f transformedPosition = transformMousePosition_aspectBoxLowerLeft(event->position, g_viewWidth, g_viewHeight, DISPLAY_WIDTH, DISPLAY_HEIGHT);
	Vector2f transformedDelta = transformMouseDelta_aspectBox(event->delta, g_viewWidth, g_viewHeight, DISPLAY_WIDTH, DISPLAY_HEIGHT);
	bool handled = call_virtual(mouseMoved, self, transformedPosition.x, transformedPosition.y, transformedDelta.x, transformedDelta.y, event->modifiers, event->timestamp);
	Shell_setCursor(call_virtual(getCursorAtPosition, self->rootContainer, transformedPosition.x, transformedPosition.y));
	return handled;
}

static bool mouseDown(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct mouseEvent * event = eventData;
	self->lastMousePosition = event->position;
	Vector2f transformedPosition = transformMousePosition_aspectBoxLowerLeft(event->position, g_viewWidth, g_viewHeight, DISPLAY_WIDTH, DISPLAY_HEIGHT);
	return call_virtual(mouseDown, self, event->buttonNumber, event->buttonMask, transformedPosition.x, transformedPosition.y, event->modifiers, event->timestamp);
}

static bool mouseDragged(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct mouseEvent * event = eventData;
	self->lastMousePosition = event->position;
	Vector2f transformedPosition = transformMousePosition_aspectBoxLowerLeft(event->position, g_viewWidth, g_viewHeight, DISPLAY_WIDTH, DISPLAY_HEIGHT);
	Vector2f transformedDelta = transformMouseDelta_aspectBox(event->delta, g_viewWidth, g_viewHeight, DISPLAY_WIDTH, DISPLAY_HEIGHT);
	return call_virtual(mouseDragged, self, event->buttonMask, transformedPosition.x, transformedPosition.y, transformedDelta.x, transformedDelta.y, event->modifiers, event->timestamp);
}

static bool mouseUp(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct mouseEvent * event = eventData;
	self->lastMousePosition = event->position;
	Vector2f transformedPosition = transformMousePosition_aspectBoxLowerLeft(event->position, g_viewWidth, g_viewHeight, DISPLAY_WIDTH, DISPLAY_HEIGHT);
	return call_virtual(mouseUp, self, event->buttonNumber, event->buttonMask, transformedPosition.x, transformedPosition.y, event->modifiers, event->timestamp);
}

static bool mouseLeave(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct mouseEvent * event = eventData;
	return call_virtual(mouseLeave, self, event->modifiers, event->timestamp);
}

static bool scrollWheel(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct mouseWheelEvent * event = eventData;
	self->lastMousePosition = event->position;
	Vector2f transformedPosition = transformMousePosition_aspectBoxLowerLeft(event->position, g_viewWidth, g_viewHeight, DISPLAY_WIDTH, DISPLAY_HEIGHT);
	return call_virtual(scrollWheel, self, transformedPosition.x, transformedPosition.y, event->deltaX, event->deltaY, event->buttonMask, event->modifiers, event->timestamp);
}

static bool keyDown(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct keyEvent * event = eventData;
	return call_virtual(keyDown, self, event->charCode, event->keyCode, event->modifiers, event->isRepeat, event->timestamp);
}

static bool keyUp(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct keyEvent * event = eventData;
	return call_virtual(keyUp, self, event->keyCode, event->modifiers, event->timestamp);
}

static bool keyModifiersChanged(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct keyEvent * event = eventData;
	return call_virtual(keyModifiersChanged, self, event->modifiers, event->lastModifiers, event->timestamp);
}

static bool gamepadAxisMoved(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct gamepadAxisEvent * event = eventData;
	call_virtual(gamepadAxisMoved, self, event->device, event->axisID, event->value, event->lastValue, event->timestamp);
	return false;
}

static bool gamepadButtonDown(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct gamepadButtonEvent * event = eventData;
	return call_virtual(gamepadButtonDown, self, event->device, event->buttonID, event->timestamp);
}

static bool gamepadButtonUp(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct gamepadButtonEvent * event = eventData;
	return call_virtual(gamepadButtonUp, self, event->device, event->buttonID, event->timestamp);
}

static bool gamepadAttached(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct Gamepad_device * device = eventData;
	return call_virtual(gamepadAttached, self, device);
}

static bool gamepadDetached(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct Gamepad_device * device = eventData;
	return call_virtual(gamepadDetached, self, device);
}

static bool draw(Atom eventID, void * eventData, void * context) {
	GameScreen * self = context;
	struct drawEvent * event = eventData;
	
	if (self->lastMousePosition.x != g_lastMousePosition.x || self->lastMousePosition.y != g_lastMousePosition.y) {
		self->lastMousePosition = g_lastMousePosition;
		Vector2f transformedPosition = transformMousePosition_aspectBoxLowerLeft(g_lastMousePosition, g_viewWidth, g_viewHeight, DISPLAY_WIDTH, DISPLAY_HEIGHT);
		call_virtual(mouseMoved, self, transformedPosition.x, transformedPosition.y, 0.0f, 0.0f, 0, event->referenceTime);
	}
	
	call_virtual(update, self, event->deltaTime);
	if (!self->active) {
		return false;
	}
	
	Renderer_clear(g_renderer, COLOR4f(0.0f, 0.0f, 0.0f, 0.0f));
	Renderer_drawLayer(g_renderer, self->renderLayer, event->referenceTime, event->deltaTime);
	
	if (call_virtual(needsRedraw, self)) {
		Shell_redisplay();
	}
	return true;
}

void GameScreen_update(GameScreen * self, double deltaTime) {
}

void GameScreen_resized(GameScreen * self) {
}

bool GameScreen_mouseDown(GameScreen * self, unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, double timestamp) {
	bool handled = call_virtual(mouseDown, self->rootContainer, buttonNumber, buttonMask, x, y, modifiers, false, timestamp);
	if (handled) {
		Shell_redisplay();
	}
	return handled;
}

bool GameScreen_mouseUp(GameScreen * self, unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, double timestamp) {
	bool handled = call_virtual(mouseUp, self->rootContainer, buttonNumber, buttonMask, x, y, modifiers, timestamp);
	if (handled) {
		Shell_redisplay();
	}
	return handled;
}

bool GameScreen_mouseMoved(GameScreen * self, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double timestamp) {
	bool handled = call_virtual(mouseMoved, self->rootContainer, x, y, deltaX, deltaY, modifiers, timestamp);
	if (handled) {
		Shell_redisplay();
	}
	return handled;
}

bool GameScreen_mouseDragged(GameScreen * self, unsigned int buttonMask, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double timestamp) {
	bool handled = call_virtual(mouseDragged, self->rootContainer, buttonMask, x, y, deltaX, deltaY, modifiers, timestamp);
	if (handled) {
		Shell_redisplay();
	}
	return handled;
}

bool GameScreen_mouseLeave(GameScreen * self, unsigned int modifiers, double timestamp) {
	return false;
}

bool GameScreen_scrollWheel(GameScreen * self, float x, float y, int deltaX, int deltaY, unsigned int buttonMask, unsigned int modifiers, double timestamp) {
	bool handled = call_virtual(scrollWheel, self->rootContainer, x, y, deltaX, deltaY, buttonMask, modifiers, false, timestamp);
	if (handled) {
		Shell_redisplay();
	}
	return handled;
}

bool GameScreen_keyDown(GameScreen * self, unsigned int charCode, unsigned int keyCode, unsigned int modifiers, bool isRepeat, double timestamp) {
	bool handled = call_virtual(keyDown, self->rootContainer, charCode, keyCode, modifiers, isRepeat, false, timestamp);
	if (handled) {
		Shell_redisplay();
	}
	return handled;
}

bool GameScreen_keyUp(GameScreen * self, unsigned int keyCode, unsigned int modifiers, double timestamp) {
	bool handled = call_virtual(keyUp, self->rootContainer, keyCode, modifiers, timestamp);
	if (handled) {
		Shell_redisplay();
	}
	return handled;
}

bool GameScreen_keyModifiersChanged(GameScreen * self, unsigned int modifiers, unsigned int lastModifiers, double timestamp) {
	bool handled = call_virtual(keyModifiersChanged, self->rootContainer, modifiers, lastModifiers, timestamp);
	if (handled) {
		Shell_redisplay();
	}
	return handled;
}

bool GameScreen_gamepadAxisMoved(GameScreen * self, struct Gamepad_device * device, unsigned int axisID, float value, float lastValue, double timestamp) {
	return false;
}

bool GameScreen_gamepadButtonDown(GameScreen * self, struct Gamepad_device * device, unsigned int buttonID, double timestamp) {
	return false;
}

bool GameScreen_gamepadButtonUp(GameScreen * self, struct Gamepad_device * device, unsigned int buttonID, double timestamp) {
	return false;
}

bool GameScreen_gamepadAttached(GameScreen * self, struct Gamepad_device * device) {
	return false;
}

bool GameScreen_gamepadDetached(GameScreen * self, struct Gamepad_device * device) {
	return false;
}

bool GameScreen_needsRedraw(GameScreen * self) {
	if (call_virtual(needsRedraw, self->rootContainer)) {
		return true;
	}
	return false;
}

void GameScreen_listRenderables(GameScreen * self, RenderableIO * renderableIO, int drawOrderOffset, Rect4i clipBounds) {
	call_virtual(listRenderables, self->rootContainer, renderableIO, drawOrderOffset, clipBounds);
}

void GameScreen_activate(GameScreen * self, Screen * lastScreen, const char * transitionName) {
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_draw, draw, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_resized, resized, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_backgrounded, backgrounded, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_foregrounded, foregrounded, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_down, mouseDown, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_up, mouseUp, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_moved, mouseMoved, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_dragged, mouseDragged, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_leave, mouseLeave, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_scroll_wheel, scrollWheel, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_key_down, keyDown, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_key_up, keyUp, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_key_modifiers_changed, keyModifiersChanged, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_gamepad_axis_move, gamepadAxisMoved, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_gamepad_button_down, gamepadButtonDown, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_gamepad_button_up, gamepadButtonUp, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_gamepad_attached, gamepadAttached, self);
	EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM_event_gamepad_detached, gamepadDetached, self);
	self->active = true;
	Shell_redisplay();
}

void GameScreen_deactivate(GameScreen * self, Screen * lastScreen, const char * transitionName) {
	self->active = false;
	self->lastMousePosition = g_lastMousePosition;
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_draw, draw, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_resized, resized, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_backgrounded, backgrounded, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_foregrounded, foregrounded, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_down, mouseDown, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_up, mouseUp, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_moved, mouseMoved, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_dragged, mouseDragged, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_leave, mouseLeave, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_mouse_scroll_wheel, scrollWheel, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_key_down, keyDown, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_key_up, keyUp, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_key_modifiers_changed, keyModifiersChanged, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_gamepad_axis_move, gamepadAxisMoved, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_gamepad_button_down, gamepadButtonDown, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_gamepad_button_up, gamepadButtonUp, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_gamepad_attached, gamepadAttached, self);
	EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM_event_gamepad_detached, gamepadDetached, self);
}
