/** 
 * @file llviewerregion.cpp
 * @brief Implementation of the LLViewerRegion class.
 *
 * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

#include "llviewerprecompiledheaders.h"

#include "llviewerregion.h"

#include "indra_constants.h"
#include "llmath.h"
#include "llhttpclient.h"
#include "llregionflags.h"
#include "llregionhandle.h"
#include "llsdmessagesystem.h"
#include "llsurface.h"
#include "message.h"
//#include "vmath.h"
#include "v3math.h"
#include "v4math.h"

#include "llagent.h"
#include "llcallingcard.h"
#include "lldir.h"
#include "lleventpoll.h"
#include "llfloatergodtools.h"
#include "llfloaterregioninfo.h"
#include "llhttpnode.h"
#include "llnetmap.h"
#include "llviewerobjectlist.h"
#include "llviewerparceloverlay.h"
#include "llvlmanager.h"
#include "llvlcomposition.h"
#include "llvocache.h"
#include "llvoclouds.h"
#include "llworld.h"

extern BOOL gNoRender;

const F32 WATER_TEXTURE_SCALE = 8.f;			//  Number of times to repeat the water texture across a region
const S16 MAX_MAP_DIST = 10;




LLViewerRegion::LLViewerRegion(const U64 &handle,
							   const LLHost &host,
							   const U32 grids_per_region_edge, 
							   const U32 grids_per_patch_edge, 
							   const F32 region_width_meters)
:	mCenterGlobal(),
	mHandle(handle),
	mHost( host ),
	mTimeDilation(1.0f),
	mName(""),
	mZoning(""),
	mOwnerID(),
	mIsEstateManager(FALSE),
	mCompositionp(NULL),
	mRegionFlags( REGION_FLAGS_DEFAULT ),
	mSimAccess( SIM_ACCESS_MIN ),
	mBillableFactor(1.0),
	mMaxTasks(MAX_TASKS_PER_REGION),
	mCacheLoaded(FALSE),
	mCacheMap(),
	mCacheEntriesCount(0),
	mCacheID(),
	mEventPoll(NULL)
{
	mWidth = region_width_meters;

	mOriginGlobal = from_region_handle(handle); 

	mLandp = new LLSurface('l', NULL);
	if (!gNoRender)
	{
		// Create the composition layer for the surface
		mCompositionp = new LLVLComposition(mLandp, grids_per_region_edge, region_width_meters/grids_per_region_edge);
		mCompositionp->setSurface(mLandp);

		// Create the surfaces
		mLandp->setRegion(this);
		mLandp->create(grids_per_region_edge,
						grids_per_patch_edge,
						mOriginGlobal,
						mWidth);
	}

	if (!gNoRender)
	{
		mParcelOverlay = new LLViewerParcelOverlay(this, region_width_meters);
	}
	else
	{
		mParcelOverlay = NULL;
	}

	setOriginGlobal(from_region_handle(handle));
	calculateCenterGlobal();

	// Create the object lists
	initStats();

	mCacheStart.append(mCacheEnd);
	
}


void LLViewerRegion::initStats()
{
	mLastNetUpdate.reset();
	mPacketsIn = 0;
	mBitsIn = 0;
	mLastBitsIn = 0;
	mLastPacketsIn = 0;
	mPacketsOut = 0;
	mLastPacketsOut = 0;
	mPacketsLost = 0;
	mLastPacketsLost = 0;
	mPingDelay = 0;
	mAlive = FALSE;					// can become false if circuit disconnects
}



LLViewerRegion::~LLViewerRegion() 
{
	gVLManager.cleanupData(this);
	// Can't do this on destruction, because the neighbor pointers might be invalid.
	// This should be reference counted...
	disconnectAllNeighbors();
	mCloudLayer.destroy();
	gWorldPointer->mPartSim.cleanupRegion(this);

	gObjectList.killObjects(this);

	delete mCompositionp;
	delete mParcelOverlay;
	delete mLandp;
	delete mEventPoll;
	
	saveCache();
}


void LLViewerRegion::loadCache()
{
	if (mCacheLoaded)
	{
		return;
	}

	// Presume success.  If it fails, we don't want to try again.
	mCacheLoaded = TRUE;

	LLVOCacheEntry *entry;

	char filename[256];
	sprintf(filename, "%s%sobjects_%d_%d.slc", 
		gDirUtilp->getExpandedFilename(LL_PATH_CACHE,"").c_str(), 
		gDirUtilp->getDirDelimiter().c_str(),
		U32(mHandle>>32)/REGION_WIDTH_UNITS, 
		U32(mHandle)/REGION_WIDTH_UNITS );

	FILE *fp = LLFile::fopen(filename, "rb");
	if (!fp)
	{
		// might not have a file, which is normal
		return;
	}

	U32 zero;
	fread(&zero, 1, sizeof(U32), fp);
	if (zero)
	{
		// a non-zero value here means bad things!
		// skip reading the cached values
		llinfos << "Cache file invalid" << llendl;
		fclose(fp);
		return;
	}

	U32 version;
	fread(&version, 1, sizeof(U32), fp);
	if (version != INDRA_OBJECT_CACHE_VERSION)
	{
		// a version mismatch here means we've changed the binary format!
		// skip reading the cached values
		llinfos << "Cache version changed, discarding" << llendl;
		fclose(fp);
		return;
	}

	LLUUID cache_id;
	fread(&cache_id.mData, UUID_BYTES, sizeof(U8), fp);
	if (mCacheID != cache_id)
	{
		llinfos << "Cache ID doesn't match for this region, discarding"
			<< llendl;
		fclose(fp);
		return;
	}

	S32 num_entries;
	fread(&num_entries, 1, sizeof(S32), fp);
	S32 i;
	for (i = 0; i < num_entries; i++)
	{
		entry = new LLVOCacheEntry(fp);
		if (!entry->getLocalID())
		{
			llwarns << "Aborting cache file load for " << filename << ", cache file corruption!" << llendl;
			delete entry;
			entry = NULL;
			break;
		}
		mCacheEnd.insert(*entry);
		mCacheMap[entry->getLocalID()] = entry;
		mCacheEntriesCount++;
	}

	fclose(fp);
}


void LLViewerRegion::saveCache()
{
	if (!mCacheLoaded)
	{
		return;
	}

	S32 num_entries = mCacheEntriesCount;
	if (0 == num_entries)
	{
		return;
	}

	char filename[256];
	sprintf(filename, "%s%sobjects_%d_%d.slc", 
		gDirUtilp->getExpandedFilename(LL_PATH_CACHE,"").c_str(), 
		gDirUtilp->getDirDelimiter().c_str(),
		U32(mHandle>>32)/REGION_WIDTH_UNITS, 
		U32(mHandle)/REGION_WIDTH_UNITS );

	FILE *fp = LLFile::fopen(filename, "wb");
	if (!fp)
	{
		llwarns << "Unable to write cache file " << filename << llendl;
		return;
	}

	// write out zero to indicate a version cache file
	U32 zero = 0;
	fwrite(&zero, 1, sizeof(U32), fp);

	// write out version number
	U32 version = INDRA_OBJECT_CACHE_VERSION;
	fwrite(&version, 1, sizeof(U32), fp);

	// write the cache id for this sim
	fwrite(&mCacheID.mData, UUID_BYTES, sizeof(U8), fp);

	fwrite(&num_entries, 1, sizeof(S32), fp);

	LLVOCacheEntry *entry;

	for (entry = mCacheStart.getNext(); entry && (entry != &mCacheEnd); entry = entry->getNext())
	{
		entry->writeToFile(fp);
	}

	mCacheMap.removeAllData();
	mCacheEnd.unlink();
	mCacheEnd.init();
	mCacheStart.deleteAll();
	mCacheStart.init();

	fclose(fp);
}

void LLViewerRegion::sendMessage()
{
	gMessageSystem->sendMessage(mHost);
}

void LLViewerRegion::sendReliableMessage()
{
	gMessageSystem->sendReliable(mHost);
}


void LLViewerRegion::setAllowDamage(BOOL b)
{
	if (b)
	{
		mRegionFlags |=  REGION_FLAGS_ALLOW_DAMAGE;
	}
	else
	{
		mRegionFlags &= ~REGION_FLAGS_ALLOW_DAMAGE;
	}
}


void LLViewerRegion::setAllowLandmark(BOOL b)
{
	if (b)
	{
		mRegionFlags |=  REGION_FLAGS_ALLOW_LANDMARK;
	}
	else
	{
		mRegionFlags &= ~REGION_FLAGS_ALLOW_LANDMARK;
	}
}

void LLViewerRegion::setAllowSetHome(BOOL b)
{
	if (b)
	{
		mRegionFlags |=  REGION_FLAGS_ALLOW_SET_HOME;
	}
	else
	{
		mRegionFlags &= ~REGION_FLAGS_ALLOW_SET_HOME;
	}
}

void LLViewerRegion::setResetHomeOnTeleport(BOOL b)
{
	if (b)
	{
		mRegionFlags |=  REGION_FLAGS_RESET_HOME_ON_TELEPORT;
	}
	else
	{
		mRegionFlags &= ~REGION_FLAGS_RESET_HOME_ON_TELEPORT;
	}
}

void LLViewerRegion::setSunFixed(BOOL b)
{
	if (b)
	{
		mRegionFlags |=  REGION_FLAGS_SUN_FIXED;
	}
	else
	{
		mRegionFlags &= ~REGION_FLAGS_SUN_FIXED;
	}
}

void LLViewerRegion::setBlockFly(BOOL b)
{
	if (b)
	{
		mRegionFlags |= REGION_FLAGS_BLOCK_FLY;
	}
	else
	{
		mRegionFlags &= ~REGION_FLAGS_BLOCK_FLY;
	}
}

void LLViewerRegion::setAllowDirectTeleport(BOOL b)
{
	if (b)
	{
		mRegionFlags |= REGION_FLAGS_ALLOW_DIRECT_TELEPORT;
	}
	else
	{
		mRegionFlags &= ~REGION_FLAGS_ALLOW_DIRECT_TELEPORT;
	}
}

void LLViewerRegion::setWaterHeight(F32 water_level)
{
	mLandp->setWaterHeight(water_level);
}

F32 LLViewerRegion::getWaterHeight() const
{
	return mLandp->getWaterHeight();
}

void LLViewerRegion::setRegionFlags(U32 flags)
{
	mRegionFlags = flags;
}


void LLViewerRegion::setOriginGlobal(const LLVector3d &origin_global) 
{ 
	mOriginGlobal = origin_global; 
	mLandp->setOriginGlobal(origin_global);
	mWind.setOriginGlobal(origin_global);
	mCloudLayer.setOriginGlobal(origin_global);
	calculateCenterGlobal();
}


void LLViewerRegion::setTimeDilation(F32 time_dilation)
{
	mTimeDilation = time_dilation;
}


LLVector3 LLViewerRegion::getOriginAgent() const
{
	return gAgent.getPosAgentFromGlobal(mOriginGlobal);
}


LLVector3 LLViewerRegion::getCenterAgent() const
{
	return gAgent.getPosAgentFromGlobal(mCenterGlobal);
}


void LLViewerRegion::setRegionNameAndZone(const char* name_and_zone)
{
	LLString name_zone(name_and_zone);
	std::string::size_type pipe_pos = name_zone.find('|');
	S32 length   = name_zone.size();
	if (pipe_pos != std::string::npos)
	{
		mName   = name_zone.substr(0, pipe_pos);
		mZoning = name_zone.substr(pipe_pos+1, length-(pipe_pos+1));
	}
	else
	{
		mName   = name_zone;
		mZoning = "";
	}

	LLString::stripNonprintable(mName);
	LLString::stripNonprintable(mZoning);
}

BOOL LLViewerRegion::canManageEstate() const
{
	return gAgent.isGodlike()
		|| isEstateManager()
		|| gAgent.getID() == getOwner();
}

const char* LLViewerRegion::getSimAccessString() const
{
	return accessToString(mSimAccess);
}


// static
std::string LLViewerRegion::regionFlagsToString(U32 flags)
{
	std::string result;

	if (flags & REGION_FLAGS_SANDBOX)
	{
		result += "Sandbox";
	}

	if (flags & REGION_FLAGS_ALLOW_DAMAGE)
	{
		result += " Not Safe";
	}

	return result;
}

char* SIM_ACCESS_STR[] = { "Free Trial",
						   "PG",
						   "Mature",
						   "Offline",
						   "Unknown" };
							
// static
const char* LLViewerRegion::accessToString(U8 access)
{
	switch(access)
	{
	case SIM_ACCESS_TRIAL:
		return SIM_ACCESS_STR[0];

	case SIM_ACCESS_PG:
		return SIM_ACCESS_STR[1];

	case SIM_ACCESS_MATURE:
		return SIM_ACCESS_STR[2];

	case SIM_ACCESS_DOWN:
		return SIM_ACCESS_STR[3];

	case SIM_ACCESS_MIN:
	default:
		return SIM_ACCESS_STR[4];
	}
}

// static
U8 LLViewerRegion::stringToAccess(const char* access_str)
{
	U8 access = SIM_ACCESS_MIN;
	if (0 == strcmp(access_str, SIM_ACCESS_STR[0]))
	{
		access = SIM_ACCESS_TRIAL;
	}
	else if (0 == strcmp(access_str, SIM_ACCESS_STR[1]))
	{
		access = SIM_ACCESS_PG;
	}
	else if (0 == strcmp(access_str, SIM_ACCESS_STR[2]))
	{
		access = SIM_ACCESS_MATURE;
	}
	return access;
}

// static
const char* LLViewerRegion::accessToShortString(U8 access)
{
	switch(access)
	{
	case SIM_ACCESS_PG:
		return "PG";

	case SIM_ACCESS_TRIAL:
		return "TR";

	case SIM_ACCESS_MATURE:
		return "M";

	case SIM_ACCESS_MIN:
	default:
		return "U";
	}
}

// static
void LLViewerRegion::processRegionInfo(LLMessageSystem* msg, void**)
{
	// send it to 'observers'
	LLFloaterGodTools::processRegionInfo(msg);
	LLFloaterRegionInfo::processRegionInfo(msg);
}



S32 LLViewerRegion::renderPropertyLines()
{
	if (mParcelOverlay)
	{
		return mParcelOverlay->renderPropertyLines();
	}
	else
	{
		return 0;
	}
}

// This gets called when the height field changes.
void LLViewerRegion::dirtyHeights()
{
	// Property lines need to be reconstructed when the land changes.
	if (mParcelOverlay)
	{
		mParcelOverlay->setDirty();
	}
}

BOOL LLViewerRegion::idleUpdate(LLTimer &timer, const F32 max_time)
{
	BOOL done = mLandp->idleUpdate();

	if (mParcelOverlay)
	{
		mParcelOverlay->idleUpdate();
	}

	return done;
}


// As above, but forcibly do the update.
void LLViewerRegion::forceUpdate()
{
	mLandp->idleUpdate();

	if (mParcelOverlay)
	{
		mParcelOverlay->idleUpdate(true);
	}
}

void LLViewerRegion::connectNeighbor(LLViewerRegion *neighborp, U32 direction)
{
	mLandp->connectNeighbor(neighborp->mLandp, direction);
	mCloudLayer.connectNeighbor(&(neighborp->mCloudLayer), direction);
}


void LLViewerRegion::disconnectAllNeighbors()
{
	mLandp->disconnectAllNeighbors();
	mCloudLayer.disconnectAllNeighbors();
}


F32 LLViewerRegion::getCompositionXY(const S32 x, const S32 y) const
{
	if (x >= 256)
	{
		if (y >= 256)
		{
			LLVector3d center = getCenterGlobal() + LLVector3d(256.f, 256.f, 0.f);
			LLViewerRegion *regionp = gWorldPointer->getRegionFromPosGlobal(center);
			if (regionp)
			{
				// OK, we need to do some hackery here - different simulators no longer use
				// the same composition values, necessarily.
				// If we're attempting to blend, then we want to make the fractional part of
				// this region match the fractional of the adjacent.  For now, just minimize
				// the delta.
				F32 our_comp = getComposition()->getValueScaled(255, 255);
				F32 adj_comp = regionp->getComposition()->getValueScaled(x - 256.f, y - 256.f);
				while (llabs(our_comp - adj_comp) >= 1.f)
				{
					if (our_comp > adj_comp)
					{
						adj_comp += 1.f;
					}
					else
					{
						adj_comp -= 1.f;
					}
				}
				return adj_comp;
			}
		}
		else
		{
			LLVector3d center = getCenterGlobal() + LLVector3d(256.f, 0, 0.f);
			LLViewerRegion *regionp = gWorldPointer->getRegionFromPosGlobal(center);
			if (regionp)
			{
				// OK, we need to do some hackery here - different simulators no longer use
				// the same composition values, necessarily.
				// If we're attempting to blend, then we want to make the fractional part of
				// this region match the fractional of the adjacent.  For now, just minimize
				// the delta.
				F32 our_comp = getComposition()->getValueScaled(255.f, (F32)y);
				F32 adj_comp = regionp->getComposition()->getValueScaled(x - 256.f, (F32)y);
				while (llabs(our_comp - adj_comp) >= 1.f)
				{
					if (our_comp > adj_comp)
					{
						adj_comp += 1.f;
					}
					else
					{
						adj_comp -= 1.f;
					}
				}
				return adj_comp;
			}
		}
	}
	else if (y >= 256)
	{
		LLVector3d center = getCenterGlobal() + LLVector3d(0.f, 256.f, 0.f);
		LLViewerRegion *regionp = gWorldPointer->getRegionFromPosGlobal(center);
		if (regionp)
		{
			// OK, we need to do some hackery here - different simulators no longer use
			// the same composition values, necessarily.
			// If we're attempting to blend, then we want to make the fractional part of
			// this region match the fractional of the adjacent.  For now, just minimize
			// the delta.
			F32 our_comp = getComposition()->getValueScaled((F32)x, 255.f);
			F32 adj_comp = regionp->getComposition()->getValueScaled((F32)x, y - 256.f);
			while (llabs(our_comp - adj_comp) >= 1.f)
			{
				if (our_comp > adj_comp)
				{
					adj_comp += 1.f;
				}
				else
				{
					adj_comp -= 1.f;
				}
			}
			return adj_comp;
		}
	}

	return getComposition()->getValueScaled((F32)x, (F32)y);
}


// ---------------- Friends ----------------

std::ostream& operator<<(std::ostream &s, const LLViewerRegion &region)
{
	s << "{ ";
	s << region.mHost;
	s << " mOriginGlobal = " << region.getOriginGlobal()<< "\n";
	s << "}";
	return s;
}


// ---------------- Protected Member Functions ----------------

void LLViewerRegion::calculateCenterGlobal() 
{
	mCenterGlobal = mOriginGlobal;
	mCenterGlobal.mdV[VX] += 0.5 * mWidth;
	mCenterGlobal.mdV[VY] += 0.5 * mWidth;
	if (mLandp)
	{
		mCenterGlobal.mdV[VZ] = 0.5*mLandp->getMinZ() + mLandp->getMaxZ();
	}
	else
	{
		mCenterGlobal.mdV[VZ] = F64( getWaterHeight() );
	}
}



void LLViewerRegion::updateNetStats()
{
	F32 dt = mLastNetUpdate.getElapsedTimeAndResetF32();

	LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mHost);
	if (!cdp)
	{
		mAlive = FALSE;
		return;
	}

	mAlive = TRUE;
	mDeltaTime = dt;

	mLastPacketsIn =	mPacketsIn;
	mLastBitsIn =		mBitsIn;
	mLastPacketsOut =	mPacketsOut;
	mLastPacketsLost =	mPacketsLost;

	mPacketsIn =				cdp->getPacketsIn();
	mBitsIn =					8 * cdp->getBytesIn();
	mPacketsOut =				cdp->getPacketsOut();
	mPacketsLost =				cdp->getPacketsLost();
	mPingDelay =				cdp->getPingDelay();

	mBitStat.addValue(mBitsIn - mLastBitsIn);
	mPacketsStat.addValue(mPacketsIn - mLastPacketsIn);
	mPacketsLostStat.addValue(mPacketsLost);
}


U32 LLViewerRegion::getPacketsLost() const
{
	LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mHost);
	if (!cdp)
	{
		llinfos << "LLViewerRegion::getPacketsLost couldn't find circuit for " << mHost << llendl;
		return 0;
	}
	else
	{
		return cdp->getPacketsLost();
	}
}

BOOL LLViewerRegion::pointInRegionGlobal(const LLVector3d &point_global) const
{
	LLVector3 pos_region = getPosRegionFromGlobal(point_global);

	if (pos_region.mV[VX] < 0)
	{
		return FALSE;
	}
	if (pos_region.mV[VX] >= mWidth)
	{
		return FALSE;
	}
	if (pos_region.mV[VY] < 0)
	{
		return FALSE;
	}
	if (pos_region.mV[VY] >= mWidth)
	{
		return FALSE;
	}
	return TRUE;
}

LLVector3 LLViewerRegion::getPosRegionFromGlobal(const LLVector3d &point_global) const
{
	LLVector3 pos_region;
	pos_region.setVec(point_global - mOriginGlobal);
	return pos_region;
}

LLVector3d LLViewerRegion::getPosGlobalFromRegion(const LLVector3 &pos_region) const
{
	LLVector3d pos_region_d;
	pos_region_d.setVec(pos_region);
	return pos_region_d + mOriginGlobal;
}

LLVector3 LLViewerRegion::getPosAgentFromRegion(const LLVector3 &pos_region) const
{
	LLVector3d pos_global = getPosGlobalFromRegion(pos_region);

	return gAgent.getPosAgentFromGlobal(pos_global);
}

LLVector3 LLViewerRegion::getPosRegionFromAgent(const LLVector3 &pos_agent) const
{
	return getPosRegionFromGlobal(gAgent.getPosGlobalFromAgent(pos_agent));
}

F32 LLViewerRegion::getLandHeightRegion(const LLVector3& region_pos)
{
	return mLandp->resolveHeightRegion( region_pos );
}

BOOL LLViewerRegion::isOwnedSelf(const LLVector3& pos)
{
	return mParcelOverlay->isOwnedSelf(pos);
}

// Owned by a group you belong to?  (officer or member)
BOOL LLViewerRegion::isOwnedGroup(const LLVector3& pos)
{
	return mParcelOverlay->isOwnedGroup(pos);
}

void LLViewerRegion::updateCoarseLocations(LLMessageSystem* msg)
{
	//llinfos << "CoarseLocationUpdate" << llendl;
	mMapAvatars.reset();

	U8 x_pos = 0;
	U8 y_pos = 0;
	U8 z_pos = 0;

	U32 pos = 0x0;

	S16 agent_index;
	S16 target_index;
	msg->getS16Fast(_PREHASH_Index, _PREHASH_You, agent_index);
	msg->getS16Fast(_PREHASH_Index, _PREHASH_Prey, target_index);

	S32 count = msg->getNumberOfBlocksFast(_PREHASH_Location);
	for(S32 i = 0; i < count; i++)
	{
		msg->getU8Fast(_PREHASH_Location, _PREHASH_X, x_pos, i);
		msg->getU8Fast(_PREHASH_Location, _PREHASH_Y, y_pos, i);
		msg->getU8Fast(_PREHASH_Location, _PREHASH_Z, z_pos, i);

		//llinfos << "  object X: " << (S32)x_pos << " Y: " << (S32)y_pos
		//		<< " Z: " << (S32)(z_pos * 4)
		//		<< llendl;

		// treat the target specially for the map, and don't add you
		// or the target
		if(i == target_index)
		{
			LLVector3d global_pos(mOriginGlobal);
			global_pos.mdV[VX] += (F64)(x_pos);
			global_pos.mdV[VY] += (F64)(y_pos);
			global_pos.mdV[VZ] += (F64)(z_pos) * 4.0;
			LLAvatarTracker::instance().setTrackedCoarseLocation(global_pos);
		}
		else if( i != agent_index)
		{
			pos = 0x0;
			pos |= x_pos;
			pos <<= 8;
			pos |= y_pos;
			pos <<= 8;
			pos |= z_pos;
			mMapAvatars.put(pos);
		}
	}
}

LLString LLViewerRegion::getInfoString()
{
	char tmp_buf[256];
	LLString info;
	
	info = "Region: ";
	getHost().getString(tmp_buf, 256);
	info += tmp_buf;
	info += ":";
	info += getName();
	info += "\n";

	U32 x, y;
	from_region_handle(getHandle(), &x, &y);
	sprintf(tmp_buf, "%d:%d", x, y);
	info += "Handle:";
	info += tmp_buf;
	info += "\n";
	return info;
}


void LLViewerRegion::cacheFullUpdate(LLViewerObject* objectp, LLDataPackerBinaryBuffer &dp)
{
	U32 local_id = objectp->getLocalID();
	U32 crc = objectp->getCRC();

	LLVOCacheEntry *entry = mCacheMap.getIfThere(local_id);

	if (entry)
	{
		// we've seen this object before
		if (entry->getCRC() == crc)
		{
			// Record a hit
			entry->recordDupe();
		}
		else
		{
			// Update the cache entry
			mCacheMap.removeData(local_id);
			delete entry;
			entry = new LLVOCacheEntry(local_id, crc, dp);
			mCacheEnd.insert(*entry);
			mCacheMap[local_id] = entry;
		}
	}
	else
	{
		// we haven't seen this object before

		// Create new entry and add to map
		if (mCacheEntriesCount > MAX_OBJECT_CACHE_ENTRIES)
		{
			entry = mCacheStart.getNext();
			mCacheMap.removeData(entry->getLocalID());
			delete entry;
			mCacheEntriesCount--;
		}
		entry = new LLVOCacheEntry(local_id, crc, dp);

		mCacheEnd.insert(*entry);
		mCacheMap[local_id] = entry;
		mCacheEntriesCount++;
	}
	return ;
}

// Get data packer for this object, if we have cached data
// AND the CRC matches. JC
LLDataPacker *LLViewerRegion::getDP(U32 local_id, U32 crc)
{
	llassert(mCacheLoaded);

	LLVOCacheEntry *entry = mCacheMap.getIfThere(local_id);

	if (entry)
	{
		// we've seen this object before
		if (entry->getCRC() == crc)
		{
			// Record a hit
			entry->recordHit();
			return entry->getDP(crc);
		}
		else
		{
			// llinfos << "CRC miss for " << local_id << llendl;
			mCacheMissCRC.put(local_id);
		}
	}
	else
	{
		// llinfos << "Cache miss for " << local_id << llendl;
		mCacheMissFull.put(local_id);
	}
	return NULL;
}

void LLViewerRegion::addCacheMissFull(const U32 local_id)
{
	mCacheMissFull.put(local_id);
}

void LLViewerRegion::requestCacheMisses()
{
	S32 full_count = mCacheMissFull.count();
	S32 crc_count = mCacheMissCRC.count();
	if (full_count == 0 && crc_count == 0) return;

	LLMessageSystem* msg = gMessageSystem;
	BOOL start_new_message = TRUE;
	S32 blocks = 0;
	S32 i;

	const U8 CACHE_MISS_TYPE_FULL = 0;
	const U8 CACHE_MISS_TYPE_CRC  = 1;

	// Send full cache miss updates.  For these, we KNOW we don't
	// have a viewer object.
	for (i = 0; i < full_count; i++)
	{
		if (start_new_message)
		{
			msg->newMessageFast(_PREHASH_RequestMultipleObjects);
			msg->nextBlockFast(_PREHASH_AgentData);
			msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
			msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
			start_new_message = FALSE;
		}

		msg->nextBlockFast(_PREHASH_ObjectData);
		msg->addU8Fast(_PREHASH_CacheMissType, CACHE_MISS_TYPE_FULL);
		msg->addU32Fast(_PREHASH_ID, mCacheMissFull[i]);
		blocks++;

		if (blocks >= 255)
		{
			sendReliableMessage();
			start_new_message = TRUE;
			blocks = 0;
		}
	}

	// Send CRC miss updates.  For these, we _might_ have a viewer object,
	// but probably not.
	for (i = 0; i < crc_count; i++)
	{
		if (start_new_message)
		{
			msg->newMessageFast(_PREHASH_RequestMultipleObjects);
			msg->nextBlockFast(_PREHASH_AgentData);
			msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
			msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
			start_new_message = FALSE;
		}

		msg->nextBlockFast(_PREHASH_ObjectData);
		msg->addU8Fast(_PREHASH_CacheMissType, CACHE_MISS_TYPE_CRC);
		msg->addU32Fast(_PREHASH_ID, mCacheMissCRC[i]);
		blocks++;

		if (blocks >= 255)
		{
			sendReliableMessage();
			start_new_message = TRUE;
			blocks = 0;
		}
	}

	// finish any pending message
	if (!start_new_message)
	{
		sendReliableMessage();
	}
	mCacheMissFull.reset();
	mCacheMissCRC.reset();

	// llinfos << "KILLDEBUG Sent cache miss full " << full_count << " crc " << crc_count << llendl;
}

void LLViewerRegion::dumpCache()
{
	const S32 BINS = 4;
	S32 hit_bin[BINS];
	S32 change_bin[BINS];

	S32 i;
	for (i = 0; i < BINS; ++i)
	{
		hit_bin[i] = 0;
		change_bin[i] = 0;
	}

	LLVOCacheEntry *entry;

	for (entry = mCacheStart.getNext(); entry && (entry != &mCacheEnd); entry = entry->getNext())
	{
		S32 hits = entry->getHitCount();
		S32 changes = entry->getCRCChangeCount();

		hits = llclamp(hits, 0, BINS-1);
		changes = llclamp(changes, 0, BINS-1);

		hit_bin[hits]++;
		change_bin[changes]++;
	}

	llinfos << "Count " << mCacheEntriesCount << llendl;
	for (i = 0; i < BINS; i++)
	{
		llinfos << "Hits " << i << " " << hit_bin[i] << llendl;
	}
	for (i = 0; i < BINS; i++)
	{
		llinfos << "Changes " << i << " " << change_bin[i] << llendl;
	}
}

void LLViewerRegion::unpackRegionHandshake()
{
	LLMessageSystem *msg = gMessageSystem;

	const S32 SIM_NAME_BUF = 256;
	U32 region_flags;
	U8 sim_access;
	char sim_name[SIM_NAME_BUF];
	LLUUID sim_owner;
	BOOL is_estate_manager;
	F32 water_height;
	F32 billable_factor;
	LLUUID cache_id;

	msg->getU32		("RegionInfo", "RegionFlags", region_flags);
	msg->getU8		("RegionInfo", "SimAccess", sim_access);
	msg->getString	("RegionInfo", "SimName", SIM_NAME_BUF, sim_name);
	msg->getUUID	("RegionInfo", "SimOwner", sim_owner);
	msg->getBOOL	("RegionInfo", "IsEstateManager", is_estate_manager);
	msg->getF32		("RegionInfo", "WaterHeight", water_height);
	msg->getF32		("RegionInfo", "BillableFactor", billable_factor);
	msg->getUUID	("RegionInfo", "CacheID", cache_id );

	setRegionFlags(region_flags);
	setSimAccess(sim_access);
	setRegionNameAndZone(sim_name);
	setOwner(sim_owner);
	setIsEstateManager(is_estate_manager);
	setWaterHeight(water_height);
	setBillableFactor(billable_factor);
	setCacheID(cache_id);

	LLVLComposition *compp = getComposition();
	if (compp)
	{
		LLUUID tmp_id;

		msg->getUUID("RegionInfo", "TerrainDetail0", tmp_id);
		compp->setDetailTextureID(0, tmp_id);
		msg->getUUID("RegionInfo", "TerrainDetail1", tmp_id);
		compp->setDetailTextureID(1, tmp_id);
		msg->getUUID("RegionInfo", "TerrainDetail2", tmp_id);
		compp->setDetailTextureID(2, tmp_id);
		msg->getUUID("RegionInfo", "TerrainDetail3", tmp_id);
		compp->setDetailTextureID(3, tmp_id);

		F32 tmp_f32;
		msg->getF32("RegionInfo", "TerrainStartHeight00", tmp_f32);
		compp->setStartHeight(0, tmp_f32);
		msg->getF32("RegionInfo", "TerrainStartHeight01", tmp_f32);
		compp->setStartHeight(1, tmp_f32);
		msg->getF32("RegionInfo", "TerrainStartHeight10", tmp_f32);
		compp->setStartHeight(2, tmp_f32);
		msg->getF32("RegionInfo", "TerrainStartHeight11", tmp_f32);
		compp->setStartHeight(3, tmp_f32);

		msg->getF32("RegionInfo", "TerrainHeightRange00", tmp_f32);
		compp->setHeightRange(0, tmp_f32);
		msg->getF32("RegionInfo", "TerrainHeightRange01", tmp_f32);
		compp->setHeightRange(1, tmp_f32);
		msg->getF32("RegionInfo", "TerrainHeightRange10", tmp_f32);
		compp->setHeightRange(2, tmp_f32);
		msg->getF32("RegionInfo", "TerrainHeightRange11", tmp_f32);
		compp->setHeightRange(3, tmp_f32);

		// If this is an UPDATE (params already ready, we need to regenerate
		// all of our terrain stuff, by
		if (compp->getParamsReady())
		{
			getLand().dirtyAllPatches();
		}
		else
		{
			compp->setParamsReady();
		}
	}


	// Now that we have the name, we can load the cache file
	// off disk.
	loadCache();

	// After loading cache, signal that simulator can start
	// sending data.
	// TODO: Send all upstream viewer->sim handshake info here.
	LLHost host = msg->getSender();
	msg->newMessage("RegionHandshakeReply");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID", gAgent.getID());
	msg->addUUID("SessionID", gAgent.getSessionID());
	msg->nextBlock("RegionInfo");
	msg->addU32("Flags", 0x0 );
	msg->sendReliable(host);
}



class BaseCapabilitiesComplete : public LLHTTPClient::Responder
{
public:
    BaseCapabilitiesComplete(LLViewerRegion* region)
		: mRegion(region)
    { }

    void error(U32 statusNum, const std::string& reason)
    {
		llinfos << "BaseCapabilitiesComplete::error "
			<< statusNum << ": " << reason << llendl;
    }

    void result(const LLSD& content)
    {
		LLSD::map_const_iterator iter;
		for(iter = content.beginMap(); iter != content.endMap(); ++iter)
		{
			mRegion->setCapability(iter->first, iter->second);
			llinfos << "BaseCapabilitiesComplete::result got capability for " 
				<< iter->first << llendl;
		}
	}

    static boost::intrusive_ptr<BaseCapabilitiesComplete> build(
								LLViewerRegion* region)
    {
		return boost::intrusive_ptr<BaseCapabilitiesComplete>(
							 new BaseCapabilitiesComplete(region));
    }

private:
	LLViewerRegion* mRegion;
};


void LLViewerRegion::setSeedCapability(const std::string& url)
{
	delete mEventPoll;
	mEventPoll = NULL;
	
	mCapabilities.clear();
	setCapability("Seed", url);

	LLSD capabilityNames = LLSD::emptyArray();
	capabilityNames.append("MapLayer");
	capabilityNames.append("MapLayerGod");
	capabilityNames.append("NewAgentInventory");
	capabilityNames.append("EventQueueGet");
	LLHTTPClient::post(url, capabilityNames, BaseCapabilitiesComplete::build(this));
}

static
LLEventPoll* createViewerEventPoll(const std::string& url)
{
	static LLHTTPNode eventRoot;
	static bool eventRootServicesAdded = false;
	if (!eventRootServicesAdded)
	{
		LLSDMessageSystem::useServices();
		LLHTTPRegistrar::buildAllServices(eventRoot);
		eventRootServicesAdded = true;
	}

	return new LLEventPoll(url, eventRoot);
}


void LLViewerRegion::setCapability(const std::string& name, const std::string& url)
{
	mCapabilities[name] = url;
	
	if (name == "EventQueueGet")
	{
		delete mEventPoll;
		mEventPoll = NULL;
		mEventPoll = createViewerEventPoll(url);
	}
}

std::string LLViewerRegion::getCapability(const std::string& name) const
{
	CapabilityMap::const_iterator iter = mCapabilities.find(name);
	if(iter == mCapabilities.end())
	{
		return "";
	}
	return iter->second;
}

