/***************************************************************************
 gfx_tools.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 <sci_memory.h>
#include <gfx_tools.h>

/* set optimisations for Win32: */
#ifdef _WIN32
#	include <memory.h>
#endif


void
gfx_clip_box_basic(rect_t *box, int maxx, int maxy)
{
	if (box->x < 0)
		box->x = 0;

	if (box->y < 0)
		box->y = 0;

	if (box->x + box->xl > maxx)
		box->xl = maxx - box->x + 1;

	if (box->y + box->yl > maxy)
		box->yl = maxy - box->y + 1;
}


gfx_mode_t *
gfx_new_mode(int xfact, int yfact, int bytespp, unsigned int red_mask, unsigned int green_mask,
	     unsigned int blue_mask, unsigned int alpha_mask, int red_shift, int green_shift,
	     int blue_shift, int alpha_shift, int palette, int flags)
{
	gfx_mode_t *mode = sci_malloc(sizeof(gfx_mode_t));
#ifdef SATISFY_PURIFY
	memset(mode, 0, sizeof(gfx_mode_t));
#endif

	mode->xfact = xfact;
	mode->yfact = yfact;
	mode->bytespp = bytespp;
	mode->red_mask = red_mask;
	mode->green_mask = green_mask;
	mode->blue_mask = blue_mask;
	mode->alpha_mask = alpha_mask;
	mode->red_shift = red_shift;
	mode->green_shift = green_shift;
	mode->blue_shift = blue_shift;
	mode->alpha_shift = alpha_shift;
	mode->flags = flags;

	if (palette) {
		mode->palette = sci_malloc(sizeof(gfx_palette_t));
#ifdef SATISFY_PURIFY
		memset(mode->palette, 0, sizeof(gfx_palette_t));
#endif
		mode->palette->max_colors_nr = palette;
		mode->palette->colors = sci_calloc(sizeof(gfx_palette_color_t), palette); /* Initialize with empty entries */
	} else mode->palette = NULL;

  return mode;
}


void
gfx_free_mode(gfx_mode_t *mode)
{
	if (mode->palette) {
		free(mode->palette->colors);
		free(mode->palette);
	}
	free(mode);
}



void
gfx_copy_pixmap_box_i(gfx_pixmap_t *dest, gfx_pixmap_t *src, rect_t box)
{
	int width, height;
	int offset;

	if ((dest->index_xl != src->index_xl) || (dest->index_yl != src->index_yl))
		return;

	gfx_clip_box_basic(&box, dest->index_xl, dest->index_yl);

	height = box.yl;
	width = box.xl;

	offset = box.x + (box.y * dest->index_xl);

	while (height--) {
		memcpy(dest->index_data + offset, src->index_data + offset, width);
		offset += dest->index_xl;
	}
}


gfx_pixmap_t *
gfx_clone_pixmap(gfx_pixmap_t *pxm, gfx_mode_t *mode)
{
	gfx_pixmap_t *clone = malloc(sizeof(gfx_pixmap_t));
	*clone = *pxm;
	clone->index_data = NULL;
	clone->colors = NULL;
	clone->data = NULL;
	gfx_pixmap_alloc_data(clone, mode);

	memcpy(clone->data, pxm->data, clone->data_size);
	if (clone->alpha_map) {
		clone->alpha_map = malloc(clone->xl * clone->yl);
		memcpy(clone->alpha_map, pxm->alpha_map, clone->xl * clone->yl);
	}

	return clone;
}

gfx_pixmap_t *
gfx_new_pixmap(int xl, int yl, int resid, int loop, int cel)
{
	gfx_pixmap_t *pxm = sci_malloc(sizeof(gfx_pixmap_t));
#ifdef SATISFY_PURIFY
	memset(pxm, 0, sizeof(gfx_pixmap_t));
#endif

	pxm->alpha_map = pxm->data = pxm->internal.info = pxm->colors = NULL;
	pxm->internal.handle = 0;

	pxm->index_xl = xl;
	pxm->index_yl = yl;

	pxm->ID = resid;
	pxm->loop = loop;
	pxm->cel = cel;

	pxm->index_data = NULL;

	pxm->flags = 0;

	return pxm;
}


void
gfx_free_pixmap(gfx_driver_t *driver, gfx_pixmap_t *pxm)
{
	if (driver) {
		if (pxm->flags & GFX_PIXMAP_FLAG_INSTALLED) {
			if (driver->capabilities & GFX_CAPABILITY_PIXMAP_REGISTRY)
				driver->unregister_pixmap(driver, pxm);
		}

		if (driver->mode->palette
		    && pxm->flags & GFX_PIXMAP_FLAG_PALETTE_ALLOCATED
		    && !(pxm->flags & GFX_PIXMAP_FLAG_DONT_UNALLOCATE_PALETTE)
		    && !(pxm->flags & GFX_PIXMAP_FLAG_EXTERNAL_PALETTE)) {
			int i;
			int error = 0;
			GFXDEBUG("UNALLOCATING %d\n", pxm->colors_nr);
			for (i = 0; i < pxm->colors_nr; i++)
				if (gfx_free_color(driver->mode->palette, pxm->colors + i))
					error++;

			if (error) {
				GFXWARN("%d errors occured while freeing %d colors of pixmap with ID %06x/%d/%d\n",
					error, pxm->colors_nr,pxm->ID, pxm->loop, pxm->cel);
			}
		}
	}

	if (pxm->index_data)
		free(pxm->index_data);

	if (pxm->alpha_map)
		free(pxm->alpha_map);

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

	if ((pxm->colors) && !(pxm->flags & GFX_PIXMAP_FLAG_EXTERNAL_PALETTE))
		free(pxm->colors);

	free(pxm);
}


gfx_pixmap_t *
gfx_pixmap_alloc_index_data(gfx_pixmap_t *pixmap)
{
	int size;

	if (pixmap->index_data) {
		GFXWARN("Attempt to allocate pixmap index data twice!\n");
		return pixmap;
	}

	size = pixmap->index_xl * pixmap->index_yl;
	if (!size)
		size = 1;

	pixmap->index_data = sci_malloc(size);
#ifdef SATISFY_PURIFY
	memset(pixmap->index_data, 0, size);
#endif
	return pixmap;
}


gfx_pixmap_t *
gfx_pixmap_free_index_data(gfx_pixmap_t *pixmap)
{
	if (!pixmap->index_data) {
		GFXWARN("Attempt to free pixmap index data twice!\n");
		return pixmap;
	}

	free(pixmap->index_data);
	pixmap->index_data = NULL;
	return pixmap;
}


gfx_pixmap_t *
gfx_pixmap_alloc_data(gfx_pixmap_t *pixmap, gfx_mode_t *mode)
{
	int size;

	if (pixmap->data) {
		GFXWARN("Attempt to allocate pixmap data twice!\n");
		return pixmap;
	}

	if (pixmap->flags & GFX_PIXMAP_FLAG_SCALED_INDEX) {
		pixmap->xl = pixmap->index_xl;
		pixmap->yl = pixmap->index_yl;
	} else {
		pixmap->xl = pixmap->index_xl * mode->xfact;
		pixmap->yl = pixmap->index_yl * mode->yfact;
	}

	size = pixmap->xl * pixmap->yl * mode->bytespp;
	if (!size)
		size = 1;

	pixmap->data = sci_malloc(pixmap->data_size = size);
	return pixmap;
}


gfx_pixmap_t *
gfx_pixmap_free_data(gfx_pixmap_t *pixmap)
{
	if (!pixmap->data) {
		GFXWARN("Attempt to free pixmap data twice!\n");
		return pixmap;
	}

	free(pixmap->data);
	pixmap->data = NULL;
	return pixmap;
}


int
gfx_alloc_color(gfx_palette_t *pal, gfx_pixmap_color_t *color)
{
	int i;
	int dr, dg, db; /* deltas */
	int bestdelta = 1 + ((0x100 * 0x100) * 3);
	int bestcolor = -1;
	int firstfree = -1;

	if (pal == NULL)
		return GFX_OK;

	if (pal->max_colors_nr <= 0) {
		GFXERROR("Palette has zero or less color entries!\n");
		return GFX_ERROR;
	}


	if (color->global_index != GFX_COLOR_INDEX_UNMAPPED) {
#if 0
		GFXDEBUG("Attempt to allocate color twice: index 0x%d (%02x/%02x/%02x)!\n",
			 color->global_index, color->r, color->g, color->b);
#endif
		return GFX_OK;
	}

	for (i = 0; i < pal->max_colors_nr; i++) {
		gfx_palette_color_t *pal_color = pal->colors + i;

		if (pal_color->lockers) {
			int delta;

			dr = abs(pal_color->r - color->r);
			dg = abs(pal_color->g - color->g);
			db = abs(pal_color->b - color->b);

			if (dr == 0 && dg == 0 && db == 0) {
				color->global_index = i;
				return GFX_OK;
			}

			delta = (dr * dr) + (dg * dg) + (db * db);
			if (delta < bestdelta) {
				bestdelta = delta;
				bestcolor = i;
			}
		} else
			if (firstfree == -1)
				firstfree = i;
	}

	if (firstfree != -1) {
		pal->colors[firstfree].r = color->r;
		pal->colors[firstfree].g = color->g;
		pal->colors[firstfree].b = color->b;
		pal->colors[firstfree].lockers = 1;
		color->global_index = firstfree;

		return 42; /* positive value to indicate that this color still needs to be set */
	}

	color->global_index = bestcolor;

	GFXWARN("Out of palette colors- doing approximated mapping!\n");
	return GFX_OK;
}


int
gfx_free_color(gfx_palette_t *pal, gfx_pixmap_color_t *color)
{
	gfx_palette_color_t *palette_color = pal->colors + color->global_index;

	if (!pal)
		return GFX_OK;

	if (color->global_index == GFX_COLOR_INDEX_UNMAPPED) {
		GFXWARN("Attempt to free unmapped color %02x/%02x/%02x!\n", color->r, color->g, color->b);
		BREAKPOINT();
		return GFX_ERROR;
	}

	if (color->global_index >= pal->max_colors_nr) {
		GFXERROR("Attempt to free invalid color index 0x%d (%02x/%02x/%02x)!\n",
			 color->global_index, color->r, color->g, color->b);
		return GFX_ERROR;
	}

	if (!palette_color->lockers) {
		GFXERROR("Attempt to free unused color index 0x%d (%02x/%02x/%02x)!\n",
			 color->global_index, color->r, color->g, color->b);
		return GFX_ERROR;
	}

	if (palette_color->lockers != GFX_COLOR_SYSTEM)
		--(palette_color->lockers);

	color->global_index = GFX_COLOR_INDEX_UNMAPPED;
	return GFX_OK;
}


gfx_pixmap_t *
gfx_pixmap_scale_index_data(gfx_pixmap_t *pixmap, gfx_mode_t *mode)
{
	byte *old, *new, *initial_new;
	byte *linestart;
	int linewidth;
	int xl, yl;
	int i, yc;
	int xfact = mode->xfact;
	int yfact = mode->yfact;

	if (xfact == 1 && yfact == 1)
		return pixmap;

	if (!pixmap)
		return NULL;

	if (pixmap->flags & GFX_PIXMAP_FLAG_SCALED_INDEX)
		return pixmap; /* Already done */

	old = pixmap->index_data;

	if (!old) {
		GFXERROR("Attempt to scale index data without index data!\n");
		return pixmap;
	}

	xl = pixmap->index_xl;
	yl = pixmap->index_yl;
	linewidth = xfact * xl;
	initial_new = new = sci_malloc(linewidth * yfact * yl);

	for (yc = 0; yc < yl; yc++) {

		linestart = new;

		if (xfact == 1) {
			memcpy(new, old, linewidth);
			new += linewidth;
			old += linewidth;
		} else for (i = 0; i < xl; i++) {
			byte fillc = *old++;
			memset(new, fillc, xfact);
			new += xfact;
		}

		for (i = 1; i < yfact; i++) {
			memcpy(new, linestart, linewidth);
			new += linewidth;
		}
	}

	free(pixmap->index_data);
	pixmap->index_data = initial_new;

	pixmap->flags |= GFX_PIXMAP_FLAG_SCALED_INDEX;

	pixmap->index_xl = linewidth;
	pixmap->index_yl *= yfact;

	return pixmap;
}
