/*
 * 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 "TextDrawable.hpp"
#include "Scene/Geometry.hpp"
#include "Shader/Shader.hpp"
#include "Scene/IFontAtlasGenerator.hpp"
#include "Base/Logger.hpp"
#include "DataFormat.hpp"
#include <utf8.h>

namespace OpenVulkano::Scene
{
	namespace
	{
		constexpr uint32_t MISSING_GLYPH_SYMBOL = '?';

		Shader DEFAULT_SHADER_BITMAP = TextDrawable::MakeDefaultShader(FontAtlasType::BITMAP);
		Shader DEFAULT_SHADER_BITMAP_SUBPIXEL = TextDrawable::MakeDefaultShader(FontAtlasType::BITMAP_SUBPIXEL);
		Shader DEFAULT_SHADER_SDF = TextDrawable::MakeDefaultShader(FontAtlasType::SDF);
		Shader DEFAULT_SHADER_MSDF = TextDrawable::MakeDefaultShader(FontAtlasType::MSDF);
	}

	Shader TextDrawable::MakeDefaultShader(const FontAtlasType type)
	{
		Shader shader;
		shader.AddShaderProgram(ShaderProgramType::VERTEX, "Shader/text");
		shader.AddShaderProgram(ShaderProgramType::FRAGMENT, std::string(type.GetDefaultFragmentShader()));
		VertexInputDescription inputDesc(0, sizeof(TextGlyphVertex));
		inputDesc.AddInputParameter(DataFormat::R32G32_SFLOAT, offsetof(TextGlyphVertex, position));
		inputDesc.AddInputParameter(DataFormat::R32G32_SFLOAT, offsetof(TextGlyphVertex, position)+8);
		inputDesc.AddInputParameter(DataFormat::R32G32_SFLOAT, offsetof(TextGlyphVertex, position)+16);
		inputDesc.AddInputParameter(DataFormat::R32G32_SFLOAT, offsetof(TextGlyphVertex, position)+24);
		inputDesc.AddInputParameter(DataFormat::R32G32_SFLOAT, offsetof(TextGlyphVertex, uv));
		inputDesc.AddInputParameter(DataFormat::R32G32_SFLOAT, offsetof(TextGlyphVertex, uv)+8);
		inputDesc.AddInputParameter(DataFormat::R32G32_SFLOAT, offsetof(TextGlyphVertex, uv)+16);
		inputDesc.AddInputParameter(DataFormat::R32G32_SFLOAT, offsetof(TextGlyphVertex, uv)+24);
		inputDesc.AddInputParameter(DataFormat::R8G8B8A8_UNORM, offsetof(TextGlyphVertex, color));
		inputDesc.AddInputParameter(DataFormat::R8G8B8A8_UNORM, offsetof(TextGlyphVertex, background));
		inputDesc.stepMode = VertexStepMode::INSTANCE;
		shader.AddVertexInputDescription(inputDesc);
		shader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING);
		shader.alphaBlend = true;
		shader.cullMode = CullMode::NONE;
		shader.topology = Topology::TRIANGLE_FAN;
		return shader;
	}

	TextDrawable::TextDrawable(const TextConfig& config)
			: Drawable(DrawEncoder::GetDrawEncoder<TextDrawable>()), m_cfg(config)
	{}

	TextDrawable::TextDrawable(const std::string& atlasMetadataFile, const TextConfig& config)
	    : TextDrawable(Utils::ReadFile(atlasMetadataFile), config)
	{}

	TextDrawable::TextDrawable(const Array<char>& atlasMetadata, const TextConfig& config)
		: Drawable(DrawEncoder::GetDrawEncoder<TextDrawable>()), m_cfg(config)
	{
		m_atlasData = std::make_shared<FontAtlas>(atlasMetadata);
	}

	TextDrawable::TextDrawable(const std::shared_ptr<FontAtlas>& atlasData, const TextConfig& config)
		: Drawable(DrawEncoder::GetDrawEncoder<TextDrawable>()), m_atlasData(atlasData), m_cfg(config)
	{
		if (!atlasData || !*atlasData) throw std::runtime_error("Cannot initialize text drawable with empty atlas data");
	}

	uint32_t TextDrawable::GetFallbackGlyph() const
	{ //TODO move into FontAtlas
		if (m_atlasData->GetGlyphs().contains(MISSING_GLYPH_SYMBOL))
		{
			return MISSING_GLYPH_SYMBOL;
		}
		Logger::RENDER->warn("Could not find glyph for character ? to use as fallback. Using first glyph instead");
		return m_atlasData->GetGlyphs().begin()->first;
	}

	void TextDrawable::GenerateText(const std::string& text, const Math::Vector2f& pos)
	{
		if (text.empty()) return;
		if (m_vertexBuffer.data) throw std::runtime_error("Text has already been initialized");
		const uint32_t fallbackGlyph = GetFallbackGlyph();

		m_text = text;
		m_symbolCount = 0;
		const size_t len = utf8::distance(text.begin(), text.end());
		m_vertexBuffer.Close();
		TextGlyphVertex* vertices = m_vertexBuffer.Init<TextGlyphVertex>(len);
		const std::map<uint32_t, GlyphInfo>& symbols = m_atlasData->GetGlyphs();

		double cursorX = pos.x;
		const double lineHeight = m_atlasData->GetLineHeight();
		double posY = pos.y;
		Math::Vector2f bmin(pos), bmax(pos);
		for (auto begin = text.begin(), end = text.end(); begin != end;)
		{
			uint32_t c = utf8::next(begin, end);
			if (c == '\n')
			{
				posY -= lineHeight;
				cursorX = pos.x;
				continue;
			}
			// TODO handle special chars

			if (!symbols.contains(c))
			{
				Logger::RENDER->warn("Could not find glyph for character {}, using fallback", c);
				c = fallbackGlyph;
			}

			const GlyphInfo& info = symbols.at(c);

			for (int i = 0; i < 4; i++)
			{
				vertices->position[i].x = info.pos[i].x + cursorX;
				vertices->uv[i] = info.uv[i];
				if (i < 2) vertices->position[i].y = posY - info.pos[i].y;
				else vertices->position[i].y = posY + info.pos[i].y;
				vertices->color = m_cfg.textColor;
				vertices->background = m_cfg.backgroundColor;
			}

			// TODO: change to lower value(or ideally remove completely) to avoid overlapping and make less space between symbols
			// when setting for depth comparison operator will be available( <= )
			cursorX += info.advance + 0.08;

			if (!m_symbolCount) bmin.x = vertices->position[0].x;
			bmax.x = std::max(bmax.x, vertices->position[1].x);
			bmax.y = std::max(bmax.y, vertices->position[2].y);
			bmin.y = std::min(bmin.y, vertices->position[1].y);
			vertices++;
			m_symbolCount++;
		}
		m_bbox.Init(bmin, bmax);

		if (!GetShader()) SetShader(GetDefaultShader(m_atlasData->GetAtlasType()));
	}

	void TextDrawable::SetAtlasData(const std::shared_ptr<FontAtlas>& atlasData)
	{
		if (!atlasData || !*atlasData) throw std::runtime_error("Cannot initialize text drawable with empty atlas data");
		m_atlasData = atlasData;
	}

	Shader* TextDrawable::GetDefaultShader(const FontAtlasType type)
	{
		switch (type)
		{
			case FontAtlasType::SDF: return &DEFAULT_SHADER_SDF;
			case FontAtlasType::MSDF: return &DEFAULT_SHADER_MSDF;
			default: Logger::RENDER->warn("No default shader for atlas type: {}", type.GetName());
			case FontAtlasType::BITMAP: return &DEFAULT_SHADER_BITMAP;
			case FontAtlasType::BITMAP_SUBPIXEL: return &DEFAULT_SHADER_BITMAP_SUBPIXEL;
		}
	}
}