/*
 * 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 "FontAtlasFactory.hpp"
#include "Scene/SdfFontAtlasGenerator.hpp"
#include "Scene/BitmapFontAtlasGenerator.hpp"
#include "Host/SystemFontResolver.hpp"
#include "Base/Logger.hpp"
#include "Host/ResourceLoader.hpp"
#include <utility>

namespace OpenVulkano::Scene
{
	FontAtlasFactory::FontIdentifier::FontIdentifier(const std::string& font_, const std::set<uint32_t>& charset,
	                                                 SubpixelLayout subpixelLayout_, float ptSize_,
	                                                 FontAtlasType atlasType_)
	    : font(font_), subpixelLayout(subpixelLayout_), ptSize(ptSize_), atlasType(atlasType_)
	{
		std::for_each(charset.begin(), charset.end(), [&](uint32_t c) { charsetHash ^= c; });
	}

	bool FontAtlasFactory::FontIdentifier::FontIdentifier::operator<(const FontIdentifier& other) const
	{
		return std::tie(atlasType, charsetHash, ptSize, subpixelLayout, font)
		       < std::tie(other.atlasType, other.charsetHash, other.ptSize, other.subpixelLayout, other.font);
	}

	FontAtlas::Ptr FontAtlasFactory::GetFontAtlasScalable(const std::string& fontIdentifier, bool msdf,
	                                                      const std::set<uint32_t>& charset) const
	{
		const auto& fontData = FindFont(fontIdentifier);
		if (fontData.Empty())
		{
			Logger::DATA->warn("Could not find font {}", fontIdentifier);
			return nullptr;
		}

		const std::set<uint32_t>& setRef = (charset.empty() ? FontAtlasGeneratorBase::LoadAllGlyphs(fontData) : charset);
		FontIdentifier id(fontIdentifier, setRef, SubpixelLayout::UNKNOWN, 0,
		                  msdf ? FontAtlasType::MSDF : FontAtlasType::SDF);

		auto it = m_atlasesCache.find(id);
		if (it != m_atlasesCache.end())
		{
			return it->second;
		}

		if (msdf)
		{
			MsdfFontAtlasGenerator msdfGen;
			msdfGen.GenerateAtlas(fontData, setRef);
			return m_atlasesCache.insert({ id, msdfGen.GetAtlas() }).first->second;
		}
		SdfFontAtlasGenerator sdfGen;
		sdfGen.GenerateAtlas(fontData, setRef);
		return m_atlasesCache.insert({ id, sdfGen.GetAtlas() }).first->second;
	}

	FontAtlas::Ptr FontAtlasFactory::GetFontAtlas(const std::string& fontIdentifier, float ptSize,
	                                              SubpixelLayout subpixelLayout,
	                                              const std::set<uint32_t>& charset) const
	{
		const auto& fontData = FindFont(fontIdentifier);
		if (fontData.Empty())
		{
			Logger::DATA->warn("Could not find font {}", fontIdentifier);
			return nullptr;
		}

		const std::set<uint32_t>& setRef = (charset.empty() ? FontAtlasGeneratorBase::LoadAllGlyphs(fontData) : charset);
		FontIdentifier id(fontIdentifier, setRef, subpixelLayout, ptSize,
		                  subpixelLayout ? FontAtlasType::BITMAP_SUBPIXEL : FontAtlasType::BITMAP);

		auto it = m_atlasesCache.find(id);
		if (it != m_atlasesCache.end())
		{
			return it->second;
		}

		FontPixelSizeConfig cfg(ptSize);
		BitmapFontAtlasGenerator bitmapGen(cfg, subpixelLayout);
		bitmapGen.GenerateAtlas(fontData, setRef);
		return m_atlasesCache.insert({ id, bitmapGen.GetAtlas() }).first->second;
	}

	Array<char> FontAtlasFactory::FindFont(const std::string& fontIdentifier) const
	{
		try
		{
			return ResourceLoader::GetInstance().GetResource(fontIdentifier);
		}
		catch (...)
		{
		}
		if (!std::filesystem::exists(fontIdentifier))
		{
			if (!m_allowSystemFonts)
			{
				return {};
			}
			return Utils::ReadFile(SystemFontResolver::GetSystemFontPath(fontIdentifier), true);
		}
		return Utils::ReadFile(fontIdentifier);
	}
}
