
#include "burnint.h"

#define XBOX

#define MAX_DUMP	4				// maximum amount of dump files

int universal_vmm_enabled[MAX_DUMP] = { 0, 0, 0, 0 };	// makes sure that non-vmm games don't crash

static unsigned int maximum_data_number[MAX_DUMP];	// maximum allowable datas
static unsigned int maximum_memory_size[MAX_DUMP];	// How much ram can we use?

static unsigned int DataRAMAvailable[MAX_DUMP];		// How much RAM is still available for use

static unsigned int retain_n_frames[MAX_DUMP];		// How many frames worth of data should be keep

static unsigned char vmm_transparency[MAX_DUMP];	// Fill buffer with this in case of errors

struct DataInfo {
	unsigned int data_offset;			// offset to data in dumped data
	unsigned int data_size;				// size of the data
	unsigned char *ptr;				// pointer to malloc

	unsigned int last_frame;			// last frame used (how long ago)
};

static struct DataInfo *DataInfoStruct[MAX_DUMP];	// Structure
static struct DataInfo *datanfo;			// pointer to structure

#ifdef XBOX
#define dump_path	"Z:\\"
#else
#define dump_path	""
#endif

char dump_name[128];					// hold name for dump file
FILE *data_dump[MAX_DUMP];				// pointer to dump file

static int vmm_data_select;				// which data dump are we using

static unsigned int data_offset[MAX_DUMP];		// offset inside file
static unsigned int data_length[MAX_DUMP];		// length of file

static inline void universal_vmm_read_data(unsigned char *dest, unsigned int size, unsigned int offset)
{
	// get everything ready
	datanfo->ptr = (unsigned char*)malloc(size);
	datanfo->data_size = size;
	datanfo->data_offset = offset;
	datanfo->last_frame = nCurrentFrame;

	// seek file and set data offset
	if (offset != data_offset[vmm_data_select])
	{
		int ofst = offset - data_offset[vmm_data_select];
		fseek (data_dump[vmm_data_select], ofst, SEEK_CUR);
	}

	data_offset[vmm_data_select] = offset + size;

	fread (datanfo->ptr, size, 1, data_dump[vmm_data_select]);

	if (offset + size >= data_length[vmm_data_select]) {
		rewind (data_dump[vmm_data_select]);
		data_offset[vmm_data_select] = 0;
	}

	memcpy (dest, datanfo->ptr, size);

	DataRAMAvailable[vmm_data_select] -= datanfo->data_size;

	return;
}

// function to actually read & free any necessary data
int universal_vmm_get_data(unsigned int offset, unsigned int size, unsigned char *dest, int select)
{
	// offset
	//	is most drawing routines offset is data_no * size_of_data
	//
	// size
	//	size is generally easy to find out, if a sprite is 16x16 pixels
	//	it will usually be 16 * 16 (256) bytes
	//
	// dest
	//      this is the destionation for the vm data -- usually the "buffer"
	//
	// select
	//	chooses which dump we will be using at this point
	//

	vmm_data_select = select;

	if (!universal_vmm_enabled[vmm_data_select]) return 0;

	int struct_ofst = -1;
	int struct_ofst2 = -1;

	datanfo = &DataInfoStruct[vmm_data_select][0];

	// first, check to see if we even need to bother reading this or not
	if ((offset + size) > data_length[vmm_data_select] || !size)
	{
		memset (dest, vmm_transparency[vmm_data_select], size);
		return 1; // failure
	}

	// check if this size/offset is already available in struct
	// if the offset is there, but the size is too small, 
	// also see if we can find an empty slot
	for (unsigned int i = 0; i < maximum_data_number[vmm_data_select]; i++)
	{
		if (datanfo->ptr) {
			if (struct_ofst < 0 && datanfo->data_offset == offset) {
				if (datanfo->data_size >= size) {
					struct_ofst = i;
					if (struct_ofst2 >= 0) break;
				} else {
					free (datanfo->ptr);
					datanfo->ptr = NULL;
					DataRAMAvailable[vmm_data_select] += datanfo->data_size;

					universal_vmm_read_data(dest, size, offset);

					memcpy (dest, datanfo->ptr, size);

					return 0; // success
				}
			}
		} else {
			if (struct_ofst2 < 0) {
				struct_ofst2 = i;
				if (struct_ofst >= 0) break;
			}
		}

		datanfo++;
	}

	// it's already in the struct
	if (struct_ofst >= 0)
	{
		// dest = datanfo->ptr;
		datanfo = &DataInfoStruct[vmm_data_select][struct_ofst];
		memcpy (dest, datanfo->ptr, size);
		datanfo->last_frame = nCurrentFrame;
		return 0; // success
	}

	// it's not already in the struct

	// check to see if we need to free up some ram
	if (DataRAMAvailable[vmm_data_select] < size)
	{
		unsigned int retain = nCurrentFrame - retain_n_frames[vmm_data_select];
		datanfo = &DataInfoStruct[vmm_data_select][0];

		for (unsigned int j = 0; j < maximum_data_number[vmm_data_select]; j++)
		{
			if (datanfo->ptr)
			{
				// this hasn't been used in 'n' frames, free it
				if (datanfo->last_frame <= retain) {
					free (datanfo->ptr);
					datanfo->ptr = NULL;

					DataRAMAvailable[vmm_data_select] += datanfo->data_size;
					datanfo->data_offset = 0xfffffff;
					datanfo->data_size = 0;
					datanfo->last_frame = 0;
				}
			}

			datanfo++;
		}
	}

	// check to see if we need to free up some ram
	if (DataRAMAvailable[vmm_data_select] < size)
	{
		while (1)
		{
			datanfo = &DataInfoStruct[vmm_data_select][0];

			int high_current = -1;
			unsigned int high_size    = 0;
			unsigned int low_frame = ~0;

			for (unsigned int i = 0; i < maximum_data_number[vmm_data_select]; i++) {
				if (datanfo->ptr != NULL) {
					if (datanfo->last_frame < low_frame || (datanfo->last_frame <= low_frame && datanfo->data_size > high_size))
					{
						high_current = i;
						high_size = datanfo->data_size;
						low_frame = datanfo->last_frame;
					}
				}

				datanfo++;
			}

			if (high_current != -1) {
				datanfo = &DataInfoStruct[vmm_data_select][high_current];
				if (datanfo->ptr) {
					free(datanfo->ptr);
					datanfo->ptr = NULL;
	
					DataRAMAvailable[vmm_data_select] += datanfo->data_size;
	
					datanfo->data_offset = 0xfffffff;
					datanfo->last_frame = 0;
					datanfo->data_size = 0;
				}
			}

			if (DataRAMAvailable[vmm_data_select] >= size) break;			
		}
	}

	// looks like we couldn't free anything, clear the dest and then stop
	if (DataRAMAvailable[vmm_data_select] < size) {
		memset (dest, vmm_transparency[vmm_data_select], size);
		return 1; // failure
	}

	struct_ofst = struct_ofst2;

	// we're ready to start the process of putting the data into ram

	if (struct_ofst < 0) {
		datanfo = &DataInfoStruct[vmm_data_select][0];

		// first, pick a slot to put everything
		for (unsigned int i = 0; i < maximum_data_number[vmm_data_select]; i++) {
			if (datanfo->ptr == NULL) {
				struct_ofst = i;
				break;
			}
			datanfo++;
		}
	} else {
		datanfo = &DataInfoStruct[vmm_data_select][struct_ofst];
	}

	// error check (no empty slots)
	if (struct_ofst == -1) {
		memset (dest, vmm_transparency[vmm_data_select], size);
		return 1; // failure
	}

	universal_vmm_read_data(dest, size, offset);

	return 0;
}


// Dump the data to the hdd and initialize variables
int UniversalVMMInit(unsigned char *data, int select, int len, int maximum_mem, int maximum_data, int retain, unsigned char transp)
{
	// *data
	//	the pointer to the data we intend to use for the data dumping
	//
	// select
	//	chooses which dump we will be using at this point
	//
	// len
	//	the length of the data
	//
	// maximum_mem
	//	the maximum amount of memory (RAM) we will allow our data to use
	//	if memory use meets or exceeds this, memory will be freed depending on size and time (in frames)
	//	since it was last used.  maximum_mem also includes memory taken up by structure that contains
	// 	sizes, pointers, and age -- this is 16 * maximum_data
	//
	// maximum_data
	//	the maximum number of structures to allocate (maximum allowable datas)
	//	setting a high value for this speeds things up, but causes the structure to become larger
	//	and reduces the useable amount of RAM
	//
	// retain
	//	how many frames a data must go unused before it is freed 
	//	lower numbers reduce the amount of ram used, but decrease speed greatly
	//
	// transp
	//	if we have an error reading the data from virtual memory, fill the data buffer with this value
	//	we do this so that the drawing routine doesn't draw garbage.

	vmm_data_select = select;

	DataInfoStruct[vmm_data_select] = (DataInfo*)malloc(maximum_data * sizeof (DataInfo));
	if (DataInfoStruct[vmm_data_select] == NULL) {
		return 1;
	}

	maximum_data_number[vmm_data_select]	= maximum_data;

	{
		datanfo = &DataInfoStruct[vmm_data_select][0];

		for (unsigned int j = 0; j < maximum_data_number[vmm_data_select]; j++) {
			datanfo->ptr = NULL;
			datanfo->data_offset = 0xfffffff;
			datanfo->last_frame = 0;
			datanfo->data_size = 0;
			datanfo++;
		}
	}

	// catch silly mistakes
	if (data == NULL || len <= 0 || maximum_mem <= 0) {
		return 1;
	}

	datanfo = &DataInfoStruct[vmm_data_select][0];
	maximum_memory_size[vmm_data_select]	= maximum_mem;
	DataRAMAvailable[vmm_data_select]	= maximum_memory_size[vmm_data_select];
	DataRAMAvailable[vmm_data_select]	-= maximum_data * sizeof (DataInfo);

	retain_n_frames[vmm_data_select]	= retain;
	data_offset[vmm_data_select]		= 0;

	sprintf (dump_name, "%sDATA_DUMP%d.BIN", dump_path, vmm_data_select);

	data_dump[vmm_data_select] = fopen(dump_name, "wb+");
	fwrite (data, len, 1, data_dump[vmm_data_select]);

	rewind(data_dump[vmm_data_select]);

	data_length[vmm_data_select] = len;

	universal_vmm_enabled[vmm_data_select] = 1;

	vmm_transparency[vmm_data_select] = transp & 0xff;

	vmm_data_select = 0;

	return 0;
}


// remove the dump file, free any used ram, and set vars to NULL
int UniversalVMMExit()
{
	for (int i = 0; i < MAX_DUMP; i++) {
		if (!universal_vmm_enabled[i]) continue;

		if (data_dump[i] != NULL) {
			fclose (data_dump[i]);
			sprintf (dump_name, "%sDATA_DUMP%d.BIN", dump_path, i);
			remove (dump_name);
			data_dump[i] = NULL;
		}

		datanfo = &DataInfoStruct[i][0];

		for (unsigned int j = 0; j < maximum_data_number[i]; j++) {
			if (datanfo->ptr != NULL) {
				free (datanfo->ptr);
			}
			datanfo->ptr = NULL;
			datanfo++;
		}

		free (DataInfoStruct[i]);
		DataInfoStruct[i] = NULL;

		vmm_data_select			= 0;
		universal_vmm_enabled[i]	= 0;
		vmm_transparency[i]		= 0;
		maximum_data_number[i]		= 0;
		maximum_memory_size[i]		= 0;
		DataRAMAvailable[i]		= 0;
		data_length[i]			= 0;
		data_offset[i]			= 0;
		retain_n_frames[i]		= 0;
	}

	datanfo = NULL;

	return 0;
}

