/*
    SDL_image:  An example image loading library for use with SDL
    Copyright (C) 1997-2009 Sam Lantinga

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

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/

/* This is a ILBM image file loading framework
   Load IFF pictures, PBM & ILBM packing methods, with or without stencil
   Written by Daniel Morais ( Daniel AT Morais DOT com ) in September 2001.
   24 bits ILBM files support added by Marc Le Douarain (http://www.multimania.com/mavati)
   in December 2002.
   EHB and HAM (specific Amiga graphic chip modes) support added by Marc Le Douarain
   (http://www.multimania.com/mavati) in December 2003.
   Stencil and colorkey fixes by David Raulo (david.raulo AT free DOT fr) in February 2004.
   Buffer overflow fix in RLE decompression by David Raulo in January 2008.
*/

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

#include "SDL_endian.h"
#include "SDL_image.h"

#ifdef LOAD_LBM


#define MAXCOLORS 256

/* Structure for an IFF picture ( BMHD = Bitmap Header ) */

typedef struct
{
    Uint16 w, h;		/* width & height of the bitmap in pixels */
    Sint16 x, y;		/* screen coordinates of the bitmap */
    Uint8 planes;		/* number of planes of the bitmap */
    Uint8 mask;			/* mask type ( 0 => no mask ) */
    Uint8 tcomp;		/* compression type */
    Uint8 pad1;			/* dummy value, for padding */
    Uint16 tcolor;		/* transparent color */
    Uint8 xAspect,		/* pixel aspect ratio */
          yAspect;
    Sint16  Lpage;		/* width of the screen in pixels */
    Sint16  Hpage;		/* height of the screen in pixels */
} BMHD;

int IMG_isLBM( SDL_RWops *src )
{
	int start;
	int   is_LBM;
	Uint8 magic[4+4+4];

	if ( !src ) 
		return 0;
	start = SDL_RWtell(src);
	is_LBM = 0;
	if ( SDL_RWread( src, magic, sizeof(magic), 1 ) )
	{
		if ( !memcmp( magic, "FORM", 4 ) &&
			( !memcmp( magic + 8, "PBM ", 4 ) ||
			  !memcmp( magic + 8, "ILBM", 4 ) ) )
		{
			is_LBM = 1;
		}
	}
	SDL_RWseek(src, start, RW_SEEK_SET);
	return( is_LBM );
}

SDL_Surface *IMG_LoadLBM_RW( SDL_RWops *src )
{
	int start;
	SDL_Surface *Image;
	Uint8       id[4], pbm, colormap[MAXCOLORS*3], *MiniBuf, *ptr, count, color, msk;
	Uint32      size, bytesloaded, nbcolors;
	Uint32      i, j, bytesperline, nbplanes, stencil, plane, h;
	Uint32      remainingbytes;
	Uint32      width;
	BMHD	      bmhd;
	char        *error;
	Uint8       flagHAM,flagEHB;

	Image   = NULL;
	error   = NULL;
	MiniBuf = NULL;

	if ( !src ) {
		/* The error message has been set in SDL_RWFromFile */
		return NULL;
	}
	start = SDL_RWtell(src);

	if ( !SDL_RWread( src, id, 4, 1 ) )
	{
		error="error reading IFF chunk";
		goto done;
	}

	/* Should be the size of the file minus 4+4 ( 'FORM'+size ) */
	if ( !SDL_RWread( src, &size, 4, 1 ) )
	{
		error="error reading IFF chunk size";
		goto done;
	}

	/* As size is not used here, no need to swap it */

	if ( memcmp( id, "FORM", 4 ) != 0 )
	{
		error="not a IFF file";
		goto done;
	}

	if ( !SDL_RWread( src, id, 4, 1 ) )
	{
		error="error reading IFF chunk";
		goto done;
	}

	pbm = 0;

	/* File format : PBM=Packed Bitmap, ILBM=Interleaved Bitmap */
	if ( !memcmp( id, "PBM ", 4 ) ) pbm = 1;
	else if ( memcmp( id, "ILBM", 4 ) )
	{
		error="not a IFF picture";
		goto done;
	}

	nbcolors = 0;

	memset( &bmhd, 0, sizeof( BMHD ) );
	flagHAM = 0;
	flagEHB = 0;

	while ( memcmp( id, "BODY", 4 ) != 0 )
	{
		if ( !SDL_RWread( src, id, 4, 1 ) ) 
		{
			error="error reading IFF chunk";
			goto done;
		}

		if ( !SDL_RWread( src, &size, 4, 1 ) )
		{
			error="error reading IFF chunk size";
			goto done;
		}

		bytesloaded = 0;

		size = SDL_SwapBE32( size );

		if ( !memcmp( id, "BMHD", 4 ) ) /* Bitmap header */
		{
			if ( !SDL_RWread( src, &bmhd, sizeof( BMHD ), 1 ) )
			{
				error="error reading BMHD chunk";
				goto done;
			}

			bytesloaded = sizeof( BMHD );

			bmhd.w 		= SDL_SwapBE16( bmhd.w );
			bmhd.h 		= SDL_SwapBE16( bmhd.h );
			bmhd.x 		= SDL_SwapBE16( bmhd.x );
			bmhd.y 		= SDL_SwapBE16( bmhd.y );
			bmhd.tcolor = SDL_SwapBE16( bmhd.tcolor );
			bmhd.Lpage 	= SDL_SwapBE16( bmhd.Lpage );
			bmhd.Hpage 	= SDL_SwapBE16( bmhd.Hpage );
		}

		if ( !memcmp( id, "CMAP", 4 ) ) /* palette ( Color Map ) */
		{
			if ( !SDL_RWread( src, &colormap, size, 1 ) )
			{
				error="error reading CMAP chunk";
				goto done;
			}

			bytesloaded = size;
			nbcolors = size / 3;
		}

		if ( !memcmp( id, "CAMG", 4 ) ) /* Amiga ViewMode  */
		{
			Uint32 viewmodes;
			if ( !SDL_RWread( src, &viewmodes, sizeof(viewmodes), 1 ) )
			{
				error="error reading CAMG chunk";
				goto done;
			}

			bytesloaded = size;
			viewmodes = SDL_SwapBE32( viewmodes );
			if ( viewmodes & 0x0800 )
				flagHAM = 1;
			if ( viewmodes & 0x0080 )
				flagEHB = 1;
		}

		if ( memcmp( id, "BODY", 4 ) )
		{
			if ( size & 1 )	++size;  	/* padding ! */
			size -= bytesloaded;
			/* skip the remaining bytes of this chunk */
			if ( size )	SDL_RWseek( src, size, RW_SEEK_CUR );
		}
	}

	/* compute some usefull values, based on the bitmap header */

	width = ( bmhd.w + 15 ) & 0xFFFFFFF0;  /* Width in pixels modulo 16 */

	bytesperline = ( ( bmhd.w + 15 ) / 16 ) * 2;

	nbplanes = bmhd.planes;

	if ( pbm )                         /* File format : 'Packed Bitmap' */
	{
		bytesperline *= 8;
		nbplanes = 1;
	}

	stencil = (bmhd.mask & 1);   /* There is a mask ( 'stencil' ) */

	/* Allocate memory for a temporary buffer ( used for
           decompression/deinterleaving ) */

	MiniBuf = (void *)malloc( bytesperline * (nbplanes + stencil) );
	if ( MiniBuf == NULL )
	{
		error="no enough memory for temporary buffer";
		goto done;
	}

	if ( ( Image = SDL_CreateRGBSurface( SDL_SWSURFACE, width, bmhd.h, (bmhd.planes==24 || flagHAM==1)?24:8, 0, 0, 0, 0 ) ) == NULL )
	   goto done;

	if ( bmhd.mask & 2 )               /* There is a transparent color */
		SDL_SetColorKey( Image, SDL_SRCCOLORKEY, bmhd.tcolor );

	/* Update palette informations */

	/* There is no palette in 24 bits ILBM file */
	if ( nbcolors>0 && flagHAM==0 )
	{
		/* FIXME: Should this include the stencil? See comment below */
		int nbrcolorsfinal = 1 << (nbplanes + stencil);
		ptr = &colormap[0];

		for ( i=0; i<nbcolors; i++ )
		{
			Image->format->palette->colors[i].r = *ptr++;
			Image->format->palette->colors[i].g = *ptr++;
			Image->format->palette->colors[i].b = *ptr++;
		}

		/* Amiga EHB mode (Extra-Half-Bright) */
		/* 6 bitplanes mode with a 32 colors palette */
		/* The 32 last colors are the same but divided by 2 */
		/* Some Amiga pictures save 64 colors with 32 last wrong colors, */
		/* they shouldn't !, and here we overwrite these 32 bad colors. */
		if ( (nbcolors==32 || flagEHB ) && (1<<bmhd.planes)==64 )
		{
			nbcolors = 64;
			ptr = &colormap[0];
			for ( i=32; i<64; i++ )
			{
				Image->format->palette->colors[i].r = (*ptr++)/2;
				Image->format->palette->colors[i].g = (*ptr++)/2;
				Image->format->palette->colors[i].b = (*ptr++)/2;
			}
		}

		/* If nbcolors < 2^nbplanes, repeat the colormap */
		/* This happens when pictures have a stencil mask */
		if ( nbrcolorsfinal > (1<<bmhd.planes) ) {
			nbrcolorsfinal = (1<<bmhd.planes);
		}
		for ( i=nbcolors; i < (Uint32)nbrcolorsfinal; i++ )
		{
			Image->format->palette->colors[i].r = Image->format->palette->colors[i%nbcolors].r;
			Image->format->palette->colors[i].g = Image->format->palette->colors[i%nbcolors].g;
			Image->format->palette->colors[i].b = Image->format->palette->colors[i%nbcolors].b;
		}
		if ( !pbm )
			Image->format->palette->ncolors = nbrcolorsfinal;
	}

	/* Get the bitmap */

	for ( h=0; h < bmhd.h; h++ )
	{
		/* uncompress the datas of each planes */

		for ( plane=0; plane < (nbplanes+stencil); plane++ )
		{
			ptr = MiniBuf + ( plane * bytesperline );

			remainingbytes = bytesperline;

			if ( bmhd.tcomp == 1 )	    /* Datas are compressed */
			{
				do
				{
					if ( !SDL_RWread( src, &count, 1, 1 ) )
					{
						error="error reading BODY chunk";
						goto done;
					}

					if ( count & 0x80 )
					{
						count ^= 0xFF;
						count += 2; /* now it */

						if ( ( count > remainingbytes ) || !SDL_RWread( src, &color, 1, 1 ) )
						{
							error="error reading BODY chunk";
							goto done;
						}
						memset( ptr, color, count );
					}
					else
					{
						++count;

						if ( ( count > remainingbytes ) || !SDL_RWread( src, ptr, count, 1 ) )
						{
						   error="error reading BODY chunk";
							goto done;
						}
					}

					ptr += count;
					remainingbytes -= count;

				} while ( remainingbytes > 0 );
			}
			else
			{
				if ( !SDL_RWread( src, ptr, bytesperline, 1 ) )
				{
					error="error reading BODY chunk";
					goto done;
				}
			}
		}

		/* One line has been read, store it ! */

		ptr = Image->pixels;
		if ( nbplanes==24 || flagHAM==1 )
			ptr += h * width * 3;
		else
			ptr += h * width;

		if ( pbm )                 /* File format : 'Packed Bitmap' */
		{
		   memcpy( ptr, MiniBuf, width );
		}
		else		/* We have to un-interlace the bits ! */
		{
			if ( nbplanes!=24 && flagHAM==0 )
			{
				size = ( width + 7 ) / 8;

				for ( i=0; i < size; i++ )
				{
					memset( ptr, 0, 8 );

					for ( plane=0; plane < (nbplanes + stencil); plane++ )
					{
						color = *( MiniBuf + i + ( plane * bytesperline ) );
						msk = 0x80;

						for ( j=0; j<8; j++ )
						{
							if ( ( plane + j ) <= 7 ) ptr[j] |= (Uint8)( color & msk ) >> ( 7 - plane - j );
							else 	                    ptr[j] |= (Uint8)( color & msk ) << ( plane + j - 7 );

							msk >>= 1;
						}
					}
					ptr += 8;
				}
			}
			else
			{
				Uint32 finalcolor = 0;
				size = ( width + 7 ) / 8;
				/* 24 bitplanes ILBM : R0...R7,G0...G7,B0...B7 */
				/* or HAM (6 bitplanes) or HAM8 (8 bitplanes) modes */
				for ( i=0; i<width; i=i+8 )
				{
					Uint8 maskBit = 0x80;
					for ( j=0; j<8; j++ )
					{
						Uint32 pixelcolor = 0;
						Uint32 maskColor = 1;
						Uint8 dataBody;
						for ( plane=0; plane < nbplanes; plane++ )
						{
							dataBody = MiniBuf[ plane*size+i/8 ];
							if ( dataBody&maskBit )
								pixelcolor = pixelcolor | maskColor;
							maskColor = maskColor<<1;
						}
						/* HAM : 12 bits RGB image (4 bits per color component) */
						/* HAM8 : 18 bits RGB image (6 bits per color component) */
						if ( flagHAM )
						{
							switch( pixelcolor>>(nbplanes-2) )
							{
								case 0: /* take direct color from palette */
									finalcolor = colormap[ pixelcolor*3 ] + (colormap[ pixelcolor*3+1 ]<<8) + (colormap[ pixelcolor*3+2 ]<<16);
									break;
								case 1: /* modify only blue component */
									finalcolor = finalcolor&0x00FFFF;
									finalcolor = finalcolor | (pixelcolor<<(16+(10-nbplanes)));
									break;
								case 2: /* modify only red component */
									finalcolor = finalcolor&0xFFFF00;
									finalcolor = finalcolor | pixelcolor<<(10-nbplanes);
									break;
								case 3: /* modify only green component */
									finalcolor = finalcolor&0xFF00FF;
									finalcolor = finalcolor | (pixelcolor<<(8+(10-nbplanes)));
									break;
							}
						}
						else
						{
							finalcolor = pixelcolor;
						}
						if ( SDL_BYTEORDER == SDL_LIL_ENDIAN )
						{
							*ptr++ = (Uint8)(finalcolor>>16);
							*ptr++ = (Uint8)(finalcolor>>8);
							*ptr++ = (Uint8)(finalcolor);
						}
						else
						{
							*ptr++ = (Uint8)(finalcolor);
							*ptr++ = (Uint8)(finalcolor>>8);
							*ptr++ = (Uint8)(finalcolor>>16);
						}

						maskBit = maskBit>>1;
					}
				}
			}
		}
	}

done:

	if ( MiniBuf ) free( MiniBuf );

	if ( error )
	{
		SDL_RWseek(src, start, RW_SEEK_SET);
		if ( Image ) {
			SDL_FreeSurface( Image );
			Image = NULL;
		}
		IMG_SetError( error );
	}

	return( Image );
}

#else /* LOAD_LBM */

/* See if an image is contained in a data source */
int IMG_isLBM(SDL_RWops *src)
{
	return(0);
}

/* Load an IFF type image from an SDL datasource */
SDL_Surface *IMG_LoadLBM_RW(SDL_RWops *src)
{
	return(NULL);
}

#endif /* LOAD_LBM */
