/** 
 * @file llvolumemgr.cpp
 *
 * $LicenseInfo:firstyear=2002&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

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


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};


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

LLVolumeMgr::LLVolumeMgr()
:	mDataMutex(NULL)
{
	// the LLMutex magic interferes with easy unit testing,
	// so you now must manually call useMutex() to use it
	//mDataMutex = new LLMutex();
}

LLVolumeMgr::~LLVolumeMgr()
{
	cleanup();

	delete mDataMutex;
	mDataMutex = NULL;
}

BOOL LLVolumeMgr::cleanup()
{
	BOOL no_refs = TRUE;
	if (mDataMutex)
	{
		mDataMutex->lock();
	}
	for (volume_lod_group_map_t::iterator iter = mVolumeLODGroups.begin(),
			 end = mVolumeLODGroups.end();
		 iter != end; iter++)
	{
		LLVolumeLODGroup *volgroupp = iter->second;
		if (volgroupp->cleanupRefs() == false)
		{
			no_refs = FALSE;
		}
 		delete volgroupp;
	}
	mVolumeLODGroups.clear();
	if (mDataMutex)
	{
		mDataMutex->unlock();
	}
	return no_refs;
}

// Always only ever store the results of refVolume in a LLPointer
// Note however that LLVolumeLODGroup that contains the volume
//  also holds a LLPointer so the volume will only go away after
//  anything holding the volume and the LODGroup are destroyed
LLVolume* LLVolumeMgr::refVolume(const LLVolumeParams &volume_params, const S32 detail)
{
	LLVolumeLODGroup* volgroupp;
	if (mDataMutex)
	{
		mDataMutex->lock();
	}
	volume_lod_group_map_t::iterator iter = mVolumeLODGroups.find(&volume_params);
	if( iter == mVolumeLODGroups.end() )
	{
		volgroupp = createNewGroup(volume_params);
	}
	else
	{
		volgroupp = iter->second;
	}
	if (mDataMutex)
	{
		mDataMutex->unlock();
	}
	return volgroupp->refLOD(detail);
}

// virtual
LLVolumeLODGroup* LLVolumeMgr::getGroup( const LLVolumeParams& volume_params ) const
{
	LLVolumeLODGroup* volgroupp = NULL;
	if (mDataMutex)
	{
		mDataMutex->lock();
	}
	volume_lod_group_map_t::const_iterator iter = mVolumeLODGroups.find(&volume_params);
	if( iter != mVolumeLODGroups.end() )
	{
		volgroupp = iter->second;
	}
	if (mDataMutex)
	{
		mDataMutex->unlock();
	}
	return volgroupp;
}

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

		volgroupp->derefLOD(volumep);
		if (volgroupp->getNumRefs() == 0)
		{
			mVolumeLODGroups.erase(params);
			delete volgroupp;
		}
	}
	if (mDataMutex)
	{
		mDataMutex->unlock();
	}

}

// protected
void LLVolumeMgr::insertGroup(LLVolumeLODGroup* volgroup)
{
	mVolumeLODGroups[volgroup->getVolumeParams()] = volgroup;
}

// protected
LLVolumeLODGroup* LLVolumeMgr::createNewGroup(const LLVolumeParams& volume_params)
{
	LLVolumeLODGroup* volgroup = new LLVolumeLODGroup(volume_params);
	insertGroup(volgroup);
	return volgroup;
}

// virtual
void LLVolumeMgr::dump()
{
	F32 avg = 0.f;
	if (mDataMutex)
	{
		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;
	if (mDataMutex)
	{
		mDataMutex->unlock();
	}
	LL_INFOS() << "Average usage of LODs " << avg << LL_ENDL;
}

void LLVolumeMgr::useMutex()
{ 
	if (!mDataMutex)
	{
		mDataMutex = new LLMutex();
	}
}

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

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

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

	if (volume_mgr.mDataMutex)
	{
		volume_mgr.mDataMutex->unlock();
	}

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

LLVolumeLODGroup::LLVolumeLODGroup(const LLVolumeParams &params)
	: mVolumeParams(params),
	  mRefs(0)
{
	for (S32 i = 0; i < NUM_LODS; i++)
	{
		mLODRefs[i] = 0;
		mAccessCount[i] = 0;
	}
}

LLVolumeLODGroup::~LLVolumeLODGroup()
{
	for (S32 i = 0; i < NUM_LODS; i++)
	{
		llassert_always(mLODRefs[i] == 0);
	}
}

// Called from LLVolumeMgr::cleanup
bool LLVolumeLODGroup::cleanupRefs()
{
	bool res = true;
	if (mRefs != 0)
	{
		LL_WARNS() << "Volume group has remaining refs:" << getNumRefs() << LL_ENDL;
		mRefs = 0;
		for (S32 i = 0; i < NUM_LODS; i++)
		{
			if (mLODRefs[i] > 0)
			{
				LL_WARNS() << " LOD " << i << " refs = " << mLODRefs[i] << LL_ENDL;
				mLODRefs[i] = 0;
				mVolumeLODs[i] = NULL;
			}
		}
		LL_WARNS() << *getVolumeParams() << LL_ENDL;
		res = false;
	}
	return res;
}

LLVolume* LLVolumeLODGroup::refLOD(const S32 detail)
{
	llassert(detail >=0 && detail < NUM_LODS);
	mAccessCount[detail]++;
	
	mRefs++;
	if (mVolumeLODs[detail].isNull())
	{
		mVolumeLODs[detail] = new LLVolume(mVolumeParams, mDetailScales[detail]);
	}
	mLODRefs[detail]++;
	return mVolumeLODs[detail];
}

BOOL LLVolumeLODGroup::derefLOD(LLVolume *volumep)
{
	llassert_always(mRefs > 0);
	mRefs--;
	for (S32 i = 0; i < NUM_LODS; i++)
	{
		if (mVolumeLODs[i] == volumep)
		{
			llassert_always(mLODRefs[i] > 0);
			mLODRefs[i]--;
#if 0 // SJB: Possible opt: keep other lods around
			if (!mLODRefs[i])
			{
				mVolumeLODs[i] = NULL;
			}
#endif
			return TRUE;
		}
	}
	LL_ERRS() << "Deref of non-matching LOD in volume LOD group" << LL_ENDL;
	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;
}

void LLVolumeLODGroup::getDetailProximity(const F32 tan_angle, F32 &to_lower, F32& to_higher)
{
	S32 detail = getDetailFromTan(tan_angle);
	
	if (detail > 0)
	{
		to_lower = tan_angle - mDetailThresholds[detail];
	}
	else
	{
		to_lower = 1024.f*1024.f;
	}

	if (detail < NUM_LODS-1)
	{
		to_higher = mDetailThresholds[detail+1] - tan_angle;
	}
	else
	{
		to_higher = 1024.f*1024.f;
	}
}

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

S32 LLVolumeLODGroup::getVolumeDetailFromScale(const F32 detail)
{
	for (S32 i = 1; i < 4; i++)
	{
		if (mDetailScales[i] > detail)
		{
			return i-1;
		}
	}

	return 3;
}

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

	std::string dump_str = llformat("%.3f %d %d %d %d", usage, mAccessCount[0], mAccessCount[1], mAccessCount[2], mAccessCount[3]);

	LL_INFOS() << dump_str << LL_ENDL;
	return usage;
}

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