// Copyright (C) 2004-2009 Christoph Kubisch & Eike Decker
// This file is part of the "Luxinia Engine".
// For conditions of distribution and use, see copyright notice in luxiniapublicsdk.txt

#include <luxcore/refsys.h>
#include <luxcore/contvector.h>
#include <luxcore/memorygeneric.h>

//////////////////////////////////////////////////////////////////////////
#define OBJREFSYS_ALLOC_PERPAGE	((512-(sizeof(lxObjRef_t)))/(sizeof(lxObjRef_t)))

typedef struct lxObjRefPage_s{
	lxObjRef_t				next;	// sacrifice one for pagelist and to keep alignment well
	lxObjRef_t				block[OBJREFSYS_ALLOC_PERPAGE];
}lxObjRefPage_t;

//////////////////////////////////////////////////////////////////////////

lxObjRefSysPTR	lxObjRefSys_new()
{
	lxObjRefSysPTR refsys = (lxObjRefSysPTR)lxMemGenAlloc(sizeof(lxObjRefSys_t));
	lxObjRefSys_init(refsys);
	return refsys;
}
void			lxObjRefSys_delete(lxObjRefSysPTR sys)
{
	lxObjRefSys_deinit(sys,NULL);
	lxMemGenFree(sys,sizeof(lxObjRefSys_t));
}

void lxObjRefSys_popNoDelete(lxObjRefSysPTR sys)
{
	if (--sys->nodelete == 0){
		while (lxContVector_size(&sys->nodelvec)){
			lxObjRef_t* cref = (lxObjRef_t*)lxContVector_back(&sys->nodelvec);

			// in the ugly case someone has created a new reference
			// within the NoDelete section
			if (cref->usecounter == 0){
				ObjRefSys_deleteRef(sys,cref);
			}
			

			lxContVector_popBack(&sys->nodelvec);
		}
	}
	LUX_ASSERT(sys->nodelete >= 0);
}

void lxObjRefSys_pushNoDelete(lxObjRefSysPTR sys)
{
	LUX_ASSERT(sys->nodelete >= 0);

	sys->nodelete++;
}


void lxObjRefSys_init(lxObjRefSysPTR sys)
{
	memset(sys,0,sizeof(lxObjRefSys_t));
	sys->allocator.lastinpage = OBJREFSYS_ALLOC_PERPAGE;
	lxContVector_init(&sys->typeinfovec,sizeof(lxObjTypeInfo_t));
	lxContVector_init(&sys->nodelvec,sizeof(lxObjRefPTR));
}

void lxObjRefSys_deinit(lxObjRefSysPTR sys, lxObjRefCheckDelete_fn *func)
{
	// cleanup all refs
	lxObjRefPage_t*	pagelist = sys->allocator.pagelist;

	while (sys->nodelete > 0){
		lxObjRefSys_popNoDelete(sys);
	}

	while (pagelist){
		lxObjRefPage_t*	page = pagelist;
		uint32		i;

		pagelist = (lxObjRefPage_t*)page->next.id.ptr;

		for (i = 0; i < OBJREFSYS_ALLOC_PERPAGE; i++)
		{
			lxObjRef_t	*cref = &page->block[i];
			if (cref->id.type >= OBJREF_TYPE_USERSTART &&
				(!func || func(cref)) &&
				sys->typeinfos[cref->id.type].fnDelete)
			{
				sys->typeinfos[cref->id.type].fnDelete(cref);
			}
		}

		lxMemGenFree(page,512);
	}

	lxContVector_clear(&sys->typeinfovec);
	lxContVector_clear(&sys->nodelvec);
	memset(sys,0,sizeof(lxObjRefSys_t));
}

void lxObjRefSys_register(lxObjRefSys_t *sys, lxObjRefType_t type,lxObjTypeInfo_t infocpy)
{
	static const lxObjTypeInfo_t nullinfo = {NULL};
	size_t sz = lxContVector_size(&sys->typeinfovec);

	LUX_ASSERT(type != OBJREF_TYPE_FREEALLOC);

	if ((size_t)type+1 > sz){
		lxContVector_resize(&sys->typeinfovec,(type+1),&nullinfo);
		sys->typeinfos = (lxObjTypeInfo_t*) lxContVector_front(&sys->typeinfovec);
	}
	sys->typeinfos[type] = infocpy;
}

void ObjRefSys_deleteAlloc(lxObjRefSys_t *sys,lxObjRef_t *data)
{
	lxObjRefAllocSys_t*	allocator = &sys->allocator;

	if (data){
		data->id.ptr = allocator->freerefs;
		data->id.type = OBJREF_TYPE_FREEALLOC;
		allocator->freerefs = data;
		LUX_PROFILING_OP(sys->count--);
	}
}

static lxObjRef_t* ObjRefSys_newAlloc(lxObjRefSys_t *sys){
	lxObjRefAllocSys_t*	allocator = &sys->allocator;
	lxObjRef_t* block;

	LUX_PROFILING_OP(sys->count++);
	if ( block = allocator->freerefs){
		allocator->freerefs = (lxObjRef_t*)block->id.ptr;
		return block;
	}
	else{
		if (allocator->lastinpage == OBJREFSYS_ALLOC_PERPAGE){
			lxObjRefPage_t *page = (lxObjRefPage_t*)lxMemGenZalloc(512);
			LUX_ASSERT(sizeof(lxObjRefPage_t) <= 512);
			page->next.id.ptr = allocator->pagelist;			
			allocator->pagelist = page;
			allocator->lastinpage = 0;
		}
		return &allocator->pagelist->block[allocator->lastinpage++];
	}
}

//////////////////////////////////////////////////////////////////////////


lxObjRefPTR	lxObjRefSys_newRef(lxObjRefSys_t *sys, lxObjRefType_t type, void *ptr)
{
	lxObjRefPTR	cref = (lxObjRefPTR) ObjRefSys_newAlloc(sys);

	LUX_ASSERT(type >= OBJREF_TYPE_USERSTART);

	cref->id.ptr = ptr;
	cref->id.type = type;
	cref->usecounter = 1;
	cref->weakcounter = 1;

	return cref;
}


void  ObjRefSys_deleteRef(lxObjRefSys_t *sys, lxObjRefPTR cref)
{
	if (!sys->nodelete){
		if (lxObjRef_getId(cref)){
			lxObjRefDelete_fn*	fndel = sys->typeinfos[cref->id.type].fnDelete;

			if (fndel){
				fndel(cref);
			}
		}
		cref->id.type = OBJREF_TYPE_DELETED;
		cref->id.ptr = NULL;
		lxObjRefSys_releaseWeakRef(sys,cref);
	}
	else{
		lxContVector_pushBack(&sys->nodelvec,cref);
	}
}

booln lxObjRefSys_invalidateRef(lxObjRefSysPTR sys,lxObjRefPTR cref, booln calldestructor)
{

	if (lxObjRef_getId(cref) && calldestructor){
		lxObjRefDelete_fn*	fndel = sys->typeinfos[cref->id.type].fnDelete;

		if (fndel){
			fndel(cref);
		}
	}
	cref->id.type = OBJREF_TYPE_DELETED;
	cref->id.ptr = NULL;
	return (cref->usecounter > 0);
}
