/*
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.  
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/


#pragma off (unreferenced)
static char rcsid[] = "$Id: mem.c 1.29 1996/05/30 10:31:57 champaign Exp $";
#pragma on (unreferenced)


// Warning( "MEM: Too many malloc's!" );
// Warning( "MEM: Malloc returnd an already alloced block!" );
// Warning( "MEM: Malloc Failed!" );
// Warning( "MEM: Freeing the NULL pointer!" );
// Warning( "MEM: Freeing a non-malloced pointer!" );
// Warning( "MEM: %d/%d check bytes were overwritten at the end of %8x", ec, CHECKSIZE, buffer  );
// Warning( "MEM: %d blocks were left allocated!", numleft );

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>

#include "pstypes.h"
#include "mono.h"
#include "error.h"

#ifdef MACINTOSH

	#include <Gestalt.h>
	#include <Memory.h>
	#include <Processes.h>
	ubyte virtual_memory_on = 0;

	#if defined(RELEASE) || defined(NDEBUG)
		#define MEMSTATS	0
	#else
		#define MEMSTATS	1
	#endif

	
	#if MEMSTATS
		static 	int		sMemStatsFileInitialized	= false;
		static 	FILE* 	sMemStatsFile 				= NULL;
		static	char  	sMemStatsFileName[32]		= "memstats.txt";
	#endif	// end of if MEMSTATS
	
#else	// no memstats on pc
	#define MEMSTATS 0
#endif

#define FULL_MEM_CHECKING 1

#if defined(FULL_MEM_CHECKING) && !defined(NDEBUG)

#define CHECKSIZE 16
#define CHECKBYTE 0xFC

#define MAX_INDEX 10000

static unsigned int MallocBase[MAX_INDEX];
static unsigned int MallocSize[MAX_INDEX];
static unsigned int MallocRealSize[MAX_INDEX];
static unsigned char Present[MAX_INDEX];
static char * Filename[MAX_INDEX];
static char * Varname[MAX_INDEX];
static int LineNum[MAX_INDEX];
static int BytesMalloced = 0;

static unsigned int SmallestAddress = 0xFFFFFFF;
static unsigned int LargestAddress = 0x0;

int show_mem_info = 1;

static int free_list[MAX_INDEX];

static int num_blocks = 0;

static int Initialized = 0;

static int LargestIndex = 0;

int out_of_memory = 0;

void mem_display_blocks();

void mem_init()
{
	int i;
	
	Initialized = 1;

	for (i=0; i<MAX_INDEX; i++ )
	{
		free_list[i] = i;
		MallocBase[i] = 0;
		MallocSize[i] = 0;
		MallocRealSize[i] = 0;
		Present[i] = 0;
		Filename[i] = NULL;
		Varname[i] = NULL;
		LineNum[i] = 0;
	}

	SmallestAddress = 0xFFFFFFF;
	LargestAddress = 0x0;

	num_blocks = 0;
	LargestIndex = 0;

	atexit(mem_display_blocks);
	
#ifdef MACINTOSH

// need to check for virtual memory since we will need to do some
// tricks based on whether it is on or not

	{
		int 			mem_type;
		THz				theD2PartionPtr = nil;
		unsigned long	thePartitionSize = 0;
		
		MaxApplZone();
		MoreMasters();			// allocate 240 more master pointers (mainly for snd handles)
		MoreMasters();
		MoreMasters();
		MoreMasters();
		virtual_memory_on = 0;
		if(Gestalt(gestaltVMAttr, &mem_type) == noErr)
			if (mem_type & 0x1)
				virtual_memory_on = 1;
				
		#if MEMSTATS
			sMemStatsFile = fopen(sMemStatsFileName, "wt");
	
			if (sMemStatsFile != NULL)
			{
				sMemStatsFileInitialized = true;
	
				theD2PartionPtr = ApplicationZone();
				thePartitionSize =  ((unsigned long) theD2PartionPtr->bkLim) - ((unsigned long) theD2PartionPtr);
				fprintf(sMemStatsFile, "\nMemory Stats File Initialized.");
				fprintf(sMemStatsFile, "\nDescent 2 launched in partition of %u bytes.\n",
						thePartitionSize);
			}
		#endif	// end of ifdef MEMSTATS
	}

#endif	// end of ifdef macintosh

}

void PrintInfo( int id )
{
	fprintf( stderr, "\tBlock '%s' created in %s, line %d.\n", Varname[id], Filename[id], LineNum[id] );
}


void * mem_malloc( unsigned int size, char * var, char * filename, int line, int fill_zero )
{
	unsigned int base;
	int i, id;
	void *ptr;
	char * pc;
	int * data;

	if (Initialized==0)
		mem_init();

#if MEMSTATS
	{
		unsigned long	theFreeMem = 0;
	
		if (sMemStatsFileInitialized)
		{
			theFreeMem = FreeMem();
		
			fprintf(sMemStatsFile,
					"\n%9u bytes free before attempting: MALLOC %9u bytes.",
					theFreeMem,
					size);
		}
	}
#endif	// end of ifdef memstats

	if ( num_blocks >= MAX_INDEX )	{
		fprintf( stderr,"\nMEM_OUT_OF_SLOTS: Not enough space in mem.c to hold all the mallocs.\n" );		
		fprintf( stderr, "\tBlock '%s' created in %s, line %d.\n", var, filename, line );
		Error( "MEM_OUT_OF_SLOTS" );
	}

	id = free_list[ num_blocks++ ];

	if (id > LargestIndex ) LargestIndex = id;

	if (id==-1)
	{
		fprintf( stderr,"\nMEM_OUT_OF_SLOTS: Not enough space in mem.c to hold all the mallocs.\n" );		
		fprintf( stderr, "\tBlock '%s' created in %s, line %d.\n", Varname[id], Filename[id], LineNum[id] );
		Error( "MEM_OUT_OF_SLOTS" );
	}

#ifndef MACINTOSH
	ptr = malloc( size+CHECKSIZE );
#else
	ptr = (void *)NewPtrClear( size+CHECKSIZE );
#endif

	/*
	for (j=0; j<=LargestIndex; j++ )
	{
		if (Present[j] && MallocBase[j] == (unsigned int)ptr )
		{
			fprintf( stderr,"\nMEM_SPACE_USED: Malloc returned a block that is already marked as preset.\n" );
			fprintf( stderr, "\tBlock '%s' created in %s, line %d.\n", Varname[id], Filename[id], Line[id] );
			Warning( "MEM_SPACE_USED" );
			Int3();
			}
	}
	*/

	if (ptr==NULL)
	{
		out_of_memory = 1;
		fprintf( stderr, "\nMEM_OUT_OF_MEMORY: Malloc returned NULL\n" );
		fprintf( stderr, "\tBlock '%s' created in %s, line %d.\n", Varname[id], Filename[id], LineNum[id] );
		Error( "MEM_OUT_OF_MEMORY" );
	}

	base = (unsigned int)ptr;
	if ( base < SmallestAddress ) SmallestAddress = base;
	if ( (base+size) > LargestAddress ) LargestAddress = base+size;

	MallocBase[id] = (unsigned int)ptr;
	data = (int *)((int)MallocBase[id]-4);
	MallocRealSize[id] = *data;
	MallocSize[id] = size;
	Varname[id] = var;
	Filename[id] = filename;
	LineNum[id] = line;
	Present[id]    = 1;

	pc = (char *)ptr;

	BytesMalloced += size;

	for (i=0; i<CHECKSIZE; i++ )
		pc[size+i] = CHECKBYTE;

	if (fill_zero)
		memset( ptr, 0, size );

	return ptr;

}

int mem_find_id( void * buffer )
{
	int i;

	for (i=0; i<=LargestIndex; i++ )
		if (Present[i]==1)
			if (MallocBase[i] == (unsigned int)buffer )
				return i;

	// Didn't find id.
	return -1;
}

int mem_check_integrity( int block_number )
{
	int * data;
	int i, ErrorCount;
	ubyte * CheckData;

	CheckData = (char *)(MallocBase[block_number] + MallocSize[block_number]);

	data = (int *)((int)MallocBase[block_number]-4);

	if ( *data != MallocRealSize[block_number] )	{
		fprintf( stderr, "\nMEM_BAD_LENGTH: The length field of an allocated block was overwritten.\n" );
		PrintInfo( block_number );
		//Int3();
		*data = MallocRealSize[block_number];
	}

	ErrorCount = 0;
			
	for (i=0; i<CHECKSIZE; i++ )
		if (CheckData[i] != CHECKBYTE ) {
			ErrorCount++;
			fprintf( stderr, "OA: %x ", &CheckData[i] );
		}

	if (ErrorCount &&  (!out_of_memory))	{
		fprintf( stderr, "\nMEM_OVERWRITE: Memory after the end of allocated block overwritten.\n" );
		PrintInfo( block_number );
		fprintf( stderr, "\t%d/%d check bytes were overwritten.\n", ErrorCount, CHECKSIZE );
		Int3();
	}

	return ErrorCount;

}

void mem_free( void * buffer )
{
	int id;

	if (Initialized==0)
		mem_init();

#if MEMSTATS
	{
		unsigned long	theFreeMem = 0;
	
		if (sMemStatsFileInitialized)
		{
			theFreeMem = FreeMem();
		
			fprintf(sMemStatsFile,
					"\n%9u bytes free before attempting: FREE", theFreeMem);
		}
	}
#endif	// end of ifdef memstats

	if (buffer==NULL  &&  (!out_of_memory))
	{
		fprintf( stderr, "\nMEM_FREE_NULL: An attempt was made to free the null pointer.\n" );
		Warning( "MEM: Freeing the NULL pointer!" );
		Int3();
		return;
	}

	id = mem_find_id( buffer );

	if (id==-1 &&  (!out_of_memory))
	{
		fprintf( stderr, "\nMEM_FREE_NOMALLOC: An attempt was made to free a ptr that wasn't\nallocated with mem.h included.\n" );
		Warning( "MEM: Freeing a non-malloced pointer!" );
		Int3();
		return;
	}
	
	mem_check_integrity( id );
	
	BytesMalloced -= MallocSize[id];

#ifndef MACINTOSH
	free( buffer );
#else
	DisposePtr( (Ptr)buffer );
#endif
	
	
	Present[id] = 0;
	MallocBase[id] = 0;
	MallocSize[id] = 0;

	free_list[ --num_blocks ] = id;
}

void mem_display_blocks()
{
	int i, numleft;

	if (Initialized==0) return;
	
#if MEMSTATS
	{	
		if (sMemStatsFileInitialized)
		{
			unsigned long	theFreeMem = 0;

			theFreeMem = FreeMem();
		
			fprintf(sMemStatsFile,
					"\n%9u bytes free before closing MEMSTATS file.", theFreeMem);
			fprintf(sMemStatsFile, "\nMemory Stats File Closed.");
			fclose(sMemStatsFile);
		}
	}
#endif	// end of ifdef memstats

	numleft = 0;
	for (i=0; i<=LargestIndex; i++ )
	{
		if (Present[i]==1 &&  (!out_of_memory))
		{
			numleft++;
			if (show_mem_info)	{
				fprintf( stderr, "\nMEM_LEAKAGE: Memory block has not been freed.\n" );
				PrintInfo( i );
			}
			mem_free( (void *)MallocBase[i] );
		}
	}

	if (numleft &&  (!out_of_memory))
	{
		Warning( "MEM: %d blocks were left allocated!", numleft );
	}

	if (show_mem_info)	{
		fprintf( stderr, "\n\nMEMORY USAGE:\n" );
		fprintf( stderr, "  %6u Kbytes dynamic data\n", (LargestAddress-SmallestAddress+512)/1024 );
		fprintf( stderr, "  %6u Kbytes code/static data.\n", (SmallestAddress-(4*1024*1024)+512)/1024 );
		fprintf( stderr, "  ---------------------------\n" );
		fprintf( stderr, "  %6u Kbytes required.\n\n", 	(LargestAddress-(4*1024*1024)+512)/1024 );
	}
}

void mem_validate_heap()
{
	int i;
	
	for (i=0; i<LargestIndex; i++  )
		if (Present[i]==1 )
			mem_check_integrity( i );
}

void mem_print_all()
{
	FILE * ef;
	int i, size = 0;

	ef = fopen( "DESCENT.MEM", "wt" );
	
	for (i=0; i<LargestIndex; i++  )
		if (Present[i]==1 )	{
			size += MallocSize[i];
			//fprintf( ef, "Var:%s\t File:%s\t Line:%d\t Size:%d Base:%x\n", Varname[i], Filename[i], Line[i], MallocSize[i], MallocBase[i] );
			fprintf( ef, "%12d bytes in %s declared in %s, line %d\n", MallocSize[i], Varname[i], Filename[i], LineNum[i]  );
		}
	fprintf( ef, "%d bytes (%d Kbytes) allocated.\n", size, size/1024 ); 
	fclose(ef);
}

#else

static int Initialized = 0;
static unsigned int SmallestAddress = 0xFFFFFFF;
static unsigned int LargestAddress = 0x0;
static unsigned int BytesMalloced = 0;

void mem_display_blocks();

#define CHECKSIZE 16
#define CHECKBYTE 0xFC

int show_mem_info = 0;

void mem_init()
{
	Initialized = 1;

	SmallestAddress = 0xFFFFFFF;
	LargestAddress = 0x0;

	atexit(mem_display_blocks);

#ifdef MACINTOSH

	// need to check for virtual memory since we will need to do some
	// tricks based on whether it is on or not
	
	{
		int 			mem_type;
		THz				theD2PartionPtr = nil;
		unsigned long	thePartitionSize = 0;
		
		MaxApplZone();
		MoreMasters();		// allocate 240 more master pointers (mainly for snd handles)
		MoreMasters();
		MoreMasters();
		MoreMasters();
		virtual_memory_on = 0;
		if(Gestalt(gestaltVMAttr, &mem_type) == noErr)
			if (mem_type & 0x1)
				virtual_memory_on = 1;
				
		#if MEMSTATS
			sMemStatsFile = fopen(sMemStatsFileName, "wt");

			if (sMemStatsFile != NULL)
			{
				sMemStatsFileInitialized = true;

				theD2PartionPtr = ApplicationZone();
				thePartitionSize =  ((unsigned long) theD2PartionPtr->bkLim) - ((unsigned long) theD2PartionPtr);
				fprintf(sMemStatsFile, "\nMemory Stats File Initialized.");
				fprintf(sMemStatsFile, "\nDescent 2 launched in partition of %u bytes.\n",
						thePartitionSize);
			}
		#endif	// end of ifdef memstats
	}
	
#endif	// end of ifdef macintosh

}

void * mem_malloc( unsigned int size, char * var, char * filename, int line, int fill_zero )
{
	unsigned int base;
	void *ptr;
	int * psize;

	if (Initialized==0)
		mem_init();

#if MEMSTATS
	{
		unsigned long	theFreeMem = 0;
	
		if (sMemStatsFileInitialized)
		{
			theFreeMem = FreeMem();
		
			fprintf(sMemStatsFile,
					"\n%9u bytes free before attempting: MALLOC %9u bytes.",
					theFreeMem,
					size);
		}
	}
#endif	// end of ifdef memstats

	if (size==0)	{
		fprintf( stderr, "\nMEM_MALLOC_ZERO: Attempting to malloc 0 bytes.\n" );
		fprintf( stderr, "\tVar %s, file %s, line %d.\n", var, filename, line );
		Error( "MEM_MALLOC_ZERO" );
		Int3();
	}

#ifndef MACINTOSH
	ptr = malloc( size + CHECKSIZE );
#else
	ptr = (void *)NewPtrClear( size+CHECKSIZE );
#endif

	if (ptr==NULL)	{
		fprintf( stderr, "\nMEM_OUT_OF_MEMORY: Malloc returned NULL\n" );
		fprintf( stderr, "\tVar %s, file %s, line %d.\n", var, filename, line );
		Error( "MEM_OUT_OF_MEMORY" );
		Int3();
	}

	base = (unsigned int)ptr;
	if ( base < SmallestAddress ) SmallestAddress = base;
	if ( (base+size) > LargestAddress ) LargestAddress = base+size;


	psize = (int *)ptr;
	psize--;
	BytesMalloced += *psize;

	if (fill_zero)
		memset( ptr, 0, size );

	return ptr;
}

void mem_free( void * buffer )
{
	int * psize = (int *)buffer;
	psize--;

	if (Initialized==0)
		mem_init();

#if MEMSTATS
	{
		unsigned long	theFreeMem = 0;
	
		if (sMemStatsFileInitialized)
		{
			theFreeMem = FreeMem();
		
			fprintf(sMemStatsFile,
					"\n%9u bytes free before attempting: FREE", theFreeMem);
		}
	}
#endif	// end of ifdef memstats

	if (buffer==NULL)	{
		fprintf( stderr, "\nMEM_FREE_NULL: An attempt was made to free the null pointer.\n" );
		Warning( "MEM: Freeing the NULL pointer!" );
		Int3();
		return;
	}

	BytesMalloced -= *psize;

#ifndef MACINTOSH
	free( buffer );
#else
	DisposePtr( (Ptr)buffer );
#endif
}

void mem_display_blocks()
{
	if (Initialized==0) return;

#if MEMSTATS
	{	
		if (sMemStatsFileInitialized)
		{
			unsigned long	theFreeMem = 0;

			theFreeMem = FreeMem();
		
			fprintf(sMemStatsFile,
					"\n%9u bytes free before closing MEMSTATS file.", theFreeMem);
			fprintf(sMemStatsFile, "\nMemory Stats File Closed.");
			fclose(sMemStatsFile);
		}
	}
#endif	// end of ifdef memstats

	if (BytesMalloced != 0 )	{
		fprintf( stderr, "\nMEM_LEAKAGE: %d bytes of memory have not been freed.\n", BytesMalloced );
	}

	if (show_mem_info)	{
		fprintf( stderr, "\n\nMEMORY USAGE:\n" );
		fprintf( stderr, "  %u Kbytes dynamic data\n", (LargestAddress-SmallestAddress+512)/1024 );
		fprintf( stderr, "  %u Kbytes code/static data.\n", (SmallestAddress-(4*1024*1024)+512)/1024 );
		fprintf( stderr, "  ---------------------------\n" );
		fprintf( stderr, "  %u Kbytes required.\n", 	(LargestAddress-(4*1024*1024)+512)/1024 );
	}
}

void mem_validate_heap()
{
}

void mem_print_all()
{
}

#endif


#ifdef MACINTOSH

// routine to try and compact and purge the process manager zone to squeeze
// some temporary memory out of it for QT purposes.

void PurgeTempMem()
{
	OSErr err;
	Handle tempHandle;
	THz appZone, processZone;
	Size heapSize;
	
	// compact the system zone to try and squeeze some temporary memory out of it
	MaxMemSys(&heapSize);
	
	// compact the Process Manager zone to get more temporary memory
	appZone = ApplicationZone();
	tempHandle = TempNewHandle(10, &err);		// temporary allocation may fail
	if (!err && (tempHandle != NULL) ) {
		processZone = HandleZone(tempHandle);
		if ( MemError() || (processZone == NULL) ) {
			DisposeHandle(tempHandle);
			return;
		}
		SetZone(processZone);
		MaxMem(&heapSize);				// purge and compact the Process Manager Zone.
		SetZone(appZone);
		DisposeHandle(tempHandle);
	}
}

#endif