/*
 * 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/.
 */

#pragma once

#include "Math/Math.hpp"
#include "Extensions/FreetypeHelper.hpp"
#include <vector>
#include <optional>

namespace OpenVulkano::Scene
{
	struct GlyphForPacking
	{
		uint32_t code;
		Math::Vector2i size;
		Math::Vector2d atlasPos;
		size_t firstGlyphByteInAtlas;
	};

	struct Shelf
	{
		inline static std::vector<Shelf> CreateShelves(uint32_t atlasWidth, std::vector<GlyphForPacking>& glyphs,
		                                               const FtFaceRecPtr& face, int channelsCount);

		Shelf(uint32_t width, uint32_t height, int pixelSize, uint32_t prevShelvesHeight) 
			: m_width(width), m_height(height), m_remainingWidth(width), m_pixelSize(pixelSize), m_prevShelvesHeight(prevShelvesHeight) {}
		bool HasSpaceForGlyph(uint32_t glyphWidth, uint32_t glyphHeight) const
		{
			return m_remainingWidth >= glyphWidth && m_height >= glyphHeight;
		}
		uint32_t GetWidth() const { return m_width; }
		uint32_t GetHeight() const { return m_height; }
		uint32_t GetNextGlyphPos() const { return m_nextGlyphPos; };
		uint32_t GetOccupiedSize() const { return ((m_width - m_remainingWidth) * m_height); }
		uint32_t GetPrevShelvesHeight() const { return m_prevShelvesHeight; }
		int GetPixelSize() const { return m_pixelSize; }

		std::optional<std::pair<uint32_t, uint32_t>> AddGlyph(uint32_t glyphWidth, uint32_t glyphHeight)
		{
			if (!HasSpaceForGlyph(glyphWidth, glyphHeight))
			{
				return {};
			}
			uint32_t insertionPos = m_nextGlyphPos;
			m_nextGlyphPos += glyphWidth;
			m_remainingWidth -= glyphWidth;

			uint32_t hOffset = m_height - (m_height - glyphHeight);
			uint32_t glyphFirstByte = (insertionPos * m_pixelSize)
			                          + ((hOffset * m_width * m_pixelSize) - (m_width * m_pixelSize))
			                          + (m_prevShelvesHeight * m_width * m_pixelSize);

			return std::make_pair(insertionPos, glyphFirstByte);
		}

	private:
		uint32_t m_width;
		uint32_t m_height;
		uint32_t m_remainingWidth;
		uint32_t m_nextGlyphPos = 0;
		uint32_t m_prevShelvesHeight;
		int m_pixelSize;
	};

	std::vector<Shelf> Shelf::CreateShelves(uint32_t atlasWidth, std::vector<GlyphForPacking>& glyphs,
	                                        const FtFaceRecPtr& face, int channelsCount)
	{
		std::vector<Shelf> shelves;
		for (GlyphForPacking& glyph : glyphs)
		{
			FT_Error error = FT_Load_Char(face.get(), glyph.code, FT_LOAD_RENDER);
			if (error)
			{
				continue;
			}

			FT_GlyphSlot slot = face->glyph;
			bool needNewShelf = true;
			uint32_t totalPrevShelvesHeight = 0;
			for (Shelf& shelf : shelves)
			{
				if (std::optional<std::pair<uint32_t, uint32_t>> opt = shelf.AddGlyph(glyph.size.x, glyph.size.y))
				{
					glyph.firstGlyphByteInAtlas = opt->second;
					glyph.atlasPos.x = opt->first;
					glyph.atlasPos.y = totalPrevShelvesHeight;
					needNewShelf = false;
					break;
				}
				totalPrevShelvesHeight += shelf.GetHeight();
			}

			if (needNewShelf)
			{
				shelves.emplace_back(atlasWidth, glyph.size.y, channelsCount, totalPrevShelvesHeight);
				Shelf& shelf = shelves.back();
				uint32_t firstByte = (*shelf.AddGlyph(glyph.size.x, glyph.size.y)).second;
				glyph.firstGlyphByteInAtlas = firstByte;
				glyph.atlasPos.x = 0;
				glyph.atlasPos.y = totalPrevShelvesHeight;
			}
		}
		return shelves;
	}
}
