/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "InputProviderGLFW.hpp"
#include "InputMappingGLFW.hpp"
#include "Input/InputManager.hpp"
#include "Base/Logger.hpp"
#include <GLFW/glfw3.h>

static_assert(GLFW_JOYSTICK_LAST == 15); // if this fails, please update it and resize the controllers array accordingly as GLFW_JOYSTICK_LAST + 1

namespace OpenVulkano::GLFW
{
	constexpr KeyboardInputMapping keyboardMapping = KeyboardInputMapping();

	InputProviderGLFW* InputProviderGLFW::INSTANCE = nullptr;

	void InputProviderGLFW::Init()
	{
		if (INSTANCE) throw PlatformInitFailedException("An instance of the GLFW input provider has already been initialized!");
		INSTANCE = this;
		glfwSetJoystickCallback(&JoystickCallback);

		// register already connected controller
		for (int i = 0; i < GLFW_JOYSTICK_LAST; i++)
		{
			if (glfwJoystickPresent(i) == GLFW_TRUE)
			{
				OnJoystickConnect(i);
			}
		}
		mouseButtons = 0;

		keyboard.Init(0, "Keyboard");
		mouse.Init(0, "Mouse");
		Input::InputManager::GetInstance()->RegisterInputDevice(&mouse);
		Input::InputManager::GetInstance()->RegisterInputDevice(&keyboard);
	}

	void InputProviderGLFW::Close()
	{
		glfwSetJoystickCallback(NULL);
		INSTANCE = nullptr;
	}

	void InputProviderGLFW::OnJoystickConnect(int joystickId)
	{
		controllers[joystickId].Init(joystickId);
		Input::InputManager::GetInstance()->RegisterInputDevice(&controllers[joystickId]);
	}

	void InputProviderGLFW::OnJoystickDisconnect(int joystickId)
	{
		Input::InputManager::GetInstance()->UnregisterInputDevice(&controllers[joystickId]);

		controllers[joystickId].Close();
	}

	void InputProviderGLFW::SetKeyboardKey(IWindow* window, int key, int scanCode, int action, int mods)
	{
		if (action == GLFW_REPEAT)
		{
			//TODO handle repeat for text inputs
		}
		else
		{
			keyboard.UpdateKey(static_cast<Input::InputKey::Keyboard::Key>(keyboardMapping.Map(key)), action == GLFW_PRESS);
		}
		//TODO handle keyboard notifications
	}

	void InputProviderGLFW::PreTick()
	{
		mouse.ClearAxes();
		keyboard.PreTick();
	}

	void InputProviderGLFW::Tick()
	{
		for(ControllerGLFW& controller : controllers)
		{
			controller.Tick();
		}
		mouse.UpdateButtons(mouseButtons);
		if (activeWindowChanged)
		{
			mouse.ClearAxes();
			activeWindowChanged = false;
		}
	}

	void InputProviderGLFW::SetMouseButton(int button, bool state)
	{
		const uint8_t bit = 1 << button;
		if (state)
		{
			mouseButtons |= bit;
		}
		else
		{
			mouseButtons ^= bit;
		}
	}

	void InputProviderGLFW::MouseEnterExitWindow(IWindow* window)
	{
		activeWindowChanged = true;
		mouse.UpdateActiveWindow(window);
		keyboard.UpdateActiveWindow(window);
	}

	void InputProviderGLFW::JoystickCallback(int joystickId, int joystickEvent)
	{
		if (joystickEvent == GLFW_CONNECTED)
		{
			INSTANCE->OnJoystickConnect(joystickId);
		}
		else if (joystickEvent == GLFW_DISCONNECTED)
		{
			INSTANCE->OnJoystickDisconnect(joystickId);
		}
		else
		{
			Logger::INPUT->warn("Unknown GLFW joystick event {0} for joystick {1}", joystickEvent, joystickId);
		}
	}
}