#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	"gfxfunc.h"
#include	"picture.h"

#define	ELEMENTS(x)	( (sizeof(x)) / (sizeof(x[0])) )

int	SaveLBM(char *name, GXPICTURE *pic);
int	WriteFileSection(char *name, void *data, size_t length, FILE *file);


GXPICTURE	*LoadLBM(void *data, size_t length);
GXPICTURE	*LoadBMP(void *data, size_t length);
GXPICTURE	*LoadGIF(void *data, size_t length);
GXPICTURE	*LoadPCX(void *data, size_t length);
int	GetSection(	UBYTE **section, size_t *nsection, 
				void *data, char *key, size_t ndata);
UWORD	ReadWord(void *data);
ULONG	ReadLong(void *data);

void	WriteLong(void *output, long value);
void	WriteWord(void *output, unsigned short value);
void	WriteByte(void *output, char value);



GXPICTURE	*((*PictureFunction[])(void *,size_t ))={LoadLBM,LoadGIF,LoadPCX,LoadBMP};

int	HiEndian=1;

#define	NPICTYPES	(sizeof(PictureFunction)/sizeof(PictureFunction[0]))
//***************************************************************************
//	Convert file into picture data
//***************************************************************************
//	input: File name
//	output: Picture structure or zero
//***************************************************************************
GXPICTURE	*LoadPicture(char *name)
{
	void		*data;
	GXPICTURE	*pic;
	int			j;
	size_t		size;
	char		xx[]={0,0,0,1};

	HiEndian=( (*((long *)&xx[0]))==1);
	

	data=GXLoadFileToMemory(name, &size);
	if(!data){
		GXPrintf("Failed to load file\n");
		return	0;
	}
	for(j=0; j<NPICTYPES; j++){
		GXPrintf("Attempting to load as function type %d\n",j);
		pic=(PictureFunction[j])(data, size);
		if(pic){
			break;
		}
	}
	GXFreeMem(data);
	return	pic;
}

//***************************************************************************
//	Kill picture loaded with above function
//***************************************************************************
//	input:  pointer to picture data
//	output: Nothing
//***************************************************************************
void	KillPicture(GXPICTURE *pic)
{
	if(pic){
		if(pic->Palette){
			GXFreeMem(pic->Palette);
		}
		if(pic->Data){
			GXFreeMem(pic->Data);
		}
		GXFreeMem(pic);
	}
}


//	Positions of variables in bitmap header
#define bmh_w					0			
#define bmh_h					2
#define bmh_x					4
#define bmh_y					6
#define bmh_nPlanes				8
#define bmh_masking				9
#define bmh_compression			10
#define bmh_pad1				11
#define bmh_transparentColor	12
#define bmh_xAspect				14
#define bmh_yAspect				15
#define bmh_pageWidth			16
#define bmh_pageHeight			18
#define bmh_SIZEOF				20


//	Palette used in IFF picture data
typedef struct{
	UBYTE	Red,Green,Blue;
}IFFPALETTE;

GXPICTURE	*LoadLBM(void *data, size_t length)
{
	UBYTE		*lbmdata;
	UBYTE		*bmhd,*cmap,*intbmp;
	size_t		nsection,nfrom,nto,sizebitmap,nloop;
	GXPICTURE	*pic=0;
	GXPALETTE	*pgpal;
	int			j,ix,iy,BytesPerRow;
	UBYTE		depth;
	SBYTE		bb,*from,*to,*uncompressed,xx,yy;
	SBYTE		*plane[8],linedata[8];
	
	GXPrintf("Entry:LoadLBM()\n");

	//	Check the header
	GXPrintf("Checking header\n");
	from=(SBYTE *)data;
	if(	(from[0]!='F')||(from[1]!='O')||(from[2]!='R')||(from[3]!='M')||
		(from[8]!='I')||(from[9]!='L')||(from[10]!='B')||(from[11]!='M')){
		goto fail;
	}
	lbmdata=((UBYTE *)from+12);
	length-=12;

	//	Read bitmap header 
	if( !GetSection(&bmhd, &nsection, lbmdata, "BMHD", length)){
		goto fail;
	}
	if(nsection!=bmh_SIZEOF){
		goto fail;
	}
	depth=*(bmhd+bmh_nPlanes);
	if(depth>8){
		goto fail;
	}

	//Initialize the structure
	pic=(GXPICTURE *)GXGetMem(sizeof(GXPICTURE), 0);
	memset(pic, 0, sizeof(GXPICTURE));
	pic->Width=ReadWord(bmhd+bmh_w);
	pic->Height=ReadWord(bmhd+bmh_h);	
	pic->BytesAcross=pic->Width;
	GXPrintf("Bitmap dimensions %dx%dx%d\n",pic->Width,pic->Height,(int)depth);
	
	//Read the palette
	if(!GetSection(&cmap, &nsection, lbmdata, "CMAP", length)){
		goto fail;
	}
	if(nsection%3){
		goto fail;
	}
	pic->NPalette=nsection/3;
	if(pic->NPalette>256){
		goto fail;
	}
	pic->Palette=(GXPALETTE *)GXGetMem(pic->NPalette*sizeof(GXPALETTE), 0);
	pgpal=pic->Palette;
	for(j=0; j<pic->NPalette; j++){
		pgpal->Red=*cmap++;
		pgpal->Green=*cmap++;
		pgpal->Blue=*cmap++;
		pgpal++;
	}

	//**************************************************************
	//	Read the interleaved bitmap
	//**************************************************************
	if( !GetSection(&intbmp, &nsection, lbmdata, "BODY", length)){
		goto fail;
	}
	//	Get bytes in each row (rounded up to 16 bit boundary)
	BytesPerRow=(pic->Width/16)*2;
	if(pic->Width%16){
		BytesPerRow+=2;
	}
	sizebitmap=BytesPerRow*pic->Height*depth;
	uncompressed=to=(SBYTE *)GXGetMem(sizebitmap, 0);
	from=(SBYTE *)intbmp;
	bb=*(bmhd+bmh_compression);
	if(!bb){
		//non compressed
		GXPrintf("Doing non-compressed loading\n");
		if(sizebitmap!=nsection){
			GXPrintf("fail 0\n");
			goto fail;
		}
		memcpy(to, intbmp, sizebitmap);
	}else{
		//byte run
		GXPrintf("Doing compressed loading\n");
		nfrom=nsection;
		nto=sizebitmap;
		while(nfrom&&nto){
			if(nfrom<=0){
				GXPrintf("fail 1\n");
				goto fail;
			}
			bb=*from++;
			nfrom--;
			if(bb>=0){
				//Non Repeat bytes
				nloop=(size_t)(bb+1);
				if( (nloop>nfrom) || (nloop>nto) ){
					GXPrintf("fail 2\n");
					goto fail;
				}
				memcpy(to, from, nloop);
				to+=nloop;
				from+=nloop;
				nto-=nloop;
				nfrom-=nloop;
			}else{
				//Repeat bytes
				nloop=(size_t)(-bb+1);
				if(nfrom<=0){
					GXPrintf("fail 3\n");
					goto fail;
				}
				if(nto<nloop){
					GXPrintf("fail 4\n");
					goto fail;
				}
				memset(to, *from++, nloop);
				to+=nloop;
				nto-=nloop;
				nfrom--;
			}
		}
		GXPrintf("After interleaving bitmap\n");
		GXPrintf("nfrom=%d; nto=%d; nloop=%d\n",nfrom,nto,nloop);
	}	

	//	Convert from interleaved bitmap to byte map
	GXPrintf("Converting from interleaved bitmaps to byte map\n");
	pic->Data=(UBYTE *)GXGetMem(pic->Width*pic->Height, 0);
	if(!pic->Data)goto fail;
	to=(SBYTE *)pic->Data;
	for(iy=0; iy<pic->Height; iy++){
		plane[0]=uncompressed + BytesPerRow*depth*iy;
		for(j=0; j<depth; j++){
			plane[j]=plane[0]+BytesPerRow*j;
		}
		for(ix=0; ix<pic->Width; ix++){
			if(! (ix&7) ){
				for(j=0; j<depth; j++){
					linedata[j]=*(plane[j]);
					plane[j]++;
				}
			}
			xx=7-(ix&7);
			xx=(1<<xx);	//=bit to test
			yy=0;
			for(j=0; j<depth; j++){
				if(xx&linedata[j]){
					yy|=(1<<j);
				}
			}
			*to++=yy;
		}
	}
	GXFreeMem(uncompressed);
	GXPrintf("Loaded LBM!!\n");
	return	pic;
fail:
	GXPrintf("nfrom=%d; nto=%d; nloop=%d\n",nfrom,nto,nloop);
	GXPrintf("Failed to load as LBM\n");
	if(pic){
		if(pic->Data){
			GXFreeMem(pic->Data);
		}
		if(pic->Palette){
			GXFreeMem(pic->Palette);
		}
		GXFreeMem(pic);
	}
	if(uncompressed){
		GXFreeMem(uncompressed);
	}
	return	0;
}

int	GetSection(	UBYTE **section, size_t *nsection, 
				void *data, char *key, size_t ndata)
{
	char 	*from;
	int		j;
	size_t	len;

	GXPrintf("Looking for %s\n",key);
	from=(char *)data;
	while(1){
		for(j=0; j<4; j++){
			if(from[j]!=key[j]){
				break;
			}
		}
		if(j==4){
			// Data found!!
			*section=(UBYTE *)&from[8];
			*nsection=ReadLong(&from[4]);
			return	1;
		}
		len=ReadLong(&from[4])+8;
		if(len>=ndata){
			//	Ran out of data
			break;			
		}
		if(len&0x01)len+=1;		//move on to even boundary
		ndata-=len;
		from+=len;
	}
	return	0;
}

//********************************************************************
//	Convert a Hi-endian long word to the format of whatever the code
//	is being run on
//********************************************************************
ULONG	ReadLong(void *data)
{
	char	*from,to[4];
	
	if(HiEndian){
		//	Hi-endian machine
		return	*((long *)data);
	}else{
		//	Lo-endian machine
		from=(char *)data;
		to[0]=from[3];
		to[1]=from[2];
		to[2]=from[1];
		to[3]=from[0];
		return *((ULONG *)&to[0]);
	}
}
//********************************************************************
//	Convert a Hi-endian word to the format of whatever the code
//	is being run on
//********************************************************************
UWORD	ReadWord(void *data)
{
	char	*from,to[4];
	
	if(HiEndian){
		//	Hi-endian machine
		return	*((UWORD *)data);
	}else{
		//	Lo-endian machine
		from=(char *)data;
		to[0]=from[1];
		to[1]=from[0];
		return *((UWORD *)&to[0]);
	}
}

GXPICTURE	*LoadBMP(void *data, size_t length)
{
	return	0;
}
GXPICTURE	*LoadGIF(void *data, size_t length)
{
	return	0;
}
GXPICTURE	*LoadPCX(void *data, size_t length)
{
	return	0;
}


//***************************************************************************
//	Copy picture data
//***************************************************************************
//	input: Picture data
//	output: Copy of picture data or 0 if it went wrong
//***************************************************************************
GXPICTURE	*CopyPicture(GXPICTURE *pic)
{
	GXPICTURE	*pic2=0;
	size_t		size;
	
	size=sizeof(GXPICTURE);
	pic2=(GXPICTURE *)GXGetMem(size, 0);
	if(!pic2)goto fail;
	memcpy(pic2, pic, size);
	pic2->Data=0;
	pic2->Palette=0;
	
	size=pic->NPalette*sizeof(GXPALETTE);
	pic2->Palette=(GXPALETTE *)GXGetMem(size, 0);
	if(!pic2->Palette)goto fail;
	memcpy(pic2->Palette, pic->Palette, size);

	size=pic->Height*pic->Width;
	pic2->Data=(UBYTE *)GXGetMem(size, 0);
	if(!pic2->Data)goto fail;
	memcpy(pic2->Data, pic->Data, size);	
	return	pic2;
fail:
	if(pic2){
		if(pic2->Data){
			GXFreeMem(pic2->Data);
		}
		if(pic2->Palette){
			GXFreeMem(pic2->Palette);
		}
		GXFreeMem(pic2);
	}
	return	0;
	
}

int	SaveLBM(char *name, GXPICTURE *pic);

int	((*SaveFunction[])(char *,GXPICTURE *))={SaveLBM};

//***************************************************************************
//	Save picture data as file
//***************************************************************************
//	input: File name, picture data, format
//	output: did it work?
//***************************************************************************

int			SavePicture(char *name, GXPICTURE *pic, int format)
{
	if( (format<0) || (format>=ELEMENTS(SaveFunction)) )return 0;
	return SaveFunction[format](name, pic);
}

//	Positions of variables in bitmap header
#define bmh_w					0			
#define bmh_h					2
#define bmh_x					4
#define bmh_y					6
#define bmh_nPlanes				8
#define bmh_masking				9
#define bmh_compression			10
#define bmh_pad1				11
#define bmh_transparentColor	12
#define bmh_xAspect				14
#define bmh_yAspect				15
#define bmh_pageWidth			16
#define bmh_pageHeight			18
#define bmh_SIZEOF				20

int	SaveLBM(char *name, GXPICTURE *pic)
{
	UBYTE		*bmh=0,*cmap=0,*to,*bitmap=0,*from,*from2,*to2,*to3;
	UBYTE		pixel,mask;
	size_t		size_cmap,size_bitmap,size_total;
	char		xx[]={0,0,0,1};
	GXPALETTE	*pal;
	int			j,BytesAcross,ix,iy,iplane;	
	UBYTE		data[4];
	FILE		*file=0;
	int			success=0;
	char		FORMStr[]="FORM",ILBMStr[]="ILBM";

	HiEndian=( (*((long *)&xx[0]))==1);
	GXPrintf("Bitmap size = %dx%d\n",pic->Width,pic->Height);

	//	Make bitmap header
	bmh=(UBYTE *)GXGetMem(bmh_SIZEOF, MEMF_ZERO);
	if(!bmh)goto doneit;
	WriteWord( bmh+bmh_w, (UWORD)pic->Width);
	WriteWord( bmh+bmh_h, (UWORD)pic->Height);
	WriteByte( bmh+bmh_nPlanes, 8); 
	WriteByte( bmh+bmh_xAspect, 1);
	WriteByte( bmh+bmh_yAspect, 1);
	WriteWord( bmh+bmh_pageWidth, 320);
	WriteWord( bmh+bmh_pageHeight, 200);

	//	Write color map
	size_cmap=pic->NPalette*3;
	cmap=(UBYTE *)GXGetMem(size_cmap, MEMF_ZERO);
	if(!cmap)goto doneit;
	to=cmap;
	pal=pic->Palette;
	for(j=0; j<pic->NPalette; j++){
		*to++=pal->Red;
		*to++=pal->Green;
		*to++=pal->Blue;
		pal++;
	}
	
	//	Write interleaved bitmap
	BytesAcross=(pic->Width/16)*2;
	if(pic->Width%16)BytesAcross+=2;
	size_bitmap=BytesAcross*8*pic->Height;
	bitmap=(UBYTE *)GXGetMem(size_bitmap, MEMF_ZERO);
	if(!bitmap)goto doneit;
	to=bitmap;
	from=(UBYTE *)pic->Data;
	for(iy=0; iy<pic->Height; iy++){
		to2=to;
		from2=from;
		mask=0x80;
		for(ix=0; ix<pic->Width; ix++){
			pixel=*from2++;
			to3=to2;
			for(iplane=0; iplane<8; iplane++){
				if( pixel&0x01 ){
					*to3|=mask;
				}
				to3+=BytesAcross;
				pixel>>=1;
			}
			mask>>=1;
			if(!mask){
				mask=0x80;
				to2++;
			}
		}
		to+=(BytesAcross*8);
		from+=pic->BytesAcross;
	}
	
	//	Write data	
	file=fopen(name, "wb");
	if(4!=fwrite(FORMStr, 1, 4, file))goto doneit;
	size_total= (size_bitmap+8) + (size_cmap+8) + (bmh_SIZEOF+8) + 4;
	WriteLong(data, size_total);
	if(4!=fwrite(data, 1, 4, file))goto doneit;
	if(4!=fwrite(ILBMStr, 1, 4, file))goto doneit;
	if( !WriteFileSection("BMHD",bmh, bmh_SIZEOF, file) ) goto doneit;
	if( !WriteFileSection("CMAP",cmap, size_cmap, file) ) goto doneit;
	if( !WriteFileSection("BODY",bitmap, size_bitmap, file) ) goto doneit;
	success=1;
	
	//	Free up stuff
doneit:
	if(file)fclose(file);
	if(cmap)GXFreeMem(cmap);
	if(bitmap)GXFreeMem(bitmap);
	if(bmh)GXFreeMem(bmh);
	return success;
}

int	WriteFileSection(char *name, void *data, size_t length, FILE *file)
{
	UBYTE	longdata[4],zero=0;

	if(strlen(name)!=4)return 0;
	if(4!=fwrite(name, 1, 4, file))return 0;
	WriteLong(longdata, length);
	if(4!=fwrite(longdata, 1, 4, file))return 0;
	if(length!=fwrite(data, 1, length, file))return 0;
	if(length&0x01){		//make sure there are an even number of bytes
		if(1!=fwrite(&zero, 1, 1, file))return 0;
	}
	return 1;
}


void	WriteWord(void *output, unsigned short value)
{
	UBYTE	*from,*to;

	from=(UBYTE *)&value;
	to=(UBYTE *)output;
	if(HiEndian){
		to[0]=from[0];
		to[1]=from[1];
	}else{
		to[0]=from[1];
		to[1]=from[0];
	}
}

void	WriteLong(void *output, long value)
{
	UBYTE	*from,*to;

	from=(UBYTE *)&value;
	to=(UBYTE *)output;
	if(HiEndian){
		to[0]=from[0];
		to[1]=from[1];
		to[2]=from[2];
		to[3]=from[3];
	}else{
		to[0]=from[3];
		to[1]=from[2];
		to[2]=from[1];
		to[3]=from[0];
	}
}


void	WriteByte(void *output, char value)
{
	*((char *)output)=value;
}

