/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "opengl-precomp.h"	 // Precompiled header
//

// Stripped out version of libCVD gltext.cpp file, ported to OpenGL>=3
// 2-BSD License.

#include <mrpt/math/TPoint2D.h>

#include <algorithm>
#include <cassert>
#include <cmath>
#include <map>

#include "gltext.h"

using namespace std;

namespace mrpt::opengl::internal
{
struct Point
{
	float x, y;
};

struct Font
{
	typedef unsigned short Index;

	struct Char
	{
		Index vertexOffset;
		Index triangleOffset;
		Index outlineOffset;
		int numTriangles;  // was: GLsizei
		int numOutlines;
		float advance;
	};

	Point* vertices;
	Index* triangles;
	Index* outlines;
	Char* characters;
	string glyphs;

	const Char* findChar(const char c) const
	{
		size_t ind = glyphs.find(c);
		if (ind == string::npos) return nullptr;
		return characters + ind;
	}

	float getAdvance(const char c) const
	{
		const Char* ch = findChar(c);
		if (!ch) return 0;
		return ch->advance;
	}

	void fill(
		const char c, std::vector<mrpt::opengl::TTriangle>& tris,
		std::vector<mrpt::math::TPoint3Df>& lines,
		const mrpt::math::TPoint2Df& cursor) const
	{
		const Char* ch = findChar(c);
		if (!ch || !ch->numTriangles) return;
		// triangles
		const Point* vs = vertices + ch->vertexOffset;
		for (int i = 0; i < ch->numTriangles / 3; i++)
		{
			const auto idx0 = triangles[ch->triangleOffset + i * 3 + 0];
			const auto idx1 = triangles[ch->triangleOffset + i * 3 + 1];
			const auto idx2 = triangles[ch->triangleOffset + i * 3 + 2];

			using P3f = mrpt::math::TPoint3Df;

			tris.emplace_back(
				P3f(cursor.x + vs[idx0].x, cursor.y + vs[idx0].y, .0f),
				P3f(cursor.x + vs[idx1].x, cursor.y + vs[idx1].y, .0f),
				P3f(cursor.x + vs[idx2].x, cursor.y + vs[idx2].y, .0f));
		}
	}

	void outline(
		const char c, std::vector<mrpt::opengl::TTriangle>& tris,
		std::vector<mrpt::math::TPoint3Df>& lines,
		const mrpt::math::TPoint2Df& cursor) const
	{
		const Char* ch = findChar(c);
		if (!ch || !ch->numOutlines) return;
		// lines
		const Point* vs = vertices + ch->vertexOffset;
		for (int i = 0; i < ch->numOutlines / 2; i++)
		{
			const auto idx0 = outlines[ch->outlineOffset + i * 2 + 0];
			const auto idx1 = outlines[ch->outlineOffset + i * 2 + 1];

			lines.emplace_back(
				cursor.x + vs[idx0].x, cursor.y + vs[idx0].y, .0f);
			lines.emplace_back(
				cursor.x + vs[idx1].x, cursor.y + vs[idx1].y, .0f);
		}
	}

	void draw(
		const char c, std::vector<mrpt::opengl::TTriangle>& tris,
		std::vector<mrpt::math::TPoint3Df>& lines,
		const mrpt::math::TPoint2Df& cursor) const
	{
		outline(c, tris, lines, cursor);
		fill(c, tris, lines, cursor);
	}
};

// the fonts defined in these headers are derived from Bitstream Vera fonts. See
// http://www.gnome.org/fonts/ for license and details
#include "glfont_mono.h"
#include "glfont_sans.h"
#include "glfont_serif.h"

struct FontData
{
	typedef map<string, Font*> FontMap;

	FontData()
	{
		fonts["sans"] = &sans_font;
		fonts["mono"] = &mono_font;
		fonts["serif"] = &serif_font;
		glSetFont("sans");
	}
	inline Font* currentFont() { return fonts[currentFontName]; }

	string currentFontName;
	FontMap fonts;
};

static struct FontData data;

void glSetFont(const std::string& fontname)
{
	if (data.fonts.count(fontname) > 0) data.currentFontName = fontname;
}

const std::string& glGetFont() { return data.currentFontName; }

std::pair<double, double> glDrawText(
	const std::string& text, std::vector<mrpt::opengl::TTriangle>& tris,
	std::vector<mrpt::math::TPoint3Df>& render_lines, TEXT_STYLE style,
	double spacing, double kerning)
{
	// Was: glPushMatrix();
	mrpt::math::TPoint2Df cursor = {0, 0};

	// figure out which operation to do on the Char (yes, this is a pointer to
	// member function :)
	void (Font::*operation)(
		const char c, std::vector<mrpt::opengl::TTriangle>& tris,
		std::vector<mrpt::math::TPoint3Df>& lines,
		const mrpt::math::TPoint2Df& cursor) const;
	switch (style)
	{
		case FILL: operation = &Font::fill; break;
		case OUTLINE: operation = &Font::outline; break;
		case NICE:
			// operation = &Font::draw; (See comments in definition of "NICE")
			operation = &Font::fill;
			break;

		default: THROW_EXCEPTION("Invalid style value");
	};

	int lines = 0;
	double max_total = 0;
	double total = 0;
	const Font* font = data.currentFont();
	const Font::Char* space = font->findChar(' ');
	const double tab_width = 8 * ((space) ? (space->advance) : 1);
	for (size_t i = 0; i < text.length(); ++i)
	{
		char c = text[i];
		if (c == '\n')
		{
			cursor.x -= total;
			cursor.y -= spacing;

			max_total = std::max(max_total, total);
			total = 0;
			++lines;
			continue;
		}
		if (c == '\t')
		{
			const float advance = tab_width - std::fmod(total, tab_width);
			total += advance;
			cursor.x += advance;
			continue;
		}
		const Font::Char* ch = font->findChar(c);
		if (!ch)
		{
			c = toupper(c);
			ch = font->findChar(c);
			if (!ch)
			{
				c = '?';
				ch = font->findChar(c);
			}
		}
		if (!ch) continue;
		(font->*operation)(c, tris, render_lines, cursor);

		double w = ch->advance + kerning;
		cursor.x += w;
		total += w;
	}

	max_total = std::max(total, max_total);
	return std::make_pair(max_total, (lines + 1) * spacing);
}

std::pair<double, double> glGetExtends(
	const std::string& text, double spacing, double kerning)
{
	int lines = 0;
	double max_total = 0;
	double total = 0;
	const Font* font = data.currentFont();
	for (size_t i = 0; i < text.length(); ++i)
	{
		char c = text[i];
		if (c == '\n')
		{
			max_total = std::max(max_total, total);
			total = 0;
			++lines;
			continue;
		}
		const Font::Char* ch = font->findChar(c);
		if (!ch)
		{
			c = toupper(c);
			ch = font->findChar(c);
			if (!ch)
			{
				c = '?';
				ch = font->findChar(c);
			}
		}
		if (!ch) continue;
		total += ch->advance + kerning;
	}
	max_total = std::max(total, max_total);
	return std::make_pair(max_total, (lines + 1) * spacing);
}

void glDrawTextTransformed(
	const std::string& text, std::vector<mrpt::opengl::TTriangle>& tris,
	std::vector<mrpt::math::TPoint3Df>& lines,
	std::vector<mrpt::img::TColor>& line_colors,
	const mrpt::poses::CPose3D& text_pose, float text_scale,
	const mrpt::img::TColor& text_color, TEXT_STYLE style, double spacing,
	double kerning)
{
	// Raw text primitives:
	std::vector<mrpt::opengl::TTriangle> new_tris;
	std::vector<mrpt::math::TPoint3Df> new_lines;
	glDrawText(text, new_tris, new_lines, style, spacing, kerning);

	// Transform triangles:
	for (auto& t : new_tris)
	{
		t.setColor(text_color);

		for (int i = 0; i < 3; i++)
		{
			text_pose.rotateVector(t.vertices[i].normal);
			t.vertices[i].xyzrgba.pt =
				text_pose.composePoint(t.vertices[i].xyzrgba.pt * text_scale);
		}
		t.computeNormals();
		tris.emplace_back(t);
	}

	// Transform line vertices:
	for (auto& v : new_lines)
	{
		lines.emplace_back(text_pose.composePoint(v * text_scale));
		line_colors.emplace_back(text_color);
	}
}

}  // namespace mrpt::opengl::internal
