/*
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.

*/


#include "client.h"


#define RoQ_INFO				0x1001
#define RoQ_QUAD_CODEBOOK		0x1002
#define RoQ_QUAD_VQ				0x1011
#define RoQ_SOUND_MONO			0x1020
#define RoQ_SOUND_STEREO		0x1021

#define RoQ_ID_MOT				0x0000
#define RoQ_ID_FCC				0x0001
#define RoQ_ID_SLD				0x0002
#define RoQ_ID_CCC				0x0003

typedef struct {
	unsigned short	id;
	unsigned int	size;
	unsigned short	argument;
} roqChunk_t;

typedef struct {
	byte			y[4];
	byte			u;
	byte			v;
} roqCell_t;

typedef struct {
	byte			idx[4];
} roqQCell_t;

typedef struct {
	char			name[MAX_QPATH];
	qboolean		playing;
	fileHandle_t	file;
	int				size;
	int				start;
	int				remaining;
	qboolean		isRoQ;
	int				rate;
	
	int				x;
	int				y;
	int				w;
	int				h;
	int				flags;

	int				sndRate;
	int				sndWidth;
	int				sndChannels;

	int				vidWidth;
	int				vidHeight;
	byte			*vidBuffer;

	int				rawWidth;
	int				rawHeight;
	byte			*rawBuffer;

	int				frameCount;
	int				frameTime;

	unsigned		palette[256];

	// PCX stuff
	byte			*pcxBuffer;

	// Order 1 Huffman stuff
	int				*hNodes1;
	int				hNumNodes1[256];

	int				hUsed[512];
	int				hCount[512];

	byte			*hBuffer;

	// RoQ stuff
	roqChunk_t		roqChunk;
	roqCell_t		roqCells[256];
	roqQCell_t		roqQCells[256];

	short			roqSndSqrTable[256];

	byte			*roqBuffer;
	byte			*roqBufferPtr[2];
} cinematic_t;

static cinematic_t	cinematics[MAX_CINEMATICS];


/*
 =================
 CIN_Skip
 =================
*/
static void CIN_Skip (cinematic_t *cin, int count){

	FS_Seek(cin->file, count, FS_SEEK_CUR);
	cin->remaining -= count;
}

/*
 =================
 CIN_SoundSqrTableInit
 =================
*/
static void CIN_SoundSqrTableInit (cinematic_t *cin){

	int		i;

	for (i = 0; i < 128; i++){
		cin->roqSndSqrTable[i] = i * i;
		cin->roqSndSqrTable[i+128] = -(i * i);
	}
}

/*
 =================
 CIN_ReadChunk
 =================
*/
static void CIN_ReadChunk (cinematic_t *cin){

	roqChunk_t	*chunk = &cin->roqChunk;

	FS_Read(&chunk->id, sizeof(chunk->id), cin->file);
	FS_Read(&chunk->size, sizeof(chunk->size), cin->file);
	FS_Read(&chunk->argument, sizeof(chunk->argument), cin->file);

	chunk->id = LittleShort(chunk->id);
	chunk->size = LittleLong(chunk->size);
	chunk->argument = LittleShort(chunk->argument);

	cin->remaining -= sizeof(roqChunk_t);
}

/*
 =================
 CIN_ReadInfo
 =================
*/
static void CIN_ReadInfo (cinematic_t *cin){

	roqChunk_t	*chunk = &cin->roqChunk;
	short		data[4];

	FS_Read(data, sizeof(data), cin->file);
	cin->remaining -= sizeof(data);

	cin->vidWidth = LittleShort(data[0]);
	cin->vidHeight = LittleShort(data[1]);

	if (cin->roqBuffer)
		Z_Free(cin->roqBuffer);

	cin->roqBuffer = Z_Malloc(cin->vidWidth * cin->vidHeight * 4 * 2);

	cin->roqBufferPtr[0] = cin->roqBuffer;
	cin->roqBufferPtr[1] = cin->roqBuffer + cin->vidWidth * cin->vidHeight * 4;

	cin->rawWidth = Clamp(cin->vidWidth, 1, cls.glConfig.maxTextureSize);
	cin->rawHeight = Clamp(cin->vidHeight, 1, cls.glConfig.maxTextureSize);

	if (cin->rawWidth != cin->vidWidth || cin->rawHeight != cin->vidHeight){
		if (cin->rawBuffer)
			Z_Free(cin->rawBuffer);

		cin->rawBuffer = Z_Malloc(cin->rawWidth * cin->rawHeight * 4);
	}
}

/*
 =================
 CIN_ReadCodebook
 =================
*/
static void CIN_ReadCodebook (cinematic_t *cin){

	roqChunk_t	*chunk = &cin->roqChunk;
	int			nv1, nv2;

	nv1 = (chunk->argument >> 8) & 0xff;
	if (!nv1)
		nv1 = 256;

	nv2 = chunk->argument & 0xff;
	if (!nv2 && (nv1 * 6 < chunk->size))
		nv2 = 256;

	FS_Read(cin->roqCells, sizeof(roqCell_t) * nv1, cin->file);
	FS_Read(cin->roqQCells, sizeof(roqQCell_t) * nv2, cin->file);
	cin->remaining -= chunk->size;
}

/*
 =================
 CIN_DecodeBlock
 =================
*/
static void CIN_DecodeBlock (byte *dst0, byte *dst1, const byte *src0, const byte *src1, float u, float v){

	int		rgb[3];

	// Convert YCbCr to RGB
	rgb[0] = 1.402 * v;
	rgb[1] = -0.34414 * u - 0.71414 * v;
	rgb[2] = 1.772 * u;

	// 1st pixel
	dst0[0] = Clamp(rgb[0] + src0[0], 0, 255);
	dst0[1] = Clamp(rgb[1] + src0[0], 0, 255);
	dst0[2] = Clamp(rgb[2] + src0[0], 0, 255);
	dst0[3] = 255;

	// 2nd pixel
	dst0[4] = Clamp(rgb[0] + src0[1], 0, 255);
	dst0[5] = Clamp(rgb[1] + src0[1], 0, 255);
	dst0[6] = Clamp(rgb[2] + src0[1], 0, 255);
	dst0[7] = 255;

	// 3rd pixel
	dst1[0] = Clamp(rgb[0] + src1[0], 0, 255);
	dst1[1] = Clamp(rgb[1] + src1[0], 0, 255);
	dst1[2] = Clamp(rgb[2] + src1[0], 0, 255);
	dst1[3] = 255;

	// 4th pixel
	dst1[4] = Clamp(rgb[0] + src1[1], 0, 255);
	dst1[5] = Clamp(rgb[1] + src1[1], 0, 255);
	dst1[6] = Clamp(rgb[2] + src1[1], 0, 255);
	dst1[7] = 255;
}

/*
 =================
 CIN_ApplyVector2x2
 =================
*/
static void CIN_ApplyVector2x2 (cinematic_t *cin, int x, int y, const roqCell_t *cell){

	byte	*dst0, *dst1;

	dst0 = cin->roqBufferPtr[0] + (y * cin->vidWidth + x) * 4;
	dst1 = dst0 + cin->vidWidth * 4;

	CIN_DecodeBlock(dst0, dst1, cell->y, cell->y+2, (float)((int)cell->u - 128), (float)((int)cell->v - 128));
}

/*
 =================
 CIN_ApplyVector4x4
 =================
*/
static void CIN_ApplyVector4x4 (cinematic_t *cin, int x, int y, const roqCell_t *cell){

	byte	*dst0, *dst1;
	byte	yp[4];
	float	u, v;

	u = (float)((int)cell->u - 128);
	v = (float)((int)cell->v - 128);

	yp[0] = yp[1] = cell->y[0];
	yp[2] = yp[3] = cell->y[1];

	dst0 = cin->roqBufferPtr[0] + (y * cin->vidWidth + x) * 4;
	dst1 = dst0 + cin->vidWidth * 4;

	CIN_DecodeBlock(dst0, dst0+8, yp, yp+2, u, v);
	CIN_DecodeBlock(dst1, dst1+8, yp, yp+2, u, v);

	yp[0] = yp[1] = cell->y[2];
	yp[2] = yp[3] = cell->y[3];

	dst0 += cin->vidWidth * 4 * 2;
	dst1 += cin->vidWidth * 4 * 2;

	CIN_DecodeBlock(dst0, dst0+8, yp, yp+2, u, v);
	CIN_DecodeBlock(dst1, dst1+8, yp, yp+2, u, v);
}

/*
 =================
 CIN_ApplyMotion4x4
 =================
*/
static void CIN_ApplyMotion4x4 (cinematic_t *cin, int x, int y, byte mv, char meanX, char meanY){

	byte	*src, *dst;
	int		x1, y1;
	int		i;

	x1 = x + 8 - (mv >> 4) - meanX;
	y1 = y + 8 - (mv & 15) - meanY;

	src = cin->roqBufferPtr[1] + (y1 * cin->vidWidth + x1) * 4;
	dst = cin->roqBufferPtr[0] + (y * cin->vidWidth + x) * 4;

	for (i = 0; i < 4; i++){
		memcpy(dst, src, 4 * 4);

		src += cin->vidWidth * 4;
		dst += cin->vidWidth * 4;
	}
}

/*
 =================
 CIN_ApplyMotion8x8
 =================
*/
static void CIN_ApplyMotion8x8 (cinematic_t *cin, int x, int y, byte mv, char meanX, char meanY){

	byte	*src, *dst;
	int		x1, y1;
	int		i;

	x1 = x + 8 - (mv >> 4) - meanX;
	y1 = y + 8 - (mv & 15) - meanY;

	src = cin->roqBufferPtr[1] + (y1 * cin->vidWidth + x1) * 4;
	dst = cin->roqBufferPtr[0] + (y * cin->vidWidth + x) * 4;

	for (i = 0; i < 8; i++){
		memcpy(dst, src, 8 * 4);

		src += cin->vidWidth * 4;
		dst += cin->vidWidth * 4;
	}
}

/*
 =================
 CIN_SmallestNode1
 =================
*/
static int CIN_SmallestNode1 (cinematic_t *cin, int numNodes){

	int		i;
	int		best, bestNode;

	best = 99999999;
	bestNode = -1;

	for (i = 0; i < numNodes; i++){
		if (cin->hUsed[i])
			continue;
		if (!cin->hCount[i])
			continue;
		if (cin->hCount[i] < best){
			best = cin->hCount[i];
			bestNode = i;
		}
	}

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

	cin->hUsed[bestNode] = true;
	return bestNode;
}

/*
 =================
 CIN_Huff1TableInit

 Reads the 64k counts table and initializes the node trees
 =================
*/
static void CIN_Huff1TableInit (cinematic_t *cin){

	int		prev;
	int		j;
	int		*node, *nodeBase;
	byte	counts[256];
	int		numNodes;

	if (!cin->hNodes1)
		cin->hNodes1 = Z_Malloc(256 * 256 * 4 * 2);

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

		// Read a row of counts
		FS_Read(counts, sizeof(counts), cin->file);
		cin->remaining -= sizeof(counts);

		for (j = 0; j < 256; j++)
			cin->hCount[j] = counts[j];

		// Build the nodes
		numNodes = 256;
		nodeBase = cin->hNodes1 + prev*256*2;

		while (numNodes != 511){
			node = nodeBase + (numNodes-256)*2;

			// Pick two lowest counts
			node[0] = CIN_SmallestNode1(cin, numNodes);
			if (node[0] == -1)
				break;

			node[1] = CIN_SmallestNode1(cin, numNodes);
			if (node[1] == -1)
				break;

			cin->hCount[numNodes] = cin->hCount[node[0]] + cin->hCount[node[1]];
			numNodes++;
		}

		cin->hNumNodes1[prev] = numNodes-1;
	}
}

/*
 =================
 CIN_Huff1Decompress
 =================
*/
static void CIN_Huff1Decompress (cinematic_t *cin, const byte *data, int size){

	const byte	*input;
	unsigned	*out;
	int			count;
	int			in;
	int			nodeNum;
	int			*nodes, *nodesBase;

	if (!cin->hBuffer)
		cin->hBuffer = Z_Malloc(cin->vidWidth * cin->vidHeight * 4);

	// Get decompressed count
	count = data[0] + (data[1]<<8) + (data[2]<<16) + (data[3]<<24);
	input = data + 4;
	out = cin->hBuffer;

	// Read bits
	nodesBase = cin->hNodes1 - 256*2;	// Nodes 0-255 aren't stored

	nodes = nodesBase;
	nodeNum = cin->hNumNodes1[0];
	while (count){
		in = *input++;
		
		if (nodeNum < 256){
			nodes = nodesBase + (nodeNum<<9);
			*out++ = cin->palette[nodeNum];
			if (!--count)
				break;
			nodeNum = cin->hNumNodes1[nodeNum];
		}
		nodeNum = nodes[nodeNum*2 + (in&1)];
		in >>= 1;
		
		if (nodeNum < 256){
			nodes = nodesBase + (nodeNum<<9);
			*out++ = cin->palette[nodeNum];
			if (!--count)
				break;
			nodeNum = cin->hNumNodes1[nodeNum];
		}
		nodeNum = nodes[nodeNum*2 + (in&1)];
		in >>= 1;
		
		if (nodeNum < 256){
			nodes = nodesBase + (nodeNum<<9);
			*out++ = cin->palette[nodeNum];
			if (!--count)
				break;
			nodeNum = cin->hNumNodes1[nodeNum];
		}
		nodeNum = nodes[nodeNum*2 + (in&1)];
		in >>= 1;
		
		if (nodeNum < 256){
			nodes = nodesBase + (nodeNum<<9);
			*out++ = cin->palette[nodeNum];
			if (!--count)
				break;
			nodeNum = cin->hNumNodes1[nodeNum];
		}
		nodeNum = nodes[nodeNum*2 + (in&1)];
		in >>= 1;
		
		if (nodeNum < 256){
			nodes = nodesBase + (nodeNum<<9);
			*out++ = cin->palette[nodeNum];
			if (!--count)
				break;
			nodeNum = cin->hNumNodes1[nodeNum];
		}
		nodeNum = nodes[nodeNum*2 + (in&1)];
		in >>= 1;
		
		if (nodeNum < 256){
			nodes = nodesBase + (nodeNum<<9);
			*out++ = cin->palette[nodeNum];
			if (!--count)
				break;
			nodeNum = cin->hNumNodes1[nodeNum];
		}
		nodeNum = nodes[nodeNum*2 + (in&1)];
		in >>= 1;
		
		if (nodeNum < 256){
			nodes = nodesBase + (nodeNum<<9);
			*out++ = cin->palette[nodeNum];
			if (!--count)
				break;
			nodeNum = cin->hNumNodes1[nodeNum];
		}
		nodeNum = nodes[nodeNum*2 + (in&1)];
		in >>= 1;
		
		if (nodeNum < 256){
			nodes = nodesBase + (nodeNum<<9);
			*out++ = cin->palette[nodeNum];
			if (!--count)
				break;
			nodeNum = cin->hNumNodes1[nodeNum];
		}
		nodeNum = nodes[nodeNum*2 + (in&1)];
		in >>= 1;
	}

	if (input - data != size && input - data != size+1)
		Com_Error(ERR_DROP, "CIN_Huff1Decompress: decompression overread by %i", (input - data) - size);
}

/*
 =================
 CIN_ReadPalette
 =================
*/
static void CIN_ReadPalette (cinematic_t *cin){

	int		i;
	byte	palette[768], *pal;

	FS_Read(palette, sizeof(palette), cin->file);
	cin->remaining -= sizeof(palette);

	pal = (byte *)cin->palette;
	for (i = 0; i < 256; i++){
		pal[i*4+0] = palette[i*3+0];
		pal[i*4+1] = palette[i*3+1];
		pal[i*4+2] = palette[i*3+2];
		pal[i*4+3] = 255;
	}
}

/*
 =================
 CIN_ResampleFrame
 =================
*/
static void CIN_ResampleFrame (cinematic_t *cin){

	int			i, j;
	unsigned	*src, *dst;
	int			frac, fracStep;

	if (cin->rawWidth == cin->vidWidth && cin->rawHeight == cin->vidHeight)
		return;

	dst = (unsigned *)cin->rawBuffer;
	fracStep = cin->vidWidth * 0x10000 / cin->rawWidth;

	for (i = 0; i < cin->rawHeight; i++, dst += cin->rawWidth){
		src = (unsigned *)cin->vidBuffer + cin->vidWidth * (i * cin->vidHeight / cin->rawHeight);
		frac = fracStep >> 1;

		for (j = 0; j < cin->rawWidth; j++){
			dst[j] = src[frac>>16];
			frac += fracStep;
		}
	}
}

/*
 =================
 CIN_ReadVideoFrame
 =================
*/
static void CIN_ReadVideoFrame (cinematic_t *cin){

	if (!cin->isRoQ){
		byte		compressed[0x20000];
		int			size;

		FS_Read(&size, sizeof(size), cin->file);
		cin->remaining -= sizeof(size);

		size = LittleLong(size);
		if (size < 1 || size > sizeof(compressed))
			Com_Error(ERR_DROP, "CIN_ReadVideoFrame: bad compressed frame size (%i)", size);

		FS_Read(compressed, size, cin->file);
		cin->remaining -= size;

		CIN_Huff1Decompress(cin, compressed, size);

		cin->vidBuffer = cin->hBuffer;
	}
	else {
		roqChunk_t	*chunk = &cin->roqChunk;
		roqQCell_t	*qcell;
		int			i, vqFlgPos, vqId, pos, xPos, yPos, x, y, xp, yp;
		short		vqFlg;
		byte		c[4], *tmp;

		vqFlg = 0;
		vqFlgPos = -1;

		xPos = yPos = 0;
		pos = chunk->size;

		while (pos > 0){
			for (yp = yPos; yp < yPos + 16; yp += 8){
				for (xp = xPos; xp < xPos + 16; xp += 8){
					if (vqFlgPos < 0){
						FS_Read(&vqFlg, sizeof(vqFlg), cin->file);
						pos -= sizeof(vqFlg);

						vqFlg = LittleShort(vqFlg);
						vqFlgPos = 7;
					}

					vqId = (vqFlg >> (vqFlgPos * 2)) & 0x3;
					vqFlgPos--;
				
					switch (vqId){
					case RoQ_ID_MOT:

						break;
					case RoQ_ID_FCC:
						FS_Read(c, 1, cin->file);
						pos--;

						CIN_ApplyMotion8x8(cin, xp, yp, c[0], (char)((chunk->argument >> 8) & 0xff), (char)(chunk->argument & 0xff));

						break;
					case RoQ_ID_SLD:
						FS_Read(c, 1, cin->file);
						pos--;

						qcell = cin->roqQCells + c[0];
						CIN_ApplyVector4x4(cin, xp, yp, cin->roqCells + qcell->idx[0]);
						CIN_ApplyVector4x4(cin, xp+4, yp, cin->roqCells + qcell->idx[1]);
						CIN_ApplyVector4x4(cin, xp, yp+4, cin->roqCells + qcell->idx[2]);
						CIN_ApplyVector4x4(cin, xp+4, yp+4, cin->roqCells + qcell->idx[3]);

						break;
					case RoQ_ID_CCC:
						for (i = 0; i < 4; i++){
							x = xp; 
							y = yp;

							if (i & 0x01) 
								x += 4;
							if (i & 0x02) 
								y += 4;
						
							if (vqFlgPos < 0){
								FS_Read(&vqFlg, sizeof(vqFlg), cin->file);
								pos -= sizeof(vqFlg);

								vqFlg = LittleShort(vqFlg);
								vqFlgPos = 7;
							}

							vqId = (vqFlg >> (vqFlgPos * 2)) & 0x3;
							vqFlgPos--;

							switch (vqId){
							case RoQ_ID_MOT:

								break;
							case RoQ_ID_FCC:
								FS_Read(c, 1, cin->file);
								pos--;

								CIN_ApplyMotion4x4(cin, x, y, c[0], (char)((chunk->argument >> 8) & 0xff), (char)(chunk->argument & 0xff));

								break;
							case RoQ_ID_SLD:
								FS_Read(c, 1, cin->file);
								pos--;

								qcell = cin->roqQCells + c[0];
								CIN_ApplyVector2x2(cin, x, y, cin->roqCells + qcell->idx[0]);
								CIN_ApplyVector2x2(cin, x+2, y, cin->roqCells + qcell->idx[1]);
								CIN_ApplyVector2x2(cin, x, y+2, cin->roqCells + qcell->idx[2]);
								CIN_ApplyVector2x2(cin, x+2, y+2, cin->roqCells + qcell->idx[3]);

								break;
							case RoQ_ID_CCC:
								FS_Read(&c, 4, cin->file);
								pos -= 4;

								CIN_ApplyVector2x2(cin, x, y, cin->roqCells + c[0]);
								CIN_ApplyVector2x2(cin, x+2, y, cin->roqCells + c[1]);
								CIN_ApplyVector2x2(cin, x, y+2, cin->roqCells + c[2]);
								CIN_ApplyVector2x2(cin, x+2, y+2, cin->roqCells + c[3]);

								break;
							}
						}

						break;
					default:
						Com_Error(ERR_DROP, "CIN_ReadVideoFrame: unknown VQ code (%i)",  vqId);
					}
				}
			}
			
			xPos += 16;
			if (xPos >= cin->vidWidth){
				xPos -= cin->vidWidth;
				yPos += 16;
			}
			if (yPos >= cin->vidHeight && pos){
				CIN_Skip(cin, pos);
				break;
			}
		}

		cin->remaining -= (chunk->size - pos);
	
		if (cin->frameCount == 0)
			memcpy(cin->roqBufferPtr[1], cin->roqBufferPtr[0], cin->vidWidth * cin->vidHeight * 4);
		else {
			tmp = cin->roqBufferPtr[0];
			cin->roqBufferPtr[0] = cin->roqBufferPtr[1];
			cin->roqBufferPtr[1] = tmp;
		}

		cin->vidBuffer = cin->roqBufferPtr[1];
	}

	// Resample
	CIN_ResampleFrame(cin);
}

/*
 =================
 CIN_ReadAudioFrame
 =================
*/
static void CIN_ReadAudioFrame (cinematic_t *cin){

	byte	data[0x40000];
	int		samples;

	if (!cin->isRoQ){
		byte		*p;
		int			start, end, len;

		start = cin->frameCount*cin->sndRate/14;
		end = (cin->frameCount+1)*cin->sndRate/14;
		samples = end - start;
		len = samples * cin->sndWidth * cin->sndChannels;

		if (cin->flags & CIN_SILENT){
			CIN_Skip(cin, len);
			return;
		}

		// HACK: gross hack to keep cinematic audio sync'ed using OpenAL
		if (cin->frameCount == 0){
			samples += 4096;

			if (cin->sndWidth == 2)
				memset(data, 0x00, 4096 * cin->sndWidth * cin->sndChannels);
			else
				memset(data, 0x80, 4096 * cin->sndWidth * cin->sndChannels);

			p = data + (4096 * cin->sndWidth * cin->sndChannels);
		}
		else
			p = data;

		FS_Read(p, len, cin->file);
		cin->remaining -= len;
	}
	else {
		roqChunk_t	*chunk = &cin->roqChunk;
		byte		compressed[0x20000];
		short		l, r;
		int			i;

		if (cin->flags & CIN_SILENT){
			CIN_Skip(cin, cin->roqChunk.size);
			return;
		}

		FS_Read(compressed, chunk->size, cin->file);
		cin->remaining -= chunk->size;

		if (chunk->id == RoQ_SOUND_MONO){
			cin->sndChannels = 1;

			l = chunk->argument;

			for (i = 0; i < chunk->size; i++){
				l += cin->roqSndSqrTable[compressed[i]];

				((short *)&data)[i] = l;
			}

			samples = chunk->size;
		}
		else if (chunk->id == RoQ_SOUND_STEREO){
			cin->sndChannels = 2;

			l = chunk->argument & 0xff00;
			r = (chunk->argument & 0xff) << 8;

			for (i = 0; i < chunk->size; i += 2){
				l += cin->roqSndSqrTable[compressed[i+0]];
				r += cin->roqSndSqrTable[compressed[i+1]];

				((short *)&data)[i+0] = l;
				((short *)&data)[i+1] = r;
			}

			samples = chunk->size / 2;
		}
	}

	// Send sound to mixer
	S_StreamRawSamples(data, samples, cin->sndRate, cin->sndWidth, cin->sndChannels);
}

/*
 =================
 CIN_ReadNextFrame
 =================
*/
static qboolean CIN_ReadNextFrame (cinematic_t *cin){

	if (!cin->isRoQ){
		int			command;
		
		while (cin->remaining > 0){
			FS_Read(&command, sizeof(command), cin->file);
			cin->remaining -= sizeof(command);

			command = LittleLong(command);

			if (cin->remaining <= 0 || command == 2)
				return false;	// Done

			if (command == 1)
				CIN_ReadPalette(cin);

			CIN_ReadVideoFrame(cin);
			CIN_ReadAudioFrame(cin);

			cin->frameCount++;
			return true;
		}

		return false;
	}
	else {
		roqChunk_t	*chunk = &cin->roqChunk;

		while (cin->remaining > 0){
			CIN_ReadChunk(cin);

			if (cin->remaining <= 0 || chunk->size > cin->remaining)
				return false;	// Done

			if (chunk->size <= 0)
				continue;

			if (chunk->id == RoQ_INFO)
				CIN_ReadInfo(cin);
			else if (chunk->id == RoQ_QUAD_CODEBOOK)
				CIN_ReadCodebook(cin);
			else if (chunk->id == RoQ_QUAD_VQ){
				CIN_ReadVideoFrame(cin);

				cin->frameCount++;
				return true;
			}
			else if (chunk->id == RoQ_SOUND_MONO || chunk->id == RoQ_SOUND_STEREO)
				CIN_ReadAudioFrame(cin);
			else
				CIN_Skip(cin, cin->roqChunk.size);
		}

		return false;
	}
}

/*
 =================
 CIN_StaticCinematic
 =================
*/
static qboolean CIN_StaticCinematic (cinematic_t *cin, const char *name){

	byte		*buffer;
	byte		*in, *out;
	pcxHeader_t	*pcx;
	int			x, y, len;
	int			dataByte, runLength;
	byte		palette[768], *pal;
	int			i;

	// Load the file
	len = FS_LoadFile(name, (void **)&buffer);
	if (!buffer)
		return false;

	// Parse the PCX file
	pcx = (pcxHeader_t *)buffer;

    pcx->xMin = LittleShort(pcx->xMin);
    pcx->yMin = LittleShort(pcx->yMin);
    pcx->xMax = LittleShort(pcx->xMax);
    pcx->yMax = LittleShort(pcx->yMax);
    pcx->hRes = LittleShort(pcx->hRes);
    pcx->vRes = LittleShort(pcx->vRes);
    pcx->bytesPerLine = LittleShort(pcx->bytesPerLine);
    pcx->paletteType = LittleShort(pcx->paletteType);

	in = &pcx->data;

	if (pcx->manufacturer != 0x0A || pcx->version != 5 || pcx->encoding != 1){
		FS_FreeFile(buffer);
		Com_Error(ERR_DROP, "CIN_StaticCinematic: invalid PCX header");
	}

	if (pcx->bitsPerPixel != 8 || pcx->colorPlanes != 1){
		FS_FreeFile(buffer);
		Com_Error(ERR_DROP, "CIN_StaticCinematic: only 8 bit PCX images supported");
	}
		
	if (pcx->xMax >= 640 || pcx->yMax >= 480 || pcx->xMax <= 0 || pcx->yMax <= 0){
		FS_FreeFile(buffer);
		Com_Error(ERR_DROP, "CIN_StaticCinematic: bad PCX file (%i x %i)", pcx->xMax, pcx->yMax);
	}

	memcpy(palette, (byte *)buffer + len - 768, 768);

	pal = (byte *)cin->palette;
	for (i = 0; i < 256; i++){
		pal[i*4+0] = palette[i*3+0];
		pal[i*4+1] = palette[i*3+1];
		pal[i*4+2] = palette[i*3+2];
		pal[i*4+3] = 255;
	}

	cin->vidWidth = pcx->xMax+1;
	cin->vidHeight = pcx->yMax+1;

	cin->pcxBuffer = out = Z_Malloc(cin->vidWidth * cin->vidHeight * 4);

	for (y = 0; y <= pcx->yMax; y++){
		for (x = 0; x <= pcx->xMax; ){
			dataByte = *in++;

			if((dataByte & 0xC0) == 0xC0){
				runLength = dataByte & 0x3F;
				dataByte = *in++;
			}
			else
				runLength = 1;

			while (runLength-- > 0){
				*(unsigned *)out = cin->palette[dataByte];

				out += 4;
				x++;
			}
		}
	}

	if (in - buffer > len){
		FS_FreeFile(buffer);
		Z_Free(cin->pcxBuffer);
		cin->pcxBuffer = NULL;
		Com_Error(ERR_DROP, "CIN_StaticCinematic: PCX file was malformed");
	}

	FS_FreeFile(buffer);

	cin->vidBuffer = cin->pcxBuffer;
	
	// Knightmare- NPOT image support
	if (cls.glConfig.textureNonPowerOfTwo) {
		cin->rawWidth = cin->vidWidth;
		cin->rawHeight = cin->vidHeight;
	}
	else {
		cin->rawWidth = 256;
		cin->rawHeight = 256;
	}

	if (cin->rawWidth != cin->vidWidth || cin->rawHeight != cin->vidHeight)
		cin->rawBuffer = Z_Malloc(cin->rawWidth * cin->rawHeight * 4);

	// Resample
	CIN_ResampleFrame(cin);

	cin->frameCount = -1;
	cin->frameTime = cls.realTime;

	cin->playing = true;

	return true;
}

/*
 =================
 CIN_HandleForVideo
 =================
*/
static cinematic_t *CIN_HandleForVideo (cinHandle_t *handle){

	cinematic_t	*cin;
	int			i;

	for (i = 0, cin = cinematics; i < MAX_CINEMATICS; i++, cin++){
		if (cin->playing)
			continue;

		*handle = i+1;
		return cin;
	}

	Com_Error(ERR_DROP, "CIN_HandleForVideo: none free\n");
}

/*
 =================
 CIN_GetVideoByHandle
 =================
*/
static cinematic_t *CIN_GetVideoByHandle (cinHandle_t handle){

	if (handle <= 0 || handle > MAX_CINEMATICS)
		Com_Error(ERR_DROP, "CIN_GetVideoByHandle: out of range");

	return &cinematics[handle-1];
}

/*
 =================
 CIN_RunCinematic
 =================
*/
qboolean CIN_RunCinematic (cinHandle_t handle){

	cinematic_t	*cin;
	int			frame;

	cin = CIN_GetVideoByHandle(handle);

	if (!cin->playing)
		return false;		// Not running
	
	if (cin->frameCount == -1)
		return true;		// Static image

	frame = (cls.realTime - cin->frameTime) * cin->rate/1000;
	if (frame <= cin->frameCount)
		return true;

	if (frame > cin->frameCount+1)
		cin->frameTime = cls.realTime - cin->frameCount * 1000/cin->rate;

	if (!CIN_ReadNextFrame(cin)){
		if (cin->flags & CIN_LOOPED){
			// Restart the cinematic
			FS_Seek(cin->file, 0, FS_SEEK_SET);
			cin->remaining = cin->size;

			// Skip over the header
			CIN_Skip(cin, cin->start);

			cin->frameCount = 0;
			cin->frameTime = cls.realTime;

			return true;
		}

		return false;	// Finished
	}

	return true;
}

/*
 =================
 CIN_SetExtents
 =================
*/
void CIN_SetExtents (cinHandle_t handle, int x, int y, int w, int h){

	cinematic_t	*cin;

	cin = CIN_GetVideoByHandle(handle);

	if (!cin->playing)
		return;			// Not running

	cin->x = x;
	cin->y = y;
	cin->w = w;
	cin->h = h;
}

/*
 =================
 CIN_DrawCinematic
 =================
*/
void CIN_DrawCinematic (cinHandle_t handle){

	cinematic_t	*cin;

	cin = CIN_GetVideoByHandle(handle);

	if (!cin->playing)
		return;			// Not running

	if (cin->rawWidth == cin->vidWidth && cin->rawHeight == cin->vidHeight)
		R_DrawStretchRaw(cin->x, cin->y, cin->w, cin->h, cin->vidBuffer, cin->vidWidth, cin->vidHeight, (cin->flags & CIN_SHADER));
	else
		R_DrawStretchRaw(cin->x, cin->y, cin->w, cin->h, cin->rawBuffer, cin->rawWidth, cin->rawHeight, (cin->flags & CIN_SHADER));
}

/*
 =================
 CIN_PlayCinematic
 =================
*/
cinHandle_t CIN_PlayCinematic (const char *name, int x, int y, int w, int h, int flags){

	cinematic_t	*cin;
	cinHandle_t	handle;
	int			i;
	char		extension[8];

	// See if already playing this cinematic
	for (i = 0, cin = cinematics; i < MAX_CINEMATICS; i++, cin++){
		if (!cin->playing)
			continue;

		if (!Q_stricmp(cin->name, name))
			return i+1;
	}

	// Find a free handle
	cin = CIN_HandleForVideo(&handle);

	// Fill it in
	Q_strncpyz(cin->name, name, sizeof(cin->name));
	cin->x = x;
	cin->y = y;
	cin->w = w;
	cin->h = h;
	cin->flags = flags;

	if (cin->flags & CIN_SYSTEM){
		CDAudio_Stop();					// Make sure CD audio isn't playing
		S_StopAllSounds();				// Make sure sound isn't playing
		UI_SetActiveMenu(UI_CLOSEMENU);	// Close the menu
	}

	Com_FileExtension(name, extension, sizeof(extension));

	if (!Q_stricmp(extension, "pcx")){
		// Static PCX image
		if (!CIN_StaticCinematic(cin, name))
			return 0;

		return handle;
	}

	if (!Q_stricmp(extension, "RoQ")){
		cin->isRoQ = true;
		cin->rate = 30;
	}
	else if (!Q_stricmp(extension, "cin")){
		cin->isRoQ = false;
		cin->rate = 14;
	}
	else
		return 0;

	// Open the cinematic file
	cin->size = FS_FOpenFile(name, &cin->file, FS_READ);
	if (!cin->file)
		return 0;

	cin->remaining = cin->size;
	
	// Read the header
	if (!cin->isRoQ){
		FS_Read(&cin->vidWidth, sizeof(cin->vidWidth), cin->file);
		FS_Read(&cin->vidHeight, sizeof(cin->vidHeight), cin->file);
		cin->vidWidth = LittleLong(cin->vidWidth);
		cin->vidHeight = LittleLong(cin->vidHeight);

		FS_Read(&cin->sndRate, sizeof(cin->sndRate), cin->file);
		FS_Read(&cin->sndWidth, sizeof(cin->sndWidth), cin->file);
		FS_Read(&cin->sndChannels, sizeof(cin->sndChannels), cin->file);
		cin->sndRate = LittleLong(cin->sndRate);
		cin->sndWidth = LittleLong(cin->sndWidth);
		cin->sndChannels = LittleLong(cin->sndChannels);

		cin->remaining -= 20;

		// Knightmare- NPOT image support
		if (cls.glConfig.textureNonPowerOfTwo) {
			cin->rawWidth = cin->vidWidth;
			cin->rawHeight = cin->vidHeight;
		}
		else {
			cin->rawWidth = 256;
			cin->rawHeight = 256;
		}

		if (cin->rawWidth != cin->vidWidth || cin->rawHeight != cin->vidHeight)
			cin->rawBuffer = Z_Malloc(cin->rawWidth * cin->rawHeight * 4);

		CIN_Huff1TableInit(cin);

		cin->start = FS_Tell(cin->file);
	}
	else {
		cin->sndRate = 22050;
		cin->sndWidth = 2;

		CIN_ReadChunk(cin);

		CIN_SoundSqrTableInit(cin);

		cin->start = FS_Tell(cin->file);
	}

	if (!(cin->flags & CIN_SILENT))
		S_StartStreaming();

	cin->frameCount = 0;
	cin->frameTime = cls.realTime;

	cin->playing = true;

	// Read the first frame
	CIN_ReadNextFrame(cin);

	return handle;
}

/*
 =================
 CIN_StopCinematic
 =================
*/
void CIN_StopCinematic (cinHandle_t handle){

	cinematic_t	*cin;

	cin = CIN_GetVideoByHandle(handle);

	if (!cin->playing)
		return;			// Not running

	if (!(cin->flags & CIN_SILENT))
		S_StopStreaming();

	if (cin->rawBuffer)
		Z_Free(cin->rawBuffer);

	if (cin->pcxBuffer)
		Z_Free(cin->pcxBuffer);

	if (cin->hNodes1)
		Z_Free(cin->hNodes1);
	if (cin->hBuffer)
		Z_Free(cin->hBuffer);

	if (cin->roqBuffer)
		Z_Free(cin->roqBuffer);

	if (cin->file)
		FS_FCloseFile(cin->file);

	memset(cin, 0, sizeof(*cin));
}
