/** 
 * @file llvolumemgr.cpp
 *
 * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

#include "linden_common.h"

#include "llvolumemgr.h"
#include "llvolume.h"


//#define DEBUG_VOLUME

LLVolumeMgr* gVolumeMgr = 0;

const F32 BASE_THRESHOLD = 0.03f;

//static
F32 LLVolumeLODGroup::mDetailThresholds[NUM_LODS] = {BASE_THRESHOLD,
													 2*BASE_THRESHOLD,
													 8*BASE_THRESHOLD,
													 100*BASE_THRESHOLD};

//static
F32 LLVolumeLODGroup::mDetailScales[NUM_LODS] = {1.f, 1.5f, 2.5f, 4.f};

//============================================================================
//static
void LLVolumeMgr::initClass()
{
	gVolumeMgr = new LLVolumeMgr();
}

//static
BOOL LLVolumeMgr::cleanupClass()
{
	BOOL res = FALSE;
	if (gVolumeMgr) {
		res = gVolumeMgr->cleanup();
		delete gVolumeMgr;
		gVolumeMgr = 0;
	}
	return res;
}

//============================================================================

LLVolumeMgr::LLVolumeMgr()
{
	mDataMutex = new LLMutex(gAPRPoolp);
//	mNumVolumes = 0;
}

LLVolumeMgr::~LLVolumeMgr()
{
	cleanup();
	delete mDataMutex;
}

BOOL LLVolumeMgr::cleanup()
{
	#ifdef DEBUG_VOLUME
	{
		lldebugs << "LLVolumeMgr::cleanup()" << llendl;
	}
	#endif
	BOOL no_refs = TRUE;
	mDataMutex->lock();
	for (volume_lod_group_map_t::iterator iter = mVolumeLODGroups.begin(),
			 end = mVolumeLODGroups.end();
		 iter != end; iter++)
	{
		LLVolumeLODGroup *volgroupp = iter->second;
		if (volgroupp->getNumRefs() != 1)
		{
			llwarns << "Volume group " << volgroupp << " has " 
					<< volgroupp->getNumRefs() << " remaining refs" << llendl;
			llwarns << volgroupp->getParams() << llendl;
			no_refs = FALSE;
		}
 		volgroupp->unref();// this );
	}
	mVolumeLODGroups.clear();
	mDataMutex->unlock();
	return no_refs;
}

LLVolume *LLVolumeMgr::getVolume(const LLVolumeParams &volume_params, const S32 detail)
{
	LLVolumeLODGroup* volgroupp;
	mDataMutex->lock();
	volume_lod_group_map_t::iterator iter = mVolumeLODGroups.find(&volume_params);
	if( iter == mVolumeLODGroups.end() )
	{
		volgroupp = new LLVolumeLODGroup(volume_params);
		const LLVolumeParams* params = &(volgroupp->getParams());
		mVolumeLODGroups[params] = volgroupp;
 		volgroupp->ref(); // initial reference
	}
	else
	{
		volgroupp = iter->second;
	}
	volgroupp->ref();// this );
	mDataMutex->unlock();
	//	mNumVolumes++;
	#ifdef DEBUG_VOLUME
	{
		lldebugs << "LLVolumeMgr::getVolume() " << (*this) << llendl;
	}
	#endif
	return volgroupp->getLOD(detail);
}

void LLVolumeMgr::cleanupVolume(LLVolume *volumep)
{
	if (volumep->isUnique())
	{
		// TomY: Don't need to manage this volume. It is a unique instance.
		return;
	}
	LLVolumeParams* params = (LLVolumeParams*) &(volumep->getParams());
	mDataMutex->lock();
	volume_lod_group_map_t::iterator iter = mVolumeLODGroups.find(params);
	if( iter == mVolumeLODGroups.end() )
	{
		llerrs << "Warning! Tried to cleanup unknown volume type! " << *params << llendl;
		mDataMutex->unlock();
		return;
	}
	else
	{
		LLVolumeLODGroup* volgroupp = iter->second;

		volgroupp->derefLOD(volumep);
		volgroupp->unref();// this );
		if (volgroupp->getNumRefs() == 1)
		{
			mVolumeLODGroups.erase(params);
			volgroupp->unref();// this );
		}
		//	mNumVolumes--;
	}
	mDataMutex->unlock();

	#ifdef DEBUG_VOLUME
	{
		lldebugs << "LLVolumeMgr::cleanupVolume() " << (*this) << llendl;
	}
	#endif
}

void LLVolumeMgr::dump()
{
	F32 avg = 0.f;
	mDataMutex->lock();
	for (volume_lod_group_map_t::iterator iter = mVolumeLODGroups.begin(),
			 end = mVolumeLODGroups.end();
		 iter != end; iter++)
	{
		LLVolumeLODGroup *volgroupp = iter->second;
		avg += volgroupp->dump();
	}
	int count = (int)mVolumeLODGroups.size();
	avg = count ? avg / (F32)count : 0.0f;
	mDataMutex->unlock();
	llinfos << "Average usage of LODs " << avg << llendl;
}

std::ostream& operator<<(std::ostream& s, const LLVolumeMgr& volume_mgr)
{
	s << "{ numLODgroups=" << volume_mgr.mVolumeLODGroups.size() << ", ";

	S32 total_refs = 0;
	volume_mgr.mDataMutex->lock();

	LLVolumeMgr::volume_lod_group_map_iter iter = volume_mgr.mVolumeLODGroups.begin();
	LLVolumeMgr::volume_lod_group_map_iter end  = volume_mgr.mVolumeLODGroups.end();
	for ( ; iter != end; ++iter)
	{
		LLVolumeLODGroup *volgroupp = iter->second;
		total_refs += volgroupp->getNumRefs();
		s << ", " << (*volgroupp);
	}

	volume_mgr.mDataMutex->unlock();

	s << ", total_refs=" << total_refs << " }";
	return s;
}

LLVolumeLODGroup::LLVolumeLODGroup(const LLVolumeParams &params)
{
	S32 i;
	mParams = params;

	for (i = 0; i < NUM_LODS; i++)
	{
		mLODRefs[i] = 0;
		mVolumeLODs[i] = NULL;
		mAccessCount[i] = 0;
	}
}

LLVolumeLODGroup::~LLVolumeLODGroup()
{
	S32 i;
	for (i = 0; i < NUM_LODS; i++)
	{
		delete mVolumeLODs[i];
		mVolumeLODs[i] = NULL;
	}
}


LLVolume * LLVolumeLODGroup::getLOD(const S32 detail)
{
	llassert(detail >=0 && detail < NUM_LODS);
	mAccessCount[detail]++;
	mLODRefs[detail]++;
	if (!mVolumeLODs[detail])
	{
		mVolumeLODs[detail] = new LLVolume(mParams, mDetailScales[detail]);
	}
	return mVolumeLODs[detail];
}

BOOL LLVolumeLODGroup::derefLOD(LLVolume *volumep)
{
	S32 i;
	for (i = 0; i < NUM_LODS; i++)
	{
		if (mVolumeLODs[i] == volumep)
		{
			mLODRefs[i]--;
			if (!mLODRefs[i])
			{
				mVolumeLODs[i] = NULL;
			}
			return TRUE;
		}
	}
	llerrs << "Deref of non-matching LOD in volume LOD group" << llendl;
	return FALSE;
}

S32 LLVolumeLODGroup::getDetailFromTan(const F32 tan_angle)
{
	S32 i = 0;
	while (i < (NUM_LODS - 1))
	{
		if (tan_angle <= mDetailThresholds[i])
		{
			return i;
		}
		i++;
	}
	return NUM_LODS - 1;
}

F32 LLVolumeLODGroup::getVolumeScaleFromDetail(const S32 detail)
{
	return mDetailScales[detail];
}

F32 LLVolumeLODGroup::dump()
{
	char dump_str[255];
	F32 usage = 0.f;
	for (S32 i = 0; i < NUM_LODS; i++)
	{
		if (mAccessCount[i] > 0)
		{
			usage += 1.f;
		}
	}
	usage = usage / (F32)NUM_LODS;

	sprintf(dump_str, "%.3f %d %d %d %d", usage, mAccessCount[0], mAccessCount[1], mAccessCount[2], mAccessCount[3]);

	llinfos << dump_str << llendl;
	return usage;
}

std::ostream& operator<<(std::ostream& s, const LLVolumeLODGroup& volgroup)
{
	s << "{ numRefs=" << volgroup.getNumRefs();
	s << ", mParams=" << volgroup.mParams;
	s << " }";
	 
	return s;
}

