#include "input.h"

#include <map>
#include <iostream>

#include "internal.h"
#include "internal/input.h"

namespace kek::Input {

static InputListener nextID = 0;

void init() {
	kekData.input = new InputData();
}

void destroy() {
	delete kekData.input;
}

void onCursorPosCallback(GLFWwindow *window, double x, double y) {
	for(auto cb : kekData.input->mouseCallbacks) {
		cb.second(window, x, y);
	}
}

void onKeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) {
	if(Input::isKeyboardCaptured()) {
		if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
			Input::uncaptureKeyboardInput(kekData.input->activeKeyboardCapture.id);
			return;
		}

		if(key == GLFW_KEY_BACKSPACE && (action == GLFW_PRESS || action == GLFW_REPEAT)) {
			kekData.input->activeKeyboardCapture.charCallback(window, KEK_INPUT_DELETE);
		}

		kekData.input->activeKeyboardCapture.keyCallback(window, key, scancode, action, mods);
		return;
	}

	for(auto cb : kekData.input->keyCallbacks) {
		cb.second(window, key, scancode, action, mods);
	}
}

void onKeyCharCallback(GLFWwindow *window, unsigned int codepoint) {
	if(Input::isKeyboardCaptured()) {
		kekData.input->activeKeyboardCapture.charCallback(window, codepoint);
		return;
	}

	for(auto cb : kekData.input->keyCharCallbacks) {
		cb.second(window, codepoint);
	}
}

void onMouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
	for(auto cb : kekData.input->mouseButtonCallbacks) {
		cb.second(window, button, action, mods);
	}
}

InputListener addPeriodicCallback(PeriodicCallback callback) {
	InputListener id = nextID++;
	kekData.input->periodicCallbacks.emplace(id, callback);
	return id;
}

void removePeriodicCallback(InputListener listener) {
	kekData.input->periodicCallbacks.erase(listener);
}

InputListener addKeyListener(KeyCallback callback) {
	InputListener id = nextID++;
	kekData.input->keyCallbacks.emplace(id, callback);
	return id;
}

void removeKeyListener(InputListener listener) {
	kekData.input->keyCallbacks.erase(listener);
}

InputListener addKeyCharListener(KeyCharCallback callback) {
	InputListener id = nextID++;
	kekData.input->keyCharCallbacks.emplace(id, callback);
	return id;
}

void removeKeyCharListener(InputListener listener) {
	kekData.input->keyCharCallbacks.erase(listener);
}

InputListener addMouseListener(MouseCallback callback) {
	InputListener id = nextID++;
	kekData.input->mouseCallbacks.emplace(id, callback);
	return id;
}

void removeMouseListener(InputListener listener) {
	kekData.input->mouseCallbacks.erase(listener);
}

InputListener addMouseButtonListener(MouseButtonCallback callback) {
	InputListener id = nextID++;
	kekData.input->mouseButtonCallbacks.emplace(id, callback);
	return id;
}

void removeMouseButtonListener(InputListener listener) {
	kekData.input->mouseButtonCallbacks.erase(listener);
}

KeyBinding createKeyBinding(std::string name, GLFWKey defaultKey) {
	if(name == KEK_INVALID_KEY_BINDING_NAME) return KEK_INVALID_ID;
	KeyBinding id = nextID++;
	KeyBindingData d;
	d.name = name;
	d.key = defaultKey;
	kekData.input->keyBindings.emplace(id, d);
	return id;
}

void reassignKeyBinding(KeyBinding binding, GLFWKey key) {
	if(binding == KEK_INVALID_ID) return;
	auto it = kekData.input->keyBindings.find(binding);
	if(it == kekData.input->keyBindings.end()) return;
	KeyBindingData d = it->second;
	d.key = key;
	it->second = d;
}

KeyBindingData getKeyBinding(KeyBinding binding) {
	auto it = kekData.input->keyBindings.find(binding);
	if(it == kekData.input->keyBindings.end()) return {.name = KEK_INVALID_KEY_BINDING_NAME};
	return it->second;
}

GLFWKeyState getKeyState(KeyBinding binding) {
	auto it = kekData.input->keyBindings.find(binding);
	if(it == kekData.input->keyBindings.end()) return -1;
	return glfwGetKey(kekData.window, it->second.key);
}

void setCursorMode(GLFWCursorMode mode) {
	glfwSetInputMode(kekData.window, GLFW_CURSOR, (int) mode);
	kekData.input->cursorMode = mode;
}

GLFWCursorMode getCursorMode() {
	return kekData.input->cursorMode;
}

KeyboardCapture captureKeyboardInput(KeyCharCallback charCallback, KeyCallback keyCallback, Callable uncaptureCallback) {
	KeyboardCapture id = nextID++;
	kekData.input->activeKeyboardCapture = {
		id,
		charCallback,
		keyCallback,
		uncaptureCallback
	};
	return id;
}

bool uncaptureKeyboardInput(KeyboardCapture capture) {
	if(capture == KEK_INVALID_ID || capture != kekData.input->activeKeyboardCapture.id) return false;
	kekData.input->activeKeyboardCapture.uncaptureCallback();
	kekData.input->activeKeyboardCapture = {
		KEK_INVALID_ID,
		KeyCharCallback(),
		KeyCallback(),
		Callable()
	};
	return true;
}

bool isKeyboardCaptured() {
	return kekData.input->activeKeyboardCapture.id != KEK_INVALID_ID;
}

}
