/*
 * 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 "InputDeviceTouch.hpp"
#include "Base/Logger.hpp"
#include <algorithm>

namespace OpenVulkano::Input
{
	void InputDeviceTouch::Init()
	{
		InputDevice::Init(InputDeviceType::TOUCH, 0, "Touch");

		m_gestureProcessor.Init();
		m_gestureProcessor.m_gestureTap.OnTap += EventHandler(this, &InputDeviceTouch::UpdateTap);
		m_gestureProcessor.m_gesturePan.OnPanStarted += EventHandler(this, &InputDeviceTouch::UpdatePanStarted);
		m_gestureProcessor.m_gesturePan.OnPanMoved += EventHandler(this, &InputDeviceTouch::UpdatePanMoved);
		m_gestureProcessor.m_gesturePan.OnPanEnded += EventHandler(this, &InputDeviceTouch::UpdatePanStarted);
		m_gestureProcessor.m_gesturePanTwoFingers.OnPanStarted += EventHandler(this, &InputDeviceTouch::UpdatePanTwoFingersStarted);
		m_gestureProcessor.m_gesturePanTwoFingers.OnPanMoved += EventHandler(this, &InputDeviceTouch::UpdatePanTwoFingersMoved);
		m_gestureProcessor.m_gesturePanTwoFingers.OnPanEnded += EventHandler(this, &InputDeviceTouch::UpdatePanTwoFingersStarted);
		m_gestureProcessor.m_gesturePinch.OnPinchStarted += EventHandler(this, &InputDeviceTouch::UpdatePinchStarted);
		m_gestureProcessor.m_gesturePinch.OnPinchMoved += EventHandler(this, &InputDeviceTouch::UpdatePinchMoved);
		m_gestureProcessor.m_gesturePinch.OnPinchEnded += EventHandler(this, &InputDeviceTouch::UpdatePinchStarted);
	}

	decltype(InputDeviceTouch::m_touches)::iterator InputDeviceTouch::FindTouch(TouchId id)
	{
		return std::find_if(m_touches.begin(), m_touches.end(), [id](const Touch& touch)
		{
			return (id == touch.GetId());
		});
	}

	TouchId InputDeviceTouch::AddTouch(const Math::Vector2f& position)
	{
		m_touches.emplace_back(m_nextId, position);
		OnTouchAdded(m_touches.back());
		return m_nextId++;
	}

	void InputDeviceTouch::TouchDown(TouchId id)
	{
		auto touchIter = FindTouch(id);

		if (touchIter != m_touches.end())
		{
			touchIter->m_down = true;
			if (!m_multiTouch && m_touches.size() > 1)
			{
				int count = 0;
				for (const auto &touch : m_touches)
				{
					if(touch.IsDown()) count++;
					if (count > 1)
					{
						Logger::INPUT->debug("Begin multitouch");
						m_multiTouch = true;
						break;
					}
				}
			}

			OnTouchDown.NotifyAll(*touchIter);
			m_gestureProcessor.TouchDown(*touchIter);
		}
		else
		{
			Logger::INPUT->warn("Received touch down event for unknown touch id.");
		}
	}

	void InputDeviceTouch::TouchUp(TouchId id)
	{
		auto touchIter = FindTouch(id);

		if (touchIter != m_touches.end())
		{
			touchIter->m_down = false;

			if (m_multiTouch)
			{
				bool hasDown = false;
				for (const auto &touch : m_touches)
				{
					if (touch.IsDown())
					{
						hasDown = true;
						break;
					}
				}
				if (!hasDown)
				{
					Logger::INPUT->debug("End multitouch");
					m_multiTouch = false;
				}
			}

			OnTouchUp.NotifyAll(*touchIter);
			m_gestureProcessor.TouchUp(*touchIter);
		}
		else
		{
			Logger::INPUT->warn("Received touch up for unknown touch id.");
		}
	}

	void InputDeviceTouch::TouchMoved(TouchId id, const Math::Vector2f& position)
	{
		auto touchIter = FindTouch(id);

		if (touchIter != m_touches.end())
		{
			touchIter->m_lastPosition = touchIter->m_position;
			touchIter->m_position = position;

			OnTouchMoved.NotifyAll(*touchIter);
			m_gestureProcessor.TouchMoved(*touchIter);
		}
		else
		{
			Logger::INPUT->warn("Received touch moved for unknown touch id");
		}
	}

	void InputDeviceTouch::RemoveTouch(TouchId id)
	{
		auto touchIter = FindTouch(id);
		if (touchIter != m_touches.end())
		{
			auto removedTouch = *touchIter;
			m_touches.erase(touchIter);

			if (m_touches.empty())
			{
				m_multiTouch = false;
			}

			OnTouchRemoved.NotifyAll(removedTouch);
		}
		else
		{
			Logger::INPUT->warn("Received remove request for unknown touch id");
		}
	}

	void InputDeviceTouch::UpdatePinchStarted(const Gesture* pinch, const PinchInfo& info)
	{
		m_lastPinchInfo = m_nextPinchInfo = info;
	}

	void InputDeviceTouch::UpdatePanStarted(const Gesture* pan, const PanInfo& info)
	{
		m_lastPanInfo = m_nextPanInfo = info;
	}

	void InputDeviceTouch::UpdatePanTwoFingersStarted(const Gesture* pan, const PanInfo& info)
	{
		m_last2FPanInfo = m_next2FPanInfo = info;
	}

	void InputDeviceTouch::UpdatePinchMoved(const Gesture* pinch, const PinchInfo& info)
	{
		m_nextPinchInfo = info;
	}

	void InputDeviceTouch::UpdatePanMoved(const Gesture* pan, const PanInfo& info)
	{
		m_nextPanInfo = info;
	}

	void InputDeviceTouch::UpdatePanTwoFingersMoved(const Gesture* pan, const PanInfo& info)
	{
		m_next2FPanInfo = info;
	}

	void InputDeviceTouch::UpdateTap(const Gesture* tap, const TapInfo& info)
	{
		m_toBePressedButtons = 1;
		m_nextTap = info.position;
		m_axes[InputKey::Touch::Axis::AXIS_TAP_DURATION] = info.duration.count();
	}

	void InputDeviceTouch::Tick()
	{
		m_lastPressedButtons = m_pressedButtons;
		m_pressedButtons = m_toBePressedButtons;
		m_toBePressedButtons = 0;

		// Tap
		auto diff = m_nextTap - m_lastTap;
		m_axes[InputKey::Touch::Axis::AXIS_TAP_X] = diff.x;
		m_axes[InputKey::Touch::Axis::AXIS_TAP_Y] = diff.y;
		m_lastTap = m_nextTap;

		// Pan
		diff = m_nextPanInfo.position - m_lastPanInfo.position;
		m_axes[InputKey::Touch::Axis::AXIS_PAN_X] = diff.x;
		m_axes[InputKey::Touch::Axis::AXIS_PAN_Y] = diff.y;
		m_lastPanInfo = m_nextPanInfo;
		diff = m_next2FPanInfo.position - m_last2FPanInfo.position;
		m_axes[InputKey::Touch::Axis::AXIS_PAN_TWO_FINGERS_X] = diff.x;
		m_axes[InputKey::Touch::Axis::AXIS_PAN_TWO_FINGERS_Y] = diff.y;
		m_last2FPanInfo = m_next2FPanInfo;

		// Pinch
		m_axes[InputKey::Touch::Axis::AXIS_PINCH] = m_nextPinchInfo.scale - m_lastPinchInfo.scale;
		diff = m_nextPinchInfo.position - m_lastPinchInfo.position;
		m_axes[InputKey::Touch::Axis::AXIS_PINCH_CENTER_X] = diff.x;
		m_axes[InputKey::Touch::Axis::AXIS_PINCH_CENTER_Y] = diff.y;
		m_lastPinchInfo = m_nextPinchInfo;
	}
}
