/*
 * 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 "ArSessionMetadata.hpp"
#include "Extensions/YamlCppConverters.hpp"
#include "Host/SystemInfo.hpp"
#include <pugixml.hpp>
#include <fmt/format.h>
#include <filesystem>

namespace OpenVulkano::AR
{
	ArSessionMetadata::ArSessionMetadata()
			: ArSessionMetadata(ArType::UNKNOWN, ArDepthFormat::UNAVAILABLE, {0,0}, { 1920, 1440 }, 60)
	{}

	ArSessionMetadata::ArSessionMetadata(ArType type, ArDepthFormat format, Math::Range<uint8_t> confRange, Math::Vector2ui resolution, float frameRate)
		: ArSessionMetadata(type, format, confRange, resolution, frameRate, SystemInfo::GetDeviceModelName(), SystemInfo::GetOsNameHumanReadable())
	{}

	ArSessionMetadata ArSessionMetadata::FromXML(const std::string& filePath)
	{
		pugi::xml_document doc;
		pugi::xml_parse_result result = doc.load_file(filePath.c_str());
		if (!result) throw std::runtime_error("Failed to read metadata xml file.");
		pugi::xml_node metaNode = doc.child("arPlatformInfo");
		const char* type = metaNode.child("type").text().as_string();
		int minRange = metaNode.child("minConfidence").text().as_int();
		int maxRange = metaNode.child("maxConfidence").text().as_int();
		const char* depthType = metaNode.child("depthType").text().as_string();
		uint32_t resX = 1920, resY = 1440;
		auto resNode = metaNode.child("imageResolution");
		if (!resNode.empty())
		{
			resX = resNode.child("width").text().as_uint();
			resY = resNode.child("height").text().as_uint();
		}
		float frameRate = metaNode.child("frameRate").text().as_float(60);
		uint32_t version = metaNode.child("version").text().as_uint(1);
		std::string device = metaNode.child("device").text().as_string("Unknown");
		std::string os = metaNode.child("os").text().as_string("iOS");
		return { ArType::GetFromHumanReadableName(type),
				 ArDepthFormat::GetFromAltName(depthType).value_or(ArDepthFormat::UNAVAILABLE),
				 Math::Range<uint8_t>(minRange, maxRange),
				 Math::Vector2ui(resX, resY), frameRate, device, os, version
		};
	}

	ArSessionMetadata ArSessionMetadata::FromYaml(const std::string& filePath)
	{
		YAML::Node meta = YAML::LoadFile(filePath);

		ArSessionMetadata metadata = {
				ArType::GetFromName(meta["Type"].Scalar()),
				ArDepthFormat::GetFromName(meta["DepthType"].Scalar()).value_or(ArDepthFormat::UNAVAILABLE),
				Math::Range<uint8_t>(meta["MinConfidence"].as<uint8_t>(), meta["MaxConfidence"].as<uint8_t>()),
				meta["Resolution"].as<Math::Vector2ui>(), meta["FrameRate"].as<float>(60),
				meta["Device"].as<std::string>("Unknown"), meta["OS"].as<std::string>("Unknown"),
				meta["Version"].as<uint32_t>(2)
		};

		auto recNode = meta["Recording"];
		if (recNode.IsMap())
		{
			metadata.recDuration = recNode["Duration"].as<int32_t>(-1);
			metadata.recFrameCount = recNode["FrameCount"].as<int64_t>(-1);
			metadata.recSkippedFrames = recNode["SkippedFrames"].as<int32_t>(-1);
		}

		return metadata;
	}

	bool ArSessionMetadata::DirHasMetadata(const std::filesystem::path& dirPath)
	{
		return	std::filesystem::exists(dirPath / RECORDING_METADATA_FILENAME_INFO) ||
				std::filesystem::exists(dirPath / RECORDING_METADATA_FILENAME_XML) ||
				std::filesystem::exists(dirPath / RECORDING_METADATA_FILENAME);
	}

	ArSessionMetadata::ArSessionMetadata(const std::filesystem::path& dirPath)
	{
		std::optional<ArSessionMetadata> metaFromFile;
		if (!is_directory(dirPath)) throw std::runtime_error("Ar recording path must be a directory!");
		std::filesystem::path xmlInfoPath(dirPath / RECORDING_METADATA_FILENAME_INFO);
		if (std::filesystem::exists(xmlInfoPath))
		{
			metaFromFile = FromXML(xmlInfoPath.string());
		}
		xmlInfoPath = dirPath / RECORDING_METADATA_FILENAME_XML;
		if (std::filesystem::exists(xmlInfoPath))
		{
			metaFromFile = FromXML(xmlInfoPath.string());
		}
		std::filesystem::path ymlInfoPath = dirPath / RECORDING_METADATA_FILENAME;
		if (std::filesystem::exists(ymlInfoPath))
		{
			metaFromFile = FromYaml(ymlInfoPath.string());
		}
		if (!metaFromFile) throw std::runtime_error("Ar recording dir is missing platform metadata file!");
		type = metaFromFile->type;
		depthFormat = metaFromFile->depthFormat;
		confidenceRange = metaFromFile->confidenceRange;
		playback = true;
	}

	std::string ArSessionMetadata::ToXML() const
	{
		return fmt::format(R"(<arPlatformInfo>
	<type>{}</type>
	<minConfidence>{}</minConfidence>
	<maxConfidence>{}</maxConfidence>
	<depthType>{}</depthType>
	<imageResolution><width>{}</width><height>{}</height></imageResolution>
	<frameRate>{}</frameRate>
	<device>{}</device>
	<os>{}</os>
	<version>{}</version>
</arPlatformInfo>)",
						type.GetHumanReadableName(), static_cast<int>(confidenceRange.min), static_cast<int>(confidenceRange.max),
						depthFormat.GetAltName(), imageResolution.x, imageResolution.y, frameRate, device, os, version);
	}

	std::string ArSessionMetadata::FinishedRecordingInfoToYaml() const
	{
		if (recFrameCount < 1 && recDuration < 1 && recSkippedFrames < 1) return "";
		return fmt::format(R"(Recording:
    Duration: {}
    FrameCount: {}
    SkippedFrames: {}
)",
						   recDuration, recFrameCount, recSkippedFrames);
	}

	std::string ArSessionMetadata::ToYaml() const
	{
		return fmt::format(R"(Type: {}
DepthType: {}
MinConfidence: {}
MaxConfidence: {}
Resolution: '({},{})'
FrameRate: {}
Device: {}
OS: {}
Version: {}
{}
)",
						   type.GetName(), depthFormat.GetName(), static_cast<int>(confidenceRange.min), static_cast<int>(confidenceRange.max),
						   imageResolution.x, imageResolution.y, frameRate, device, os, version, FinishedRecordingInfoToYaml());
	}
}