/*
 * Heavilly based on:
 *
 * FLI file parser for the MPlayer program by Mike Melanson
 * FLI Decoder for MPlayer (C) 2001 Mike Melanson
 *
 * Additional code and bug fixes by Roberto Togni
 * For information on the FLI format, as well as various traps to
 * avoid while programming one, visit:
 * http://www.pcisys.net/~melanson/codecs/
 *
 * Screwed up by Vladimir A. Smirnov.
 */

#include <v_lib.h>

#if 0

/* begin libfli.h */
/* fixme: are these needed ???
 * unified interface is far better ! */
 
#ifdef __cplusplus
extern "C"
#endif /* __cplusplus */

	vhandle_t	V_GLOBAL fliOpenRead(char *name);

	fileControl(handle,vid_frame_width,&width_ret);
	fileControl(handle,vid_frame_height,&width_ret);
	fileControl(handle,vid_num_frames,&numframes_ret);
	fileControl(handle,vid_fps,&fps_ret);
	fileRead(...,len) : len _must_ be 'framesize' (width * height * 4)
	fileSeek(): offset must be multiply of 'framesize'

	/* helpers & wrappers: to vidstrm.c */
	vboolean	V_GLOBAL
	vidFrameDimensions(vhandle_t handle,u32_t *width_ret,u32_t *height_ret)
	{
		vboolean ok = false;
		/* assume success of frame_height after successful frame_width */
		if(width_ret)
			ok = fileControl(handle,vid_frame_width,&width_ret);
		if(height_ret)
			ok = fileControl(handle,vid_frame_height,&height_ret);
		return ok;
	}
	vboolean	V_GLOBAL
	vidNumFrames(vhandle_t handle,u32_t *numframes_ret)
	{
		if(numframes_ret)
			return fileControl(handle,vid_num_frames,&numframes_ret);
		return false;
	}
	vboolean	V_GLOBAL
	vidFPS(vhandle_t handle,u32_t *fps_ret)
	{
		if(fps_ret)
			return fileControl(handle,vid_fps,&fps_ret);
		return false;
	}
	/* frame_num: number of frame (multiplied by framesize befor fcntl()) */
	vboolean	V_GLOBAL
	vidSeekToFrame(vhandle_t handle,u32_t frame_num)
	{
		u32_t w, h;
		u64_t newpos = frame_num * w * h << 2;
		if(!vidFrameDimensions(handle,&w,&h))
			return false;
		return (newpos == fileSeek(handle,SEEK_SET,newpos)) ? true : false;
	}
   

#ifdef __cplusplus
extern "C"
#endif /* __cplusplus */

#endif /* 0 */

#define LE_16(x) (LE2ME_SHORT(*(u16_t*)(x)))
#define LE_32(x) (LE2ME_LONG(*(u32_t*)(x)))

#define FLI_256_COLOR 4
#define FLI_DELTA	 7
#define FLI_COLOR	 11
#define FLI_LC		12
#define FLI_BLACK	 13
#define FLI_BRUN	  15
#define FLI_COPY	  16
#define FLI_MINI	  18

typedef struct fli_frames_s
{

	libv_object_t	base;
	
	/*
	mmio_codec_t	codec;
	mmio_container_t container;
	base->
	
	???
	
	*/
	u32_t			num_frames;
	u32_t			current_frame;
	vhandle_t		dhandle;
	u32_t			width, height, speed, fps;
	vec_t			frametime;
	u32_t			*filepos;
	u32_t			*frame_size;
	u8_t			*palette;
	u8_t			*raw_data;
	u32_t			raw_data_size;
	u8_t			*ghost_image;
	u8_t			*decoded;
	
}	fli_frames_t, *fli_frames_p;

V_LOCAL void fli_seek_to_frame(fli_frames_t *frames,u32_t newpos)
{
	if(newpos < 0)
		newpos = 0;
	else
  		if(newpos > frames->num_frames)
			newpos = frames->num_frames;
	frames->current_frame = newpos;
	if(frames->current_frame == 0)
	{
		memZero(frames->palette,1024);
		memZero(frames->ghost_image,frames->width * frames->height);
	}
}

V_LOCAL vboolean fli_fill_buffer(fli_frames_t *frames,vboolean loop)
{
	u32_t frame_size;
	if(frames->current_frame >= frames->num_frames)
	{
		if(loop)
			fli_seek_to_frame(frames,0);
		else
			return false;
	}
	frame_size = frames->frame_size[frames->current_frame];
	fileSeek(frames->dhandle,frames->filepos[frames->current_frame],SEEK_SET);
	frames->raw_data_size = fileRead(frames->dhandle,
		frames->raw_data,frame_size);
	frames->current_frame++;
	return frames->raw_data_size == frame_size;
}

V_LOCAL void fli_free_framestruct(fli_frames_p frames)
{
	memFree(frames->raw_data);
	memFree(frames->decoded);
	memFree(frames->ghost_image);
	fileClose(frames->dhandle);
	memFree(frames);
}

V_LOCAL fli_frames_p fli_open_read(vhandle_t dhandle)
{
	fli_frames_t *frames;
	u32_t num_frames;
	u32_t frame_number;
	u32_t frame_size, frame_size_max;
	u32_t magic_number;
	/* go back to the beginning */
	fileSeek(dhandle,4,SEEK_SET);
	magic_number = fileGetLittleShort(dhandle);
	if((magic_number != 0xAF11) && (magic_number != 0xAF12))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"fli_open_read(): wrong magic (%04X)\n",
			magic_number);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	/* fetch the number of frames */
	num_frames = fileGetLittleShort(dhandle);
	/* allocate enough entries for the indices */
	frames = (fli_frames_t *)memAlloc(sizeof(fli_frames_t)+
		2 * num_frames * sizeof(u32_t));
	frames->filepos = (u32_t *)(frames + 1);
	frames->frame_size = frames->filepos + num_frames;
	frames->num_frames = num_frames;
	frames->current_frame = 0;
	frames->dhandle = dhandle;
	frames->width = fileGetLittleShort(dhandle);
	frames->height = fileGetLittleShort(dhandle);
	/* skip the video depth and flags */
	fileSeek(dhandle,4,SEEK_CUR);
	/* get the speed */
	frames->speed = fileGetLittleShort(dhandle);
	if (frames->speed == 0)
		frames->speed = 1;
	if (magic_number == 0xAF11)
		frames->speed = (frames->speed * 100) / 7;
	frames->fps = 1000 / frames->speed;
	frames->frametime = (vec_t)1 / frames->fps;
	/* build the frame index */
	fileSeek(dhandle,128,SEEK_SET);
	frame_number = 0;
	frame_size_max = 16;

	/* FIXME: skip 'mini' FLC chunk */
	/* FIXME: skip last delta chunk, we are not using it at all */

	while((!fileEof(dhandle)) && (frame_number < num_frames))
	{
		frames->filepos[frame_number] = fileTell(dhandle);
		frame_size = fileGetLittleLong(dhandle);
		magic_number = fileGetLittleShort(dhandle);
		fileSeek(dhandle,frame_size - 6,SEEK_CUR);
		/* if this chunk has the right magic number, index it */
		if((magic_number == 0xF1FA) || (magic_number == 0xF5FA))
		{
			frames->frame_size[frame_number] = frame_size;
			if(frame_size_max < frame_size)
				frame_size_max = frame_size;
			frame_number++;
		}
	}
	/* save the actual number of frames indexed */
	frames->num_frames = frame_number;
	/* allocate storage for palette, raw data, decoded RGBA data and ghost */
	frames->raw_data = (u8_t*)memAlloc(INT_ALIGN(frame_size_max));
	frames->decoded = (u8_t*)memAlloc(frames->width * frames->height * 4);
	frames->ghost_image = (u8_t*)memAlloc(frames->width * frames->height);
	frames->palette = (u8_t*)memAlloc(1024);
	memZero(frames->palette,1024);
	return frames;
}

V_LOCAL void
decode_fli_frame(fli_frames_p fliframes)
{
	unsigned char *encoded = fliframes->raw_data;
	unsigned char *decoded = fliframes->decoded;
	int width = fliframes->width;
	int height = fliframes->height;
	int bytes_per_pixel = 4;
	int stream_ptr = 0;
	int stream_ptr_after_color_chunk;
	int pixel_ptr;
	int palette_ptr1;
	int palette_ptr2;
	unsigned char *palette = fliframes->palette;
	unsigned char palette_idx1;
	unsigned char palette_idx2;

	unsigned int frame_size;
	int num_chunks;

	unsigned int chunk_size;
	int chunk_type;

	int i, j;

	int color_packets;
	int color_changes;
	int color_scale;

	int lines;
	int compressed_lines;
	int starting_line;
	signed short line_packets;
	int y_ptr;
	int line_inc = width * bytes_per_pixel;
	signed char byte_run;
	int pixel_skip;
	int update_whole_frame = 0; /* Palette change flag */
	
	unsigned char *fli_ghost_image = fliframes->ghost_image;
	
	int ghost_pixel_ptr;
	int ghost_y_ptr;

	frame_size = LE_32(&encoded[stream_ptr]);
	stream_ptr += 6;  /* skip the magic number */
	num_chunks = LE_16(&encoded[stream_ptr]);
	stream_ptr += 10;  /* skip padding */

	/* iterate through the chunks */
	frame_size -= 16;
	
	while ((frame_size > 0) && (num_chunks > 0))
	{
		chunk_size = LE_32(&encoded[stream_ptr]);
		stream_ptr += 4;
		chunk_type = LE_16(&encoded[stream_ptr]);
		stream_ptr += 2;

		switch (chunk_type)
		{
			case FLI_256_COLOR:
			case FLI_COLOR:
				stream_ptr_after_color_chunk = stream_ptr + chunk_size - 6;
				if (chunk_type == FLI_COLOR)
					color_scale = 4;
				else
					color_scale = 1;
				/* set up the palette */
				color_packets = LE_16(&encoded[stream_ptr]);
				stream_ptr += 2;
				palette_ptr1 = 0;
				for (i = 0; i < color_packets; i++)
				{
					/* first byte is how many colors to skip */
					palette_ptr1 += (encoded[stream_ptr++] * 4);
					/* wrap around, for good measure */
					if (palette_ptr1 >= 1024)
						palette_ptr1 = 0;
					/* next byte indicates how many entries to change */
					color_changes = encoded[stream_ptr++];
					/* if there are 0 color changes, there are actually 256 */
					if (color_changes == 0)
						color_changes = 256;
					for (j = 0; j < color_changes; j++)
					{
						palette[palette_ptr1++] = encoded[stream_ptr + 0] * color_scale;
						palette[palette_ptr1++] = encoded[stream_ptr + 1] * color_scale;
						palette[palette_ptr1++] = encoded[stream_ptr + 2] * color_scale;
						palette_ptr1++;
						stream_ptr += 3;
					}
				}
				/*
				 * color chunks sometimes have weird 16-bit alignment issues;
				 * therefore, take the hardline approach and set the stream_ptr
				 * to the value calculate w.r.t. the size specified by the color
				 * chunk header
				 */
				stream_ptr = stream_ptr_after_color_chunk;
				/*
				 * Palette has changed, must update frame
				 */
				update_whole_frame = 1;
				break;
			case FLI_DELTA:
				y_ptr = ghost_y_ptr = 0;
				compressed_lines = LE_16(&encoded[stream_ptr]);
				stream_ptr += 2;
				while (compressed_lines > 0)
				{
					line_packets = LE_16(&encoded[stream_ptr]);
					stream_ptr += 2;
					if (line_packets < 0)
					{
						line_packets = -line_packets;
						y_ptr += (line_packets * line_inc);
						ghost_y_ptr += (line_packets * width);
					}
					else
					{
						pixel_ptr = y_ptr;
						ghost_pixel_ptr = ghost_y_ptr;
						for (i = 0; i < line_packets; i++)
						{
							/* account for the skip bytes */
							pixel_skip = encoded[stream_ptr++];
							pixel_ptr += pixel_skip * bytes_per_pixel;
							ghost_pixel_ptr += pixel_skip;
							byte_run = encoded[stream_ptr++];
							if (byte_run < 0)
							{
								byte_run = -byte_run;
								palette_ptr1 = (palette_idx1 = encoded[stream_ptr++]) * 4;
								palette_ptr2 = (palette_idx2 = encoded[stream_ptr++]) * 4;
								for (j = 0; j < byte_run; j++)
								{
									fli_ghost_image[ghost_pixel_ptr++] = palette_idx1;
									decoded[pixel_ptr++] = palette[palette_ptr1 + 0];
									decoded[pixel_ptr++] = palette[palette_ptr1 + 1];
									decoded[pixel_ptr++] = palette[palette_ptr1 + 2];
									if (bytes_per_pixel == 4) /* 32bpp */
										pixel_ptr++;
									fli_ghost_image[ghost_pixel_ptr++] = palette_idx2;
									decoded[pixel_ptr++] = palette[palette_ptr2 + 0];
									decoded[pixel_ptr++] = palette[palette_ptr2 + 1];
									decoded[pixel_ptr++] = palette[palette_ptr2 + 2];
									if (bytes_per_pixel == 4) /* 32bpp */
										pixel_ptr++;
								}
							}
							else
							{
								for (j = 0; j < byte_run * 2; j++)
								{
									palette_ptr1 = (palette_idx1 = encoded[stream_ptr++]) * 4;
									fli_ghost_image[ghost_pixel_ptr++] = palette_idx1;
									decoded[pixel_ptr++] = palette[palette_ptr1 + 0];
									decoded[pixel_ptr++] = palette[palette_ptr1 + 1];
									decoded[pixel_ptr++] = palette[palette_ptr1 + 2];
									if (bytes_per_pixel == 4) /* 32bpp */
										pixel_ptr++;
								}
							}
						}
						y_ptr += line_inc;
						ghost_y_ptr += width;
						compressed_lines--;
					}
				}
				break;
			case FLI_LC:
				/* line compressed */
				starting_line = LE_16(&encoded[stream_ptr]);
				stream_ptr += 2;
				y_ptr = starting_line * line_inc;
				ghost_y_ptr = starting_line * width;

				compressed_lines = LE_16(&encoded[stream_ptr]);
				stream_ptr += 2;
				while (compressed_lines > 0)
				{
					pixel_ptr = y_ptr;
					ghost_pixel_ptr = ghost_y_ptr;
					line_packets = encoded[stream_ptr++];
					if (line_packets > 0)
					{
						for (i = 0; i < line_packets; i++)
						{
							/* account for the skip bytes */
							pixel_skip = encoded[stream_ptr++];
							pixel_ptr += pixel_skip * bytes_per_pixel;
							ghost_pixel_ptr += pixel_skip;
							byte_run = encoded[stream_ptr++];
							if (byte_run > 0)
							{
								for (j = 0; j < byte_run; j++)
								{
									palette_ptr1 = (palette_idx1 = encoded[stream_ptr++]) * 4;
									fli_ghost_image[ghost_pixel_ptr++] = palette_idx1;
									decoded[pixel_ptr++] = palette[palette_ptr1 + 0];
									decoded[pixel_ptr++] = palette[palette_ptr1 + 1];
									decoded[pixel_ptr++] = palette[palette_ptr1 + 2];
									if (bytes_per_pixel == 4) /* 32bpp */
										pixel_ptr++;
								}
							}
							else
							{
								byte_run = -byte_run;
								palette_ptr1 = (palette_idx1 = encoded[stream_ptr++]) * 4;
								for (j = 0; j < byte_run; j++)
								{
									fli_ghost_image[ghost_pixel_ptr++] = palette_idx1;
									decoded[pixel_ptr++] = palette[palette_ptr1 + 0];
									decoded[pixel_ptr++] = palette[palette_ptr1 + 1];
									decoded[pixel_ptr++] = palette[palette_ptr1 + 2];
									if (bytes_per_pixel == 4) /* 32bpp */
										pixel_ptr++;
								}
							}
						}
					}
					y_ptr += line_inc;
					ghost_y_ptr += width;
					compressed_lines--;
				}
				break;
			case FLI_BLACK:
				/*
				 * set the whole frame to color 0 (which is usually black)
				 * by clearing the ghost image and trigger a full frame update
				 */
				memZero(fli_ghost_image, width * height * sizeof(unsigned char));
				update_whole_frame = 1;
				break;
			case FLI_BRUN:
				/*
				 * byte run compression
				 */
				y_ptr = 0;
				ghost_y_ptr = 0;
				for (lines = 0; lines < height; lines++)
				{
					pixel_ptr = y_ptr;
					ghost_pixel_ptr = ghost_y_ptr;
					line_packets = encoded[stream_ptr++];
					for (i = 0; i < line_packets; i++)
					{
						byte_run = encoded[stream_ptr++];
						if (byte_run > 0)
						{
							palette_ptr1 = (palette_idx1 = encoded[stream_ptr++]) * 4;
							for (j = 0; j < byte_run; j++)
							{
								fli_ghost_image[ghost_pixel_ptr++] = palette_idx1;
								decoded[pixel_ptr++] = palette[palette_ptr1 + 0];
								decoded[pixel_ptr++] = palette[palette_ptr1 + 1];
								decoded[pixel_ptr++] = palette[palette_ptr1 + 2];
								if (bytes_per_pixel == 4) /* 32bpp */
									pixel_ptr++;
							}
						}
						else  /* copy bytes if byte_run < 0 */
						{
							byte_run = -byte_run;
							for (j = 0; j < byte_run; j++)
							{
								palette_ptr1 = (palette_idx1 = encoded[stream_ptr++]) * 4;
								fli_ghost_image[ghost_pixel_ptr++] = palette_idx1;
								decoded[pixel_ptr++] = palette[palette_ptr1 + 0];
								decoded[pixel_ptr++] = palette[palette_ptr1 + 1];
								decoded[pixel_ptr++] = palette[palette_ptr1 + 2];
								if (bytes_per_pixel == 4) /* 32bpp */
									pixel_ptr++;
							}
						}
					}
					y_ptr += line_inc;
					ghost_y_ptr += width;
				}
				break;

			case FLI_COPY:
				/*
				 * copy the chunk (uncompressed frame) to the ghost image and
				 * schedule the whole frame to be updated
				 */
				if (chunk_size - 6 > width * height)
					break;
				else
					memCopy(fli_ghost_image, &encoded[stream_ptr], chunk_size - 6);
				stream_ptr += chunk_size - 6;
				update_whole_frame = 1;
				break;
			case FLI_MINI:
				/* sort of a thumbnail? disregard this chunk... */
				stream_ptr += chunk_size - 6;
				break;
			default:
				break;
		}
		frame_size -= chunk_size;
		num_chunks--;
	}
	if (update_whole_frame)
	{
		pixel_ptr = ghost_pixel_ptr = 0;
		while (pixel_ptr < (width * height * bytes_per_pixel))
		{
			palette_ptr1 = fli_ghost_image[ghost_pixel_ptr++] * 4;
			decoded[pixel_ptr++] = palette[palette_ptr1 + 0];
			decoded[pixel_ptr++] = palette[palette_ptr1 + 1];
			decoded[pixel_ptr++] = palette[palette_ptr1 + 2];
			if (bytes_per_pixel == 4) /* 32bpp */
				pixel_ptr++;
		}
	}
}


/*
vhandle_t	V_GLOBAL
fliOpenRead(char *name)
{
	mm_stream_t *stream = mmCreateStream();
	mm_codec_t *codec = mmCreateCodec();
	codec->base.destroy = fli_destroy_decoder;
	codec->decode_video = fli_decode_video;
	stream->read_video_packet = fli_read_video;
	stream->codec = codec;
	
	return stream;
}

	
vboolean	V_GLOBAL
fliGetWidthHeight(vhandle_t flifile,u32_t *width_ret,u32_t *height_ret)
{
	u32_t w,h;
	if(fileControl(flifile,pctl_codec_video_width,&w) &&
		fileControl(flifile,pctl_codec_video_height,&h))
	{
		if(width_ret)
			*width_ret = w;
		if(height_ret)
			*height_ret = h;
		return true;
	}
	return false;
}

vboolean	V_GLOBAL
fliGetNumFrames(vhandle_t flifile,u32_t *numframes_ret)
{
	u32_t nf;
	if(fileControl(flifile,pctl_codec_video_frames_total,&nf))
	{
		if(numframes_ret)
			*numframes_ret = nf;
		return true;
	}
	return false;
}

vboolean	V_GLOBAL
fliGetFPS(vhandle_t flifile,u32_t *fps_ret)
{
	u32_t fps;
	if(fileControl(flifile,pctl_codec_video_frames_per_second,&fps))
	{
		if(fps_ret)
			*fps_ret = nf;
		return true;
	}
	return false;
}

vboolean	V_GLOBAL
fliSeekToFrame(vhandle_t flifile,u32_t frame_num)
{
	return fileControl(flifile,pctl_codec_seek_to_frame,&frame_num);
}

vboolean V_GLOBAL
fliDecodeVideoRGBA(vhandle_t flifile,void_p dest,vboolean loop)
{
	cpace = COLORSPACE_RGBA;
	fileControl(flifile,pctl_codec_output_colorspace,&cpace);
	fileRead(flifile,dest,MM_ONE_FRAME);
}

void		V_GLOBAL fliClose(vhandle_t flifile)
{
	fileClose(flifile);
}

*/

/*

void fli_dump(fli_frames_p frames)
{
	u32_t i;
	printf("num_frames %u\n",frames->num_frames);
	printf("current_frame %u\n",frames->current_frame);
	printf("width %u\n",frames->width);
	printf("height %u\n",frames->height);
	printf("speed %u\n",frames->speed);
	printf("fps %u\n",frames->fps);
	printf("frametime %g\n",frames->frametime);
	for(i=0;i<frames->num_frames;i++)
		printf("Frame %u:pos %u, size %u\n",i,frames->filepos[i],frames->frame_size[i]);
}
*/

void decompose_fli(fli_frames_p frames)
{
	int count=0;
	char framename[256];
	fli_seek_to_frame(frames,0);
	while(fli_fill_buffer(frames,false))
	{
		decode_fli_frame(frames);
		sprintf(framename,"frames/frame%03d.tga",count++);
		tgaSave(framename,frames->width,frames->height,frames->decoded);
	}
}
	
int main(int argc,char *argv[])
{
	vhandle_t in;
	fli_frames_p frames;
	logStartConsoleLog();
	logUpperLevel(LOG_TRACE);

	if(argc!=2)
		return -1;
			
	in  = fileOpenRead(argv[1]);
	if(in == LIBV_INVALID_HANDLE)
		return -1;

	frames = fli_open_read(in);
	
	if(!frames)
	{
		fileClose(in);
		return -1;
	}
	
	decompose_fli(frames);
	
	fli_free_framestruct(frames);
	return 0;
}
