/*
   Copyright (C) 1999-2007 id Software, Inc. and contributors.
   For a list of contributors, see the accompanying CONTRIBUTORS file.

   This file is part of GtkRadiant.

   GtkRadiant 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.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

// lbmlib.c

#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include "image.h"
#include "lbmlib.h"
#include "bmp.h"

#define LittleLong( a ) GINT32_FROM_LE( a )
#define LittleShort( a ) GINT16_FROM_LE( a )

#include <stdio.h>

#define Sys_Printf g_FuncTable.m_pfnSysPrintf
#define Sys_FPrintf g_FuncTable.m_pfnSysFPrintf

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

   LOAD PCX

   ============================================================================
 */
typedef struct
{
	char manufacturer;
	char version;
	char encoding;
	char bits_per_pixel;
	unsigned short xmin, ymin, xmax, ymax;
	unsigned short hres, vres;
	unsigned char palette[48];
	char reserved;
	char color_planes;
	unsigned short bytes_per_line;
	unsigned short palette_type;
	char filler[58];
	unsigned char data;     // unbounded
} pcx_t;

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

   TARGA IMAGE

   ============================================================================
 */
typedef struct _TargaHeader
{
	unsigned char id_length, colormap_type, image_type;
	unsigned short colormap_index, colormap_length;
	unsigned char colormap_size;
	unsigned short x_origin, y_origin, width, height;
	unsigned char pixel_size, attributes;
} TargaHeader;

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

   BMP LOADING

   =========================================================
 */
typedef struct
{
	char id[2];
	unsigned long fileSize;
	unsigned long reserved0;
	unsigned long bitmapDataOffset;
	unsigned long bitmapHeaderSize;
	unsigned long width;
	unsigned long height;
	unsigned short planes;
	unsigned short bitsPerPixel;
	unsigned long compression;
	unsigned long bitmapDataSize;
	unsigned long hRes;
	unsigned long vRes;
	unsigned long colors;
	unsigned long importantColors;
	unsigned char palette[256][4];
} BMPHeader_t;

static void LoadBMP( const char *name, byte ** pic, int *width, int *height ){
	int columns, rows, numPixels;
	byte *pixbuf;
	int row, column;
	byte *buf_p;
	byte *buffer;
	unsigned int length;
	BMPHeader_t bmpHeader;
	byte *bmpRGBA;

	*pic = NULL;

	//
	// load the file
	//
	length = vfsLoadFile( (char *)name, (void **)&buffer, 0 );
	if ( length == (unsigned int) -1 ) {
		return;
	}

	buf_p = buffer;

	bmpHeader.id[0] = *buf_p++;
	bmpHeader.id[1] = *buf_p++;
	bmpHeader.fileSize = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.reserved0 = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.bitmapDataOffset = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.bitmapHeaderSize = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.width = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.height = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.planes = LittleShort( *(short *) buf_p );
	buf_p += 2;
	bmpHeader.bitsPerPixel = LittleShort( *(short *) buf_p );
	buf_p += 2;
	bmpHeader.compression = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.bitmapDataSize = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.hRes = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.vRes = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.colors = LittleLong( *(long *) buf_p );
	buf_p += 4;
	bmpHeader.importantColors = LittleLong( *(long *) buf_p );
	buf_p += 4;

	memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) );

	if ( bmpHeader.bitsPerPixel == 8 ) {
		buf_p += 1024;
	}

	if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' ) {
		Sys_Printf( "LoadBMP: only Windows-style BMP files supported (%s)\n", name );
		return;
	}
	if ( bmpHeader.fileSize != length ) {
		Sys_Printf( "LoadBMP: header size does not match file size (%d vs. %d) (%s)\n",
					bmpHeader.fileSize, length, name );
		return;
	}
	if ( bmpHeader.compression != 0 ) {
		Sys_Printf( "LoadBMP: only uncompressed BMP files supported (%s)\n", name );
		return;
	}
	if ( bmpHeader.bitsPerPixel < 8 ) {
		Sys_Printf( "LoadBMP: monochrome and 4-bit BMP files not supported (%s)\n", name );
		return;
	}

	columns = bmpHeader.width;
	rows = bmpHeader.height;
	if ( rows < 0 ) {
		rows = -rows;
	}
	numPixels = columns * rows;

	if ( width ) {
		*width = columns;
	}
	if ( height ) {
		*height = rows;
	}

	bmpRGBA = reinterpret_cast < unsigned char *>( g_malloc( numPixels * 4 ) );
	*pic = bmpRGBA;


	for ( row = rows - 1; row >= 0; row-- )
	{
		pixbuf = bmpRGBA + row * columns * 4;

		for ( column = 0; column < columns; column++ )
		{
			unsigned char red, green, blue, alpha;
			int palIndex;
			unsigned short shortPixel;

			switch ( bmpHeader.bitsPerPixel )
			{
			case 8:
				palIndex = *buf_p++;
				*pixbuf++ = bmpHeader.palette[palIndex][2];
				*pixbuf++ = bmpHeader.palette[palIndex][1];
				*pixbuf++ = bmpHeader.palette[palIndex][0];
				*pixbuf++ = 0xff;
				break;
			case 16:
				shortPixel = *(unsigned short *) pixbuf;
				pixbuf += 2;
				*pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7;
				*pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2;
				*pixbuf++ = ( shortPixel & ( 31 ) ) << 3;
				*pixbuf++ = 0xff;
				break;
			case 24:
				blue = *buf_p++;
				green = *buf_p++;
				red = *buf_p++;
				*pixbuf++ = red;
				*pixbuf++ = green;
				*pixbuf++ = blue;
				*pixbuf++ = 255;
				break;
			case 32:
				blue = *buf_p++;
				green = *buf_p++;
				red = *buf_p++;
				alpha = *buf_p++;
				*pixbuf++ = red;
				*pixbuf++ = green;
				*pixbuf++ = blue;
				*pixbuf++ = alpha;
				break;
			default:
				Sys_Printf( "LoadBMP: illegal pixel_size '%d' in file '%s'\n", bmpHeader.bitsPerPixel,
							name );
				g_free( *pic );
				*pic = NULL;
				return;
				break;
			}
		}
	}

	vfsFreeFile( buffer );

}


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

   PCX LOADING

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


/*
   ==============
   LoadPCX
   ==============
 */

/* RR2DO2 */
#define DECODEPCX( b, d, r ) d = *b++; if ( ( d & 0xC0 ) == 0xC0 ) {r = d & 0x3F; d = *b++; }else{r = 1; }

static void LoadPCX( const char *filename, byte **pic, byte **palette, int *width, int *height ){
	byte  *raw;
	pcx_t *pcx;
	int x, y, lsize;
	int len;
	int dataByte, runLength;
	byte  *out, *pix;


	/* load the file */
	len = vfsLoadFile( filename, (void **)&raw, 0 );
	if ( len == -1 ) {
		Error( "LoadPCX: Couldn't read %s", filename );
	}


	/* parse the PCX file */
	pcx = (pcx_t *)raw;
	raw = &pcx->data;

	pcx->xmin = LittleShort( pcx->xmin );
	pcx->ymin = LittleShort( pcx->ymin );
	pcx->xmax = LittleShort( pcx->xmax );
	pcx->ymax = LittleShort( pcx->ymax );
	pcx->hres = LittleShort( pcx->hres );
	pcx->vres = LittleShort( pcx->vres );
	pcx->bytes_per_line = LittleShort( pcx->bytes_per_line );
	pcx->palette_type = LittleShort( pcx->palette_type );

	if ( pcx->manufacturer != 0x0a
		 || pcx->version != 5
		 || pcx->encoding != 1
		 || pcx->bits_per_pixel != 8
		 || pcx->xmax >= 640
		 || pcx->ymax >= 480 ) {
		Error( "Bad pcx file %s", filename );
	}

	if ( palette ) {
		*palette = (byte *)malloc( 768 );
		memcpy( *palette, (byte *)pcx + len - 768, 768 );
	}

	if ( width ) {
		*width = pcx->xmax + 1;
	}
	if ( height ) {
		*height = pcx->ymax + 1;
	}

	if ( !pic ) {
		return;
	}

	out = (byte *)malloc( ( pcx->ymax + 1 ) * ( pcx->xmax + 1 ) );
	if ( !out ) {
		Error( "LoadPCX: couldn't allocate" );
	}

	*pic = out;
	pix = out;

	/* RR2DO2: pcx fix  */
	lsize = pcx->color_planes * pcx->bytes_per_line;

	/* go scanline by scanline */
	for ( y = 0; y <= pcx->ymax; y++, pix += pcx->xmax + 1 )
	{
		/* do a scanline */
		for ( x = 0; x <= pcx->xmax; )
		{
			/* RR2DO2 */
			DECODEPCX( raw, dataByte, runLength );
			while ( runLength-- > 0 )
				pix[ x++ ] = dataByte;
		}

		/* RR2DO2: discard any other data */
		while ( x < lsize )
		{
			DECODEPCX( raw, dataByte, runLength );
			x++;
		}
		while ( runLength-- > 0 )
			x++;
	}

	/* validity check */
	if ( raw - (byte *) pcx > len ) {
		Error( "PCX file %s was malformed", filename );
	}
	free( pcx );
}

/*
   ==============
   LoadPCX32
   ==============
 */
static void LoadPCX32( const char *filename, byte ** pic, int *width, int *height ){
	byte *palette;
	byte *pic8;
	int i, c, p;
	byte *pic32;

	LoadPCX( filename, &pic8, &palette, width, height );
	if ( !pic8 ) {
		*pic = NULL;
		return;
	}

	c = ( *width ) * ( *height );
	pic32 = *pic = reinterpret_cast < unsigned char *>( g_malloc( 4 * c ) );
	for ( i = 0; i < c; i++ )
	{
		p = pic8[i];
		pic32[0] = palette[p * 3];
		pic32[1] = palette[p * 3 + 1];
		pic32[2] = palette[p * 3 + 2];
		pic32[3] = 255;
		pic32 += 4;
	}

	g_free( pic8 );
	g_free( palette );
}

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

   TARGA LOADING

   TTimo: added code to get rid of alphachannel from prefs or ignore it if completely empty
    was required since Radiant is using alpha channel when binding the textures for proper curry operation
    can be fully turned off from the prefs though
   =========================================================
 */

/*
   =============
   LoadTGA
   =============
 */
void LoadTGA( const char *name, byte ** pic, int *width, int *height ){
	int columns, rows, numPixels;
	byte *pixbuf;
	int row, column;
	byte *buf_p;
	byte *buffer;
	TargaHeader targa_header;
	byte *targa_rgba;

	*pic = NULL;

	//
	// load the file
	//
	int nLen = vfsLoadFile( (char *)name, (void **)&buffer, 0 );
	if ( nLen == -1 ) {
		return;
	}

	buf_p = buffer;

	targa_header.id_length = *buf_p++;
	targa_header.colormap_type = *buf_p++;
	targa_header.image_type = *buf_p++;

	targa_header.colormap_index = LittleShort( *(short *) buf_p );
	buf_p += 2;
	targa_header.colormap_length = LittleShort( *(short *) buf_p );
	buf_p += 2;
	targa_header.colormap_size = *buf_p++;
	targa_header.x_origin = LittleShort( *(short *) buf_p );
	buf_p += 2;
	targa_header.y_origin = LittleShort( *(short *) buf_p );
	buf_p += 2;
	targa_header.width = LittleShort( *(short *) buf_p );
	buf_p += 2;
	targa_header.height = LittleShort( *(short *) buf_p );
	buf_p += 2;
	targa_header.pixel_size = *buf_p++;
	targa_header.attributes = *buf_p++;

	bool bAlphaOK = false;

	if ( targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3 ) {
		Sys_Printf( "LoadTGA: TGA type %d not supported\n", targa_header.image_type );
		Sys_Printf( "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n" );
		return;
	}

	if ( targa_header.colormap_type != 0 ) {
		Sys_Printf( "LoadTGA: colormaps not supported\n" );
		return;
	}

	if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 )
		 && targa_header.image_type != 3 ) {
		Sys_Printf( "LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n" );
		return;
	}

	columns = targa_header.width;
	rows = targa_header.height;
	numPixels = columns * rows;

	if ( width ) {
		*width = columns;
	}
	if ( height ) {
		*height = rows;
	}

	targa_rgba = reinterpret_cast < unsigned char *>( g_malloc( numPixels * 4 ) );
	*pic = targa_rgba;

	if ( targa_header.id_length != 0 ) {
		buf_p += targa_header.id_length; // skip TARGA image comment

	}
	if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) {
		// Uncompressed RGB or gray scale image
		for ( row = rows - 1; row >= 0; row-- )
		{
			pixbuf = targa_rgba + row * columns * 4;
			for ( column = 0; column < columns; column++ )
			{
				unsigned char red, green, blue, alphabyte;
				switch ( targa_header.pixel_size )
				{
				case 8:
					blue = *buf_p++;
					green = blue;
					red = blue;
					*pixbuf++ = red;
					*pixbuf++ = green;
					*pixbuf++ = blue;
					*pixbuf++ = 255;
					break;

				case 24:
					blue = *buf_p++;
					green = *buf_p++;
					red = *buf_p++;
					*pixbuf++ = red;
					*pixbuf++ = green;
					*pixbuf++ = blue;
					*pixbuf++ = 255;
					break;
				case 32:
					blue = *buf_p++;
					green = *buf_p++;
					red = *buf_p++;
					alphabyte = *buf_p++;
					// detect if the whole alpha channel is 0
					if ( alphabyte != 0 ) {
						bAlphaOK = true;
					}
					*pixbuf++ = red;
					*pixbuf++ = green;
					*pixbuf++ = blue;
					*pixbuf++ = alphabyte;
					break;
				default:
					Sys_Printf( "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size,
								name );
					g_free( *pic );
					*pic = NULL;
					return;
					break;
				}
			}
		}

		if ( !bAlphaOK ) {
			if ( targa_header.pixel_size == 32 ) {
				Sys_FPrintf( SYS_WRN, "WARNING: %s has empty alpha channel\n", name );
			}
			// disable the alpha value
			for ( row = rows - 1; row >= 0; row-- )
			{
				pixbuf = targa_rgba + row * columns * 4;
				for ( column = 0; column < columns; column++ )
				{
					// 32 bit
					pixbuf += 3;
					*pixbuf++ = 255;
				}
			}
		}
	}
	else if ( targa_header.image_type == 10 ) { // Runlength encoded RGB images
		unsigned char red, green, blue, alphabyte, packetHeader, packetSize, j;

		red = 0;
		green = 0;
		blue = 0;
		alphabyte = 0xff;

		for ( row = rows - 1; row >= 0; row-- )
		{
			pixbuf = targa_rgba + row * columns * 4;
			for ( column = 0; column < columns; )
			{
				packetHeader = *buf_p++;
				packetSize = 1 + ( packetHeader & 0x7f );
				if ( packetHeader & 0x80 ) { // run-length packet
					switch ( targa_header.pixel_size )
					{
					case 24:
						blue = *buf_p++;
						green = *buf_p++;
						red = *buf_p++;
						alphabyte = 255;
						break;
					case 32:
						blue = *buf_p++;
						green = *buf_p++;
						red = *buf_p++;
						alphabyte = *buf_p++;
						// detect if the whole alpha channel is 0
						if ( alphabyte != 0 ) {
							bAlphaOK = true;
						}
						break;
					default:
						Sys_Printf( "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size,
									name );
						g_free( *pic );
						*pic = NULL;
						return;
						break;
					}

					for ( j = 0; j < packetSize; j++ )
					{
						*pixbuf++ = red;
						*pixbuf++ = green;
						*pixbuf++ = blue;
						*pixbuf++ = alphabyte;
						column++;
						if ( column == columns ) { // run spans across rows
							column = 0;
							if ( row > 0 ) {
								row--;
							}
							else{
								goto breakOut;
							}
							pixbuf = targa_rgba + row * columns * 4;
						}
					}
				}
				else
				{   // non run-length packet
					for ( j = 0; j < packetSize; j++ )
					{
						switch ( targa_header.pixel_size )
						{
						case 24:
							blue = *buf_p++;
							green = *buf_p++;
							red = *buf_p++;
							*pixbuf++ = red;
							*pixbuf++ = green;
							*pixbuf++ = blue;
							*pixbuf++ = 255;
							break;
						case 32:
							blue = *buf_p++;
							green = *buf_p++;
							red = *buf_p++;
							alphabyte = *buf_p++;
							// detect if the whole alpha channel is 0
							if ( alphabyte != 0 ) {
								bAlphaOK = true;
							}
							*pixbuf++ = red;
							*pixbuf++ = green;
							*pixbuf++ = blue;
							*pixbuf++ = alphabyte;
							break;
						default:
							Sys_Printf( "LoadTGA: illegal pixel_size '%d' in file '%s'\n",
										targa_header.pixel_size, name );
							g_free( *pic );
							*pic = NULL;
							return;
							break;
						}
						column++;
						if ( column == columns ) { // pixel packet run spans across rows
							column = 0;
							if ( row > 0 ) {
								row--;
							}
							else{
								goto breakOut;
							}
							pixbuf = targa_rgba + row * columns * 4;
						}
					}
				}
			}
breakOut:;
		}

		if ( !bAlphaOK ) {
			if ( targa_header.pixel_size == 32 ) {
				Sys_FPrintf( SYS_WRN, "WARNING: %s has empty alpha channel\n", name );
			}
			// disable the alpha value
			for ( row = rows - 1; row >= 0; row-- )
			{
				pixbuf = targa_rgba + row * columns * 4;
				for ( column = 0; column < columns; column++ )
				{
					// 32 bit
					pixbuf += 3;
					*pixbuf++ = 255;
				}
			}
		}

	}

	// vertically flipped
	if ( ( targa_header.attributes & ( 1 << 5 ) ) ) {
		int flip;
		for ( row = 0; row < .5f * rows; row++ )
		{
			for ( column = 0; column < columns; column++ )
			{
				flip = *( (int*)targa_rgba + row * columns + column );
				*( (int*)targa_rgba + row * columns + column ) = *( (int*)targa_rgba + ( ( rows - 1 ) - row ) * columns + column );
				*( (int*)targa_rgba + ( ( rows - 1 ) - row ) * columns + column ) = flip;
			}
		}
	}

	vfsFreeFile( buffer );
}

//===================================================================

/*
   =================
   LoadImage

   Loads any of the supported image types into a cannonical
   32 bit format.
   =================
 */
void LoadImage( const char *name, byte ** pic, int *width, int *height ){
	int len;
	*pic = NULL;
	*width = 0;
	*height = 0;

	len = strlen( name );
	if ( len < 5 ) {
		return;
	}

	if ( !g_strcasecmp( name + len - 4, ".tga" ) ) {
		LoadTGA( name, pic, width, height );
	}
	else if ( !g_strcasecmp( name + len - 4, ".pcx" ) ) {
		LoadPCX32( name, pic, width, height );
	}
	else if ( !g_strcasecmp( name + len - 4, ".bmp" ) ) {
		LoadBMP( name, pic, width, height );
	}
	/*
	   else if (!g_strcasecmp (name + len - 4, ".jpg"))
	   {
	   LoadJPG (name, pic, width, height);
	   }
	 */
}
