/*
 * 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 "ArCameraController.hpp"
#include "AR/ArSession.hpp"
#include "Base/Logger.hpp"
#include "Scene/Camera.hpp"

namespace OpenVulkano
{
	ArCameraController::ArCameraController() = default;

	ArCameraController::ArCameraController(Scene::Camera* camera, const std::shared_ptr<AR::ArSession>& session, const Math::Matrix4f& registration)
			: CameraController(camera), m_arSession(session), m_registration(registration), m_updated(false)
	{
		CommonInit();
	}

	ArCameraController::~ArCameraController()
	{
		if (m_arSession) Close();
	}

	void ArCameraController::CommonInit()
	{
		//m_registration = Math::Utils::translate(Math::Vector3f_SIMD(0.0f, 0.0f, -2.5f)) * Math::Utils::rotate(Math::Matrix4f(1), M_PI_2f, {0,1,0});
		if (m_arSession)
		{
			if (!m_arSession->IsRunning())
			{
				Logger::AR->warn("AR session is not yet running. Starting it before registering ArCameraController.");
				m_arSession->Start();
			}
		}
		else
		{
			if (AR::ArSession::IsNativeArAvailable())
			{
				auto createResult = AR::ArSession::Create();
				if (createResult.IsSuccess())
				{
					m_arSession = createResult.session;
					m_arSession->Start();
				}
				else
				{
					Logger::AR->critical("Failed to create AR session for ArCameraController! Error: " + createResult.statusMessage);
					SetCamera(nullptr);
					return;
				}
			}
			else
			{
				Logger::AR->critical("No native AR implementation available! Can't start ArCameraController.");
				SetCamera(nullptr);
				return;
			}
		}

		m_updated = false;
		m_arSession->OnNewCameraViewMatrix += EventHandler(this, &ArCameraController::UpdateCameraViewMatrix);
	}

	void ArCameraController::Init(Scene::Camera* camera, const std::shared_ptr<AR::ArSession>& session, const Math::Matrix4f& registration)
	{
		if (GetCamera()) return;
		CameraController::Init(camera);
		m_arSession = session;
		m_registration = registration;
		CommonInit();
	}

	void ArCameraController::Tick()
	{
		if (!GetCamera() || !m_updated) return;
		std::unique_lock lock(m_mutex);

		Math::Matrix4f mat = m_registration * Math::Utils::inverse(m_viewMatrixToUpdate);
		GetCamera()->SetMatrix(mat);
		m_updated = false;
	}

	void ArCameraController::Close()
	{
		if (!GetCamera()) return;
		m_arSession->OnNewCameraViewMatrix -= EventHandler(this, &ArCameraController::UpdateCameraViewMatrix);
		SetCamera(nullptr);
		m_arSession = nullptr;
	}

	void ArCameraController::UpdateCameraViewMatrix(const Math::Matrix4f& transform)
	{
		std::unique_lock lock(m_mutex);
		m_viewMatrixToUpdate = transform;
		m_updated = true;
	}
}