#include <clib/alib_protos.h>
#include <clib/datatypes_protos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/graphics_protos.h>
#include <clib/powerpc_protos.h>
#include <clib/macros.h>
#include <datatypes/pictureclass.h>
#include <exec/memory.h>
#include <setjmp.h>
#include <string.h>
#include "gif.h"

void write_gif(struct GIF *gif);

extern struct Library *SuperClassBase;

struct FFRHandle
{
struct StandardPacket fh_Pkt;
struct MsgPort *fh_ReplyPort;
struct FileHandle *fh_Handle;
UBYTE *fh_Buffer,*fh_Pre,*fh_Ptr;
LONG fh_BufferSize,fh_Left;
BOOL fh_Flag;
};

#define LM_to_uint(a,b) ((((b)&0xFF)<<8)|((a)&0xFF))
#define BitSet(byte,bit) ((byte)&(bit))
#define INTERLACE	0x40
#define COLORMAPFLAG	0x80

#define ReadOK(file,buffer,len) (FFRRead(file,buffer,len)==len)
#define MAX_LZW_BITS	12	
#define LZW_TABLE_SIZE (1<<MAX_LZW_BITS)

#define BUFFER_SIZE 16384
#define FIRST_BLOCK 512
#define MySetIoErr(e) ((Process *)FindTask(0))->pr_Result2=(e)

#define ERR_NOMEM 1
#define ERR_BADDATA 2

static void BeginRead(FFRHandle *FH,LONG Bytes)
{
struct StandardPacket *SP;
SP=&FH->fh_Pkt;
SP->sp_Msg.mn_Node.ln_Name=(char *)&SP->sp_Pkt;
SP->sp_Msg.mn_ReplyPort=FH->fh_ReplyPort;
SP->sp_Msg.mn_Length=sizeof(struct StandardPacket);
SP->sp_Pkt.dp_Link=&SP->sp_Msg;
SP->sp_Pkt.dp_Port=FH->fh_ReplyPort;
SP->sp_Pkt.dp_Type=ACTION_READ;
SP->sp_Pkt.dp_Arg1=FH->fh_Handle->fh_Arg1;
SP->sp_Pkt.dp_Arg2=(LONG)FH->fh_Pre;
SP->sp_Pkt.dp_Arg3=Bytes;
PutMsg(FH->fh_Handle->fh_Type,&SP->sp_Msg);
}

static LONG EndRead(FFRHandle *FH)
{
struct StandardPacket *SP;
SP=&FH->fh_Pkt;
WaitPort(FH->fh_ReplyPort);
GetMsg(FH->fh_ReplyPort);
if(SP->sp_Pkt.dp_Res1<0)
	{
	FH->fh_Flag=FALSE;
	MySetIoErr(SP->sp_Pkt.dp_Res2);
	return FALSE;
	}
FH->fh_Ptr=FH->fh_Pre;
FH->fh_Pre=FH->fh_Buffer;
FH->fh_Buffer=FH->fh_Ptr;
FH->fh_Left=SP->sp_Pkt.dp_Res1;
if(SP->sp_Pkt.dp_Res1==SP->sp_Pkt.dp_Arg3) BeginRead(FH,FH->fh_BufferSize);
else FH->fh_Flag=FALSE;
return TRUE;
}

static void CloseFFR(FFRHandle *FH)
{
if(FH->fh_Flag)
	{
	WaitPort(FH->fh_ReplyPort);
	GetMsg(FH->fh_ReplyPort);
	}
FreeVecPPC(FH->fh_Pre);
FreeVecPPC(FH->fh_Buffer);
DeleteMsgPort(FH->fh_ReplyPort);
FreeVecPPC(FH);
}

static struct FFRHandle *OpenFFR(BPTR Handle)
{
LONG BufferSize,FirstBlock;
struct InfoData *info;
BPTR DirLock;
FFRHandle *FH;
BufferSize=BUFFER_SIZE;
FirstBlock=FIRST_BLOCK;
if(info=(struct InfoData *)AllocVecPPC(sizeof(struct InfoData),MEMF_PUBLIC,32))
	{
	if(DirLock=ParentOfFH(Handle))
		{
		if(Info(DirLock,info))
		if(info->id_BytesPerBlock>0)
			{
			FirstBlock=info->id_BytesPerBlock;
			for(BufferSize=FirstBlock;BufferSize<BUFFER_SIZE;BufferSize<<=1);
			}
		UnLock(DirLock);
		}
	FreeVecPPC(info);
	}
if(FH=(FFRHandle *)AllocVecPPC(sizeof(struct FFRHandle),MEMF_PUBLIC|MEMF_CLEAR,32))
	{
	if(FH->fh_ReplyPort=CreateMsgPort())
		{
		if(FH->fh_Buffer=(UBYTE *)AllocVecPPC(BufferSize,MEMF_PUBLIC,32))
			{
			if(FH->fh_Pre=(UBYTE *)AllocVecPPC(BufferSize,MEMF_PUBLIC,32))
				{
				FH->fh_Handle=(FileHandle *)BADDR(Handle);
				FH->fh_BufferSize=BufferSize;
				FH->fh_Flag=TRUE;
				BeginRead(FH,FirstBlock-(Seek(Handle,0,OFFSET_CURRENT)%FirstBlock));
				return FH;
				}
			FreeVecPPC(FH->fh_Buffer);
			}
		DeleteMsgPort(FH->fh_ReplyPort);
		}
	FreeVecPPC(FH);
	}
MySetIoErr(ERROR_NO_FREE_STORE);
return 0;
}

static LONG FFRGetC(FFRHandle *FH)
{
if(FH->fh_Left)
	{
	FH->fh_Left--;
	return *FH->fh_Ptr++;
	}
if(!FH->fh_Flag) MySetIoErr(0);
else
if(EndRead(FH))
if(FH->fh_Left)
	{
	FH->fh_Left--;
	return *FH->fh_Ptr++;
	}
else MySetIoErr(0);
return -1;
}

static LONG FFRRead(FFRHandle *FH,APTR Buffer,LONG Bytes)
{
LONG Left;
UBYTE *Ptr;
if(Bytes<=FH->fh_Left)
	{
	memcpy(Buffer,FH->fh_Ptr,Bytes);
	FH->fh_Ptr+=Bytes;
	FH->fh_Left-=Bytes;
	return Bytes;
	}
Left=Bytes;
Ptr=(UBYTE *)Buffer;
while(Left>0)
	{
	LONG Size=MIN(FH->fh_Left,Left);
	if(Size)
		{
		memcpy(Ptr,FH->fh_Ptr,Size);
		FH->fh_Ptr+=Size;
		FH->fh_Left-=Size;
		Ptr+=Size;
		Left-=Size;
		}
	if(Left)
	if(!FH->fh_Flag) break;
	else
	if(!EndRead(FH)) return -1;
	}
return Bytes-Left;
}

__inline long ReadByte(struct GIF *gif)
{
long c=FFRGetC(gif->input_file);
if(c<0) longjmp(gif->jmpbuf,ERR_BADDATA);
return c;
}

static long GetDataBlock(struct GIF *gif,char *buf)
{
long count=ReadByte(gif);
if(count>0)
	{
	if(!ReadOK(gif->input_file,buf,count)) longjmp(gif->jmpbuf,ERR_BADDATA);
	}
return count;
}

static void SkipDataBlocks(struct GIF *gif)
{
char buf[256];
while(GetDataBlock(gif,buf)>0);
}

/*
static void DoExtension(struct GIF *gif)
{
long c=ReadByte(gif);
SkipDataBlocks(gif);
}
*/

static void ReInitLZW(struct GIF *gif)
{
gif->code_size=gif->input_code_size+1;
gif->limit_code=gif->clear_code<<1;
gif->max_code=gif->clear_code+2;
gif->sp=gif->symbol_stack;
}

static void InitLZWCode(struct GIF *gif)
{
gif->last_byte=2;
gif->last_bit=0;
gif->cur_bit=0;
gif->out_of_blocks=FALSE;
gif->clear_code=1<<gif->input_code_size;
gif->end_code=gif->clear_code+1;
gif->first_time=TRUE;
ReInitLZW(gif);
}

static long GetCode(struct GIF *gif)
{
long accum,offs,count;
while((gif->cur_bit+gif->code_size)>gif->last_bit)
	{
	if(gif->out_of_blocks) return gif->end_code;
	gif->code_buf[0]=gif->code_buf[gif->last_byte-2];
	gif->code_buf[1]=gif->code_buf[gif->last_byte-1];
	if(!(count=GetDataBlock(gif,&gif->code_buf[2])))
		{
		gif->out_of_blocks=TRUE;
		return gif->end_code;
		}
	gif->cur_bit=(gif->cur_bit-gif->last_bit)+16;
	gif->last_byte=count+2;
	gif->last_bit=gif->last_byte*8;
	}
offs=gif->cur_bit>>3;
accum=gif->code_buf[offs+2]&0xFF;
accum<<=8;
accum|=gif->code_buf[offs+1]&0xFF;
accum<<=8;
accum|=gif->code_buf[offs]&0xFF;
accum>>=(gif->cur_bit&7);
gif->cur_bit+=gif->code_size;
return accum&((1<<gif->code_size)-1);
}

static long LZWReadByte(struct GIF *gif)
{
long code,incode;
if(gif->first_time)
	{
	gif->first_time=FALSE;
	code=gif->clear_code;
	}
else
	{
	if(gif->sp>gif->symbol_stack) return *(--gif->sp);
	code=GetCode(gif);
	}
if(code==gif->clear_code)
 	{
	ReInitLZW(gif);
	do
		{
		code=GetCode(gif);
		}
	while(code==gif->clear_code);
/*
	if(code>gif->clear_code) code=0;
*/
	gif->firstcode=gif->oldcode=code;
	return code;
	}
if(code==gif->end_code)
	{
	if(!gif->out_of_blocks)
		{
		SkipDataBlocks(gif);
		gif->out_of_blocks=TRUE;
		}
	return 0;
	}
incode=code;
if(code>=gif->max_code)
	{
	if(code>gif->max_code) incode=0;
	*(gif->sp++)=gif->firstcode;
	code=gif->oldcode;
	}
while(code>=gif->clear_code)
	{
	*(gif->sp++)=gif->symbol_tail[code];
	code=gif->symbol_head[code];
	}
gif->firstcode=code;
if((code=gif->max_code)<LZW_TABLE_SIZE)
	{
	gif->symbol_head[code]=gif->oldcode;
	gif->symbol_tail[code]=gif->firstcode;
	gif->max_code++;
	if((gif->max_code>=gif->limit_code)&&(gif->code_size<MAX_LZW_BITS))
		{
		gif->code_size++;
		gif->limit_code<<=1;
		}
	}
gif->oldcode=incode;
return gif->firstcode;
}

static void load_interlaced_image(struct GIF *gif)
{
ULONG i;
UBYTE *sptr=gif->interlaced_image;
for(i=gif->image_width*gif->image_height;i>0;i--) *(sptr++)=LZWReadByte(gif);
gif->pass2_offset=(gif->image_height+7)/8;
gif->pass3_offset=gif->pass2_offset+(gif->image_height+3)/8;
gif->pass4_offset=gif->pass3_offset+(gif->image_height+1)/4;
}

static void start_input_gif(struct GIF *gif,struct BitMapHeader *bh)
{
UBYTE colors[256*3];
UBYTE buf[256];
struct BitMap *bm;
ULONG width,height,depth;
ULONG colormaplen;
if(!ReadOK(gif->input_file,buf,6)) longjmp(gif->jmpbuf,ERR_BADDATA);
if(!ReadOK(gif->input_file,buf,7)) longjmp(gif->jmpbuf,ERR_BADDATA);
gif->image_width=width=LM_to_uint(buf[0],buf[1]);
gif->image_height=height=LM_to_uint(buf[2],buf[3]);
gif->depth=depth=(buf[4]&0x07)+1;
colormaplen=1<<depth;
bh->bmh_PageWidth=width;
bh->bmh_PageHeight=height;

if(BitSet(buf[4],COLORMAPFLAG))
	{
	if(!ReadOK(gif->input_file,colors,(1<<depth)*3)) longjmp(gif->jmpbuf,ERR_BADDATA);
	}
for(;;)
	{
	long c=ReadByte(gif);
	if(c==';') longjmp(gif->jmpbuf,ERR_BADDATA);
	if(c=='!')
		{
/* GIF89a extension */
//		DoExtension(gif);
		long count;
		c=ReadByte(gif);
		do
			{
			count=ReadByte(gif);
			if(count>0)
				{
				if(!ReadOK(gif->input_file,buf,count)) longjmp(gif->jmpbuf,ERR_BADDATA);
				switch(c)
					{
					case 0xF9:
						if(buf[0])
							{
							bh->bmh_Masking=mskHasTransparentColor;
							bh->bmh_Transparent=buf[3];
							}
						break;
					}
				}
			}
		while(count>0);
      continue;
		}
	if(c!=',') continue;
	if(!ReadOK(gif->input_file,buf,9)) longjmp(gif->jmpbuf,ERR_BADDATA);
	gif->image_width=width=LM_to_uint(buf[4],buf[5]);
	gif->image_height=height=LM_to_uint(buf[6],buf[7]);
	gif->is_interlaced=BitSet(buf[8],INTERLACE);
	if(BitSet(buf[8],COLORMAPFLAG))
		{
		gif->depth=depth=(buf[8]&0x07)+1;
		colormaplen=1<<depth;
		if(!ReadOK(gif->input_file,colors,colormaplen*3)) longjmp(gif->jmpbuf,ERR_BADDATA);
		}
	gif->input_code_size=ReadByte(gif);
	if(gif->input_code_size<2||gif->input_code_size>=MAX_LZW_BITS)
		{
		longjmp(gif->jmpbuf,ERR_BADDATA);
		}
	break;
	}
bh->bmh_Left=0;
bh->bmh_Top=0;
bh->bmh_Width=width;
bh->bmh_Height=height;
bh->bmh_Depth=depth;
bh->bmh_Compression=cmpByteRun1;
bh->bmh_XAspect=0;
bh->bmh_YAspect=0;

if(SuperClassBase->lib_Version>=43)
	{
	ULONG error;
	SetDTAttrs(gif->obj,0,0,DTA_ErrorNumber,&error,DTA_NominalHoriz,width,DTA_NominalVert,height,PDTA_SourceMode,PMODE_V43,PDTA_ModeID,0,PDTA_NumColors,colormaplen,TAG_END);
	if(error) longjmp(gif->jmpbuf,ERR_NOMEM);
	}
else
	{
	if(!(bm=AllocBitMap(width,height,depth,BMF_CLEAR|BMF_DISPLAYABLE,0))) longjmp(gif->jmpbuf,ERR_NOMEM);
	SetDTAttrs(gif->obj,0,0,DTA_NominalHoriz,width,DTA_NominalVert,height,PDTA_BitMap,bm,
	PDTA_ModeID,BestModeID(BIDTAG_NominalWidth,width,BIDTAG_NominalHeight,height,BIDTAG_DesiredWidth,width,BIDTAG_DesiredHeight,height,BIDTAG_Depth,depth,TAG_END),
	PDTA_NumColors,colormaplen,
	TAG_END);
	}
gif->bm=bm;

	{
	ULONG *cregs;
	UBYTE *cr;
	GetDTAttrs(gif->obj,PDTA_ColorRegisters,&cr,PDTA_CRegs,&cregs,TAG_END);
	if(cr&&cregs)
		{
		ULONG i,n=colormaplen*3;
		for(i=0;i<n;i++)
			{
			*cr=colors[i];
			*(cregs++)=(*(cr++))<<24;
			}
		}
	}

if(!(gif->symbol_head=(UWORD *)AllocVecPPC(LZW_TABLE_SIZE*sizeof(UWORD),MEMF_CLEAR,32))) longjmp(gif->jmpbuf,ERR_NOMEM);
if(!(gif->symbol_tail=(UBYTE *)AllocVecPPC(LZW_TABLE_SIZE*sizeof(UBYTE),MEMF_CLEAR,32))) longjmp(gif->jmpbuf,ERR_NOMEM);
if(!(gif->symbol_stack=(UBYTE *)AllocVecPPC(LZW_TABLE_SIZE*sizeof(UBYTE),MEMF_CLEAR,32))) longjmp(gif->jmpbuf,ERR_NOMEM);
InitLZWCode(gif);
if(gif->is_interlaced)
	{
	if(!(gif->interlaced_image=(UBYTE *)AllocVecPPC(width*height,MEMF_CLEAR,32))) longjmp(gif->jmpbuf,ERR_NOMEM);
	}
if(!(gif->buffer=(UBYTE *)AllocVecPPC(width*height,MEMF_CLEAR,32))) longjmp(gif->jmpbuf,ERR_NOMEM);
}

static void free_input_gif(struct GIF *gif)
{
FreeVecPPC(gif->symbol_head);
FreeVecPPC(gif->symbol_tail);
FreeVecPPC(gif->symbol_stack);
if(gif->is_interlaced) FreeVecPPC(gif->interlaced_image);
FreeVecPPC(gif->buffer);
}

ULONG GetPicturePPC(Object *obj,BPTR file,struct BitMapHeader *bh)
{
ULONG error=0;
struct GIF *gif;
if(gif=(struct GIF *)AllocVecPPC(sizeof(struct GIF),MEMF_CLEAR,32))
	{
	struct FFRHandle *fr;
	if(fr=OpenFFR(file))
		{
		long err;
		gif->obj=obj;
		gif->input_file=fr;
		if(!(err=setjmp(gif->jmpbuf)))
			{
			ULONG x,y;
			UBYTE *ptr;
			start_input_gif(gif,bh);
			ptr=gif->buffer;
			if(!gif->is_interlaced)
				{
				for(y=gif->image_width*gif->image_height;y>0;y--) *(ptr++)=LZWReadByte(gif);
				}
			else
				{
				load_interlaced_image(gif);
				for(y=0;y<gif->image_height;y++)
					{
					UBYTE *sptr;
					ULONG irow;
					switch(y&7)
						{
						case 0:
							irow=y>>3;
							break;
						case 4:
							irow=(y>>3)+gif->pass2_offset;
							break;
						case 2:
						case 6:
							irow=(y>>2)+gif->pass3_offset;
							break;
						default:
							irow=(y>>1)+gif->pass4_offset;
						}
					sptr=gif->interlaced_image+irow*gif->image_width;
					for(x=gif->image_width;x>0;x--) *(ptr++)=*(sptr++);
					}
				}
			write_gif(gif);
			}
		else if(err==ERR_NOMEM) error=ERROR_NO_FREE_STORE;
		else error=DTERROR_INVALID_DATA;
		free_input_gif(gif);
		CloseFFR(fr);
		}
	FreeVecPPC(gif);
	}
else error=ERROR_NO_FREE_STORE;
return error;
}
