/*

	Copyright (C) 2004 Michael Liebscher	
	Copyright (C) 1997-2001 Id Software, Inc.
	Copyright (C) 1995 Spencer Kimball and Peter Mattis.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/*
 *	texture_manager.c:	Texture manager.
 *	
 *	Author:	Michael Liebscher <johnnycanuck@users.sourceforge.net>	 
 *	Date:	2004  
 *
 *	Acknowledgement:
 *	Portion of this code was derived from
 *	The GIMP (an image manipulation program) and was originally
 *	written by Spencer Kimball and Peter Mattis.
 *
 *	Portion of this code was derived from Quake II, and was originally
 *	written by Id Software, Inc.
 *
 */

#include "../wolfiphone.h"

PRIVATE texture_t	ttextures[ MAX_TEXTURES ];
PRIVATE int			numttextures;

PRIVATE texture_t	*r_notexture;		// use for bad textures

cvar_t	*gl_round_down;

int registration_sequence;
W32 texture_registration_sequence;

extern int currentTextures[ 4 ];
extern int currenttmu;
extern int glMaxTexSize;

PRIVATE INLINECALL GLenum WrapToGL( TWrapMode mode )
{
	if( mode == Repeat )
	{
		return GL_REPEAT;
	}
	else
	{
		return GL_CLAMP_TO_EDGE;
	}
}

PRIVATE INLINECALL GLenum MagFilterToGL( TMagFilter MagFilter )
{	
	switch( MagFilter )
	{
		case Nearest: 
			return GL_NEAREST;
			
		case Linear: 
			return GL_LINEAR;
			
		default: 
			break;
	}
	
	return GL_LINEAR;
}



PRIVATE INLINECALL GLenum MinFilterToGL( _boolean MipMap, TMinFilter MinFilter )
{
	if( MipMap )
	{
		switch( MinFilter )
		{
			case NearestMipMapOff: 
				return GL_NEAREST;
				
			case NearestMipMapNearest: 
				return GL_NEAREST_MIPMAP_NEAREST;
				
			case NearestMipMapLinear: 
				return GL_NEAREST_MIPMAP_LINEAR;
				
			case LinearMipMapOff: 
				return GL_LINEAR;
				
			case LinearMipMapNearest: 
				return GL_LINEAR_MIPMAP_NEAREST;
				
			case LinearMipMapLinear: 
				return GL_LINEAR_MIPMAP_LINEAR;
				
			default: 
				break;
		}
	}
	else
	{
		switch( MinFilter )
		{
			case NearestMipMapOff:
			case NearestMipMapNearest:
			case NearestMipMapLinear:
				return GL_NEAREST;
				
			case LinearMipMapOff:
			case LinearMipMapNearest:
			case LinearMipMapLinear:
				return GL_LINEAR;
				
			default: 
				break;
		}
	}
	
	return GL_LINEAR;
}


/*
-----------------------------------------------------------------------------
 Function: TM_TextureList_f	-Console function to list loaded textures.
 
 Parameters: Nothing.
 
 Returns: Nothing.
 
 Notes: 
-----------------------------------------------------------------------------
*/
PUBLIC void TM_TextureList_f( void )
{
	int		i;
	texture_t	*image;
	int		texels;
	const char *palstrings[ 2 ] =
	{
		"RGB",
		"PAL"
	};

	Com_Printf( "------------------\n" );
	texels = 0;

	for( i = 0, image = ttextures ; i < numttextures ; ++i, ++image )
	{
		if( image->texnum <= 0 )
			continue;
		
		texels += image->upload_width * image->upload_height;
		
		switch( image->type )
		{
		case TT_Sprite:
			Com_Printf( "S" );
			break;
			
		case TT_Wall:
			Com_Printf( "W" );
			break;
			
		case TT_Pic:
			Com_Printf( "P" );
			break;
			
		default:
			Com_Printf( " " );
			break;
		}

		Com_Printf( " %3i %3i %s: %s\n",
			image->upload_width, image->upload_height, palstrings[ 0 ], image->name );
	}
	
	Com_Printf( "Total texel count (not counting mipmaps): %i\n", texels );
	
}

texture_t *TM_AllocateTexture( const char *name ) {
	texture_t *tex;
	int		i;
	
	assert( strlen( name ) < sizeof( tex->name ) );
	
	// find a free texture_t space
	for( i = 0, tex = ttextures; i < numttextures; ++i, ++tex )
	{
		if( ! tex->texnum )
		{
			break;
		}
	}
	
	if( i == numttextures )
	{
		if( numttextures == MAX_TEXTURES )
		{
			Com_DPrintf( "MAX_TEXTURES reached\n" );
			return r_notexture;
		}
		
		numttextures++;
	}		
	tex = &ttextures[ i ];	
	memset( tex, 0, sizeof( *tex ) );
	my_strlcpy( tex->name, name, MAX_GAMEPATH );
	tex->registration_sequence = texture_registration_sequence;

   // don't let R_Bind skip the next bind call
   currentTextures[ currenttmu ] = -1;
   
   pfglGenTextures( 1, &tex->texnum );
   pfglBindTexture( GL_TEXTURE_2D, tex->texnum );
		   
	return tex;
}

/*
-----------------------------------------------------------------------------
 Function: TM_LoadTexture	-Load raw image into video memory.
 
 Parameters: 
			name -[in] Name of texture image.
			data -[in] Raw pixel data in the format described by PixelFormat.
			width -[in] Width of image in pixels.
			height -[in] Height of image in pixels.
			type -[in]
			PixelFormat [in]
 
 Returns: Pointer to filled out texture_t structure.
 
 Notes: Any texture that was not touched on this registration sequence will be freed.
-----------------------------------------------------------------------------
*/
PUBLIC texture_t *TM_LoadTexture( const char *name, W8 *data, int width, int height, texturetype_t type, W16 bytes )
{
	texture_t	*tex;
	W8 *scaled;
	W16 scaled_width, scaled_height;

	tex = TM_AllocateTexture( name );
	
	tex->width = width;
	tex->height = height;
	tex->type = type;
	tex->bytes = bytes;
	
	switch( type )
	{
		case TT_Pic: 
			tex->MipMap = false;	
			tex->WrapS = Clamp;
			tex->WrapT = Clamp;
			tex->MinFilter = Nearest;
			tex->MagFilter = NearestMipMapOff;
			break;

		case TT_Wall:
			tex->MipMap = true;	
			tex->WrapS = Repeat;
			tex->WrapT = Repeat;
//			tex->MinFilter = LinearMipMapLinear;
			tex->MinFilter = LinearMipMapNearest;
			tex->MagFilter = Linear;
			break;

		default:
			tex->WrapS = Repeat;
			tex->WrapT = Repeat;
			tex->MinFilter = Nearest;
			tex->MagFilter = NearestMipMapOff;
			break;
	}


	for( scaled_width = 1 ; scaled_width < tex->width ; scaled_width <<= 1 )
	{
		;
	}
	
	if( gl_round_down->value && scaled_width > tex->width && tex->MipMap ) 
	{
		scaled_width >>= 1;
	}
	
	for( scaled_height = 1 ; scaled_height < tex->height ; scaled_height <<= 1 )
	{
		;
	}
	
	if( gl_round_down->value && scaled_height > tex->height && tex->MipMap )
	{
		scaled_height >>= 1;
	}

	// let people sample down the world textures for speed
	if( tex->MipMap )
	{
		scaled_width >>= (int)gl_picmip->value;
		scaled_height >>= (int)gl_picmip->value;
	}

	// don't ever bother with > glMaxTexSize textures
	if( scaled_width > glMaxTexSize )
	{
		scaled_width = glMaxTexSize;
	}
	if( scaled_height > glMaxTexSize )
	{
		scaled_height = glMaxTexSize;
	}
	
	if( scaled_width < 1 )
	{
		scaled_width = 1;
	}
	if( scaled_height < 1 )
	{
		scaled_height = 1;
	}
	
	tex->upload_width = scaled_width;
	tex->upload_height = scaled_height;
	
	if( scaled_width == tex->width && scaled_height == tex->height )
	{
		scaled = data;
	}
	else
	{
		scaled = Z_Malloc( scaled_width * scaled_height * tex->bytes );
		TM_ResampleTexture( data, tex->width, tex->height, scaled, scaled_width, scaled_height, tex->bytes, INTERPOLATION_NONE );
	}
	
	{
		// upload base image	
		GLenum internalFormat[] = { GL_LUMINANCE, GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA };
#if 0		
		GLenum externalFormat[] = { GL_LUMINANCE, GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_UNSIGNED_SHORT_5_6_5,  GL_UNSIGNED_SHORT_5_5_5_1 };
		pfglTexImage2D( GL_TEXTURE_2D, 0, internalFormat[ tex->bytes ], scaled_width, scaled_height, 0, tex->bytes == 4 ? GL_RGBA : GL_RGB, externalFormat[ tex->bytes ], scaled );
#else		
		pfglTexImage2D( GL_TEXTURE_2D, 0, internalFormat[ tex->bytes ], scaled_width, scaled_height, 0, tex->bytes == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, scaled );
#endif
		
		
		// upload mipmaps if required
#ifdef IPHONE
		glGenerateMipmapOES( GL_TEXTURE_2D );
#else		
		if( tex->MipMap )
		{
			int miplevel = 0;
			
			while( TM_MipMap( scaled, &scaled_width, &scaled_height, tex->bytes ) )
			{
				pfglTexImage2D( GL_TEXTURE_2D, ++miplevel, internalFormat[ tex->bytes ], scaled_width, scaled_height, 0, tex->bytes == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, scaled );
			}
		}
#endif		
	}
	
	if ( scaled != data ) {
		Z_Free( scaled );
	}
	
	pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, WrapToGL( tex->WrapS ) );
	pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, WrapToGL( tex->WrapT ) );
	pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, MinFilterToGL( tex->MipMap, tex->MinFilter ) );
	pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, MagFilterToGL( tex->MagFilter ) );		
	
#ifdef IPHONE
	if ( type == TT_Wall ) {
		pfglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f );
	} else {
		pfglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 0 );
	}
#endif
	

	return tex;
}


/*
-----------------------------------------------------------------------------
 Function: TM_FreeUnusedTextures	-Free unused textures.
 
 Parameters: Nothing.
 
 Returns: Nothing.
 
 Notes: Any texture that was not touched on this registration sequence will be freed.
-----------------------------------------------------------------------------
*/
PUBLIC void TM_FreeUnusedTextures( void )
{
#if 0	
	int		i;
	texture_t	*tex;

	// never free r_notexture texture
	r_notexture->registration_sequence = texture_registration_sequence;	

	for( i = 0, tex = ttextures ; i < numttextures ; ++i, ++tex )
	{
		if( tex->registration_sequence == texture_registration_sequence )
			continue;		// used this sequence
			
		if( ! tex->registration_sequence )
			continue;		// free image_t slot
			
		if( tex->type == TT_Pic )
			continue;		// don't free pics
			
		// free texture
		R_DeleteTexture( tex->texnum );
		memset( tex, 0, sizeof( *tex ) );
	}
#endif	
}


/*
-----------------------------------------------------------------------------
 Function: TM_FindTexture	-Find texture.
 
 Parameters: name -[in] Name of the texture to find.
			 type -[in] Type of texture (see texturetype_t).
 
 Returns: r_notexture if the texture is not found, otherwise it will
			return a valid texture_t structure.
 
 Notes:
-----------------------------------------------------------------------------
*/
PUBLIC texture_t *TM_FindTexture( const char *name, texturetype_t type )
{
	texture_t	*tex;
	int	i, len;
	W8	*data;			/* raw texture data */
	W16	width, height;	/* width, height of texture */
	W16 bytes;
	char	digested[1024];
	filehandle_t		*fh;
	
	if( ! name || ! *name )
	{
		return r_notexture;
	}
	
	// Check for file extension
	len = strlen( name );
	if( len < 5 )
	{
		return r_notexture;
	}

	// look for it in the texture cache
	for( i = 0, tex = ttextures; i < numttextures; ++i, ++tex )
	{
		if( ! strcmp( name, tex->name ) )
		{
			tex->registration_sequence = texture_registration_sequence;
			return tex;
		}
	}

	//
	// load the texture from disk
	//
	data = NULL;
	if( strcmp( name + len - 4, ".tga" ) ) {
		return r_notexture;
	}

//	Com_Printf( "Loading texture: %s\n", name );
	
	// look for the pre-digested 5551 version
	strcpy( digested, name );
	strcpy( digested + len - 4, ".5551" );
	fh = FS_OpenFile( digested, 0 );
	if ( fh ) {
		typedef struct {
			int		internalFormat;
			int		externalFormat;
			int		bpp;
		} formatInfo_t;
		
		static formatInfo_t formatInfo[7] = {
// the wall exporter always saved as 5551 even when there was no alpha
			{ GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, 16 },
			{ GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, 16 },
			{ GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 16 },
			{ GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, GL_UNSIGNED_BYTE, 4 },
			{ GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, GL_UNSIGNED_BYTE, 4 },
			{ GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, GL_UNSIGNED_BYTE, 2 },
			{ GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, GL_UNSIGNED_BYTE, 2 },
		};
		
		
		picHeader_t *ph = (picHeader_t *)fh->filedata;
		
		int noMips = 0;
		formatInfo_t *fi;
		if ( ph->picFormat & PF_NO_MIPS ) {
			noMips = 1;
			fi = &formatInfo[ph->picFormat&~PF_NO_MIPS];
		} else {
			fi = &formatInfo[ph->picFormat];
		}
		int	w = ph->uploadWidth;
		int h = ph->uploadHeight;
		int	l = 0;
		texture_t *tx = TM_AllocateTexture( name );
		tx->width = ph->srcWidth;
		tx->height = ph->srcHeight;
		tx->upload_width = w;
		tx->upload_height = h;
		tx->header = *ph;
		tx->maxS = (float)ph->srcWidth / ph->uploadWidth;
		tx->maxT = (float)ph->srcHeight / ph->uploadHeight;
		unsigned char *s = (unsigned char *)(ph+1);
		while( 1 ) {
			int	size = (w*h*fi->bpp)/8;
			if ( fi->bpp < 16 ) {
				if ( size < 32 ) {
					// minimum PVRTC size
					size = 32;
				}
				qglCompressedTexImage2D( GL_TEXTURE_2D, l, fi->internalFormat, w, h, 0, 
							   size, s );
			} else {
				qglTexImage2D( GL_TEXTURE_2D, l, fi->internalFormat, w, h, 0, 
							   fi->internalFormat, fi->externalFormat, s );
			}
		   if ( w == 1 && h == 1 ) {
			   break;
		   }
			if ( noMips ) {
				break;
			}
		   l++;
		   s += size;
		   w >>= 1;
		   if ( w == 0 ) {
			   w = 1;
		   }
		   h >>= 1;
		   if ( h == 0 ) {
			   h = 1;
		   }
		}
		FS_CloseFile( fh );
		if ( noMips ) {
			pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		} else {
			pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
		}
		pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		
		if ( type == TT_Wall ) {
			pfglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f );
			pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
		} else {
			pfglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 0 );
			pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
		}
		pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
		return tx;
	}

	// load a normal TGA
	LoadTGA( name, &data, &width, &height, &bytes );
	if ( data ) {
		tex = TM_LoadTexture( name, data, width, height, type, bytes );
		MM_FREE( data );
		tex->maxS = tex->maxT = 1.0f;
		return tex;
	}
	
	// load a jpg
	{
		int jpgSize = 0;
		W8 *jpgData;
		// try jpeg if no tga exists
		strcpy( digested, name );
		strcpy( digested + len - 4, ".jpg" );
		fh = FS_OpenFile( digested, 0 );
		if ( fh == NULL ) {
			Com_Printf( "Failed to find texture %s\n", name );
			return r_notexture;
		}
		jpgSize = FS_GetFileSize( fh );
		jpgData = fh->ptrStart;
		
		SysIPhoneLoadJPG( jpgData, jpgSize, &data, &width, &height, &bytes );
		FS_CloseFile( fh );

		if ( ! data ) {
			free( jpgData );
			return r_notexture; 
		}
		tex = TM_LoadTexture( name, data, width, height, type, bytes );
		MM_FREE( data );
		tex->maxS = tex->maxT = 1.0f;
		return tex;
	}

	return NULL;
}

/*
-----------------------------------------------------------------------------
 Function: TM_GetTextureSize	-Find texture.
 
 Parameters:width -[out] Width of texture.
			height -[out] Height of texture.
			name -[in] Name of the texture to get dimensions of.
			
 
 Returns: Nothing.
 
 Notes: If texture is not found, width and height are -1.
-----------------------------------------------------------------------------
*/
PUBLIC void TM_GetTextureSize( SW32 *width, SW32 *height, const char *name )
{
	texture_t *tex;

	tex = TM_FindTexture( name, TT_Pic );
	if( ! tex )
	{
		*width = *height = -1;
		return;
	}

	*width = tex->width;
	*height = tex->height;
}







/* Note: cubic function no longer clips result */
PRIVATE INLINECALL double
cubic (double dx,
       int    jm1,
       int    j,
       int    jp1,
       int    jp2)
{
  /* Catmull-Rom - not bad */
  return (double) ((( ( - jm1 + 3 * j - 3 * jp1 + jp2 ) * dx +
                       ( 2 * jm1 - 5 * j + 4 * jp1 - jp2 ) ) * dx +
                     ( - jm1 + jp1 ) ) * dx + (j + j) ) / 2.0;
}

_boolean pixel_region_has_alpha( int bytes )
{
	if( bytes == 2 || bytes == 4 )
	{
		return true;
	}
	else
	{
		return false;
	}
}


PRIVATE void
expand_line( double               *dest,
             double               *src,
             int                   bytes,
             int                   old_width,
             int                   width )
{
	double  ratio;
	int     x,b;
	int     src_col;
	double  frac;
	double *s;

	ratio = old_width / (double) width;

	/* we can overflow src's boundaries, so we expect our caller to have
		allocated extra space for us to do so safely (see scale_region ()) */

	/* this could be optimized much more by precalculating the coefficients for
		each x */

	for( x = 0; x < width; ++x )
	{
		src_col = ((int) (x * ratio + 2.0 - 0.5)) - 2;
		/* +2, -2 is there because (int) rounds towards 0 and we need
			to round down */
		frac = (x * ratio - 0.5) - src_col;
		s = &src[ src_col * bytes ];
		for( b = 0 ; b < bytes ; b++ )
			dest[ b ] = cubic( frac, (int)s[ b - bytes ], (int)s[ b ], (int)s[ b + bytes ], (int)s[ b + bytes * 2 ] );

		dest += bytes;
	}

}


PRIVATE void
shrink_line( double               *dest,
             double               *src,
             int                   bytes,
             int                   old_width,
             int                   width )
{
	int          x;
	int          b;
	double      *srcp;
	double      *destp;
	double       accum[4];
	double       slice;
	const double avg_ratio = (double) width / old_width;
	const double inv_width = 1.0 / width;
	int          slicepos;      /* slice position relative to width */

#if 0

	Com_DPrintf( "shrink_line bytes=%d old_width=%d width=%d interp=%d "
              "avg_ratio=%f\n",
              bytes, old_width, width, interp, avg_ratio);

#endif

//	g_return_if_fail( bytes <= 4 );

	/* This algorithm calculates the weighted average of pixel data that
		each output pixel must receive, taking into account that it always
		scales down, i.e. there's always more than one input pixel per each
		output pixel.  */

	srcp = src;
	destp = dest;

	slicepos = 0;

	/* Initialize accum to the first pixel slice.  As there is no partial
		pixel at start, that value is 0.  The source data is interleaved, so
		we maintain BYTES accumulators at the same time to deal with that
		many channels simultaneously.  */
	for( b = 0 ; b < bytes ; ++b )
	{
		accum[ b ] = 0.0;
	}

	for( x = 0 ; x < width ; x++ )
    {
		/* Accumulate whole pixels.  */
		do
		{
			for( b = 0 ; b < bytes ; b++ )
				accum[ b ] += *srcp++;

			slicepos += width;
        }
		while( slicepos < old_width );
		slicepos -= old_width;

		if( ! (slicepos < width))
			Com_Printf( "Assertion (slicepos < width) failed. Please report.\n" );

		if( slicepos == 0 )
		{
			/* Simplest case: we have reached a whole pixel boundary.  Store
				the average value per channel and reset the accumulators for
				the next round.

				The main reason to treat this case separately is to avoid an
				access to out-of-bounds memory for the first pixel.  */
			for (b = 0; b < bytes; b++)
			{
				*destp++ = accum[b] * avg_ratio;
				accum[b] = 0.0;
			}
		}
		else
        {
			for( b = 0; b < bytes; b++ )
            {
				/* We have accumulated a whole pixel per channel where just a
					slice of it was needed.  Subtract now the previous pixel's
					extra slice.  */
				slice = srcp[- bytes + b] * slicepos * inv_width;
				*destp++ = (accum[b] - slice) * avg_ratio;

				/* That slice is the initial value for the next round.  */
				accum[b] = slice;
            }
        }
    }

	/* Sanity check: srcp should point to the next-to-last position, and
		slicepos should be zero.  */
	if( ! (srcp - src == old_width * bytes && slicepos == 0) )
	{
		Com_Printf ("Assertion (srcp - src == old_width * bytes && slicepos == 0)"
               " failed. Please report.");
	}
}

PRIVATE void pixel_region_get_row( W8 *src, int y, int width, W8 *tmp_src, int BytesPerPixel )
{
	int i;
    unsigned long k = 0;
    unsigned char *scanline = tmp_src;
	unsigned char *ptr = src;
    
	for( i = 0 ; i < (width * BytesPerPixel) ; ++i )
	{
		scanline[ k++ ] = ptr[ y * width * BytesPerPixel + i ];
	}		
}

PRIVATE void pixel_region_set_row( W8 *dest,
		      int         BytesPerPixel,
		      int         y,
		      int         width,
		      W8 *data )
{
	int i;
	unsigned long k = 0;
    unsigned char *scanline = dest;
	unsigned char *ptr = data;

	for( i = 0 ; i < (width * BytesPerPixel) ; ++i )
	{
		scanline[ y * width * BytesPerPixel + i ] = ptr[ k++ ];
	}	
}

PRIVATE void
get_premultiplied_double_row( W8 *in,
                              int PRbytes,
                              int         x,
                              int         y,
                              int         w,
                              double     *row,
                              W8      *tmp_src,
                              int         n )
{
	int b;
	int bytes = PRbytes;

	pixel_region_get_row( in, y, w, tmp_src, bytes );

	if( pixel_region_has_alpha( bytes ) )
    {
		/* premultiply the alpha into the double array */
		double *irow  = row;
		int     alpha = bytes - 1;
		double  mod_alpha;

		for( x = 0; x < w; ++x )
		{
			mod_alpha = tmp_src[ alpha ] / 255.0;
			for( b = 0; b < alpha; ++b )
			{
				irow[ b ] = mod_alpha * tmp_src[ b ];
			}
				
			irow[ b ] = tmp_src[ alpha ];
			irow += bytes;
			tmp_src += bytes;
		}
    }
	else /* no alpha */
	{
		for( x = 0; x < w * bytes; ++x )
		{
			row[ x ] = tmp_src[ x ];
		}
	}

	/* set the off edge pixels to their nearest neighbor */
	for( b = 0; b < 2 * bytes; b++ )
	{
		row[ b - 2 * bytes ] = row[ b % bytes ];
	}

	for( b = 0; b < bytes * 2; b++ )
	{
		row[ b + w * bytes ] = row[ (w - 1) * bytes + b % bytes ];
	}
}


PRIVATE INLINECALL void
rotate_pointers( W8 **p, W32 n )
{
	W32  i;
	W8  *tmp;

	tmp = p[ 0 ];
	for( i = 0 ; i < n-1 ; i++ )
    {
		p[ i ] = p[ i + 1 ];
    }

	p[ i ] = tmp;
}

PRIVATE void
get_scaled_row( double              **src,
                int                   y,
                int                   new_width,
                double               *row,
                W8			        *src_tmp,
                W8			        *srcPR,
                int old_width,
                int old_height,
                int bytes )
{
	/* get the necesary lines from the source image, scale them,
		and put them into src[] */
	rotate_pointers( (unsigned char  **)src, 4 );
	
	if( y < 0 )
	{
		y = 0;
	}

	if( y < old_height )
	{
		get_premultiplied_double_row( srcPR, bytes, 0, y, old_width,
                                    row, src_tmp, 1 );
		if( new_width > old_width )
		{
			expand_line( src[3], row, bytes, old_width, new_width );
		}
		else if( old_width > new_width )
		{
			shrink_line( src[3], row, bytes, old_width, new_width );
		}
		else /* no scailing needed */
		{
			memcpy( src[3], row, sizeof( double ) * new_width * bytes );
		}
	}
	else
	{
		memcpy( src[3], src[2], sizeof( double ) * new_width * bytes );
	}
}


/* 
non-interpolating scale_region.
 */
PRIVATE void
scale_region_no_resample( W8 *in, int inwidth, int inheight,
                          W8 *out, int outwidth, int outheight, char bytes )
{
	int   *x_src_offsets;
	int   *y_src_offsets;
	W8 *src;
	W8 *dest;
	int    width, height, orig_width, orig_height;
	int    last_src_y;
	int    row_bytes;
	int    x, y, b;
  

	orig_width = inwidth;
	orig_height = inheight;

	width = outwidth;
	height = outheight;
  

	/*  the data pointers...  */
	x_src_offsets = (int *) MM_MALLOC( sizeof( int ) * width * bytes );
	y_src_offsets = (int *) MM_MALLOC( sizeof( int ) * height );
	src  = (unsigned char *) MM_MALLOC( orig_width * bytes);
	dest = (unsigned char *) MM_MALLOC( width * bytes);

	/*  pre-calc the scale tables  */
	for( b = 0; b < bytes; b++ )
	{
		for( x = 0; x < width; x++ )
		{
			x_src_offsets[ b + x * bytes ] =
				b + bytes * ((x * orig_width + orig_width / 2) / width);
		}
	}

	for( y = 0; y < height; y++ )
	{
		y_src_offsets[ y ] = (y * orig_height + orig_height / 2) / height;
	}

	/*  do the scaling  */
	row_bytes = width * bytes;
	last_src_y = -1;
	for( y = 0; y < height; y++ )
    {
		/* if the source of this line was the same as the source
		*  of the last line, there's no point in re-rescaling.
		*/
		if( y_src_offsets[ y ] != last_src_y )
        {
			pixel_region_get_row( in, y_src_offsets[ y ], orig_width, src, bytes );
			//pixel_region_get_row( srcPR, 0, y_src_offsets[y], orig_width, src, 1 );
			for( x = 0 ; x < row_bytes ; x++ )
            {
				dest[ x ] = src[ x_src_offsets[ x ] ];
            }
			last_src_y = y_src_offsets[ y ];
        }
		
		pixel_region_set_row( out, bytes, y, width, dest );
    }

	MM_FREE( x_src_offsets );
	MM_FREE( y_src_offsets );
	MM_FREE( src );
	MM_FREE( dest );
}


/*
-----------------------------------------------------------------------------
 Function: TM_ResampleTexture	-Resize texture.
 
 Parameters: 
			in -[in] Original texture data.
			inwidth -[in] Original width of texture in pixels.
			inheight -[in] Original height of texture in pixels.
			out -[in/out] Resized texture data.
			outwidth -[in] New width of texture in pixels.
			outheight -[in] New height of texture in pixels.
			bytes -[in] Number of bytes per pixel.
			interpolation -[in] see InterpolationType
				
 
 Returns: Nothing.
 
 Notes: 
-----------------------------------------------------------------------------
*/
PUBLIC void TM_ResampleTexture( W8 *in, int inwidth, int inheight, W8 *out, int outwidth, int outheight, W8 bytes, InterpolationType interpolation )
{
	double *src[ 4 ];
	W8  *src_tmp;
	W8  *dest;
	double *row, *accum;
	int     b;
	int     width, height;
	int     orig_width, orig_height;
	double  y_rat;
	int     i;
	int     old_y = -4;
	int     new_y;
	int     x, y;


	if( interpolation == INTERPOLATION_NONE )
	{
		scale_region_no_resample( in, inwidth, inheight, out, outwidth, outheight, bytes );
		return;
    }

	
	orig_width = inwidth;
	orig_height = inheight;

	width = outwidth;
	height = outheight;

#if 0

  Com_DPrintf( "scale_region: (%d x %d) -> (%d x %d)\n",
              orig_width, orig_height, width, height );

#endif

	/*  find the ratios of old y to new y  */
	y_rat = (double) orig_height / (double) height;
	

	/*  the data pointers...  */
	for( i = 0 ; i < 4 ; ++i )
	{
		src[ i ] = (double *) MM_MALLOC( sizeof( double ) * width * bytes );
	}

	dest = (PW8) MM_MALLOC( width * bytes);

	src_tmp = (PW8) MM_MALLOC( orig_width * bytes );

	/* offset the row pointer by 2*bytes so the range of the array
		is [-2*bytes] to [(orig_width + 2)*bytes] */
	row = (double *) MM_MALLOC( sizeof( double ) * (orig_width + 2 * 2) * bytes );
	row += bytes * 2;

	accum = (double *) MM_MALLOC( sizeof( double ) * width * bytes );


	/*  Scale the selected region  */

	for( y = 0 ; y < height ; y++ )
    {
		
		if( height < orig_height )
        {
			int          max;
			double       frac;
			const double inv_ratio = 1.0 / y_rat;

			if( y == 0 ) /* load the first row if this is the first time through */
			{
				get_scaled_row( &src[0], 0, width, row, src_tmp, in, orig_width, orig_height, bytes );
			}

			new_y = (int)(y * y_rat);
			frac = 1.0 - (y * y_rat - new_y);
			for( x = 0 ; x < width * bytes; ++x )
			{
				accum[x] = src[3][x] * frac;
			}

			max = (int) ((y + 1) * y_rat) - new_y - 1;

			get_scaled_row( &src[ 0 ], ++new_y, width, row, src_tmp, in, orig_width, orig_height, bytes  );

			while( max > 0 )
            {
				for( x = 0 ; x < width * bytes ; ++x )
				{
					accum[x] += src[ 3 ][ x ];
				}
				
				get_scaled_row( &src[ 0 ], ++new_y, width, row, src_tmp, in, orig_width, orig_height, bytes );
				max--;
            }

			frac = (y + 1) * y_rat - ((int) ((y + 1) * y_rat));
			for( x = 0 ; x < width * bytes ; ++x )
			{
				accum[ x ] += frac * src[ 3 ][ x ];
				accum[ x ] *= inv_ratio;
			}
		}
		else if( height > orig_height )
        {
			double p0, p1, p2, p3;
			double dy;

			new_y = (int)floor( y * y_rat - 0.5 );

			while( old_y <= new_y )
            {
				/* get the necesary lines from the source image, scale them,
					and put them into src[] */
				get_scaled_row( &src[ 0 ], old_y + 2, width, row, src_tmp, in, orig_width, orig_height, bytes );
				old_y++;
			}			
			
			dy = (y * y_rat - 0.5) - new_y;

			p0 = cubic( dy, 1, 0, 0, 0 );
			p1 = cubic( dy, 0, 1, 0, 0 );
			p2 = cubic( dy, 0, 0, 1, 0 );
			p3 = cubic( dy, 0, 0, 0, 1 );
                
			for( x = 0 ; x < width * bytes ; ++x )
			{
				accum[ x ] = ( p0 * src[ 0 ][ x ] + p1 * src[ 1 ][ x ] +
                              p2 * src[ 2 ][ x ] + p3 * src[ 3 ][ x ] );
			}
			
              
        }
		else /* height == orig_height */
		{
			get_scaled_row( &src[ 0 ], y, width, row, src_tmp, in, orig_width, orig_height, bytes );
			memcpy( accum, src[ 3 ], sizeof( double ) * width * bytes );
        }

		if( pixel_region_has_alpha( bytes ) )
        {
			/* unmultiply the alpha */
			double  inv_alpha;
			double *p = accum;
			int     alpha = bytes - 1;
			int     result;
			W8		*d = dest;

			for( x = 0 ; x < width ; ++x )
            {
				if( p[ alpha ] > 0.001 )
                {
					inv_alpha = 255.0 / p[ alpha ];
					for( b = 0 ; b < alpha ; b++ )
                    {
						result = RINT( inv_alpha * p[ b ] );
						if( result < 0 )
						{
							d[ b ] = 0;
						}
						else if( result > 255 )
						{
							d[ b ] = 255;
						}
						else
						{
							d[ b ] = result;
						}
                    }
					result = RINT( p[ alpha ] );
					if( result > 255 )
					{
						d[ alpha ] = 255;
					}
					else
					{
						d[ alpha ] = result;
					}
                }
				else /* alpha <= 0 */
				{
					for( b = 0 ; b <= alpha ; ++b )
					{
						d[ b ] = 0;
					}
				}

				d += bytes;
				p += bytes;
            }
        }
		else
        {
			int w = width * bytes;

			for( x = 0 ; x < w ; ++x )
            {
				if( accum[ x ] < 0.0 )
				{
					dest[ x ] = 0;
				}
				else if( accum[ x ] > 255.0 )
				{
					dest[ x ] = 255;
				}
				else
				{
					dest[ x ] = RINT( accum[ x ] );
				}
            }
        }
		pixel_region_set_row( out, bytes, y, width, dest );
    }

	/*  free up temporary arrays  */
	MM_FREE( accum );

	for( i = 0 ; i < 4 ; ++i )
	{
		MM_FREE( src[ i ] );
	}

	MM_FREE( src_tmp );
	MM_FREE( dest );

	row -= 2 * bytes;
	MM_FREE( row );
}


/*
-----------------------------------------------------------------------------
 Function: TM_MipMap	-Generate MipMap.
 
 Parameters: 
				in -[in/out] Texture data.
				width -[in] Width of texture in pixels.
				height -[in] Height of texture in pixels.
 
 Returns: Nothing.
 
 Notes: Operates in place, quartering the size of the texture.
-----------------------------------------------------------------------------
*/
PUBLIC _boolean TM_MipMap( PW8 in, W16 *width, W16 *height, W16 bytes )
{
	W16 new_width, new_height;

	if( *width == 1 && *height == 1 ) 
	{
		return false;
	}

	
	if( *width < 2 )
	{
		new_width = 1;
	}
	else
	{
		new_width = *width >> 1;
	}

	if( *height < 2 )
	{
		new_height = 1;
	}
	else
	{
		new_height = *height >> 1;
	}
	
	TM_ResampleTexture( in, *width, *height, in, new_width, new_height, bytes, INTERPOLATION_CUBIC );

	*width = new_width;
	*height = new_height;

	return true;
}


/*
-----------------------------------------------------------------------------
 Function: TM_Init	-Initialize Texture Manager.
 
 Parameters: Nothing.
 
 Returns: Nothing.
 
 Notes: Generates default texture.
-----------------------------------------------------------------------------
*/
PUBLIC void TM_Init( void )
{
	W8 *ptr;
	W8 *data;
	int x, y;

	gl_round_down = Cvar_Get ("gl_round_down", "1", CVAR_INIT);

	texture_registration_sequence = 1;	

// create a checkerboard texture
	data = MM_MALLOC( 16 * 16 * 4 );
	for( y = 0; y < 16; ++y )
	{
		for( x = 0; x < 16; ++x )
		{
			ptr = &data[ (y * 16 + x) * 4 ];
			if( (y < 8) ^ (x < 8) )
			{ 
				ptr[ 0 ] = ptr[ 1 ] = ptr[ 2 ] = 0x00; 
				ptr[ 3 ] = 0xFF; 
			}
			else
			{ 
				ptr[ 0 ] = ptr[ 1 ] = ptr[ 2 ] = 0xFF;
				ptr[ 3 ] = 0xFF; 
			}
		}
	}
	
	r_notexture = TM_LoadTexture( "***r_notexture***", data, 16, 16, TT_Pic, 4 );

	MM_FREE( data );


	Cmd_AddCommand( "listTextures", TM_TextureList_f );

	
}

/*
-----------------------------------------------------------------------------
 Function: TM_Shutdown	-Shutdown Texture Manager.
 
 Parameters: Nothing.
 
 Returns: Nothing.
 
 Notes: 
-----------------------------------------------------------------------------
*/
PUBLIC void TM_Shutdown( void )
{
	int		i;
	texture_t	*tex;

	for( i = 0, tex = ttextures; i < numttextures; ++i, ++tex )
	{
		if( ! tex->registration_sequence )
		{
			continue;		// free image_t slot
		}
		
		// free texture
		R_DeleteTexture( tex->texnum );
		memset( tex, 0, sizeof( *tex ) );
	}

	Cmd_RemoveCommand( "listTextures" );
}

