/***
*
*	Copyright (c) 1998, Valve LLC. All rights reserved.
*	
*	This product contains software technology licensed from Id 
*	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
*	All Rights Reserved.
*
****/

#include "qlumpy.h"
#include "math.h"

#pragma warning (disable : 4244)

typedef struct
{
	short	ofs, length;
} row_t;

typedef struct
{
	int		width, height;
	int		widthbits, heightbits;
	unsigned char	data[4];
} qtex_t;

typedef struct
{
	int			width, height;
	byte		data[4];			// variably sized
} qpic_t;


// Font stuff

#define NUM_GLYPHS 256
const unsigned kFontMarker = 254;

typedef struct
{
	short startoffset;
	short charwidth;
} charinfo;

typedef struct
{
	int 		width, height;
	int			rowcount;
	int			rowheight;
	charinfo	fontinfo[ NUM_GLYPHS ];
	byte 		data[4];
} qfont_t;


extern qboolean		fTransparent255;


#define SCRN(x,y)       (*(byteimage+(y)*byteimagewidth+x))

void GrabPalette16( void );

extern qboolean do16bit;

/*
==============
GrabRaw

filename RAW x y width height
==============
*/
void GrabRaw (void)
{
	int             x,y,xl,yl,xh,yh,w,h;
	byte            *screen_p;
	int             linedelta;

	GetToken (false);
	xl = atoi (token);
	GetToken (false);
	yl = atoi (token);
	GetToken (false);
	w = atoi (token);
	GetToken (false);
	h = atoi (token);

	if (xl == -1)
	{
		xl = yl = 0;
		w = byteimagewidth;
		h = byteimageheight;
	}

	xh = xl+w;
	yh = yl+h;

	screen_p = byteimage + yl*byteimagewidth + xl;
	linedelta = byteimagewidth - w;

	for (y=yl ; y<yh ; y++)
	{
		for (x=xl ; x<xh ; x++)
		{
			*lump_p++ = *screen_p;
			*screen_p++ = 0;
		}
		screen_p += linedelta;
	}
}



/*
==============
GrabPalette

filename PALETTE [startcolor endcolor]
==============
*/
void GrabPalette (void)
{
	int start, end, length;

	if (TokenAvailable())
	{
		GetToken (false);
		start = atoi (token);
		GetToken (false);
		end = atoi (token);
	}
	else
	{
		start = 0;
		end = 255;
	}

	length = 3*(end-start+1);
	memcpy (lump_p, lbmpalette+start*3, length);
	lump_p += length;
}


/*
==============
GrabPic

filename qpic x y width height
==============
*/
void GrabPic (void)
{
	int             x,y,xl,yl,xh,yh;
	int             width;
	qpic_t 			*header;

	GetToken (false);
	xl = atoi (token);
	GetToken (false);
	yl = atoi (token);
	GetToken (false);
	xh = xl+atoi (token);
	GetToken (false);
	yh = yl+atoi (token);

	if (xl == -1)
	{
		xl = yl = 0;
		xh = byteimagewidth;
		yh = byteimageheight;
	}

	if (xh<xl || yh<yl || xl < 0 || yl<0) // || xh>319 || yh>239)
		Error ("GrabPic: Bad size: %i, %i, %i, %i",xl,yl,xh,yh);

	//
	// fill in header
	//
	header = (qpic_t *)lump_p;
	width = xh-xl;
	header->width = LittleLong(width);
	header->height = LittleLong(yh-yl);

	//
	// start grabbing posts
	//
	lump_p = (byte *)header->data;

	for (y=yl ; y< yh ; y++)
		for (x=xl ; x<xh ; x++)
			*lump_p++ = SCRN(x,y);

	// New for 16bpp display
	if( do16bit )
		GrabPalette16();
}

/*
=============================================================================

COLORMAP GRABBING

=============================================================================
*/

/*
===============
BestColor
===============
*/
byte BestColor (int r, int g, int b, int start, int stop)
{
	int	i;
	int	dr, dg, db;
	int	bestdistortion, distortion;
	int	bestcolor;
	byte	*pal;

//
// let any color go to 0 as a last resort
//
	bestdistortion = ( (int)r*r + (int)g*g + (int)b*b )*2;
	bestcolor = 0;

	pal = lbmpalette + start*3;
	for (i=start ; i<= stop ; i++)
	{
		dr = r - (int)pal[0];
		dg = g - (int)pal[1];
		db = b - (int)pal[2];
		pal += 3;
		distortion = dr*dr + dg*dg + db*db;
		if (distortion < bestdistortion)
		{
			if (!distortion)
				return i;		// perfect match

			bestdistortion = distortion;
			bestcolor = i;
		}
	}

	return bestcolor;
}


/*
==============
GrabColormap

filename COLORMAP levels fullbrights
the first map is an identiy 0-255
the final map is all black except for the fullbrights
the remaining maps are evenly spread
fullbright colors start at the top of the palette.
==============
*/
void GrabColormap (void)
{
	int		levels, brights;
	int		l, c;
	float	frac, red, green, blue;
		
	GetToken (false);
	levels = atoi (token);
	GetToken (false);
	brights = atoi (token);

// identity lump
	for (l=0 ; l<256 ; l++)
		*lump_p++ = l;

// shaded levels
	for (l=1;l<levels;l++)
	{
		frac = 1.0 - (float)l/(levels-1);
		for (c=0 ; c<256-brights ; c++)
		{
			red = lbmpalette[c*3];
			green = lbmpalette[c*3+1];
			blue = lbmpalette[c*3+2];

			red = (int)(red*frac+0.5);
			green = (int)(green*frac+0.5);
			blue = (int)(blue*frac+0.5);
			
//
// note: 254 instead of 255 because 255 is the transparent color, and we
// don't want anything remapping to that
//
			*lump_p++ = BestColor(red,green,blue, 0, 254);
		}
		for ( ; c<256 ; c++)
			*lump_p++ = c;
	}
	
	*lump_p++ = brights;
}

/*
==============
GrabColormap2

experimental -- not used by quake

filename COLORMAP2 range levels fullbrights
fullbright colors start at the top of the palette.
Range can be greater than 1 to allow overbright color tables.

the first map is all 0
the last (levels-1) map is at range
==============
*/
void GrabColormap2 (void)
{
	int		levels, brights;
	int		l, c;
	float	frac, red, green, blue;
	float	range;
	
	GetToken (false);
	range = atof (token);
	GetToken (false);
	levels = atoi (token);
	GetToken (false);
	brights = atoi (token);

// shaded levels
	for (l=0;l<levels;l++)
	{
		frac = range - range*(float)l/(levels-1);
		for (c=0 ; c<256-brights ; c++)
		{
			red = lbmpalette[c*3];
			green = lbmpalette[c*3+1];
			blue = lbmpalette[c*3+2];

			red = (int)(red*frac+0.5);
			green = (int)(green*frac+0.5);
			blue = (int)(blue*frac+0.5);
			
//
// note: 254 instead of 255 because 255 is the transparent color, and we
// don't want anything remapping to that
//
			*lump_p++ = BestColor(red,green,blue, 0, 254);
		}

		// fullbrights allways stay the same
		for ( ; c<256 ; c++)
			*lump_p++ = c;
	}
	
	*lump_p++ = brights;
}

/*
=============================================================================

MIPTEX GRABBING

=============================================================================
*/

typedef struct
{
	char		name[16];
	unsigned	width, height;
	unsigned	offsets[4];		// four mip maps stored
} miptex_t;

byte	pixdata[256];

float 	linearpalette[256][3];
float 	d_red, d_green, d_blue;
int		colors_used;
int		color_used[256];
float	maxdistortion;

byte AddColor( float r, float g, float b )
{
	int i;
	for (i = 0; i < 255; i++)
	{
		if (!color_used[i])
		{
			linearpalette[i][0] = r;
			linearpalette[i][1] = g;
			linearpalette[i][2] = b;
			if (r < 0) r = 0.0;
			if (r > 1.0) r = 1.0;
			lbmpalette[i*3+0] = pow( r, 1.0 / 2.2) * 255;
			if (g < 0) g = 0.0;
			if (g > 1.0) g = 1.0;
			lbmpalette[i*3+1] = pow( g, 1.0 / 2.2) * 255;
			if (b < 0) b = 0.0;
			if (b > 1.0) b = 1.0;
			lbmpalette[i*3+2] = pow( b, 1.0 / 2.2) * 255;
			color_used[i] = 1;
			colors_used++;
			return i;
		}
	}
	return 0;
}

/*
=============
AveragePixels
=============
*/
byte AveragePixels (int count)
{
	float   r,g,b;
	int		i;
	int		vis;
	int		pix;
	float 	dr, dg, db;
	float 	bestdistortion, distortion;
	int		bestcolor;
	byte	*pal;
	
	vis = 0;
	r = g = b = 0;

	for (i=0 ; i<count ; i++)
	{
		pix = pixdata[i];
		r += linearpalette[pix][0];
		g += linearpalette[pix][1];
		b += linearpalette[pix][2];
	}

	r /= count;
	g /= count;
	b /= count;

	r += d_red;
	g += d_green;
	b += d_blue;
	
//
// find the best color
//
//	bestdistortion = r*r + g*g + b*b;
	bestdistortion = 3.0;
	bestcolor = -1;

	for ( i=0; i<255; i++)
	{
		if (color_used[i])
		{
			pix = i;	//pixdata[i];

			dr = r - linearpalette[i][0];
			dg = g - linearpalette[i][1];
			db = b - linearpalette[i][2];

			distortion = dr*dr + dg*dg + db*db;
			if (distortion < bestdistortion)
			{
				if (!distortion)
				{
					d_red = d_green = d_blue = 0;	// no distortion yet
					return pix;		// perfect match
				}

				bestdistortion = distortion;
				bestcolor = pix;
			}
		}
	}


	if (bestdistortion > 0.001 && colors_used < 255)
	{
		// printf("%f %f %f\n", r, g, b );
		bestcolor = AddColor( r, g, b );
		d_red = d_green = d_blue = 0;
		bestdistortion = 0;
	}
	else
	{
		// error diffusion
		d_red = r - linearpalette[bestcolor][0];
		d_green = g - linearpalette[bestcolor][1];
		d_blue = b - linearpalette[bestcolor][2];
	}

	if (bestdistortion > maxdistortion)
		maxdistortion = bestdistortion;
	return bestcolor;
}


/*
==============
GrabMip

filename MIP x y width height
must be multiples of sixteen
==============
*/
void GrabMip (void)
{
	int             i,j,x,y,xl,yl,xh,yh,w,h;
	byte            *screen_p, *source, testpixel;
	int             linedelta;
	miptex_t		*qtex;
	int				miplevel, mipstep;
	int				xx, yy, pix;
	int				count;
	
	GetToken (false);
	xl = atoi (token);
	GetToken (false);
	yl = atoi (token);
	GetToken (false);
	w = atoi (token);
	GetToken (false);
	h = atoi (token);

	if (xl == -1)
	{
		xl = yl = 0;
		w = byteimagewidth;
		h = byteimageheight;
	}

	if ( (w & 15) || (h & 15) )
		Error ("line %i: miptex sizes must be multiples of 16", scriptline);

	xh = xl+w;
	yh = yl+h;

	qtex = (miptex_t *)lump_p;
	qtex->width = LittleLong(w);
	qtex->height = LittleLong(h);
	strcpy (qtex->name, lumpname); 
	
	lump_p = (byte *)&qtex->offsets[4];
	
	screen_p = byteimage + yl*byteimagewidth + xl;
	linedelta = byteimagewidth - w;

	source = lump_p;
	qtex->offsets[0] = LittleLong(lump_p - (byte *)qtex);

	for (y=yl ; y<yh ; y++)
	{
		for (x=xl ; x<xh ; x++)
		{
			pix = *screen_p;
			*screen_p++ = 0;
//			if (pix == 255)
//				pix = 0;
			*lump_p++ = pix;
		}
		screen_p += linedelta;
	}

	// calculate gamma corrected linear palette
	for (i = 0; i < 256; i++)
	{
		for (j = 0; j < 3; j++)
		{
			float f;
			f = lbmpalette[i*3+j] / 255.0;
			linearpalette[i][j] = pow(f, 2.2 ); // assume textures are done at 2.2, we want to remap them at 1.0
		}
	}

	maxdistortion = 0;
	if (!fTransparent255)
	{
		// figure out what palette entries are actually used
		colors_used = 0;
		for (i = 0; i < 256; i++)
			color_used[i] = 0;

		for (x = 0; x < w; x++)
		{
			for (y = 0; y < h; y++)
			{
				if (!color_used[source[ y*w + x]])
				{
					color_used[source[ y*w + x]] = 1;
					colors_used++;
				}
			}
		}
	}
	else
	{
		// assume palette full if it's a transparent texture
		colors_used = 256;
		for (i = 0; i < 256; i++)
			color_used[i] = 1;
	}
	// printf("colors_used %d : ", colors_used );


	//
	// subsample for greater mip levels
	//

	for (miplevel = 1 ; miplevel<4 ; miplevel++)
	{
		int pixTest;
		d_red = d_green = d_blue = 0;	// no distortion yet
		qtex->offsets[miplevel] = LittleLong(lump_p - (byte *)qtex);
		
		mipstep = 1<<miplevel;
		pixTest = (int)( (float)(mipstep * mipstep) * 0.4 );	// 40% of pixels

		for (y=0 ; y<h ; y+=mipstep)
		{
			for (x = 0 ; x<w ; x+= mipstep)
			{
				count = 0;
				for (yy=0 ; yy<mipstep ; yy++)
					for (xx=0 ; xx<mipstep ; xx++)
					{
						testpixel = source[ (y+yy)*w + x + xx ];
						
						// If 255 is not transparent, or this isn't a transparent pixel, add it in to the image filter
						if ( !fTransparent255 || testpixel != 255 ) {
							pixdata[count] = testpixel;
							count++;
						}
					}
				if ( count <= pixTest )	// Solid pixels account for < 40% of this pixel, make it transparent
				{
					*lump_p++ = 255;
				}
				else
				{
					*lump_p++ = AveragePixels (count);
				}
			}	
		}
	}

	// printf(" %d %f\n", colors_used, maxdistortion );

	if( do16bit )
		GrabPalette16();
}


/*
=============================================================================

PALETTE GRABBING

=============================================================================
*/


void GrabPalette16( void )
{
	int i;

	// Write out palette in 16bit mode
	*(unsigned short *) lump_p = 256;	// palette size
	lump_p += sizeof(short);

	memcpy( lump_p, lbmpalette, 768 );
	lump_p += 768;
}



/*
=============================================================================

FONT GRABBING

=============================================================================
*/


/*
==============
GrabFont

font x y width height startglyph
==============
*/
void GrabFont( void )
{
	int		x, y, y2, xl, x2, yl, xh, yh, i, j;
	int		index, offset;
	int		width;
	int		iCurX;	// current x in destination
	int		iMaxX;  // max x in destination
	
	byte	*pbuf, *pCur;
	qfont_t 			*header;


	iMaxX = 255;
	iCurX = 0;

	// Set up header
	header = (qfont_t *)lump_p;
	memset( header, 0, sizeof(qfont_t) );

	GetToken( false );
	header->width = header->rowheight = atoi( token );  //mwh why does width equal rowheight? 
	header->height = 1;
	lump_p = (byte *)header->data;
	pCur = (byte *)lump_p;
	memset( lump_p, 0xFF, 256 * 160);

	GetToken( false );
	index = atoi( token );

	while( index != -1 )
	{
		// Get/Process source bitmap coordinates
		GetToken (false);
		xl = atoi (token);
		GetToken (false);
		yl = atoi (token);
		GetToken (false);
		xh = xl-1+atoi (token);
		GetToken (false);
		yh = atoi (token) - 1;
		if (xl == -1)
		{
			xl = yl = 0;
			xh = byteimagewidth;
			yh = byteimageheight;
		}

		if( xh<xl || yh<yl || xl < 0 || yl<0 )
			Error( "GrabFont line %1: Bad size: %i, %i, %i, %i", scriptline, xl, yl, xh, yh );

		//
		// Fill in font information
		// Create a bitmap that is up to 256 wide and as tall as we need to accomadate the font.
		// We limit the bitmap to 256 because some 3d boards have problems with textures bigger 
		// than that. 
		//
		for( y=yl; y<yh; y+=header->rowheight+1 )
		{
			// Make sure we're at a marker
			if( y != yl )
			{
				for( y2=y-header->rowheight; y2<yh; y2++ )
					if( kFontMarker == (unsigned) SCRN(xl,y2) )
						break;

				if( y2 == yh )
					break;
				else if( y2 != y )
					Error( "GrabFont line %d: rowheight doesn't seem to match bitmap (%d, %d)\n", scriptline, y, y2 );
			}

			for( x=xl; x<xh; )
			{
				// find next marker
				for( x2=x+1; x2<xh; x2++ )
					if( kFontMarker == (unsigned) SCRN(x2,y) )
						break;

				// check for end of row
				if( x2 == xh )
					break;

				// Set up glyph information
				if( index >= NUM_GLYPHS )
				{
					printf( "GrabFont: Glyph out of range\n" );
					goto getout;
				}
		
				// Fill in glyph info
				header->fontinfo[ index ].charwidth = x2 - x - 1;
				
				// update header				

				// output glyph data
				iCurX += header->fontinfo[index].charwidth;
				
				// Will this glyph fit on this row?
				if (iCurX >= iMaxX)
				{	
					// Nope -- move to next row
					pCur = (byte *)lump_p + 256 * header->rowheight * header->height;
					header->height++;
					iCurX = header->fontinfo[index].charwidth;
				} 
			
				// copy over the glyph bytes
				pbuf = pCur;
				header->fontinfo[ index ].startoffset = pCur - (byte *) header->data;
				

				for(j = 1; j <= header->rowheight; j++)
				{
					byte *psrc = byteimage + (y + j) * byteimagewidth + (x + 1);

					for(i = x + 1; i < x2; i++)
						*pbuf++ = *psrc++;

					pbuf = pCur + j * 256;
				}
				
				// move the lump pointer to point at the next possible glyph
				pCur += header->fontinfo[index].charwidth;
				x = x2;
				index++;
			}
		}

		// Get next ASCII index
getout:
		GetToken (false);
		index = atoi (token);
	}

	// advance the lump pointer so that the last row is saved.
	lump_p += (256 * header->rowheight) * header->height;
	
	// JAY: Round up to the next power of 2 for GL
	offset = header->height * header->rowheight;

	y = (offset>128)?256:(offset>64)?128:(offset>32)?64:(offset>16)?32:16;
	if ( offset != y )
	{
		printf("Rounding font from 256x%d to 256x%d\n", offset, y );
		lump_p += (256 * (y - offset));
	}
	header->rowcount = header->height;
	header->height = y;

	if( do16bit )
		GrabPalette16();
}

