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

#ifdef	ROQ_SUPPORT

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];

void Q_strncpyz (char *dst, const char *src, int dstSize);


typedef struct
{
	int         renderer;
	const char *renderer_string;
	const char *vendor_string;
	const char *version_string;
	const char *extensions_string;

	qboolean	allow_cds;
	int			max_texsize;
	qboolean	mtexcombine;
	qboolean	NV_texshaders;
	qboolean	ATI_fragshaders;
	qboolean	anisotropic;
	float		max_anisotropy;
} glconfig_t;
extern	glconfig_t gl_config;


/*
=================
Q_strcat

Never goes past bounds or leaves without a terminating 0
=================
*/
void Q_strcat (char *dst, const char *src, int dstSize)
{
	int	len;

	len = strlen(dst);
	if (len >= dstSize)
		Com_Error(ERR_FATAL, "Q_strcat: already overflowed");

	Q_strncpyz(dst + len, src, dstSize - len);
}


/*
=================
Com_FileExtension

Returns the extension, if any (does not include the .)
=================
*/
void Com_FileExtension (const char *path, char *dst, int dstSize)
{
	const char	*s, *last;

	s = last = path + strlen(path);
	while (*s != '/' && *s != '\\' && s != path){
		if (*s == '.'){
			last = s+1;
			break;
		}

		s--;
	}

	Q_strncpyz(dst, last, dstSize);
}

/*
=================
Com_DefaultExtension

If path doesn't have a .EXT, append newExtension (newExtension should
include the .)
=================
*/
void Com_DefaultExtension (char *path, int maxSize, const char *newExtension)
{
	char	*s;

	s = path + strlen(path);
	while (*s != '/' && *s != '\\' && s != path){
		if (*s == '.')
			return;			// It has an extension

		s--;
	}

	Q_strcat(path, newExtension, maxSize);
}

/*
=================
Com_DefaultPath

If path doesn't have a / or \\, append newPath (newPath should not
include the /)
=================
*/
void Com_DefaultPath (char *path, int maxSize, const char *newPath)
{
	char    oldPath[MAX_OSPATH], *s;
		
	s = path;
	while (*s){
		if (*s == '/' || *s == '\\')
			return;		// It has a path
		
		s++;
	}

	Q_strncpyz(oldPath, path, sizeof(oldPath));
	Com_sprintf(path, maxSize, "%s/%s", newPath, oldPath);
}

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

/*
=================
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);

	//if (cin->frameCount < 20)
	//	Com_Printf("read ROQ chunk of size %u, id %u, argument %u, %u remaining\n", chunk->size, chunk->id, chunk->argument, cin->remaining);
}

#define Clamp(a,b,c)		(((a) < (b)) ? (b) : ((a) > (c)) ? (c) : (a))
/*
=================
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, gl_config.max_texsize); // was 512
	cin->rawHeight = Clamp(cin->vidHeight, 1, gl_config.max_texsize); // was 512

	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 = (unsigned *)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);
	S_RawSamples(samples, cin->sndRate, cin->sndWidth, cin->sndChannels, data, false);
}

/*
=================
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++;
			cl.cinematicframe = 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++;
				cl.cinematicframe = 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;
	pcx_t		*pcx;
	int			x, y, len;
	int			dataByte, runLength;
	byte		palette[768], *pal;
	int			i;

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

	// Parse the PCX file
	pcx = (pcx_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->bytes_per_line = LittleShort(pcx->bytes_per_line);
    pcx->palette_type = LittleShort(pcx->palette_type);

	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->bits_per_pixel != 8 || pcx->color_planes != 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;

	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;
	cl.cinematicframe = cin->frameCount;
	cl.cinematictime = cin->frameTime;

	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");
	return NULL;
}

/*
=================
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];
}

/*
==================
SCR_RunCinematic
==================
*/
void SCR_RunCinematic (void)
{
	// Do nothing
	//CIN_RunCinematic(cls.cinematicHandle);
}

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

	/*if (cls.key_dest != key_game)
	{	// pause if menu or console is up
		//cl.cinematictime = cls.realtime - cl.cinematicframe*1000/14;
		cin->frameTime = cls.realtime - cin->frameTime*1000/14;
		cl.cinematictime = cin->frameTime;
		return true;
	}*/

	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;
			cl.cinematicframe = cin->frameCount;
			cl.cinematictime = cin->frameTime;

			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;
}

/*
=================
SCR_PlayCinematic
=================
*/
void SCR_PlayCinematic (char *name)
{
	char	filename[MAX_QPATH];

	// If currently playing another, stop it
	SCR_StopCinematic();

	Com_DPrintf("SCR_PlayCinematic( %s )\n", name);

	cl.cinematicframe = 0;
	if (!Q_stricmp(name+strlen(name)-4, ".pcx"))
	{
		Q_strncpyz(filename, name, sizeof(filename));
		Com_DefaultPath(filename, sizeof(filename), "pics");
		cl.cinematicframe = -1;
		cl.cinematictime = 1;
		SCR_EndLoadingPlaque ();
		cls.state = ca_active;
	}
	else
	{
		Q_strncpyz(filename, name, sizeof(filename));
		Com_DefaultPath(filename, sizeof(filename), "video");
		Com_DefaultExtension(filename, sizeof(filename), ".cin");
	}

	cls.cinematicHandle = CIN_PlayCinematic(filename, 0, 0, viddef.width, viddef.height, CIN_SYSTEM);
	if (!cls.cinematicHandle)
	{
		Com_Printf("Cinematic %s not found\n", filename);
		cl.cinematictime = 0;	// done
		SCR_FinishCinematic();
	}
	else
	{
		SCR_EndLoadingPlaque ();
		cls.state = ca_active;
		cl.cinematicframe = 0;
		cl.cinematictime = Sys_Milliseconds ();
	}
}

/*
=================
SCR_StopCinematic
=================
*/
void SCR_StopCinematic (void)
{
	if (!cls.cinematicHandle)
		return;

	Com_DPrintf("SCR_StopCinematic()\n");

	CIN_StopCinematic(cls.cinematicHandle);
	cls.cinematicHandle = 0;
}

/*
====================
SCR_FinishCinematic

Called when either the cinematic completes, or it is aborted
====================
*/
void SCR_FinishCinematic (void)
{
	// tell the server to advance to the next map / cinematic
	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
	SZ_Print (&cls.netchan.message, va("nextserver %i\n", cl.servercount));
}

/*
=================
SCR_DrawCinematic
=================
*/
qboolean SCR_DrawCinematic (void)
{

	if (cl.cinematictime <= 0)
		return false;

	if (!cls.cinematicHandle)
		return false;

	if (!CIN_RunCinematic(cls.cinematicHandle))
	{
		SCR_StopCinematic();
		SCR_FinishCinematic();
		return false;
	}

	CIN_SetExtents(cls.cinematicHandle, 0, 0, viddef.width, viddef.height);
	CIN_DrawCinematic(cls.cinematicHandle);
	return true;
}

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

	cinematic_t	*cin;

	cin = CIN_GetVideoByHandle(handle);

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

	if (cin->frameCount == -1) // Knightmare- HACK to show JPG endscreens
	{
		char picname[MAX_QPATH] = "/";
		strcat(picname, cin->name);
		R_DrawStretchPic (0, 0, viddef.width, viddef.height, picname, 1.0);
		return;
	} // end JPG hack

	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, (char *)name))
			return i+1;
	}

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

	if (!Q_stricmp(extension, "cin")) // RoQ autoreplace hack
	{
		char s[MAX_QPATH];
		int len;
		len = strlen(name);
		strcpy (s, name);
		s[len-3]='r'; s[len-2]='o'; s[len-1]='q';
		handle  = CIN_PlayCinematic (s, x, y ,w, h, flags);
		if (handle)
			return handle;
	}

	// 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);	
		M_ForceMenuOff();				// 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_strcasecmp(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;

		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_FTell(cin->file);
	}
	else
	{
		cin->sndRate = 22050;
		cin->sndWidth = 2;

		CIN_ReadChunk(cin);
		
		CIN_SoundSqrTableInit(cin);

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

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

	cin->frameCount = 0;
	cin->frameTime = cls.realtime;
	cl.cinematicframe = cin->frameCount;
	cl.cinematictime = cin->frameTime;

	cin->playing = true;

	// Read the first frame
	CIN_ReadNextFrame(cin);

	return handle;
}

/*
=================
CIN_StopCinematic
=================
*/
void CIN_StopCinematic (cinHandle_t handle)
{
	cinematic_t	*cin;

	if (!handle)
		return;

	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));
}
#endif // ROQ_SUPPORT
