/***************************************************************************
 font.c Copyright (C) 2000 Christoph Reichenbach


 This program may be modified and copied freely according to the terms of
 the GNU general public license (GPL), as long as the above copyright
 notice and the licensing information contained herein are preserved.

 Please refer to www.gnu.org for licensing details.

 This work is provided AS IS, without warranty of any kind, expressed or
 implied, including but not limited to the warranties of merchantibility,
 noninfringement, and fitness for a specific purpose. The author will not
 be held liable for any damage caused by this work or derivatives of it.

 By using this source code, you agree to the licensing terms as stated
 above.


 Please contact the maintainer for bug reports or inquiries.

 Current Maintainer:

    Christoph Reichenbach (CR) <jameson@linuxgames.com>

***************************************************************************/


#include <gfx_system.h>
#include <gfx_resource.h>
#include <gfx_tools.h>

int font_counter = 0;

void
gfxr_free_font(gfx_bitmap_font_t *font)
{
	if (font->widths)
		free(font->widths);

	if (font->data)
		free(font->data);

	--font_counter;

	free(font);
}



void
scale_char(byte *dest, byte *src, int width, int height, int newwidth, int xfact, int yfact)
{
	int x, y;

	for (y = 0; y < height; y++) {
		int yc;
		byte *bdest = dest;
		byte *bsrc = src;

		for (x = 0; x < width; x++) {
			int xbitc;
			int bits = 0;
			int value = 0;

			for (xbitc = 128; xbitc; xbitc >>= 1) {
				int xc;

				for (xc = 0; xc < xfact; xc++) {
					if (*bsrc & xbitc)
						value |= 1;
					value <<= 1;

					if (++bits == 8) {
						*bdest++ = value;
						bits = value = 0;
					}
				}
			}
			bsrc++;
		}

		src += width;
		for (yc = 1; yc < yfact; yc++) {
			memcpy(dest + newwidth, dest, newwidth);
			dest += newwidth;
		}
		dest += newwidth;
	}
}

gfx_bitmap_font_t *
gfxr_scale_font_unfiltered(gfx_bitmap_font_t *orig_font, gfx_mode_t *mode)
{
	gfx_bitmap_font_t *font = sci_malloc(sizeof(gfx_bitmap_font_t));
	int height = orig_font->height * mode->yfact;
	int width = 0;
	int byte_width;
	int i;

	font->chars_nr = orig_font->chars_nr;
	for (i = 0; i < font->chars_nr; i++)
		if (orig_font->widths[i] > width)
			width = orig_font->widths[i];

	width *= mode->xfact;
	byte_width = (width + 7) >> 3;
	if (byte_width == 3)
		byte_width = 4;
	if (byte_width > 4)
		byte_width = (byte_width + 3) & ~3;

	font->row_size = byte_width;
	font->height = height;
	font->line_height = orig_font->line_height * mode->yfact;

	font->widths = sci_malloc(sizeof(int) * orig_font->chars_nr);
	font->char_size = byte_width * height;
	font->data = sci_malloc(font->chars_nr * font->char_size);

	for (i = 0; i < font->chars_nr; i++) {
		font->widths[i] = orig_font->widths[i] * mode->xfact;
		scale_char(font->data + font->char_size * i,
			   orig_font->data + orig_font->char_size * i,
			   orig_font->row_size, orig_font->height,
			   font->row_size,
			   mode->xfact, mode->yfact);
	}
	return font;
}


gfx_bitmap_font_t *
gfxr_scale_font(gfx_bitmap_font_t *orig_font, gfx_mode_t *mode, gfxr_font_scale_filter_t filter)
{
	GFXWARN("This function hasn't been tested yet!\n");

	switch (filter) {

	case GFXR_FONT_SCALE_FILTER_NONE:
		return gfxr_scale_font_unfiltered(orig_font, mode);

	default:
		GFXERROR("Invalid font filter mode %d!\n", filter);
		return NULL;
	}
}




text_fragment_t *
gfxr_font_calculate_size(gfx_bitmap_font_t *font, int max_width, char *text,
			 int *width, int *height, int *lines, int flags)
{
	int est_char_width = font->widths[(font->chars_nr > 'M')? 'M' : font->chars_nr - 1];
	/* 'M' is typically among the widest chars */
	int fragments_nr;
	text_fragment_t *fragments;
	int lineheight = font->line_height;
	int maxheight = lineheight;
	int last_breakpoint = 0;
	int last_break_width = 0;
	int max_allowed_width = max_width;
	int maxwidth = 0, localmaxwidth = 0;
	int current_fragment = 1;
	char *breakpoint_ptr = NULL;
	unsigned char foo;


	if (max_width>1) fragments_nr = 3 + (strlen(text) * est_char_width)*3 / (max_width << 1); else fragments_nr = 1;

	fragments = sci_calloc(sizeof(text_fragment_t), fragments_nr);


	fragments[0].offset = text;

	while ((foo = *text++)) {

		if (foo >= font->chars_nr) {
			GFXWARN("Invalid char 0x%02x (max. 0x%02x) encountered in text string '%s', font %04x\n",
				foo, font->chars_nr, text, font->ID);
			if (font->chars_nr > ' ')
				foo = ' ';
			else {
				free(fragments);
				return NULL;
			}
		}

		if (((foo == '\n') || (foo == 0x0d))
		    && !(flags & GFXR_FONT_FLAG_NO_NEWLINES)) {

			fragments[current_fragment-1].length = text - 1 - fragments[current_fragment-1].offset;

			if (*text)
				maxheight += lineheight;

			if (foo == 0x0d && *text == '\n')
				text++; /* Interpret DOS-style CR LF as single NL */

			fragments[current_fragment++].offset = text;

			if (localmaxwidth > maxwidth)
				maxwidth = localmaxwidth;

			if (current_fragment == fragments_nr)
				fragments = sci_realloc(fragments, sizeof(text_fragment_t) * (fragments_nr <<= 1));

			localmaxwidth = 0;

		} else { /* foo != '\n' */
			localmaxwidth += font->widths[foo];

			if (localmaxwidth > max_allowed_width) {
				int blank_break = 1; /* break is at a blank char, i.e. not within a word */

				maxheight += lineheight;

				if (last_breakpoint == 0) { /* Text block too long and without whitespace? */
					last_breakpoint = localmaxwidth - font->widths[foo];
					last_break_width = 0;
					--text;
					blank_break = 0; /* non-blank break */
				} else {
					text = breakpoint_ptr + 1;
					assert(breakpoint_ptr);
				}

				if (last_breakpoint == 0) {
					GFXWARN("Warning: maxsize %d too small for '%s'\n",
						max_allowed_width, text);
				}

				if (last_breakpoint > maxwidth)
					maxwidth = last_breakpoint;

				fragments[current_fragment-1].length = text - blank_break - fragments[current_fragment-1].offset;
				fragments[current_fragment++].offset = text;

				if (current_fragment == fragments_nr)
					fragments = sci_realloc(fragments, sizeof(text_fragment_t *) * (fragments_nr <<= 1));

				localmaxwidth = localmaxwidth - last_breakpoint;
				if (!(flags & GFXR_FONT_FLAG_COUNT_WHITESPACE))
					localmaxwidth -= last_break_width;
				last_breakpoint = localmaxwidth = 0;

			} else if (*text == ' ') {
					last_breakpoint = localmaxwidth;
					last_break_width = font->widths[foo];
					breakpoint_ptr = text;
				}

		}
	}

	if (localmaxwidth > maxwidth)
		*width = localmaxwidth;
	else
		*width = maxwidth;

	*height = maxheight;
	*lines = current_fragment;

	fragments[current_fragment-1].length = text - fragments[current_fragment-1].offset - 1;

	return fragments;
}


static inline void
render_char(byte *dest, byte *src, int width, int line_width, int lines, int bytes_per_src_line, int fg0, int fg1, int bg)
{
	int x, y;

	for (y = 0; y < lines; y++) {
		int dat = 0;
		byte *vdest = dest;
		byte *vsrc = src;
		int xc = 0;

		for (x = 0; x < width; x++) {
			if (!xc) {
				dat = *vsrc++;
				xc = 8;
			}
			xc--;

			if (dat & 0x80)
				*vdest++ = ((xc ^ y) & 1)? fg0 : fg1; /* dither */
			else
				*vdest++ = bg;

			dat <<= 1;
		}
		src += bytes_per_src_line;
		dest += line_width;
	}
}

gfx_pixmap_t *
gfxr_draw_font(gfx_bitmap_font_t *font, char *stext, int characters,
	       gfx_pixmap_color_t *fg0, gfx_pixmap_color_t *fg1, gfx_pixmap_color_t *bg)
{
	unsigned char *text = (unsigned char *) stext;
	int height = font->height;
	int line_height = font->line_height;
	int width = 0;
	gfx_pixmap_t *pxm;
	int fore_0, fore_1, back;
	int i;
	byte *offset;

	for (i = 0; i < characters; i++) {
		int ch = (int) text[i];

		if (ch >= font->chars_nr) {
			GFXERROR("Invalid character 0x%02x encountered!\n", text[i]);
			return NULL;
		}

		width += font->widths[ch];
	}

	pxm = gfx_pixmap_alloc_index_data(gfx_new_pixmap(width, height, GFX_RESID_NONE, 0, 0));

	pxm->colors_nr = !!fg0 + !!fg1 + !!bg;
	pxm->colors = sci_malloc(sizeof(gfx_pixmap_color_t) * pxm->colors_nr);
#ifdef SATISFY_PURIFY
	memset(pxm->colors, 0, sizeof(gfx_pixmap_color_t) * pxm->colors_nr);
#endif
	pxm->flags |= GFX_PIXMAP_FLAG_PALETTE_ALLOCATED | GFX_PIXMAP_FLAG_DONT_UNALLOCATE_PALETTE;

	i = 0;

	if (fg0) {
		memcpy(pxm->colors + i, fg0, sizeof(gfx_pixmap_color_t));
		fore_0 = i++;
	} else fore_0 = GFX_COLOR_INDEX_TRANSPARENT;

	if (fg1) {
		memcpy(pxm->colors + i, fg1, sizeof(gfx_pixmap_color_t));
		fore_1 = i++;
	} else fore_1 = GFX_COLOR_INDEX_TRANSPARENT;

	if (bg) {
		memcpy(pxm->colors + i, bg, sizeof(gfx_pixmap_color_t));
		back = i++;
	} else back = GFX_COLOR_INDEX_TRANSPARENT;

	offset = pxm->index_data;

	memset(pxm->index_data, back, pxm->index_xl * pxm->index_yl);
	for (i = 0; i < characters; i++) {
		unsigned char ch = text[i];
		int width = font->widths[ch];

		render_char(offset, font->data + (ch * font->char_size), width,
			    pxm->index_xl, pxm->index_yl, font->row_size,
			    fore_0, fore_1, back);

		offset += width;
	}

	return pxm;
}

