/*
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.

This file is part of GtkRadiant.

GtkRadiant 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.

GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "qdata.h"
#include "inout.h"

byte	*soundtrack;
char	base[32];

/*
===============================================================================

WAV loading

===============================================================================
*/

typedef struct
{
	int			rate;
	int			width;
	int			channels;
	int			loopstart;
	int			samples;
	int			dataofs;		// chunk starts this many bytes from file start
} wavinfo_t;


byte	*data_p;
byte 	*iff_end;
byte 	*last_chunk;
byte 	*iff_data;
int 	iff_chunk_len;


int		samplecounts[0x10000];

wavinfo_t	wavinfo;

short GetLittleShort(void)
{
	short val = 0;
	val = *data_p;
	val = val + (*(data_p+1)<<8);
	data_p += 2;
	return val;
}

int GetLittleLong(void)
{
	int val = 0;
	val = *data_p;
	val = val + (*(data_p+1)<<8);
	val = val + (*(data_p+2)<<16);
	val = val + (*(data_p+3)<<24);
	data_p += 4;
	return val;
}

void FindNextChunk(char *name)
{
	while (1)
	{
		data_p=last_chunk;

		if (data_p >= iff_end)
		{	// didn't find the chunk
			data_p = NULL;
			return;
		}
		
		data_p += 4;
		iff_chunk_len = GetLittleLong();
		if (iff_chunk_len < 0)
		{
			data_p = NULL;
			return;
		}
//		if (iff_chunk_len > 1024*1024)
//			Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len);
		data_p -= 8;
		last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 );
		if (!strncmp(data_p, name, 4))
			return;
	}
}

void FindChunk(char *name)
{
	last_chunk = iff_data;
	FindNextChunk (name);
}


void DumpChunks(void)
{
	char	str[5];
	
	str[4] = 0;
	data_p=iff_data;
	do
	{
		memcpy (str, data_p, 4);
		data_p += 4;
		iff_chunk_len = GetLittleLong();
		printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len);
		data_p += (iff_chunk_len + 1) & ~1;
	} while (data_p < iff_end);
}

/*
============
GetWavinfo
============
*/
wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength)
{
	wavinfo_t	info;
	int     i;
	int     format;
	int		samples;

	memset (&info, 0, sizeof(info));

	if (!wav)
		return info;
		
	iff_data = wav;
	iff_end = wav + wavlength;

// find "RIFF" chunk
	FindChunk("RIFF");
	if (!(data_p && !strncmp(data_p+8, "WAVE", 4)))
	{
		printf("Missing RIFF/WAVE chunks\n");
		return info;
	}

// get "fmt " chunk
	iff_data = data_p + 12;
// DumpChunks ();

	FindChunk("fmt ");
	if (!data_p)
	{
		printf("Missing fmt chunk\n");
		return info;
	}
	data_p += 8;
	format = GetLittleShort();
	if (format != 1)
	{
		printf("Microsoft PCM format only\n");
		return info;
	}

	info.channels = GetLittleShort();
	info.rate = GetLittleLong();
	data_p += 4+2;
	info.width = GetLittleShort() / 8;

// get cue chunk
	FindChunk("cue ");
	if (data_p)
	{
		data_p += 32;
		info.loopstart = GetLittleLong();
//		Com_Printf("loopstart=%d\n", sfx->loopstart);

	// if the next chunk is a LIST chunk, look for a cue length marker
		FindNextChunk ("LIST");
		if (data_p)
		{
			if (!strncmp (data_p + 28, "mark", 4))
			{	// this is not a proper parse, but it works with cooledit...
				data_p += 24;
				i = GetLittleLong ();	// samples in loop
				info.samples = info.loopstart + i;
			}
		}
	}
	else
		info.loopstart = -1;

// find data chunk
	FindChunk("data");
	if (!data_p)
	{
		printf("Missing data chunk\n");
		return info;
	}

	data_p += 4;
	samples = GetLittleLong ();

	if (info.samples)
	{
		if (samples < info.samples)
			Error ("Sound %s has a bad loop length", name);
	}
	else
		info.samples = samples;

	info.dataofs = data_p - wav;

	return info;
}

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

/*
==============
LoadSoundtrack
==============
*/
void LoadSoundtrack (void)
{
	char	name[1024];
	FILE	*f;
	int		len;
	int     i, val, j;

	soundtrack = NULL;
	sprintf (name, "%svideo/%s/%s.wav", gamedir, base, base);
	printf ("%s\n", name);
	f = fopen (name, "rb");
	if (!f)
	{
		printf ("no soundtrack for %s\n", base);
		return;
	}
	len = Q_filelength(f);
	soundtrack = malloc(len);
	fread (soundtrack, 1, len, f);
	fclose (f);

	wavinfo = GetWavinfo (name, soundtrack, len);

	// count samples for compression
	memset (samplecounts, 0, sizeof(samplecounts));

	j = wavinfo.samples/2;
	for (i=0 ; i<j ; i++)
	{
		val = ((unsigned short *)( soundtrack + wavinfo.dataofs))[i];
		samplecounts[val]++;
	}
	val = 0;
	for (i=0 ; i<0x10000 ; i++)
		if (samplecounts[i])
			val++;

	printf ("%i unique sample values\n", val);
}

/*
==================
WriteSound
==================
*/
void WriteSound (FILE *output, int frame)
{
	int		start, end;
	int		count;
	int		empty = 0;
	int		i;
	int		sample;
	int		width;

	width = wavinfo.width * wavinfo.channels;

	start = frame*wavinfo.rate/14;
	end = (frame+1)*wavinfo.rate/14;
	count = end - start;

	for (i=0 ; i<count ; i++)
	{
		sample = start+i;
		if (sample > wavinfo.samples || !soundtrack)
			fwrite (&empty, 1, width, output);
		else
			fwrite (soundtrack + wavinfo.dataofs + sample*width, 1, width,output);
	}
}

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

/*
==================
MTF
==================
*/
cblock_t MTF (cblock_t in)
{
	int			i, j, b, code;
	byte		*out_p;
	int			index[256];
	cblock_t	out;

	out_p = out.data = malloc(in.count + 4);

	// write count
	*out_p++ = in.count&255;
	*out_p++ = (in.count>>8)&255;
	*out_p++ = (in.count>>16)&255;
	*out_p++ = (in.count>>24)&255;

	for (i=0 ; i<256 ; i++)
		index[i] = i;

	for (i=0 ; i<in.count ; i++)
	{
		b = in.data[i];
		code = index[b];
		*out_p++ = code;
		
		// shuffle b indexes to 0
		for (j=0 ; j<256 ; j++)
			if (index[j] < code)
				index[j]++;
		index[b] = 0;
	}

	out.count = out_p - out.data;

	return out;
}


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

int		bwt_size;
byte	*bwt_data;

int bwtCompare (const void *elem1, const void *elem2)
{
	int		i;
	int		i1, i2;
	int		b1, b2;

	i1 = *(int *)elem1;
	i2 = *(int *)elem2;

	for (i=0 ; i<bwt_size ; i++)
	{
		b1 = bwt_data[i1];
		b2 = bwt_data[i2];
		if (b1 < b2)
			return -1;
		if (b1 > b2)
			return 1;
		if (++i1 == bwt_size)
			i1 = 0;
		if (++i2 == bwt_size)
			i2 = 0;
	}

	return 0;
}

/*
==================
BWT
==================
*/
cblock_t BWT (cblock_t in)
{
	int		*sorted;
	int		i;
	byte	*out_p;
	cblock_t	out;

	bwt_size = in.count;
	bwt_data = in.data;

	sorted = malloc(in.count*sizeof(*sorted));
	for (i=0 ; i<in.count ; i++)
		sorted[i] = i;
	qsort (sorted, in.count, sizeof(*sorted), bwtCompare);

	out_p = out.data = malloc(in.count + 8);

	// write count
	*out_p++ = in.count&255;
	*out_p++ = (in.count>>8)&255;
	*out_p++ = (in.count>>16)&255;
	*out_p++ = (in.count>>24)&255;

	// write head index
	for (i=0 ; i<in.count ; i++)
		if (sorted[i] == 0)
			break;
	*out_p++ = i&255;
	*out_p++ = (i>>8)&255;
	*out_p++ = (i>>16)&255;
	*out_p++ = (i>>24)&255;

	// write the L column
	for (i=0 ; i<in.count ; i++)
		*out_p++ = in.data[(sorted[i]+in.count-1)%in.count];

	free (sorted);

	out.count = out_p - out.data;

	return out;
}

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

typedef struct hnode_s
{
	int			count;
	qboolean	used;
	int			children[2];
} hnode_t;

int			numhnodes;
hnode_t		hnodes[512];
unsigned	charbits[256];
int			charbitscount[256];

int	SmallestNode (void)
{
	int		i;
	int		best, bestnode;

	best = 99999999;
	bestnode = -1;
	for (i=0 ; i<numhnodes ; i++)
	{
		if (hnodes[i].used)
			continue;
		if (!hnodes[i].count)
			continue;
		if (hnodes[i].count < best)
		{
			best = hnodes[i].count;
			bestnode = i;
		}
	}

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

	hnodes[bestnode].used = true;
	return bestnode;
}

void BuildChars (int nodenum, unsigned bits, int bitcount)
{
	hnode_t	*node;

	if (nodenum < 256)
	{
		if (bitcount > 32)
			Error ("bitcount > 32");
		charbits[nodenum] = bits;
		charbitscount[nodenum] = bitcount;
		return;
	}

	node = &hnodes[nodenum];
	bits <<= 1;
	BuildChars (node->children[0], bits, bitcount+1);
	bits |= 1;
	BuildChars (node->children[1], bits, bitcount+1);
}


/*
==================
Huffman
==================
*/
cblock_t Huffman (cblock_t in)
{
	int			i;
	hnode_t		*node;
	int			outbits, c;
	unsigned	bits;
	byte		*out_p;
	cblock_t	out;
	int			max, maxchar;

	// count
	memset (hnodes, 0, sizeof(hnodes));
	for (i=0 ; i<in.count ; i++)
		hnodes[in.data[i]].count++;

	// normalize counts
	max = 0;
	maxchar = 0;
	for (i=0 ; i<256 ; i++)
	{
		if (hnodes[i].count > max)
		{
			max = hnodes[i].count;
			maxchar = i;
		}
	}
	if (max == 0)
		Error ("Huffman: max == 0");

	for (i=0 ; i<256 ; i++)
	{
		hnodes[i].count = (hnodes[i].count*255+max-1) / max;
	}

	// build the nodes
	numhnodes = 256;
	while (numhnodes != 511)
	{
		node = &hnodes[numhnodes];

		// pick two lowest counts
		node->children[0] = SmallestNode ();
		if (node->children[0] == -1)
			break;	// no more

		node->children[1] = SmallestNode ();
		if (node->children[1] == -1)
		{
			if (node->children[0] != numhnodes-1)
				Error ("Bad smallestnode");
			break;
		}
		node->count = hnodes[node->children[0]].count + 
			hnodes[node->children[1]].count;
		numhnodes++;
	}

	BuildChars (numhnodes-1, 0, 0);

	out_p = out.data = malloc(in.count*2 + 1024);
	memset (out_p, 0, in.count*2+1024);

	// write count
	*out_p++ = in.count&255;
	*out_p++ = (in.count>>8)&255;
	*out_p++ = (in.count>>16)&255;
	*out_p++ = (in.count>>24)&255;

	// save out the 256 normalized counts so the tree can be recreated
	for (i=0 ; i<256 ; i++)
		*out_p++ = hnodes[i].count;

	// write bits
	outbits = 0;
	for (i=0 ; i<in.count ; i++)
	{
		c = charbitscount[in.data[i]];
		bits = charbits[in.data[i]];
		while (c)
		{
			c--;
			if (bits & (1<<c))
				out_p[outbits>>3] |= 1<<(outbits&7);
			outbits++;
		}
	}

	out_p += (outbits+7)>>3;

	out.count = out_p - out.data;

	return out;
}

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

/*
==================
RLE
==================
*/
#define	RLE_CODE	0xe8
#define	RLE_TRIPPLE	0xe9

int	rle_counts[256];
int	rle_bytes[256];

cblock_t RLE (cblock_t in)
{
	int		i;
	byte	*out_p;
	int		val;
	int		repeat;
	cblock_t	out;

	out_p = out.data = malloc (in.count*2);

	// write count
	*out_p++ = in.count&255;
	*out_p++ = (in.count>>8)&255;
	*out_p++ = (in.count>>16)&255;
	*out_p++ = (in.count>>24)&255;

	for (i=0 ; i<in.count ; )
	{
		val = in.data[i];
		rle_bytes[val]++;
		repeat = 1;
		i++;
		while (i<in.count && repeat < 255 && in.data[i] == val)
		{
			repeat++;
			i++;
		}
if (repeat < 256)
rle_counts[repeat]++;
		if (repeat > 3 || val == RLE_CODE)
		{
			*out_p++ = RLE_CODE;
			*out_p++ = val;
			*out_p++ = repeat;
		}
		else
		{
			while (repeat--)
				*out_p++ = val;
		}
	}

	out.count = out_p - out.data;
	return out;
}

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

unsigned	lzss_head[256];
unsigned	lzss_next[0x20000];

/*
==================
LZSS
==================
*/
#define	BACK_WINDOW		0x10000
#define	BACK_BITS		16
#define	FRONT_WINDOW	16
#define	FRONT_BITS		4
cblock_t LZSS (cblock_t in)
{
	int		i;
	byte	*out_p;
	cblock_t	out;
	int		val;
	int		j, start, max;
	int		bestlength, beststart;
	int		outbits;

if (in.count >= sizeof(lzss_next)/4)
Error ("LZSS: too big");

	memset (lzss_head, -1, sizeof(lzss_head));

	out_p = out.data = malloc (in.count*2);
	memset (out.data, 0, in.count*2);

	// write count
	*out_p++ = in.count&255;
	*out_p++ = (in.count>>8)&255;
	*out_p++ = (in.count>>16)&255;
	*out_p++ = (in.count>>24)&255;

	outbits = 0;
	for (i=0 ; i<in.count ; )
	{
		val = in.data[i];
#if 1
// chained search
		bestlength = 0;
		beststart = 0;

		max = FRONT_WINDOW;
		if (i + max > in.count)
			max = in.count - i;

		start = lzss_head[val];
		while (start != -1 && start >= i-BACK_WINDOW)
		{			
			// count match length
			for (j=0 ; j<max ; j++)
				if (in.data[start+j] != in.data[i+j])
					break;
			if (j > bestlength)
			{
				bestlength = j;
				beststart = start;
			}
			start = lzss_next[start];
		}

#else
// slow simple search
		// search for a match
		max = FRONT_WINDOW;
		if (i + max > in.count)
			max = in.count - i;

		start = i - BACK_WINDOW;
		if (start < 0)
			start = 0;
		bestlength = 0;
		beststart = 0;
		for ( ; start < i ; start++)
		{
			if (in.data[start] != val)
				continue;
			// count match length
			for (j=0 ; j<max ; j++)
				if (in.data[start+j] != in.data[i+j])
					break;
			if (j > bestlength)
			{
				bestlength = j;
				beststart = start;
			}
		}
#endif
		beststart = BACK_WINDOW - (i-beststart);

		if (bestlength < 3)
		{	// output a single char
			bestlength = 1;

			out_p[outbits>>3] |= 1<<(outbits&7);	// set bit to mark char
			outbits++;
			for (j=0 ; j<8 ; j++, outbits++)
				if (val & (1<<j) )
					out_p[outbits>>3] |= 1<<(outbits&7);
		}
		else
		{	// output a phrase
			outbits++;	// leave a 0 bit to mark phrase
			for (j=0 ; j<BACK_BITS ; j++, outbits++)
				if (beststart & (1<<j) )
					out_p[outbits>>3] |= 1<<(outbits&7);
			for (j=0 ; j<FRONT_BITS ; j++, outbits++)
				if (bestlength & (1<<j) )
					out_p[outbits>>3] |= 1<<(outbits&7);
		}

		while (bestlength--)
		{
			val = in.data[i];
			lzss_next[i] = lzss_head[val];
			lzss_head[val] = i;
			i++;
		}
	}

	out_p += (outbits+7)>>3;
	out.count = out_p - out.data;
	return out;
}

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

#define	MIN_REPT	15
#define	MAX_REPT	0
#define	HUF_TOKENS	(256+MAX_REPT)

unsigned	charbits1[256][HUF_TOKENS];
int			charbitscount1[256][HUF_TOKENS];

hnode_t		hnodes1[256][HUF_TOKENS*2];
int			numhnodes1[256];

int			order0counts[256];

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

	best = 99999999;
	bestnode = -1;
	for (i=0 ; i<numhnodes ; i++)
	{
		if (hnodes[i].used)
			continue;
		if (!hnodes[i].count)
			continue;
		if (hnodes[i].count < best)
		{
			best = hnodes[i].count;
			bestnode = i;
		}
	}

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

	hnodes[bestnode].used = true;
	return bestnode;
}


/*
==================
BuildChars1
==================
*/
void BuildChars1 (int prev, int nodenum, unsigned bits, int bitcount)
{
	hnode_t	*node;

	if (nodenum < HUF_TOKENS)
	{
		if (bitcount > 32)
			Error ("bitcount > 32");
		charbits1[prev][nodenum] = bits;
		charbitscount1[prev][nodenum] = bitcount;
		return;
	}

	node = &hnodes1[prev][nodenum];
	bits <<= 1;
	BuildChars1 (prev, node->children[0], bits, bitcount+1);
	bits |= 1;
	BuildChars1 (prev, node->children[1], bits, bitcount+1);
}


/*
==================
BuildTree1
==================
*/
void BuildTree1 (int prev)
{
	hnode_t		*node, *nodebase;
	int			numhnodes;

	// build the nodes
	numhnodes = HUF_TOKENS;
	nodebase = hnodes1[prev];
	while (1)
	{
		node = &nodebase[numhnodes];

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

		node->children[1] = SmallestNode1 (nodebase, numhnodes);
		if (node->children[1] == -1)
			break;

		node->count = nodebase[node->children[0]].count + 
			nodebase[node->children[1]].count;
		numhnodes++;
	}
	numhnodes1[prev] = numhnodes-1;
	BuildChars1 (prev, numhnodes-1, 0, 0);
}


/*
==================
Huffman1_Count
==================
*/
void Huffman1_Count (cblock_t in)
{
	int		i;
	int		prev;
	int		v;
	int		rept;

	prev = 0;
	for (i=0 ; i<in.count ; i++)
	{
		v = in.data[i];
		order0counts[v]++;
		hnodes1[prev][v].count++;
		prev = v;
#if 1
		for (rept=1 ; i+rept < in.count && rept < MAX_REPT ; rept++)
			if (in.data[i+rept] != v)
				break;
		if (rept > MIN_REPT)
		{
			hnodes1[prev][255+rept].count++;
			i += rept-1;
		}
#endif
	}
}


/*
==================
Huffman1_Build
==================
*/
byte	scaled[256][HUF_TOKENS];
void Huffman1_Build (FILE *f)
{
	int		i, j, v;
	int		max;
	int		total;

	for (i=0 ; i<256 ; i++)
	{
		// normalize and save the counts
		max = 0;
		for (j=0 ; j<HUF_TOKENS ; j++)
		{
			if (hnodes1[i][j].count > max)
				max = hnodes1[i][j].count;
		}
		if (max == 0)
			max = 1;
		total = 0;
		for (j=0 ; j<HUF_TOKENS ; j++)
		{	// easy to overflow 32 bits here!
			v = (hnodes1[i][j].count*(double)255+max-1)/max;
			if (v > 255)
				Error ("v > 255");
			scaled[i][j] = hnodes1[i][j].count = v;
			if (v)
				total++;
		}
		if (total == 1)
		{	// must have two tokens
			if (!scaled[i][0])
				scaled[i][0] = hnodes1[i][0].count = 1;
			else
				scaled[i][1] = hnodes1[i][1].count = 1;
		}

		BuildTree1 (i);
	}

#if 0
	// count up the total bits
	total = 0;
	for (i=0 ; i<256 ; i++)
		for (j=0 ; j<256 ; j++)
			total += charbitscount1[i][j] * hnodes1[i][j].count;

	total = (total+7)/8;
	printf ("%i bytes huffman1 compressed\n", total);
#endif

	fwrite (scaled, 1, sizeof(scaled), f);
}

/*
==================
Huffman1

Order 1 compression with pre-built table
==================
*/
cblock_t Huffman1 (cblock_t in)
{
	int			i;
	int			outbits, c;
	unsigned	bits;
	byte		*out_p;
	cblock_t	out;
	int			prev;
	int			v;
	int			rept;

	out_p = out.data = malloc(in.count*2 + 1024);
	memset (out_p, 0, in.count*2+1024);

	// write count
	*out_p++ = in.count&255;
	*out_p++ = (in.count>>8)&255;
	*out_p++ = (in.count>>16)&255;
	*out_p++ = (in.count>>24)&255;

	// write bits
	outbits = 0;
	prev = 0;
	for (i=0 ; i<in.count ; i++)
	{
		v = in.data[i];

		c = charbitscount1[prev][v];
		bits = charbits1[prev][v];
		if (!c)
			Error ("!bits");
		while (c)
		{
			c--;
			if (bits & (1<<c))
				out_p[outbits>>3] |= 1<<(outbits&7);
			outbits++;
		}

		prev = v;
#if 1
		// check for repeat encodes
		for (rept=1 ; i+rept < in.count && rept < MAX_REPT ; rept++)
			if (in.data[i+rept] != v)
				break;
		if (rept > MIN_REPT)
		{
			c = charbitscount1[prev][255+rept];
			bits = charbits1[prev][255+rept];
			if (!c)
				Error ("!bits");
			while (c)
			{
				c--;
				if (bits & (1<<c))
					out_p[outbits>>3] |= 1<<(outbits&7);
				outbits++;
			}
			i += rept-1;
		}
#endif
	}

	out_p += (outbits+7)>>3;

	out.count = out_p - out.data;

	return out;
}

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


/*
===================
LoadFrame
===================
*/
cblock_t LoadFrame (char *base, int frame, int digits, byte **palette)
{
	int			ten3, ten2, ten1, ten0;
	cblock_t	in;
	int			width, height;
	char		name[1024];
	FILE		*f;

	in.data = NULL;
	in.count = -1;

	ten3 = frame/1000;
	ten2 = (frame-ten3*1000)/100;
	ten1 = (frame-ten3*1000-ten2*100)/10;
	ten0 = frame%10;

	if (digits == 4)
		sprintf (name, "%svideo/%s/%s%i%i%i%i.pcx", gamedir, base, base, ten3, ten2, ten1, ten0);
	else
		sprintf (name, "%svideo/%s/%s%i%i%i.pcx", gamedir, base, base, ten2, ten1, ten0);

	f = fopen(name, "rb");
	if (!f)
	{
		in.data = NULL;
		return in;
	}
	fclose (f);

	printf ("%s\n", name);
	Load256Image (name, &in.data, palette, &width, &height);
	in.count = width*height;
// FIXME: map 0 and 255!

#if 0
	// rle compress
	rle = RLE(in);
	free (in.data);

	return rle;
#endif

	return in;
}

/*
===============
Cmd_Video

video <directory> <framedigits>
===============
*/
void Cmd_Video (void)
{
	char	savename[1024];
	char	name[1024];
	FILE	*output;
	int		startframe, frame;
	byte	*palette;
	int		width, height;
	byte	current_palette[768];
	int		command;
	int		i;
	int		digits;
	cblock_t	in, huffman;
	int		swap;


	GetToken (false);
	strcpy (base, token);
	if (g_release)
	{
//		sprintf (savename, "video/%s.cin", token);
//		ReleaseFile (savename);
		return;
	}

	GetToken (false);
	digits = atoi(token);

	// optionally skip frames
	if (TokenAvailable ())
	{
		GetToken (false);
		startframe = atoi(token);
	}
	else
		startframe=0;

	sprintf (savename, "%svideo/%s.cin", gamedir, base);


	// clear stuff
	memset (charbits1, 0, sizeof(charbits1));
	memset (charbitscount1, 0, sizeof(charbitscount1));
	memset (hnodes1, 0, sizeof(hnodes1));
	memset (numhnodes1, 0, sizeof(numhnodes1));
	memset (order0counts, 0, sizeof(order0counts));


	// load the entire sound wav file if present
	LoadSoundtrack ();

	if (digits == 4)
		sprintf (name, "%svideo/%s/%s0000.pcx", gamedir, base, base);
	else
		sprintf (name, "%svideo/%s/%s000.pcx", gamedir, base, base);

	printf ("%s\n", name);
	Load256Image (name, NULL, &palette, &width, &height);

	output = fopen (savename, "wb");
	if (!output)
		Error ("Can't open %s", savename);

	// write header info
	i = LittleLong (width);
	fwrite (&i, 4, 1, output);
	i = LittleLong (height);
	fwrite (&i, 4, 1, output);
	i = LittleLong (wavinfo.rate);
	fwrite (&i, 4, 1, output);
	i = LittleLong (wavinfo.width);
	fwrite (&i, 4, 1, output);
	i = LittleLong (wavinfo.channels);
	fwrite (&i, 4, 1, output);

	// build the dictionary
	for ( frame=startframe ;  ; frame++)
	{
		printf ("counting ", frame);
		in = LoadFrame (base, frame, digits, &palette);
		if (!in.data)
			break;
		Huffman1_Count (in);
		free (in.data);
	}
	printf ("\n");

	// build nodes and write counts
	Huffman1_Build (output);


	memset (current_palette, 0, sizeof(current_palette));

	// compress it with the dictionary
	for (frame=startframe ;  ; frame++)
	{
		printf ("packing ", frame);
		in = LoadFrame (base, frame, digits, &palette);
		if (!in.data)
			break;

		// see if the palette has changed
		for (i=0 ; i<768 ; i++)
			if (palette[i] != current_palette[i])
			{
				// write a palette change
				memcpy (current_palette, palette, sizeof(current_palette));
				command = LittleLong(1);
				fwrite (&command, 1, 4, output);
				fwrite (current_palette, 1, sizeof(current_palette), output);
				break;
			}
		if (i == 768)
		{
			command = 0;	// no palette change
			fwrite (&command, 1, 4, output);
		}

		// save the image
		huffman = Huffman1 (in);
		printf ("%5i bytes after huffman1\n", huffman.count);

		swap = LittleLong (huffman.count);
		fwrite (&swap, 1, sizeof(swap), output);

		fwrite (huffman.data, 1, huffman.count, output);

		// save some sound samples
		WriteSound (output, frame);

		free (palette);
		free (in.data);
		free (huffman.data);
	}
	printf ("\n");

	// write end-of-file command
	command = 2;
	fwrite (&command, 1, 4, output);

	printf ("Total size: %i\n", ftell (output));

	fclose (output);

	if (soundtrack)
		free (soundtrack);
}
