

#include "globalincs/pstypes.h"
#include "cutscene/mvelib.h"
#include "cfile/cfile.h"


static const char MVE_HEADER[]  = "Interplay MVE File\x1A";
static const short MVE_HDRCONST1 = 0x001A;
static const short MVE_HDRCONST2 = 0x0100;
static const short MVE_HDRCONST3 = 0x1133;


// -----------------------------------------------------------
// public MVEFILE functions
// -----------------------------------------------------------

// utility functions for mvefile and mveplayer
short mve_get_short(ubyte *data)
{
	short value;
	value = data[0] | (data[1] << 8);
	return value;
}

ushort mve_get_ushort(ubyte *data)
{
	ushort value;
	value = data[0] | (data[1] << 8);
	return value;
}

int mve_get_int(ubyte *data)
{
	int value;
	value = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
	return value;
}

// open an MVE file
MVEFILE *mvefile_open(char *filename)
{
	int cf_opened = 0;
	int mve_valid = 1;
	char lower_name[MAX_FILENAME_LEN];
	char buffer[20];
	MVEFILE *file;

	// create the file
	file = (MVEFILE *)vm_malloc(sizeof(MVEFILE));

	// set defaults
	file->stream = NULL;
	file->cur_chunk = NULL;
	file->buf_size = 0;
	file->cur_fill = 0;
	file->next_segment = 0;

	// lower case filename for checking
	strncpy(lower_name, filename, strlen(filename)+1);
	strlwr(lower_name);
	
	char *p = strchr( lower_name, '.' );
	if ( p ) *p = 0;

	strcat_s( lower_name, ".mve" );

	// NOTE: CF_TYPE *must* be ANY to get movies off of the CDs
	// assume lower case filename for *nix
	file->stream = cfopen(lower_name, "rb", CFILE_NORMAL, CF_TYPE_ANY);
	if ( file->stream ) {
		cf_opened = 1;
	}

	if (!cf_opened) {
		mvefile_close(file);
		return NULL;
	}

	// initialize the buffer
	file->cur_chunk = (ubyte *)vm_malloc(100 + 1024);
	file->buf_size = 100 + 1024;

	// verify the file's header
	cfread_string(buffer, 20, file->stream);
	
	if (strcmp(buffer, MVE_HEADER))
		mve_valid = 0;

	if (cfread_short(file->stream) != MVE_HDRCONST1)
		mve_valid = 0;

	if (cfread_short(file->stream) != MVE_HDRCONST2)
		mve_valid = 0;

	if (cfread_short(file->stream) != MVE_HDRCONST3)
		mve_valid = 0;

	if (!mve_valid) {
		mvefile_close(file);
		return NULL;
	}

	// now, prefetch the next chunk
	mvefile_fetch_next_chunk(file);

	return file;
}

// close a MVE file
void mvefile_close(MVEFILE *file)
{
	// free the stream
	if (file->stream)
		cfclose(file->stream);

	file->stream = NULL;

	// free the buffer
	if (file->cur_chunk)
		vm_free(file->cur_chunk);

	file->cur_chunk = NULL;

	// not strictly necessary
	file->buf_size = 0;
	file->cur_fill = 0;
	file->next_segment = 0;

	// free the struct
	vm_free(file);
}

// get the size of the next segment
int mvefile_get_next_segment_size(MVEFILE *file)
{
	// if nothing is cached, fail
	if (file->cur_chunk == NULL || file->next_segment >= file->cur_fill)
		return -1;

	// if we don't have enough data to get a segment, fail
	if (file->cur_fill - file->next_segment < 4)
		return -1;

	// otherwise, get the data length
	return mve_get_short(file->cur_chunk + file->next_segment);
}

// get type of next segment in chunk (0xff if no more segments in chunk)
ubyte mvefile_get_next_segment_major(MVEFILE *file)
{
	// if nothing is cached, fail
	if (file->cur_chunk == NULL || file->next_segment >= file->cur_fill)
		return 0xff;

	// if we don't have enough data to get a segment, fail
	if (file->cur_fill - file->next_segment < 4)
		return 0xff;

	// otherwise, get the data length
	return file->cur_chunk[file->next_segment + 2];
}

// get subtype (version) of next segment in chunk (0xff if no more segments in chunk)
ubyte mvefile_get_next_segment_minor(MVEFILE *file)
{
	// if nothing is cached, fail
	if (file->cur_chunk == NULL || file->next_segment >= file->cur_fill)
		return 0xff;

	// if we don't have enough data to get a segment, fail
	if (file->cur_fill - file->next_segment < 4)
		return 0xff;

	// otherwise, get the data length
	return file->cur_chunk[file->next_segment + 3];
}

// see next segment (return NULL if no next segment)
ubyte *mvefile_get_next_segment(MVEFILE *file)
{
	// if nothing is cached, fail
	if (file->cur_chunk == NULL || file->next_segment >= file->cur_fill)
		return NULL;

	// if we don't have enough data to get a segment, fail
	if (file->cur_fill - file->next_segment < 4)
		return NULL;

	// otherwise, get the data length
	return file->cur_chunk + file->next_segment + 4;
}

// advance to next segment
void mvefile_advance_segment(MVEFILE *file)
{
	// if nothing is cached, fail
	if (file->cur_chunk == NULL || file->next_segment >= file->cur_fill)
		return;

	// if we don't have enough data to get a segment, fail
	if (file->cur_fill - file->next_segment < 4)
		return;

	// else, advance to next segment
	file->next_segment += (4 + mve_get_ushort(file->cur_chunk + file->next_segment));
}

// fetch the next chunk (return 0 if at end of stream)
int mvefile_fetch_next_chunk(MVEFILE *file)
{
	ubyte buffer[4];
	ubyte *new_buffer;
	ushort length;

	// fail if not open
	if (file->stream == NULL)
		return 0;

	// fail if we can't read the next segment descriptor
	if (cfread(buffer, 1, 4, file->stream) < 4)
		return 0;

	// pull out the next length
	length = mve_get_short(buffer);

	// setup a new buffer if needed --
	// only allocate new buffer is old one is too small
	if (length > file->buf_size) {
		// allocate new buffer
		new_buffer = (ubyte *)vm_malloc(100 + length);

		// copy old data
		if (file->cur_chunk && file->cur_fill)
			memcpy(new_buffer, file->cur_chunk, file->cur_fill);

		// free old buffer
		if (file->cur_chunk) {
			vm_free(file->cur_chunk);
			file->cur_chunk = NULL;
		}

		// install new buffer
		file->cur_chunk = new_buffer;
		file->buf_size = 100 + length;
	}

	// read the chunk
	if (length > 0) {
		if (cfread(file->cur_chunk, 1, length, file->stream) < length)
			return 0;
	}

	file->cur_fill = length;
	file->next_segment = 0;

	return 1;
}

// -----------------------------------------------------------
// public MVESTREAM functions
// -----------------------------------------------------------

// open an MVE stream
MVESTREAM *mve_open(char *filename)
{
	MVESTREAM *stream;

	// allocate
	stream = (MVESTREAM *)vm_malloc(sizeof(MVESTREAM));

	// defaults
	stream->movie = NULL;

	// open
	stream->movie = mvefile_open(filename);

	if (stream->movie == NULL) {
		mve_close(stream);
		return NULL;
	}

	return stream;
}

// close an MVE stream
void mve_close(MVESTREAM *stream)
{
	// close MVEFILE
	if (stream->movie)
		mvefile_close(stream->movie);

	stream->movie = NULL;

	vm_free(stream);
}

// play next chunk
int mve_play_next_chunk(MVESTREAM *stream)
{
	ubyte major, minor;
	ubyte *data;
	int len;

	// loop over segments
	major = mvefile_get_next_segment_major(stream->movie);
	while (major != 0xff) {
		// check whether to handle the segment
		if (major < 32) {
			minor = mvefile_get_next_segment_minor(stream->movie);
			len = mvefile_get_next_segment_size(stream->movie);
			data = mvefile_get_next_segment(stream->movie);

			switch (major) {
				case 0x00:
					mve_end_movie();
					break;
				case 0x01:
					mve_end_chunk();
					break;
				case 0x02:
					mve_timer_create(data);
					break;
				case 0x03:
					mve_audio_createbuf(minor, data);
					break;
				case 0x04:
					mve_audio_play();
					break;
				case 0x05:
					if (!mve_video_createbuf(minor, data))
						return 0;
					break;
				case 0x07:
					mve_video_display();
					break;
				case 0x08:
					mve_audio_data(major, data);
					break;
				case 0x09:
					mve_audio_data(major, data);
					break;
				case 0x0a:
					if (!mve_video_init(data))
						return 0;
					break;
				case 0x0c:
					mve_video_palette(data);
					break;
				case 0x0f:
					mve_video_codemap(data, len);
					break;
				case 0x11:
					mve_video_data(data, len);
					break;
				default:
					break;
			}
		}

		// advance to next segment
		mvefile_advance_segment(stream->movie);
		major = mvefile_get_next_segment_major(stream->movie);
	}

	if (!mvefile_fetch_next_chunk(stream->movie))
		return 0;

	// return status
	return 1;
}
