/*
Copyright (C) 1997-2001 Id Software, Inc.

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/*
	r_cin.c

	CIN File Decoder for cinematic textures

	Adapted from executable source by Robert 'Heffo' Heffernan
	1/1/2002, 1:34pm AEDST
*/

#include "r_local.h"
#include "r_cin.h"

typedef struct
{
	byte	*data;
	int		count;
} cblock_t;

void R_CIN_StopCinematic (void);
int	SmallestNode1 (int numhnodes);
void Huff1TableInit (void);
cblock_t Huff1Decompress (cblock_t in);
byte *R_CIN_ReadNextFrame (void);
void R_CIN_RunCinematic (void);
qboolean R_CIN_DrawCinematic (void);
void R_CIN_PlayCinematic (char *arg);
void R_CIN_StartCinematic (void);

cinematics_t	*cin;

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

long GetInteger (byte *data)
{
	long b0, b1, b2, b3;

	b0 = (data[0] & 0xFF);
	b1 = (data[1] & 0xFF) << 8;
	b2 = (data[2] & 0xFF) << 16;
	b3 = (data[3] & 0xFF) << 24;

	return b0 + b1 + b2 + b3;
}

/*
==================
R_CIN_StopCinematic
==================
*/
void R_CIN_StopCinematic (void)
{
	cin->time = 0;	// done
	if (cin->pic)
	{
		free(cin->pic);
		cin->pic = NULL;
	}
	if (cin->pic_pending)
	{
		free(cin->pic_pending);
		cin->pic_pending = NULL;
	}
	if (cin->cinematic_file)
	{
		FS_FreeFile(cin->cinematic_file);
		cin->cinematic_file = NULL;
		cin->offset = NULL;
	}
	if (cin->hnodes1)
	{
		free(cin->hnodes1);
		cin->hnodes1 = NULL;
	}
}

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

/*
==================
SmallestNode1
==================
*/
int	SmallestNode1 (int numhnodes)
{
	int		i;
	int		best, bestnode;

	best = 99999999;
	bestnode = -1;
	for (i=0 ; i<numhnodes ; i++)
	{
		if (cin->h_used[i])
			continue;
		if (!cin->h_count[i])
			continue;
		if (cin->h_count[i] < best)
		{
			best = cin->h_count[i];
			bestnode = i;
		}
	}

	if (bestnode == -1)
		return -1;

	cin->h_used[bestnode] = true;
	return bestnode;
}


/*
==================
Huff1TableInit

Reads the 64k counts table and initializes the node trees
==================
*/
void Huff1TableInit (void)
{
	int		prev;
	int		j;
	int		*node, *nodebase;
	byte	counts[256];
	int		numhnodes;

	cin->hnodes1 = malloc(256*256*2*4);
	memset (cin->hnodes1, 0, 256*256*2*4);

	for (prev=0 ; prev<256 ; prev++)
	{
		memset (cin->h_count,0,sizeof(cin->h_count));
		memset (cin->h_used,0,sizeof(cin->h_used));

		// read a row of counts
		memcpy(counts, cin->offset, sizeof(counts)); cin->offset += sizeof(counts);
		for (j=0 ; j<256 ; j++)
			cin->h_count[j] = counts[j];

		// build the nodes
		numhnodes = 256;
		nodebase = cin->hnodes1 + prev*256*2;

		while (numhnodes != 511)
		{
			node = nodebase + (numhnodes-256)*2;

			// pick two lowest counts
			node[0] = SmallestNode1 (numhnodes);
			if (node[0] == -1)
				break;	// no more

			node[1] = SmallestNode1 (numhnodes);
			if (node[1] == -1)
				break;

			cin->h_count[numhnodes] = cin->h_count[node[0]] + cin->h_count[node[1]];
			numhnodes++;
		}

		cin->numhnodes1[prev] = numhnodes-1;
	}

	cin->framestart = cin->offset;
}

/*
==================
Huff1Decompress
==================
*/
cblock_t Huff1Decompress (cblock_t in)
{
	byte		*input;
	byte		*out_p;
	int			nodenum;
	int			count;
	cblock_t	out;
	int			inbyte;
	int			*hnodes, *hnodesbase;
//int		i;

	// get decompressed count
	count = GetInteger(in.data);
	input = in.data + 4;
	out_p = out.data = malloc(count);

	// read bits

	hnodesbase = cin->hnodes1 - 256*2;	// nodes 0-255 aren't stored

	hnodes = hnodesbase;
	nodenum = cin->numhnodes1[0];
	while (count)
	{
		inbyte = *input++;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin->numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin->numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin->numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin->numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin->numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin->numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin->numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin->numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
	}

	/*if (input - in.data != in.count && input - in.data != in.count+1)
	{
		Com_Printf ("Decompression overread by %i", (input - in.data) - in.count);
	}*/
	out.count = out_p - out.data;

	return out;
}

/*
==================
R_CIN_ReadNextFrame
==================
*/
byte *R_CIN_ReadNextFrame (void)
{
	int		command;
	byte	compressed[0x20000];
	int		size;
	byte	*pic;
	cblock_t	in, huf1;
	int		start, end, count, i;
	byte	*rp = ( byte * ) cin->palette;

	// read the next frame
	command = GetInteger(cin->offset); cin->offset += 4;
	command = LittleLong(command);

	if (command == 2)
		return NULL;	// last frame marker

	if (command == 1)
	{	// read palette
		memcpy(cin->rawpalette, cin->offset, sizeof(cin->rawpalette));
		cin->offset += sizeof(cin->rawpalette);

		for ( i = 0; i < 256; i++ )
		{
			rp[i*4+0] = cin->rawpalette[i*3+0];
			rp[i*4+1] = cin->rawpalette[i*3+1];
			rp[i*4+2] = cin->rawpalette[i*3+2];
			rp[i*4+3] = 0xff;
		}
	}

	// decompress the next frame
	size = GetInteger(cin->offset); cin->offset += 4;
	if (size > sizeof(compressed) || size < 1)
		VID_Error (ERR_DROP, "Bad compressed frame size");
	memcpy(compressed, cin->offset, size);
	cin->offset += size;

	// read sound
	start = cin->frame*cin->s_rate/14;
	end = (cin->frame+1)*cin->s_rate/14;
	count = end - start;

	cin->offset += (count*cin->s_width*cin->s_channels);

	in.data = compressed;
	in.count = size;

	huf1 = Huff1Decompress (in);

	pic = huf1.data;

	cin->frame++;

	return pic;
}


/*
==================
R_CIN_RunCinematic
==================
*/
void R_CIN_RunCinematic (void)
{
	int		frame;

	if(!cin && !cin->cinematic_file)
		return;

	frame = (Sys_Milliseconds() - cin->time)*14.0/1000;
	if (frame <= cin->frame)
		return;

	if (frame > cin->frame+1)
	{
		//Com_Printf ("Dropped frame: %i > %i\n", frame, cin->frame+1);
		cin->time = Sys_Milliseconds() - cin->frame*1000/14;
	}
	if (cin->pic)
		free(cin->pic);
	cin->pic = cin->pic_pending;
	cin->pic_pending = NULL;
	cin->pic_pending = R_CIN_ReadNextFrame ();
	if (!cin->pic_pending)
	{
		R_CIN_StartCinematic ();
		cin->pic_pending = R_CIN_ReadNextFrame ();
	}

	R_CIN_DrawCinematic();
}

/*
==================
R_CIN_DrawCinematic

Returns true if a cinematic is active, meaning the view rendering
should be skipped
==================
*/
qboolean R_CIN_DrawCinematic (void)
{
	int				i, j;
	byte			*inrow;
	unsigned		frac, fracstep;
	static unsigned image[MAX_SCALE_SIZE*MAX_SCALE_SIZE];
	unsigned		*out;

	if (cin->time <= 0)
	{
		return false;
	}

	if (!cin->pic)
	{
		return true;
	}

	GL_Bind (cin->texnum);

	out = image;
	fracstep = cin->width*0x10000/cin->p2_width;
	for (i=0 ; i<cin->p2_height ; i++, out += cin->p2_width)
	{
		inrow = cin->pic + cin->width*(i*cin->height/cin->p2_height);
		frac = fracstep >> 1;
		for (j=0 ; j<cin->p2_width ; j+=4)
		{
			out[j] = cin->palette[inrow[frac>>16]];
			frac += fracstep;
			out[j+1] = cin->palette[inrow[frac>>16]];
			frac += fracstep;
			out[j+2] = cin->palette[inrow[frac>>16]];
			frac += fracstep;
			out[j+3] = cin->palette[inrow[frac>>16]];
			frac += fracstep;
		}
	}

	qglTexImage2D (GL_TEXTURE_2D, 0, gl_tex_solid_format, cin->p2_width, cin->p2_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);

	qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	return true;
}

/*
==================
R_CIN_PlayCinematic
==================
*/
void R_CIN_PlayCinematic (char *arg)
{
 int  size;
 
 cin->frame = 0;
 
 size = FS_LoadFile(arg, (void **)&cin->cinematic_file);
 if(size == -1)
 {
  VID_Error (ERR_DROP, "Cinematic %s not found.\n", arg);
  cin->time = 0; // done
  return;
 }
 
 cin->offset = cin->cinematic_file;
 
 cin->width = GetInteger(cin->offset); cin->offset += 4;
 cin->height = GetInteger(cin->offset); cin->offset += 4;
 cin->s_rate = GetInteger(cin->offset); cin->offset += 4;
 cin->s_width = GetInteger(cin->offset); cin->offset += 4;
 cin->s_channels = GetInteger(cin->offset); cin->offset += 4;
 
 for( cin->p2_width = 2 ; cin->p2_width <= cin->width ; cin->p2_width <<= 1 );
 cin->p2_width >>= 1;
 
 if(cin->p2_width >= MAX_SCALE_SIZE)
 {
  cin->p2_width = MAX_SCALE_SIZE;
 }
 
 for( cin->p2_height = 2 ; cin->p2_height <= cin->height ; cin->p2_height <<= 1 );
 cin->p2_height >>= 1;
 
 if(cin->p2_height >= MAX_SCALE_SIZE)
 {
  cin->p2_height = MAX_SCALE_SIZE;
 }
 
 Huff1TableInit ();
 
 cin->frame = 0;
 cin->pic = R_CIN_ReadNextFrame ();
 cin->time = Sys_Milliseconds();
}

/*
==================
R_CIN_StartCinematic
==================
*/
void R_CIN_StartCinematic (void)
{
	cin->offset = cin->framestart;

	cin->frame = 0;
	cin->time = Sys_Milliseconds();

	cin->pic = R_CIN_ReadNextFrame ();
}


/*
==================
CIN_OpenCin
==================
*/
cinematics_t cinpool[8];
cinematics_t *CIN_OpenCin (char *name)
{
	int i;

	for(i=0; i<8; i++)
	{
		if(cinpool[i].texnum)
			continue;

		cin = &cinpool[i];
		R_CIN_PlayCinematic(name);

		return &cinpool[i];
	}

	return NULL;
}

void CIN_ProcessCins (void)
{
	int i;

	for(i=0; i<8; i++)
	{
		if(cinpool[i].texnum)
		{
			cin = &cinpool[i];

			R_CIN_RunCinematic();
		}
	}
}

void CIN_FreeCin (int texnum)
{
	int i;

	for(i=0; i<8; i++)
	{
		if(cinpool[i].texnum == texnum)
		{
			cin = &cinpool[i];
			R_CIN_StopCinematic();
			cinpool[i].texnum = 0;

			return;
		}
	}
}