//////////////////////////////////////////////////////////////////////////////////////////////
// Font.cpp
// Copyright by Bjoern Ganster
// The roots of this code go way back in time, but this was put together as a class in 2022
//////////////////////////////////////////////////////////////////////////////////////////////

#include "Font.h"

#include <SDL_ttf.h>

#include <iostream>

#define FILE_NUMBER FILE_NO_FONT_CPP

// Default constructor is private. Use loadFromFile or loadFromSystem instead.
Font::Font()
: VirtualClass("Font", FILE_NO_FONT_CPP)
{
}

// Small help function to determine the width of a single character
void Font::findCharDims(unsigned char c, int maxCharWidth)
{
	SET_SCOPE;
	mU1[c] = (c&15)*maxCharWidth; // (c &15): char column
	int u2 = mU1[c]+maxCharWidth;
	mV1[c] = mFontTexture.getHeight() - (c/16)*mCharHeight + mCharHeight; // (c/16): char row
	mV2[c] = mV1[c]+mCharHeight;
	int result = 0;

	for (int x = mU1[c]; x < u2; ++x) {
		int y = mV1[c];
		while (y < mV2[c]) {
			unsigned char r, g, b;
			mFontTexture.getRGB(x, y, r, g, b);
			if (r > 128) {
				y = mV2[c];
				mU2[c] = x+mCharSpacing;
			} else {
				++y;
			}
		}
	}

	mU2[c] = putInRange<int>(mU1[c] + mCharSpacing, mU2[c], mU1[c] + maxCharWidth);
	RETURN;
}

// Load a font from file
Font* Font::loadFromFile(const PathChar* fontFN, int width, int height, int spacing)
{
	SET_SCOPE;
	Font* newFont = nullptr; 
	{
		Texture* temp = new Texture;
		if (!temp->loadFromFile(fontFN)) {
			delete temp;
			RETURN nullptr;
		}

		// Now copy the temp texture to the font texture and construct an alpha channel
		Font* newFont = new Font();
		newFont->mFontTexture.resize(temp->getWidth(), temp->getHeight(), GL_RGBA);
		newFont->mCharHeight = height;
		for (size_t x = 0; x < temp->getWidth(); x++) {
			for (size_t y = 0; y < temp->getHeight(); y++) {
				unsigned char r, g, b;
				temp->getRGB(x, y, r, g, b);
				unsigned char a = (b > 100) ? 255 : 0;
				newFont->mFontTexture.setRGBA(x, y, r, g, b, a);
			}
		}

		delete temp; // can't be used outside its scope so it doesn't have to be NULLed
	}

	newFont->mMaxCharWidth = 0;
	newFont->mCharHeight = height;
	newFont->mCharSpacing = spacing;

	for (int i = 32; i < 256; ++i) {
		//newFont->mCharWidth[i] = width;
		newFont->findCharDims(i, width);
		int w = newFont->mU2[i]-newFont->mU1[i];
		if (w > newFont->mMaxCharWidth) {
			newFont->mMaxCharWidth = w;
		}
	}
	//newFont->mU2[' '] = newFont->mU2['I'];

	RETURN newFont;
}

// Construct font from texture
Font::Font(const Texture& texture, int charWidth, int charHeight, int spacing)
: VirtualClass("Font", Font_ID)
{
	SET_SCOPE;
	// For rendering directly, we would have to resize the texture to powers of two, but it doesn't work yet (no idea why).
	/*int w = 32;
	int h = 32;
	while (w < texture.getWidth())
		w *= 2;
	while (h < texture.getHeight())
		h *= 2;
	mFontTexture.resize(w, h, GL_RGBA);*/

	// If we copy the texture without resizing, it works, but we still have artefacts ...
	mFontTexture.resize(texture.getWidth(), texture.getHeight(), GL_RGBA);

	// We need to add an alpha channel
	for (size_t x = 0; x < texture.getWidth(); x++) {
		for (size_t y = 0; y < texture.getHeight(); y++) {
			unsigned char r, g, b;
			texture.getRGB(x, y, r, g, b);
			unsigned char a = b; //(b > 100) ? 255 : 0;
			mFontTexture.setRGBA(x, y, a, a, a, a);
		}
	}

	mCharHeight = charHeight;
	mCharSpacing = spacing;
	mMaxCharWidth = charHeight;

	for (int i = 0; i < 32; ++i) {
		mU1[i] = 0;
		mU2[i] = 3 * mCharSpacing;
		mV1[i] = 0;
		mV2[i] = mCharHeight;
	}
	for (int i = 32; i < 256; ++i) {
		findCharDims(i, charWidth);
	}
	mMaxCharWidth = mU2['W'] - mU1['W'];
	//mU2[' '] = 3 * spacing;
	mU2[' '] = mU2['e'] - mU1['e'];

	RETURN;
}

// Build a font texture by rendering a font into a texture
Font* Font::loadFromSystem(const char* fontName, int height, int spacing)
{
	#ifdef _WIN32
	SET_SCOPE;
	if (height <= 0
	|| bgstrlen(fontName) == 0)
	{
		RETURN nullptr;
	}

	// Render font to texture (WinAPI, GDI)
	SET_OP;
	HDC baseDC = GetDC(0);
	HDC dc = CreateCompatibleDC(baseDC);
	int fontSizeY = height;
	HFONT hFont = CreateFontA(fontSizeY, 0, 0, 0, 0, 0, 0, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, 
		CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, fontName);
	SelectObject(dc, hFont);

	TEXTMETRIC metric;
	GetTextMetrics(dc, &metric);
	int fontSizeX = metric.tmMaxCharWidth;
	int bmW = 32;
	int bmH = 32;

	// For direct rendering, we need width and height to be powers of two ...
	SET_OP;
	while (bmW < 16 * (fontSizeX))
		bmW *= 2;
	while (bmH < 14 * (fontSizeY))
		bmH *= 2;

	HBITMAP memBM = CreateCompatibleBitmap(dc, bmW, bmH);
	SelectObject(dc, memBM);

	SET_OP;
	RECT r;
	r.left = 0;
	r.top = 0;
	r.right = bmW;
	r.bottom = bmH;
	HBRUSH brush = CreateSolidBrush(RGB(255, 255, 255));
	FillRect(dc, &r, brush);

	for (int y = 0; y < 14; ++y) {
		for (int x = 0; x < 16; ++x) {
			char text[2];
			text[0] = 16*y+x+32;
			text[1] = 0;
			RECT rect;
			rect.left = x * fontSizeX;
			rect.top = y * fontSizeY;
			rect.right = rect.left + fontSizeX;
			rect.bottom = rect.top + fontSizeY;
			DrawTextA(dc, text, 1, &rect, DT_LEFT);
		}
	}

	SET_OP;
	int bytesPerPixel = 4;
	BITMAPINFO info;
	memset(&info, 0, sizeof(BITMAPINFO));
	info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	info.bmiHeader.biWidth = bmW;
	info.bmiHeader.biHeight = bmH;
	info.bmiHeader.biPlanes = 1;
	info.bmiHeader.biBitCount = 8 * bytesPerPixel;
	info.bmiHeader.biCompression = BI_RGB;
	info.bmiHeader.biSizeImage = bytesPerPixel * bmW * bmH;

	Font* font = new Font;
	font->mFontTexture.resize(bmW, bmH, GL_RGBA);
	unsigned char* data = new unsigned char[info.bmiHeader.biSizeImage]; //font->getPicData();
	GetDIBits(dc, memBM, 0, bmH, data, &info, DIB_RGB_COLORS);
	for (int y = 0; y < bmH; ++y) {
		for (int x = 0; x < bmW; ++x) {
			unsigned char r = data[bytesPerPixel*bmW*y+bytesPerPixel*x];
			//unsigned char g = data[bytesPerPixel*bmW*y+bytesPerPixel*x+1];
			//unsigned char b = data[bytesPerPixel*bmW*y+bytesPerPixel*x+2];
			//font->setRGB(x, y, r, g, b);
			r = 255-r;
			font->mFontTexture.setRGBA(x, y, r, r, r, r);
		}
	}

	SET_OP;
	font->mCharHeight = fontSizeY;
	font->mCharSpacing = spacing;

	for (int i = 32; i < 256; ++i) {
		font->findCharDims(i, fontSizeX);
	}
	font->mMaxCharWidth = font->mU2['W'] - font->mU1['W'];
	font->mU2[' '] = 3 * spacing;

	// Clean up ...
	ReleaseDC(0, baseDC);
	DeleteDC(dc);
	DeleteObject(hFont);
	DeleteObject(memBM);
	DeleteObject(brush);

	COUT << "Using system font " << fontName << " (" << font->mMaxCharWidth << "x" << font->mCharHeight << ")" << std::endl;

	delete data;
	RETURN font;

	// Much easier to use SDL_TTF?
	#else
	return nullptr;
	#endif
}

// Obtain width of text on screen
// Non-ASCII characters are clamped to the 0..255 range
int Font::textWidth (const BGCPString& text)
{
	SET_SCOPE;
	size_t pos = 0;
	int result = 0;
	int thisLineLen = 0;

	while (pos < text.getByteCount()) {
		char c = text.get<char>(pos);
		if (c >= 32) {
			thisLineLen += mU2[c]-mU1[c];
			result = std::max(thisLineLen, result);
		} else {
			result = std::max(thisLineLen, result);
			thisLineLen = 0;
		}
		pos += text.getCharSize(pos);
	}

	RETURN result;
}

// Render text using the font directly
void Font::renderText(const BGCPString& str, int x1, int y1, int x2)
{
	SET_SCOPE;
	if (str.empty()) {
		RETURN;
	}

	// Check number of characters that fit into maxWidth
	BGCPString txtcpy = str;
	txtcpy.convertToCP(CP_UTF16_LE);
	const char16_t* chars = txtcpy.getPtr<char16_t>();
	size_t len = txtcpy.getByteCount();
	size_t pos = textWidth(txtcpy);
	auto count = txtcpy.getCharCount();

	// Render part of string
	SET_OP;
	mFontTexture.use();
	double y2 = y1+mCharHeight;
	size_t w = mFontTexture.getPotWidth();
	size_t h = mFontTexture.getPotHeight();
	int maxWidth = x2-x1;

	SET_OP;
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0.5);
	glBegin(GL_QUADS);
	size_t i = 0;
	int x = x1;

	while (i < count /*&& maxWidth >= 0*/) {
		char16_t c = chars[i];
		if (c >= 32 && (int) c < 256) {
			x2 = x+mU2[c]-mU1[c];
			SET_OP;
			int u1 = mU1[c];
			int u2 = mU2[c];
			int v1 = mV1[c];
			int v2 = mV2[c];
			glTexCoord2d(double(u1)/w, double(v2)/h);
			glVertex2d(x, y1);
			glTexCoord2d(double(u2)/w, double(v2)/h);
			glVertex2d(x2, y1);
			glTexCoord2d(double(u2)/w, double(v1)/h);
			glVertex2d(x2, y2);
			glTexCoord2d(double(u1)/w, double(v1)/h);
			glVertex2d(x, y2);
			x = x2;
		} else if (c == 9) {
			// Simulate next column - in this function, we have no way of knowing where that would be ...
			x += 100;
		} else {
			x = x1;
			y1 += mCharHeight;
			y2 += mCharHeight;
		}
		++i;
		maxWidth -= mU2[c] - mU1[c];
	}
	glEnd();
	glDisable(GL_ALPHA_TEST);
	glDisable(GL_TEXTURE_2D);
	RETURN;
}

// Write text into a target texture
void Font::renderText(Texture* target, const BGCPString& txt, int x1, int y1, int x2, int y2, unsigned char r, 
    unsigned char g, unsigned char b)
{
	SET_SCOPE;
	int rx = x1;
	int ry = y1;
	size_t cc = txt.getCharCount();
	size_t i = 0;

	while (i < cc) {
		char c = txt.get<char>(i);
		if (c == 0x0a || c == 0x0d || rx > x2) {
			i += txt.getCharSize(i);
			rx = x1;
			ry -= mCharHeight + mCharSpacing;
			char d = txt.get<char>(i);
			if (d == 0x0a && c == 0x0d) {
				i += txt.getCharSize(i);
			}
		} else if (c >= 32) {
			int u1 = mU1[c];
			int v1 = mV1[c];
			int u2 = mU2[c];
			int v2 = mV2[c];
			if (rx+u2-u1 <= x2 && ry+mCharHeight >= y2) {
				target->copyNonTransparent(&mFontTexture, u1, v1, u2, v2, rx, ry, r, g, b);
			}
			rx += u2-u1;
			i += txt.getCharSize(i);
		} else {
			rx += 2 * mCharSpacing;
			i += txt.getCharSize(i);
		}
	}
	RETURN;
}

// Get character data
int Font::getCharWidth(char c) const
{
	return mU2[c]-mU1[c];
}
int Font::getMaxCharWidth() const
{
	return mMaxCharWidth;
}
int Font::getCharHeight() const
{
	return mCharHeight;
}
int Font::getCharSpacing() const
{
	return mCharSpacing;
}

Font* gDefaultFont = nullptr;

// Access default font
void setDefaultFont(Font* newVal)
{
	gDefaultFont = newVal;
}

Font* getDefaultFont()
{
	return gDefaultFont;
}

// load font
Font* Font::loadFromTTF(void* fontFileData, size_t fontFileSize, int fontHeight)
{
	SET_SCOPE;
	if (fontFileData == nullptr || fontFileSize == 0) {
		//messageBox("MechVM Error", "Cannot launch without font(1)", MB_OK);
		RETURN nullptr;
	}
	SDL_RWops* pFontRWops = SDL_RWFromConstMem(fontFileData, fontFileSize);
	if (pFontRWops == nullptr) {
		//messageBox("MechVM Error", "Cannot launch without font rwops", MB_OK);
		RETURN nullptr;
	}
	TTF_Font* sdlFont = TTF_OpenFontRW(pFontRWops, 1, fontHeight);
	if (sdlFont == nullptr) {
		//messageBox("MechVM Error", "Cannot launch without font (2)", MB_OK);
		RETURN nullptr;
	}
	Font* result = new Font();
	SDL_Color black = {0, 0, 0};
	SDL_Color white = {0xff, 0xff, 0xff, 0xff};
	SDL_Surface* charSurface[256];
	result->mMaxCharWidth = 0;
	result->mCharHeight = fontHeight;
	result->mCharSpacing = 1;
	memset(charSurface, 0, 256*sizeof(void*));
	memset(result->mU1, 0, 256*sizeof(int));
	memset(result->mV1, 0, 256*sizeof(int));
	memset(result->mU2, 0, 256*sizeof(int));
	memset(result->mV2, 0, 256*sizeof(int));

	for (int i = 32; i < 128; ++i) {
		char str[2];
		str[0] = i;
		str[1] = 0;
		charSurface[i] = TTF_RenderText_Solid(sdlFont, str, white);
		max_update(result->mMaxCharWidth, charSurface[i]->w);
	}
	/*if (sdlSurface == nullptr) {
		messageBox("MechVM Error", "Cannot create surface for font (2)", MB_OK);
		return 0;
	}*/
	/*SDL_Texture* sdlTexture = SDL_CreateTextureFromSurface(renderer, sdlSurface);
	if (sdlTexture == nullptr) {
		messageBox("MechVM Error", "Cannot create texture for font (2)", MB_OK);
		return 0;
	}*/

	// Construct texture from sdlSurface
	Texture& fontTexture = result->mFontTexture;
	fontTexture.resize(16 * (result->mMaxCharWidth+1), 16 * (fontHeight+1), GL_RGBA);
	fontTexture.fillRectI(0, 0, fontTexture.getWidth(), fontTexture.getHeight(), 0, 0, 0, 0);
	for (int c = 0; c < 256; ++c) {
		int cx = (c % 16) * (result->mMaxCharWidth+1);
		int cy = (c / 16) * (fontHeight+1);
		result->mU1[c] = cx;
		result->mV1[c] = cy;
		result->mU2[c] = cx;
		result->mV2[c] = cy + fontHeight;

		if (charSurface[c] != nullptr) {
			uint8_t* sourcePixels = (uint8_t*)charSurface[c]->pixels;
			for (int y = 0; y < charSurface[c]->h; y++) {
				for (int x = 0; x < charSurface[c]->w; x++) {
					char r = sourcePixels[y * charSurface[c]->pitch +x];
					if (r > 0) {
						fontTexture.setRGB(x+cx, charSurface[c]->h-y-1+cy, 0xff, 0xff, 0xff);
					}
				}
			}
			result->mU2[c] += charSurface[c]->w;
		}
	}
	//fontTexture.saveToFile(L"font.bmp", 0, 0, fontTexture.getWidth(), fontTexture.getHeight());
	RETURN result;
}
