/*
 * $Id$
 *
 * $Date$
 * $Revision$
 *
 * (C) 1999 by Hyperion
 * All rights reserved
 *
 * This file is part of the MiniGL library project
 * See the file Licence.txt for more details
 *
 */

#include "sysinc.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

static char rcsid[] UNUSED = "$Id$";

/* The following functions take a span of pixels and pack them into an
 * appropriate texture format. The input data is always of the form:
 * GLubyte r;
 * GLubyte g;
 * GLubyte b;
 * GLubyte a;
 * with a stride of stride.
 * The function will select the appropriate component(s) and write them into
 * it's destination tightly packed.
 */

#define R_COMPONENT	0
#define G_COMPONENT 1
#define B_COMPONENT 2
#define A_COMPONENT 3

void write_a8(GLubyte *from, GLubyte *to, GLsizei num, GLsizei stride)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		*to++ = from[A_COMPONENT];
		from += stride;
	}
}

void write_l8(GLubyte *from, GLubyte *to, GLsizei num, GLsizei stride)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		*to++ = from[R_COMPONENT];
		from += stride;
	}
}

void write_l8a8(GLubyte *from, GLubyte *to, GLsizei num, GLsizei stride)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		*to++ = from[R_COMPONENT];
		*to++ = from[A_COMPONENT];
		from += stride;
	}
}

void write_i8(GLubyte *from, GLubyte *to, GLsizei num, GLsizei stride)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		*to++ = from[R_COMPONENT];
		from += stride;
	}
}

void write_rgb(GLubyte *from, GLubyte *to, GLsizei num, GLsizei stride)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		*to++ = from[R_COMPONENT];
		*to++ = from[G_COMPONENT];
		*to++ = from[B_COMPONENT];
		from += stride;
	}
}

void write_rgba(GLubyte *from, GLubyte *to, GLsizei num, GLsizei stride)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		*to++ = from[A_COMPONENT];
		*to++ = from[R_COMPONENT];
		*to++ = from[G_COMPONENT];
		*to++ = from[B_COMPONENT];
		from += stride;
	}
}

void write_rgb5(GLubyte *from, GLubyte *to, GLsizei num, GLsizei stride)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		* (uint16 *)to = ((from[R_COMPONENT] & 0xf8) << 8)
					   | ((from[G_COMPONENT] & 0xfc) << 3)
					   | ((from[B_COMPONENT] & 0xf8) >> 3);
		to += 2;
		from += stride;
	}
}

void write_rgba4(GLubyte *from, GLubyte *to, GLsizei num, GLsizei stride)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		* (uint16 *)to = ((from[A_COMPONENT] & 0xf0) << 8)
					   | ((from[R_COMPONENT] & 0xf0) << 4)
					   | ((from[G_COMPONENT] & 0xf0)     )
					   | ((from[B_COMPONENT] & 0xf0) >> 4);
		to += 2;
		from += stride;
	}
}

void write_rgb5_a1(GLubyte *from, GLubyte *to, GLsizei num, GLsizei stride)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		* (uint16 *)to = ((from[A_COMPONENT] & 0x80) << 8)
					   | ((from[R_COMPONENT] & 0xf8) << 7)
					   | ((from[G_COMPONENT] & 0xf8) << 2)
					   | ((from[B_COMPONENT] & 0xf8) >> 3);
		to += 2;
		from += stride;
	}
}

/* Internalformat to Warp3D format mapping table.
 * For each possible internal format, this table lists an appropriate Warp3D
 * format.
 */
struct InternalToW3D i2w3d[] =
{
	{GL_ALPHA,				W3D_A8,				1,	write_a8},
	{GL_LUMINANCE,			W3D_L8,				1,	write_l8},
	{1,						W3D_L8,				1,	write_l8},
	{GL_LUMINANCE_ALPHA,	W3D_L8A8,			2,	write_l8a8},
	{2,						W3D_L8A8,			2,	write_l8a8},
	{GL_INTENSITY,			W3D_I8,				1,	write_i8},
	{GL_RGB,				W3D_R8G8B8,			3,	write_rgb},
	{3,						W3D_R8G8B8,			3,	write_rgb},
	{GL_RGBA,				W3D_A8R8G8B8,		4,	write_rgba},
	{4,						W3D_A8R8G8B8,		4,	write_rgba},
	{GL_RGB5,				W3D_R5G6B5,			2,	write_rgb5},
	{GL_RGB4,				W3D_R5G6B5,			2,	write_rgb5},
	{GL_RGBA4,				W3D_A4R4G4B4,		2,	write_rgba4},
	{GL_RGB5_A1,			W3D_A1R5G5B5,		2,	write_rgb5_a1},
	{GL_RGB8,				W3D_R8G8B8,			3,	write_rgb},
	{GL_RGBA8,				W3D_A8R8G8B8,		4,	write_rgba},
	{GL_NONE,				0,					0}
};

/* Return index of appropriate entry */
int32 SelectInternalFormat(GLcontext context, GLenum internalformat)
{
	int32 i;

	if (context->ConvertTo16Bit == GL_TRUE)
	{
		if (internalformat == GL_RGB || internalformat == 3)
			internalformat = GL_RGB5;
		else if (internalformat == GL_RGBA || internalformat == 4)
			internalformat = GL_RGBA4;
	}

	for (i = 0; i2w3d[i].internalformat != GL_NONE; i++)
		if (i2w3d[i].internalformat == internalformat)
			return i;

	return -1;
}

/* Format/Type unpack table.
 * This table has entries for format and type, and is used to select an unpack
 * function depending on format and type. Note that not all combinations are
 * supported
 */

#define REDBYTE(rgb)        (((UWORD)rgb & 0xF800) >> 8)
#define GREENBYTE(rgb)      (((UWORD)rgb & 0x07E0) >> 3)
#define BLUEBYTE(rgb)       (((UWORD)rgb & 0x001F) << 3)
#define REDBYTEA(rgba)      (((UWORD)rgba & 0x0f00) >> 4)
#define GREENBYTEA(rgba)    (((UWORD)rgba & 0x00f0))
#define BLUEBYTEA(rgba)     (((UWORD)rgba & 0x000f) << 4)
#define ALPHABYTEA(rgba)    (((UWORD)rgba & 0xf000) >> 8)

#define REDBYTE5551(rgba)   (((UWORD)rgba & 0x7C00) >> 7)
#define GREENBYTE5551(rgba) (((UWORD)rgba & 0x03e0) >> 2)
#define BLUEBYTE5551(rgba)  (((UWORD)rgba & 0x001F) << 3)
#define ALPHABYTE5551(rgba)	(((UWORD)rgba & 0x8000) ? 0xff : 0x00)

void unpack_ci_b(void *_context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;
	GLcontext context = (GLcontext)_context;
	uint8 *pal = (uint8 *)context->PaletteData;

	if (context->PaletteFormat == GL_RGB)
	{
		// Palette is RGB
		for (i = 0; i < num; i++)
		{
			uint32 idx = *from++;
			to[R_COMPONENT] = pal[idx*3];
			to[G_COMPONENT] = pal[idx*3+1];
			to[B_COMPONENT] = pal[idx*3+2];
			to[A_COMPONENT] = 0xff;

			to += 4;
		}
	}
	else
	{
		// Palette is RGBA
		for (i = 0; i < num; i++)
		{
			uint32 idx = *from++;
			to[R_COMPONENT] = pal[idx*4];
			to[G_COMPONENT] = pal[idx*4+1];
			to[B_COMPONENT] = pal[idx*4+2];
			to[A_COMPONENT] = pal[idx*4+2];

			to += 4;
		}
	}
}

void unpack_a_b(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		to[A_COMPONENT] = *from++;
		to[R_COMPONENT] = 0;		// FIXME: Really ?
		to[G_COMPONENT] = 0;		// FIXME: Really ?
		to[B_COMPONENT] = 0;		// FIXME: Really ?

		to += 4;
	}
}

void unpack_rgb_b(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		to[R_COMPONENT] = *from++;
		to[G_COMPONENT] = *from++;
		to[B_COMPONENT] = *from++;
		to[A_COMPONENT] = 0xff;

		to += 4;
	}
}

void unpack_rgb_us565(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;
	GLushort *src = (GLushort *)from;

	for (i = 0; i < num; i++)
	{
		GLushort rgb = *src++;
		to[R_COMPONENT] = REDBYTE(rgb);
		to[G_COMPONENT] = GREENBYTE(rgb);
		to[B_COMPONENT] = BLUEBYTE(rgb);
		to[A_COMPONENT] = 0xff;

		to += 4;
	}
}

#if 1
void unpack_rgba_b(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;
	GLuint *src = (GLuint *)from;
	GLuint *dst = (GLuint *)to;

	for (i = 0; i < num; i++)
		*dst++ = *src++;
}
#else
void unpack_rgba_b(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		to[A_COMPONENT] = *from++;
		to[R_COMPONENT] = *from++;
		to[G_COMPONENT] = *from++;
		to[B_COMPONENT] = *from++;

		to += 4;
	}
}
#endif


void unpack_rgba_us4444(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;
	GLushort *src = (GLushort *)from;

	for (i = 0; i < num; i++)
	{
		GLushort rgb = *src++;
		to[R_COMPONENT] = REDBYTEA(rgb);
		to[G_COMPONENT] = GREENBYTEA(rgb);
		to[B_COMPONENT] = BLUEBYTEA(rgb);
		to[A_COMPONENT] = ALPHABYTEA(rgb);

		to += 4;
	}
}

void unpack_bgr_b(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		to[B_COMPONENT] = *from++;
		to[G_COMPONENT] = *from++;
		to[R_COMPONENT] = *from++;
		to[A_COMPONENT] = 0xff;

		to += 4;
	}
}

void unpack_bgra_b(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		to[B_COMPONENT] = *from++;
		to[G_COMPONENT] = *from++;
		to[R_COMPONENT] = *from++;
		to[A_COMPONENT] = *from++;

		to += 4;
	}
}

void unpack_bgra_1555rev(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;
	GLushort *src = (GLushort *)from;

	for (i = 0; i < num; i++)
	{
		GLushort rgb = *src++;

		to[R_COMPONENT] = (GLubyte)REDBYTE5551(rgb);
		to[G_COMPONENT] = (GLubyte)GREENBYTE5551(rgb);
		to[B_COMPONENT] = (GLubyte)BLUEBYTE5551(rgb);
		to[A_COMPONENT] = (GLubyte)ALPHABYTE5551(rgb);

		to += 4;
	}
}

void unpack_l_b(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		to[R_COMPONENT] = *from++;
		to[A_COMPONENT] = 0;		// FIXME: Really ?
		to[G_COMPONENT] = 0;		// FIXME: Really ?
		to[B_COMPONENT] = 0;		// FIXME: Really ?

		to += 4;
	}
}

void unpack_la_b(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	GLsizei i;

	for (i = 0; i < num; i++)
	{
		to[R_COMPONENT] = *from++;
		to[A_COMPONENT] = *from++;
		to[G_COMPONENT] = 0;		// FIXME: Really ?
		to[B_COMPONENT] = 0;		// FIXME: Really ?

		to += 4;
	}
}

void unpack_bitmap(void *context, GLubyte *from, GLubyte *to, GLsizei num)
{
	/* FIXME: Honor Unpack skip pixels */
	GLsizei i, j;
	unsigned int mask;
	GLuint *dst = (GLuint *)to;

	for (i = 0; i < num/8; i++)
	{
		mask = 0x80;
		GLubyte cur = *from++;

		for (j = 0; j < 8; j++)
		{
			if (cur & mask)
				*dst++ = 0xffffffff;
			else
				*dst++ = 0;

			mask >>= 1;
		}
	}
}

struct FormatTypeToUnpack ft2u[] =
{
	{GL_COLOR_INDEX,		GL_UNSIGNED_BYTE,			unpack_ci_b,		1},
	{GL_ALPHA,				GL_UNSIGNED_BYTE, 			unpack_a_b,			1},
/*	{GL_ALPHA,				GL_UNSIGNED_SHORT_4_4_4_4,	0,	1}, */
	{GL_RGB,				GL_UNSIGNED_BYTE,			unpack_rgb_b,		3},
	{GL_RGB,				GL_UNSIGNED_SHORT_5_6_5,	unpack_rgb_us565,	2},
	{GL_RGBA,				GL_UNSIGNED_BYTE,			unpack_rgba_b,		4},
	{GL_RGBA,				GL_UNSIGNED_SHORT_4_4_4_4,	unpack_rgba_us4444,	2},
	{GL_BGR,				GL_UNSIGNED_BYTE,			unpack_bgr_b,		3},
/*	{GL_BGR,				GL_UNSIGNED_SHORT_5_6_5,	0,	2}, */
	{GL_BGRA,				GL_UNSIGNED_BYTE,			unpack_bgra_b,		4},
	{GL_BGRA,				GL_UNSIGNED_SHORT_1_5_5_5_REV, unpack_bgra_1555rev, 2},
/*	{GL_BGRA,				GL_UNSIGNED_SHORT_4_4_4_4,	0,	2}, */
	{GL_LUMINANCE,			GL_UNSIGNED_BYTE,			unpack_l_b,			1},
	{GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE,			unpack_la_b,		2},
/*	{GL_LUMINANCE_ALPHA,	GL_UNSIGNED_SHORT_4_4_4_4,	0,  2}, */
	{GL_NONE,				GL_NONE,					0,  0}
};


int32 SelectUnpacker(GLcontext context, GLenum format, GLenum type)
{
	int32 i;

	for (i = 0; ft2u[i].format != GL_NONE; i++)
		if (format == ft2u[i].format && type == ft2u[i].type)
			return i;

	return -1;
}


uint32 MGLConvert(GLcontext context, GLenum internalformat, GLuint width,
	GLuint height, GLenum format, GLenum type, GLubyte *source, GLubyte *dest)
{
	int32 internal;
	int32 unpack;
	int h;
	int srcWidth;
	int dstWidth;

	internal = SelectInternalFormat(context, internalformat);
	unpack = SelectUnpacker(context, format, type);

	if (unpack == -1)
	{
		dprintf("Unknown format/type\n");
		return 0;
	}
	
	if (internal == -1)
	{
		dprintf("Unknown internal format\n");
		return 0;
	}

	if (context->ConvertBufferSize < 4*width || !context->ConvertBuffer)
	{
		dprintf("ConvertBuffer too small (%ld), reallocating (%ld)\n",
			context->ConvertBufferSize, 4*width);
		if (context->ConvertBuffer)
			IExec->FreeVec(context->ConvertBuffer);

		context->ConvertBuffer = IExec->AllocVec(4*width, MEMF_ANY);
		context->ConvertBufferSize = 4*width;
	}

	/* Select correct source stride, taking care of the unpack row length */
	srcWidth = width * ft2u[unpack].stride;
	if (context->pixel_store.UnpackRowLength > 0)
	    srcWidth = context->pixel_store.UnpackRowLength * ft2u[unpack].stride;

	/* Destination stride is the width time the texel size */
	dstWidth = i2w3d[internal].w3dBpp * width;

	/* Compute the address of the first pixel (taking skip pixels/rows into
	 * account)
	 */
	source += context->pixel_store.UnpackSkipRows * srcWidth
		   +  context->pixel_store.UnpackSkipPixels * ft2u[unpack].stride;

	/* Now convert */
//	dprintf("Converting from source = %p via convertBuffer = %p to convertBuffer = %p\n",
//		source, context->ConvertBuffer, dest);
//	dprintf("Width = %ld, Height = %ld\n", width, height);
		
	for (h = 0; h < height; h++)
	{
		ft2u[unpack].unpack((GLcontext)context, source, context->ConvertBuffer, width);
		i2w3d[internal].write(context->ConvertBuffer, dest, width, 4);

		source += srcWidth;
		dest += dstWidth;
	}

//	dprintf("Wrote %ld bytes of data\n", height*dstWidth);
	
	return i2w3d[internal].w3dFormat;
}


void MGLUpdate(GLcontext context, GLint xoffset, GLint yoffset, GLsizei width,
	GLsizei height, GLubyte *source, GLubyte *dest, GLsizei destWidth,
	GLint internal, GLint unpacker)
{
	int h;
	int srcWidth;
	int dstWidth;

	if (unpacker == -1 || internal == -1 || source == 0 || dest == 0)
		return;

	if (context->ConvertBufferSize < 4*width || !context->ConvertBuffer)
	{
		if (context->ConvertBuffer)
			IExec->FreeVec(context->ConvertBuffer);

		context->ConvertBuffer = IExec->AllocVec(4*width, MEMF_ANY);
		context->ConvertBufferSize = 4*width;
	}

	/* Select correct source stride, taking care of the unpack row length */
	srcWidth = width * ft2u[unpacker].stride;
	if (context->pixel_store.UnpackRowLength > 0)
	    srcWidth = context->pixel_store.UnpackRowLength * ft2u[unpacker].stride;

	/* Destination stride is the width time the texel size */
	dstWidth = i2w3d[internal].w3dBpp * destWidth;

	/* Compute the address of the first pixel (taking skip pixels/rows into
	 * account).
	 * FIXME: Is this really true here ?
	 */
//	source += context->CurUnpackSkipRows * srcWidth
//		   +  context->CurUnpackSkipPixels * ft2u[unpack].stride;

	/* Compute destination pixels address */
	dest += dstWidth * yoffset
		 +  i2w3d[internal].w3dBpp * xoffset;

	/* Now convert */
	for (h = 0; h < height; h++)
	{
		ft2u[unpacker].unpack((GLcontext)context, source, context->ConvertBuffer, width);
		i2w3d[internal].write(context->ConvertBuffer, dest, width, 4);

		source += srcWidth;
		dest += dstWidth;
	}
}
