#include <stdlib.h>
#include <string.h> /* memmove() */
#include <iostream>

#include <SST/SST_Concurrency.h>

#include <ZUtil/ZAlloc.hpp>

SST_Mutex AllocLock = SST_Concurrency_CreateMutex();

#if ZALLOC_CHECK_ALLOC

static ZAllocInfo* AllocList = NULL;

int ZAllocInfo::CurrentAllocCount = 0;
int ZAllocInfo::CurrentCheckpoint = 0;

void ZAllocAddTrack(uintptr_t _address, size_t _size, const char *_file, int _line, int _checkpoint)
{	
	ZAllocInfo *info = (ZAllocInfo*)malloc(sizeof(ZAllocInfo));
	size_t len = strlen(_file);

	info->Address = _address;
	info->Size = _size;

	if(len > sizeof(info->File)-1)
		len = sizeof(info->File)-1;
		
	memcpy(info->File, _file, len);
	info->File[len] = '\0';

	info->Line = _line;
	info->Checkpoint = _checkpoint;

	info->Next = NULL;

	//Mutex may be NULL if doing pre-main() init 
	if (AllocLock)
		SST_Concurrency_LockMutex(AllocLock);

	info->Next = AllocList;
	AllocList = info;

	#if	ZALLOC_EXTRA_SPAMMY
	std::cerr << "Inc Alloc Count " << ZAllocInfo::CurrentAllocCount << " -> " << (ZAllocInfo::CurrentAllocCount+1) << std::endl;
	#endif

	ZAllocInfo::CurrentAllocCount++;

	//Mutex may be NULL if doing pre-main() init 
	if (AllocLock)
		SST_Concurrency_UnlockMutex(AllocLock);
}

void ZAllocRemoveTrack(uintptr_t _address)
{
	//Mutex may be NULL if doing pre-main() init 
	if (AllocLock)
		SST_Concurrency_LockMutex(AllocLock);

	ZAllocInfo *prev = NULL;
	ZAllocInfo *cur = AllocList;

	while (cur != NULL)
	{
		if (cur->Address == _address)
		{
			if (prev == NULL) //i.e. removing head
				AllocList = cur->Next;
			else
				prev->Next = cur->Next;	//Remove "this" from the chain. link parent's next pointer to this one's next pointer so nobody has a reference to "this".

			free(cur);

			#if	ZALLOC_EXTRA_SPAMMY
			std::cerr << "Dec Alloc Count " << ZAllocInfo::CurrentAllocCount << " -> " << (ZAllocInfo::CurrentAllocCount-1) << std::endl;
			#endif

			ZAllocInfo::CurrentAllocCount--;

			break;
		}

		prev = cur;
		cur = cur->Next;
	}

	//Mutex may be NULL if doing pre-main() init
	if (AllocLock)
		SST_Concurrency_UnlockMutex(AllocLock);
}

void ZAllocDumpUnfreed(ZMemState *_state)
{
	SST_Concurrency_LockMutex(AllocLock);

	size_t totalSize = 0;
	int checkpoint = _state == NULL ? 0 : _state->Checkpoint;

	std::cerr << "--- ZAlloc Unfreed Memory Dump ---" << std::endl << std::endl;
	std::cerr << ZAllocInfo::CurrentAllocCount << " Current Unfreed Allocations:" << std::endl;

	ZAllocInfo *cur = AllocList;

	while (cur != NULL)
	{
		if (cur->Checkpoint >= checkpoint)
		{		      
			std::cerr << cur->File << "\n\tLINE: " << cur->Line << "\n\tADDRESS " << (void*)cur->Address << " (" << cur->Size << " bytes) unfreed!" << std::endl;
			totalSize += cur->Size;
		}

		cur = cur->Next;		
	}

	std::cerr << "-----------------------------------------------------------\n";
	std::cerr << "Total Unfreed: " << totalSize << " bytes" << std::endl << std::endl;

	std::cerr << "---  End Unfreed Memory Dump   ---" << std::endl;

	SST_Concurrency_UnlockMutex(AllocLock);
}

//Debug Operator New
void * operator new(size_t _size, const char *_file, int _line, int _checkpoint)
throw()
{
	void *ptr = (void*)malloc(_size);

	ZAllocAddTrack((uintptr_t)ptr, _size, _file, _line, _checkpoint);

	return ptr;
}

void * operator new[](size_t _size, const char* _file, int _line, int _checkpoint)
throw()
{
	void *ptr = (void*)malloc(_size);

	ZAllocAddTrack((uintptr_t)ptr, _size, _file, _line, _checkpoint);

	return ptr;
}

//Debug Delete
void operator delete(void *_ptr, const char* _file, int _line, int _checkpoint)
{	
	(void)_file;
	(void)_line;
	(void)_checkpoint; 

	ZAllocRemoveTrack((uintptr_t)_ptr);

	free(_ptr);
}

void operator delete[](void *_ptr, const char* _file, int _line, int _checkpoint)
{
	(void)_file;
	(void)_line;
	(void)_checkpoint;

	ZAllocRemoveTrack((uintptr_t)_ptr);

	free(_ptr);
}
#ifndef __sun /* TODO: some error with Solaris Studio C++ compiler. */
void operator delete(void *_ptr)
{	
	ZAllocRemoveTrack((uintptr_t)_ptr);

	free(_ptr);
}

void operator delete[](void *_ptr)
{	
	ZAllocRemoveTrack((uintptr_t)_ptr);

	free(_ptr);
}
#endif

#endif

/////////////////////////////////////
/* Static New Functions and Fields */
/////////////////////////////////////

//This contains all our statically allocated pointers
void **StaticAllocArray = NULL;

//This is how many we can hold
int StaticAllocCapacity = 0;

//This is how many we've got
int StaticAllocSize = 0;

//This iterates and deletes
void ZAllocDeleteStatic(void)
{
	int i;

	//Mutex may be NULL if doing pre-main() init 
	if (AllocLock)
		SST_Concurrency_LockMutex(AllocLock);

	//Iterate, delete, then delete our static alloc array
	for (i = 0; i < StaticAllocSize; i++)
		free(StaticAllocArray[i]);

	free(StaticAllocArray);

	StaticAllocArray = NULL;
	StaticAllocCapacity = 0;
	StaticAllocSize = 0;

	//Mutex may be NULL if doing pre-main() init
	if (AllocLock)
		SST_Concurrency_UnlockMutex(AllocLock);
}

void ZAllocAddStaticPtr(void *_ptr)
{
	//Mutex may be NULL if doing pre-main() init 
	if (AllocLock)
		SST_Concurrency_LockMutex(AllocLock);

	//If we need to, register with atexit to call ZAllocDeleteStatic
	if (StaticAllocArray == NULL)
		atexit(ZAllocDeleteStatic);

	//Check to see if StaticAllocArray is full
	if (StaticAllocSize >= StaticAllocCapacity )
	{
		//Allocate new one
		size_t newCapacity = StaticAllocCapacity == 0 ? 128 : StaticAllocCapacity * 2;
		void *temp = malloc(newCapacity * sizeof(void*));

		//Make sure we don't need to crash hard here
		if (temp == NULL)
		{
			std::cerr << "ZAlloc failed to allocate static tracking array!" << std::endl;
			exit(-1);
		}

		//Copy the old data in
		memmove(temp, StaticAllocArray, StaticAllocSize * sizeof(void*));

		//Free the old pointer
		free(StaticAllocArray);
		
		//Reset our pointer
		StaticAllocArray = (void**)temp;
	}

	//Add new void*
	StaticAllocArray[StaticAllocSize] = _ptr;

	//Increment size
	StaticAllocSize++;

	//Mutex may be NULL if doing pre-main() init
	if (AllocLock)
		SST_Concurrency_UnlockMutex(AllocLock);
}

//Static operator new
void * operator new(size_t _size, znew_static_t _staticAlloc)
{
	URFP(_staticAlloc);

	void *ptr = (void*)malloc(_size);

	ZAllocAddStaticPtr(ptr);

	return ptr;
}

void * operator new[](size_t _size, znew_static_t _staticAlloc)
{
	URFP(_staticAlloc);

	void *ptr = (void*)malloc(_size);

	ZAllocAddStaticPtr(ptr);

	return ptr;
}
