#include "defaultplayercontroller.h"
#include "input.h"
#include "internal.h"
#include "internal/physics.h"
#include "internal/ui.h"
#include "noclipplayercontroller.h"
#include "ui.h"
#include "uielements.h"

namespace kek::Defaults {

static DefaultPlayerController *defaultController;
static NoclipPlayerController *noclipController;

static KeyBinding
	keyOptions,
	keyToggleCursorMode,
	keyToggleNoclip,
	keyExit;

static ButtonElement *options;

static void defaultInput(GLFWwindow *window, void *data) {
	if(Input::isKeyboardCaptured()) return;

	// TODO: move input handling to controller class, add NoclipController
	kekData.player->controller->update();
}

static void doUIMouseHover(double x, double y) {
	UIElement *hoveredElement = nullptr;

	for(UIElement *element : kekData.ui->elements) {
		UIPoint childPos = element->getPosition();
		UIPoint hoveredAt = UIPoint((int) x - childPos.x, (int) y - childPos.y);

		UIBounds bounds = element->getBounds();
		if(!bounds.contains(hoveredAt)) continue;

		hoveredElement = element;
	}

	for(UIElement *element : kekData.ui->elements) {
		if(element->hovering && element != hoveredElement) {
			element->hoverExitAll();
		}
	}

	if(hoveredElement) {
		UIPoint childPos = hoveredElement->getPosition();
		UIPoint hoveredAt = UIPoint((int) x - childPos.x, (int) y - childPos.y);
		hoveredElement->hoverAll(hoveredAt, UIPoint((int) x, (int) y));
	}
}

static void doUIMouseClick(double x, double y, GLFWMouseButton button) {
	UIElement *clickedElement = nullptr;

	for(UIElement *element : kekData.ui->elements) {
		UIPoint childPos = element->getPosition();
		UIPoint clickedAt = UIPoint((int) x - childPos.x, (int) y - childPos.y);

		UIBounds bounds = element->getBounds();
		if(!bounds.contains(clickedAt)) continue;

		clickedElement = element;
	}

	if(clickedElement) {
		UIPoint childPos = clickedElement->getPosition();
		UIPoint clickedAt = UIPoint((int) x - childPos.x, (int) y - childPos.y);
		clickedElement->clickAll(clickedAt, UIPoint((int) x, (int) y), button);
	}
}

static void defaultKeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods, void *data) {
	if(key == Input::getKeyBinding(keyExit).key && action == GLFW_PRESS) {
		glfwSetWindowShouldClose(window, true);
	}

	if(key == Input::getKeyBinding(keyOptions).key && action == GLFW_PRESS) {
		options->visible = !options->visible;
	}

	if(key == Input::getKeyBinding(keyToggleCursorMode).key && action == GLFW_PRESS) {
		if(Input::getCursorMode() == GLFWCursorMode::CAPTURE) {
			Input::setCursorMode(GLFWCursorMode::FREE);
			double x, y;
			glfwGetCursorPos(kekData.window, &x, &y);
			doUIMouseHover(x, y);
		} else {
			Input::setCursorMode(GLFWCursorMode::CAPTURE);

			for(auto e : UI::getElements()) {
				if(e->hovering) e->hoverExitAll();
			}
		}
	}

	if(key == Input::getKeyBinding(keyToggleNoclip).key && action == GLFW_PRESS) {
		kekData.player->noclip = !kekData.player->noclip;
		kekData.player->controller = kekData.player->noclip ? (PlayerController *) noclipController : (PlayerController *) defaultController;
		if(kekData.player->noclip) {
			kekData.physics->world->removeRigidBody(kekData.player->physics->body);
		} else {
			kekData.physics->world->addRigidBody(kekData.player->physics->body);
		}
	}
}

static void defaultMouseCallback(GLFWwindow *window, double x, double y, void *data) {
	static bool firstMouse = true;
	static double lastX = 0, lastY = 0;
	if(firstMouse) {
		lastX = x;
		lastY = y;
		firstMouse = false;
	}

	switch(Input::getCursorMode()) {
		case GLFWCursorMode::CAPTURE: {
			float xoff = lastX - x;
			float yoff = lastY - y;

			xoff *= 0.1f;
			yoff *= 0.1f;

			kekData.activeCamera->rotateYaw(xoff);
			kekData.activeCamera->rotatePitch(yoff);
			break;
		}
		case GLFWCursorMode::FREE:
		case GLFWCursorMode::HIDDEN: {
			doUIMouseHover(x, y);
			break;
		}
	}

	lastX = x;
	lastY = y;
}

static void defaultMouseButtonCallback(GLFWwindow *window, int button, int action, int mods, void *data) {
	switch(Input::getCursorMode()) {
		case GLFWCursorMode::CAPTURE: {
			// TODO
			break;
		}
		case GLFWCursorMode::FREE:
		case GLFWCursorMode::HIDDEN: {
			switch(action) {
				case GLFW_PRESS:
					break;
				case GLFW_RELEASE: {
					double x, y;
					glfwGetCursorPos(window, &x, &y);
					doUIMouseClick(x, y, (GLFWMouseButton) button);
				}
			}

			break;
		}
	}
}

void init() {
	defaultController = new DefaultPlayerController();
	noclipController = new NoclipPlayerController();
	defaultController->keyForward = noclipController->keyForward = Input::createKeyBinding("Forward", GLFW_KEY_W);
	defaultController->keyBackward = noclipController->keyBackward = Input::createKeyBinding("Backward", GLFW_KEY_S);
	defaultController->keyLeft = noclipController->keyLeft = Input::createKeyBinding("Left", GLFW_KEY_A);
	defaultController->keyRight = noclipController->keyRight = Input::createKeyBinding("Right", GLFW_KEY_D);
	defaultController->keyJump = noclipController->keyUp = Input::createKeyBinding("Jump", GLFW_KEY_SPACE);
	noclipController->keyDown = Input::createKeyBinding("Down", GLFW_KEY_LEFT_CONTROL);
	keyOptions = Input::createKeyBinding("Options", GLFW_KEY_Q);
	keyToggleCursorMode = Input::createKeyBinding("Toggle Cursor Mode", GLFW_KEY_TAB);
	keyToggleNoclip = Input::createKeyBinding("Toggle Noclip", GLFW_KEY_N);
	keyExit = Input::createKeyBinding("Exit", GLFW_KEY_ESCAPE);

	Input::addPeriodicCallback(PeriodicCallback(defaultInput, nullptr));
	Input::addKeyListener(KeyCallback(defaultKeyCallback, nullptr));
	Input::addMouseListener(MouseCallback(defaultMouseCallback, nullptr));
	Input::addMouseButtonListener(MouseButtonCallback(defaultMouseButtonCallback, nullptr));

	options = new ButtonElement(uiPx(0), uiPx(100), uiPx(100), uiPx(50));
	// UI::addElement(options);

	kekData.player = new Player();
	btVector3 positions[2];
	positions[0] = btVector3(0, -KEK_PLAYER_HEIGHT / 2 + KEK_PLAYER_RADIUS, 0);
	positions[1] = btVector3(0, KEK_PLAYER_HEIGHT / 2 + KEK_PLAYER_RADIUS, 0);
	float radii[2] = {KEK_PLAYER_RADIUS, KEK_PLAYER_RADIUS};
	btCollisionShape *shape = new btMultiSphereShape(positions, radii, 2);
	btRigidBody *body = new btRigidBody(0, nullptr, shape);
	body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
	body->setFriction(3);
	body->setActivationState(DISABLE_DEACTIVATION);
	kekData.physics->world->addRigidBody(body);

	btTransform from;
	from.setIdentity();
	from.setOrigin(btVector3(0, 0, 0));

	btTransform to;
	to.setIdentity();
	to.setOrigin(btVector3(1, 1, 1));

	btCollisionWorld::ClosestConvexResultCallback cb = btCollisionWorld::ClosestConvexResultCallback(from.getOrigin(), to.getOrigin());
	kekData.physics->world->convexSweepTest((btConvexShape *) shape, from, to, cb);

	kekData.player->physics = new PlayerPhysicsObjectData();
	kekData.player->physics->body = body;
	kekData.player->moveTo(glm::vec3(5, 10, 0));
	kekData.player->controller = defaultController;
}

void destroy() {
}

}
