#include "Bitmap.h"

#include <SDL.h>
#include <stdbool.h>

#include "ExMath.h"
#include "Log.h"
#include "RPK.h"

Bmp_t Bmp_Create(int width, int height)
{
	Bmp_t bmp;
	bmp.w = width;
	bmp.h = height;
	bmp.pixels = SDL_malloc(width * height * sizeof(bmcol_t));
	return bmp;
}

void Bmp_Destroy(Bmp_t *bmp)
{
	SDL_free(bmp->pixels);
}

Bmp_t Bmp_CreateFromPAKEntry(const RPKFile *rpak, const char *name)
{
	Bmp_t bmp;
	size_t datalen;
	void *data;
	int i;

	datalen = RPK_GetEntryData(rpak, name, &data);
	if (datalen == 0)
	{
		Msg_Fatal("Unable to load bitmap with name '%s'", name);
	}

	bmp = Bmp_Create(((unsigned short *)data)[0], ((unsigned short *)data)[1]);

	for (i = 0; i < bmp.w * bmp.h; i++)
	{
		byte r = ((byte *)data + 4)[i * 4 + 0];
		byte g = ((byte *)data + 4)[i * 4 + 1];
		byte b = ((byte *)data + 4)[i * 4 + 2];
		byte a = ((byte *)data + 4)[i * 4 + 3];
		bmp.pixels[i] = (a << 24) | (r << 16) | (g << 8) | (b << 0);
	}

	SDL_free(data);
	return bmp;
}

#define FONT_MAXGLYPHS 32*3
#define FONT_MAXWIDTH 10

struct BmpFont {
	unsigned char height;
	unsigned char widths[FONT_MAXGLYPHS];
	unsigned char glyphs[FONT_MAXGLYPHS][FONT_MAXWIDTH];
};

static struct BmpFont fontMiniserif;
static struct BmpFont fontTiny;

static struct BmpFont *activeFont;

void LoadFont(const RPKFile *rpak, const char *name, struct BmpFont *font)
{
	size_t datalen, glyphDataOffset;
	char *data;
	int i;

	datalen = RPK_GetEntryData(rpak, name, (void**)&data);
	if (datalen == 0)
	{
		Msg_Fatal("Unable to read font '%s'", name);
	}

	font->height = data[0];

	/* If the highest bit in the height is set, the font is fixed-width and
	 * the width table only contains one value. */
	if (font->height & 0x80)
	{
		byte width = data[1];
		SDL_memset(font->widths, width, FONT_MAXGLYPHS);
		font->height &= 0x7f;
		glyphDataOffset = 2;
	}
	else
	{
		SDL_memcpy(font->widths, data + 1, FONT_MAXGLYPHS);
		glyphDataOffset = FONT_MAXGLYPHS + 1;
	}
	
	for (i = 0; i < FONT_MAXGLYPHS-1; i++)
	{
		SDL_memcpy(&font->glyphs[i], data + glyphDataOffset, font->widths[i]);
		glyphDataOffset += font->widths[i];
	}

	SDL_free(data);
}

#define FONT_VALIDGLYPH(c) ((c) >= ' ' && (c) <= '~')

void Bmp_InitFonts(const RPKFile *rpak)
{
	LoadFont(rpak, "MiniSerif", &fontMiniserif);
	LoadFont(rpak, "Tiny", &fontTiny);
	activeFont = &fontMiniserif;
}

void Bmp_SetActiveFont(enum BmpFonts font)
{
	if (font == BMPFONT_TINY)
		activeFont = &fontTiny;
	else
		activeFont = &fontMiniserif;
}

#define MAX_WORD_LENGTH 24
static char *FindNextWord(const char *msg)
{
	static char next[MAX_WORD_LENGTH];
	const char *p = msg;
	int length = 0;
	while (!SDL_isspace(*p++) && length < (MAX_WORD_LENGTH - 1))
	{
		length++;
	}
	SDL_memcpy(next, msg, length);
	next[MAX_WORD_LENGTH - 1] = '\0';
	return next;
}

void Bmp_TextSize(const char *msg, int *width, int *height, int wrapWidth)
{
	int lineWidth = 0;
	size_t i;

	*width = 0;
	*height = 0;

	for (i = 0; i < SDL_strlen(msg); i++)
	{
		bool needsWrap = false;
		char c = msg[i];
		int w = 0;

		if (FONT_VALIDGLYPH(c))
			w = activeFont->widths[c - 32];

		if (SDL_isspace(c))
		{
			const char *nextWord = FindNextWord(msg);
			int w, h;
			Bmp_TextSize(nextWord, &w, &h, 9999);
			if ((lineWidth + w) > wrapWidth)
				needsWrap = true;
		}

		if (c == '\n' || needsWrap || lineWidth + w > wrapWidth)
		{
			*height += activeFont->height + 1;
			if (lineWidth > *width)
				*width = lineWidth;
			lineWidth = 0;
			/* Skip the next space if one exists to avoid whitespace on the left side. */
			if (SDL_isspace(c)) continue;
		}

		lineWidth += w + 1; /* Adds 1 to account for the gap between characters */
	}

	if (lineWidth > *width)
		*width = lineWidth;
	*height += activeFont->height + 1;
}

void Bmp_DrawGlyph(Bmp_t *bmp, char c, bmcol_t col, int xo, int yo)
{
	int idx, x, y, w;
	if (!FONT_VALIDGLYPH(c))
		return;
	idx = c - 32;
	w = activeFont->widths[idx];
	for (x = 0; x < w; x++)
	{
		unsigned char colbits = activeFont->glyphs[idx][x];
		int xp = x + xo;
		for (y = 0; y < activeFont->height; y++)
		{
			int yp = y + yo;
			if (xp < 0 || xp >= bmp->w || yp < 0 || yp >= bmp->h)
				continue;
			if (colbits & (1 << y))
				bmp->pixels[xp + yp * bmp->w] = col;
		}
	}
}

void Bmp_DrawText(Bmp_t *bmp, const char *msg, bmcol_t col, int xo, int yo, int wrapWidth)
{
	size_t i, n = SDL_strlen(msg);
	int xoffs = 0, yoffs = 0;

	wrapWidth = MATH_MIN(wrapWidth, (bmp->w - xo));

	for (i = 0; i < n; i++)
	{
		bool needsWrap = false;
		char c = msg[i];
		int idx = c - 32;
		int w = 0;

		if (FONT_VALIDGLYPH(c))
			w = activeFont->widths[idx];

		if (SDL_isspace(c))
		{
			const char *nextWord = FindNextWord(msg);
			int w, h;
			Bmp_TextSize(nextWord, &w, &h, 9999);
			if ((xoffs + w) > wrapWidth)
				needsWrap = true;
		}

		if (c == '\n' || needsWrap || xoffs + w > wrapWidth)
		{
			xoffs = 0;
			yoffs += activeFont->height + 1;
			/* Skip the next space if one exists to avoid whitespace on the left side. */
			if (SDL_isspace(c)) continue;
		}

		Bmp_DrawGlyph(bmp, c, col, xo + xoffs, yo + yoffs);
		xoffs += w + 1;
	}
}

void Bmp_Resize(Bmp_t *bmp, int w, int h)
{
	Bmp_Destroy(bmp);
	*bmp = Bmp_Create(w, h);
}

void Bmp_Blit(Bmp_t *dst, Bmp_t *src, int xo, int yo)
{
	int x, y;
	for (y = 0; y < src->h; y++)
	{
		int yp = y + yo;
		if (yp < 0 || yp >= dst->h) continue;

		for (x = 0; x < src->w; x++)
		{
			int xp = x + xo;
			if (xp < 0 || xp >= dst->w) continue;

			bmcol_t col = src->pixels[x + y * src->w];
			if (col > 0)
				dst->pixels[xp + yp * dst->w] = col;
		}
	}
}

void Bmp_BlitRegion(Bmp_t *dst, Bmp_t *src, int xo, int yo, int rx, int ry, int rw, int rh)
{
	int x, y;
	for (y = 0; y < rh; y++)
	{
		int yp = y + yo;
		if (yp < 0 || yp >= dst->h) continue;

		for (x = 0; x < rw; x++)
		{
			int xp = x + xo;
			if (xp < 0 || xp >= dst->w) continue;

			bmcol_t col = src->pixels[(x + rx) + (y + ry) * src->w];
			if (col > 0)
				dst->pixels[xp + yp * dst->w] = col;
		}
	}
}

void Bmp_BlitRegionScaled(Bmp_t *dst, Bmp_t *src, int xo, int yo, int rx, int ry, int rw, int rh, int scale)
{
	int x, y;
	for (y = 0; y < rh*scale; y++)
	{
		int yp = y + yo;
		if (yp < 0 || yp >= dst->h) continue;

		for (x = 0; x < rw * scale; x++)
		{
			int xp = x + xo;
			if (xp < 0 || xp >= dst->w) continue;

			bmcol_t col = src->pixels[((x / scale) + rx) + ((y / scale) + ry) * src->w];
			if (col > 0)
				dst->pixels[xp + yp * dst->w] = col;
		}
	}
}

void Bmp_Clear(Bmp_t *bm, bmcol_t col)
{
	int i;
	for (i = 0; i < bm->w * bm->h; i++)
	{
		bm->pixels[i] = col;
	}
}

void Bmp_FillRegion(Bmp_t *bmp, bmcol_t col, int xo, int yo, int w, int h)
{
	int x, y;
	for (y = yo; y < h+yo; y++)
	{
		if (y < 0 || y >= bmp->h)
			continue;

		for (x = xo; x < w+xo; x++)
		{
			if (x < 0 || x >= bmp->w)
				continue;

			if (col == 0)
				continue;

			unsigned char alpha = (col >> 24) & 0xff;
			if (alpha == 0xff)
			{
				bmp->pixels[x + y * bmp->w] = col;
			}
			else
			{
				float aMod = alpha / 255.0f;
				float invMod = (255 - alpha) / 255.0f;
				
				bmcol_t bgcol = bmp->pixels[x + y * bmp->w];

				unsigned char r = (unsigned char)(((col >> 16) & 0xff) * aMod + ((bgcol >> 16) & 0xff) * invMod);
				unsigned char g = (unsigned char)(((col >> 8) & 0xff) * aMod + ((bgcol >> 8) & 0xff) * invMod);
				unsigned char b = (unsigned char)(((col >> 0) & 0xff) * aMod + ((bgcol >> 0) & 0xff) * invMod);

				bmp->pixels[x + y * bmp->w] = (r << 16) | (g << 8) | b;
			}
		}
	}
}
