/*
 * 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/.
 */

#pragma once

#include "InputDevice.hpp"
#include "Math/Math.hpp"

namespace OpenVulkano
{
	class IWindow;

	namespace Input
	{
		class InputDeviceMouse : public InputDevice
		{
			float axes[InputKey::Mouse::Axis::AXIS_LAST + 2] = { 0 };
			uint8_t pressedButtons = 0, lastPressedButtons = 0;
			double mousePosX = 0, mousePosY = 0;
			IWindow* lastWindow = nullptr;

		protected:
			InputDeviceMouse() = default;
			
			void Init(int index, const std::string& name);
			
			void UpdateButtons(uint8_t newPressedButtons);

			void UpdatePosition(double posX, double posY);

			void UpdateWheel(float wheelX, float wheelY);

			void UpdateActiveWindow(IWindow* window);

			void ClearAxes();

		private:
			[[nodiscard]] bool GetLastButton(InputKey::Mouse::Button button) const
			{
				return lastPressedButtons & (1 << button);
			}

		protected:
			[[nodiscard]] float ReadAxis(int16_t key) const final
			{
				return GetAxis(static_cast<InputKey::Mouse::Axis>(key));
			}

			[[nodiscard]] bool ReadButton(int16_t key) const final
			{
				return GetButton(static_cast<InputKey::Mouse::Button>(key));
			}

			[[nodiscard]] bool ReadButtonUp(int16_t key) const final
			{
				return GetButtonUp(static_cast<InputKey::Mouse::Button>(key));
			}

			[[nodiscard]] bool ReadButtonDown(int16_t key) const final
			{
				return GetButtonDown(static_cast<InputKey::Mouse::Button>(key));
			}

		public:
			[[nodiscard]] bool GetButton(const InputKey::Mouse::Button button) const
			{
				return pressedButtons & (1 << button);
			}

			[[nodiscard]] bool GetButtonUp(const InputKey::Mouse::Button button) const
			{
				return !GetButton(button) && GetLastButton(button);
			}

			[[nodiscard]] bool GetButtonDown(const InputKey::Mouse::Button button) const
			{
				return GetButton(button) && !GetLastButton(button);
			}

			[[nodiscard]] float GetAxis(const InputKey::Mouse::Axis axis) const
			{
				return axes[axis];
			}

			[[nodiscard]] float GetWheel() const
			{
				return GetAxis(InputKey::Mouse::Axis::AXIS_WHEEL_Y);
			}

			[[nodiscard]] float GetWheelX() const
			{
				return GetAxis(InputKey::Mouse::Axis::AXIS_WHEEL_X);
			}

			[[nodiscard]] float GetWheelY() const
			{
				return GetAxis(InputKey::Mouse::Axis::AXIS_WHEEL_Y);
			}

			[[nodiscard]] Math::Vector2d GetMousePosition() const
			{
				return { mousePosX, mousePosY };
			}

			void GetMousePosition(double& posX, double& posY) const
			{
				posX = mousePosX;
				posY = mousePosY;
			}

			[[nodiscard]] Math::Vector2d GetMousePosition(const IWindow* window) const
			{
				if (window == lastWindow)
				{
					return GetMousePosition();
				}
				return { std::numeric_limits<float>::quiet_NaN(), std::numeric_limits<float>::quiet_NaN() };
			}

			void GetMousePosition(const IWindow* window, double& posX, double& posY) const
			{
				if (window == lastWindow)
				{
					posX = mousePosX;
					posY = mousePosY;
				}
				else
				{
					posX = std::numeric_limits<double>::quiet_NaN();
					posY = std::numeric_limits<double>::quiet_NaN();
				}
			}

			[[nodiscard]] IWindow* GetActiveWindow() const
			{
				return lastWindow;
			}

			bool IsInWindow(const IWindow* window) const
			{
				return window == lastWindow;
			}
		};
	}
}