#pragma once

#include <ft2build.h>
#include <memory>
#include FT_FREETYPE_H
#include <glm/glm.hpp>

#include <map>
#include <string>

#include "color.h"
#include "resource.h"

namespace kek {

struct Character {
	unsigned int textureX;
	unsigned int textureY;
	glm::ivec2 size;
	glm::ivec2 bearing;
	unsigned int advance;
};

struct CharacterBlock {
	unsigned int textureID;
	std::map<unsigned int, Character> characters;
};

struct TextMetrics {
	int offsetX;
	int offsetY;
	int width;
	int height;

	TextMetrics()
		: offsetX(0),
		  offsetY(0),
		  width(0),
		  height(0) {}

	TextMetrics(int offsetX, int offsetY, unsigned int width, unsigned int height)
		: offsetX(offsetX),
		  offsetY(offsetY),
		  width(width),
		  height(height) {}
};

struct FontMetrics {
	int lineHeight;
	int ascender;
	int descender;

	FontMetrics(int lineHeight, int ascender, int descender) {
		this->lineHeight = lineHeight;
		this->ascender = ascender;
		this->descender = descender;
	}
};

class Font;

struct TextBlock {
	unsigned int characterBlock;
	unsigned int length;

	unsigned int bufferSize;
	unsigned int vao;
	unsigned int vbo;
};

struct RenderChar {
	float data[24];

	RenderChar(float xPos, float yPos, float w, float h, float texL, float texR, float texU, float texD) {
		float coords[] = {
			xPos, yPos, texL, texU,
			xPos, yPos + h, texL, texD,
			xPos + w, yPos, texR, texU,
			xPos + w, yPos, texR, texU,
			xPos, yPos + h, texL, texD,
			xPos + w, yPos + h, texR, texD};
		for(int i = 0; i < 24; i++) data[i] = coords[i];
	}
};

class TextObject {

  private:
	std::shared_ptr<Font> font;
	std::string text;

	TextMetrics unscaledMetrics;

  public:
	std::map<unsigned int, TextBlock> blocks;

	TextObject(std::shared_ptr<Font> font, std::string text);

	~TextObject();

	TextObject(const TextObject &) = delete;

	TextObject &operator=(const TextObject &) = delete;

	TextObject(TextObject &&other);

	TextObject &operator=(TextObject &&other);

	std::shared_ptr<Font> getFont();

	void setText(std::string text);

	std::string getText();

	TextMetrics getMetrics(unsigned int offset, unsigned int length, int sizePixels);

	TextMetrics getMetrics(int sizePixels);

  private:
	void destroy();

	void allocateBuffer(TextBlock *block, int numChars);

	TextMetrics measureChars(std::u32string str, unsigned int offset, unsigned int length, std::map<unsigned int, std::vector<RenderChar>> *chars);

	void loadChars();
};

class Font {

  private:
	int lineHeight;
	int ascender;
	int descender;

	MemoryBuffer *fileMemory;
	FT_Face face;

	std::map<unsigned int, CharacterBlock> characterBlocks;

  public:
	Font(std::string path);

	~Font();

	CharacterBlock getCharacterBlock(unsigned int block);

	FontMetrics getDefaultMetrics();

	FontMetrics getMetrics(int sizePixels);

	// Draws text normally.
	// y specifies the baseline of the drawn text
	void drawText(TextObject *textObject, int x, int y, int sizePixels, Color color);

	// Draws text normally.
	// y specifies the baseline of the drawn text
	void drawText(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color);

	// Draws text, with (x,y) being the top-left corner of the text drawn
	void drawTextFromOrigin(TextObject *textObject, int x, int y, int sizePixels, Color color);

	// Draws text, with (x,y) being the top-left corner of the text drawn
	void drawTextFromOrigin(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color);

	// Draws text centered around (x,y)
	void drawTextCentered(TextObject *textObject, int x, int y, int sizePixels, Color color);

	// Draws text centered around (x,y)
	void drawTextCentered(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color);

	// Loads a font from resources
	static std::shared_ptr<Font> load(std::string path);

  private:
	CharacterBlock generateCharacterBlock(unsigned int block);
};

}
