/** 
 * @file llviewerparcelmgr.cpp
 * @brief Viewer-side representation of owned land
 *
 * Copyright (c) 2002-2007, Linden Research, Inc.
 * 
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlife.com/developers/opensource/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at http://secondlife.com/developers/opensource/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 */

#include "llviewerprecompiledheaders.h"

#include "llviewerparcelmgr.h"

// Library includes
#include "audioengine.h"
#include "indra_constants.h"
#include "llcachename.h"
#include "llgl.h"
#include "llmediaengine.h"
#include "llparcel.h"
#include "llsecondlifeurls.h"
#include "message.h"

// Viewer includes
#include "llagent.h"
#include "llfloatergroupinfo.h"
#include "llviewerwindow.h"
#include "llviewercontrol.h"
#include "llfirstuse.h"
#include "llfloaterbuyland.h"
#include "llfloatergroups.h"
#include "llfloaterhtml.h"
#include "llfloatersellland.h"
#include "llfloatertools.h"
#include "llnotify.h"
#include "llresmgr.h"
#include "llstatusbar.h"
#include "llui.h"
#include "llviewerimagelist.h"
#include "llviewermenu.h"
#include "llviewerparceloverlay.h"
#include "llviewerregion.h"
//#include "llwebbrowserctrl.h"
#include "llworld.h"
#include "lloverlaybar.h"
#include "roles_constants.h"

const F32 PARCEL_COLLISION_DRAW_SECS = 1.f;

// Globals
LLViewerParcelMgr *gParcelMgr = NULL;

U8* LLViewerParcelMgr::sPackedOverlay = NULL;

LLUUID gCurrentMovieID = LLUUID::null;

// Local functions
void optionally_start_music(const LLString& music_url);
void callback_start_music(S32 option, void* data);
void optionally_prepare_video(const LLParcel *parcelp);
void callback_prepare_video(S32 option, void* data);
void prepare_video(const LLParcel *parcelp);
void start_video(const LLParcel *parcelp);
void stop_video();
void callback_god_force_owner(S32 option, void* user_data);

struct LLGodForceOwnerData
{
	LLUUID mOwnerID;
	S32 mLocalID;
	LLHost mHost;

	LLGodForceOwnerData(
		const LLUUID& owner_id,
		S32 local_parcel_id,
		const LLHost& host) :
		mOwnerID(owner_id),
		mLocalID(local_parcel_id),
		mHost(host) {}
};

//
// Methods
//
LLViewerParcelMgr::LLViewerParcelMgr()
:	mSelected(FALSE),
	mSelectedMultipleOwners(FALSE),
	mWholeParcelSelected(FALSE),
	mWestSouth(),
	mEastNorth(),
	mSelectedDwell(0.f),
	mAgentParcelSequenceID(-1),
	mHoverWestSouth(),
	mHoverEastNorth(),
	mRenderCollision(FALSE),
	mRenderSelection(TRUE),
	mCollisionBanned(0),
	mCollisionTimer(),
	mMediaParcelId(0),
	mMediaRegionId(0)
{
	mParcel = new LLParcel();
	mAgentParcel = new LLParcel();
	mHoverParcel = new LLParcel();
	mCollisionParcel = new LLParcel();

	mParcelsPerEdge = S32(	REGION_WIDTH_METERS / PARCEL_GRID_STEP_METERS );
	mHighlightSegments = new U8[(mParcelsPerEdge+1)*(mParcelsPerEdge+1)];
	resetSegments(mHighlightSegments);

	mCollisionSegments = new U8[(mParcelsPerEdge+1)*(mParcelsPerEdge+1)];
	resetSegments(mCollisionSegments);

	mBlockedImageID.set(gViewerArt.getString("noentrylines.tga"));
	mBlockedImage = gImageList.getImage(mBlockedImageID, TRUE, TRUE);

	mPassImageID.set(gViewerArt.getString("noentrypasslines.tga"));
	mPassImage = gImageList.getImage(mPassImageID, TRUE, TRUE);

	S32 overlay_size = mParcelsPerEdge * mParcelsPerEdge / PARCEL_OVERLAY_CHUNKS;
	sPackedOverlay = new U8[overlay_size];

	mAgentParcelOverlay = new U8[mParcelsPerEdge * mParcelsPerEdge];
	S32 i;
	for (i = 0; i < mParcelsPerEdge * mParcelsPerEdge; i++)
	{
		mAgentParcelOverlay[i] = 0;
	}
}


LLViewerParcelMgr::~LLViewerParcelMgr()
{
	delete mParcel;
	mParcel = NULL;

	delete mAgentParcel;
	mAgentParcel = NULL;

	delete mCollisionParcel;
	mCollisionParcel = NULL;

	delete mHoverParcel;
	mHoverParcel = NULL;

	delete[] mHighlightSegments;
	mHighlightSegments = NULL;

	delete[] mCollisionSegments;
	mCollisionSegments = NULL;

	// weird, this crashes if I use an array delete on it!
	delete sPackedOverlay;
	sPackedOverlay = NULL;

	delete[] mAgentParcelOverlay;
	mAgentParcelOverlay = NULL;
}


void LLViewerParcelMgr::destroyGL()
{
	mBlockedImage = NULL;
	mPassImage = NULL;
}


void LLViewerParcelMgr::restoreGL()
{
	mBlockedImage = gImageList.getImage(mBlockedImageID, TRUE, TRUE);
	mPassImage = gImageList.getImage(mPassImageID, TRUE, TRUE);
}


void LLViewerParcelMgr::dump()
{
	llinfos << "Parcel Manager Dump" << llendl;
	llinfos << "mSelected " << S32(mSelected) << llendl;
	llinfos << "Selected parcel: " << llendl;
	llinfos << mWestSouth << " to " << mEastNorth << llendl;
	mParcel->dump();
	llinfos << "banning " << mParcel->mBanList.size() << llendl;
	
	access_map_const_iterator cit = mParcel->mBanList.begin();
	access_map_const_iterator end = mParcel->mBanList.end();
	for ( ; cit != end; ++cit)
	{
		llinfos << "ban id " << (*cit).first << llendl;
	}
	llinfos << "Hover parcel:" << llendl;
	mHoverParcel->dump();
	llinfos << "Agent parcel:" << llendl;
	mAgentParcel->dump();
}


LLViewerRegion* LLViewerParcelMgr::getSelectionRegion()
{
	if (!gWorldp) return NULL;

	return gWorldp->getRegionFromPosGlobal( mWestSouth );
}


void LLViewerParcelMgr::getDisplayInfo(S32* area_out, S32* claim_out,
									   S32* rent_out,
									   BOOL* for_sale_out,
									   F32* dwell_out)
{
	S32 area = 0;
	S32 price = 0;
	S32 rent = 0;
	BOOL for_sale = FALSE;
	F32 dwell = 0.f;

	if (mSelected)
	{
		if (mSelectedMultipleOwners)
		{
			area = getClaimableArea();
		}
		else
		{
			area = getSelectedArea();
		}

		if (mParcel->getForSale())
		{
			price = mParcel->getSalePrice();
			for_sale = TRUE;
		}
		else
		{
			price = area * mParcel->getClaimPricePerMeter();
			for_sale = FALSE;
		}

		rent = mParcel->getTotalRent();

		dwell = mSelectedDwell;
	}

	*area_out = area;
	*claim_out = price;
	*rent_out = rent;
	*for_sale_out = for_sale;
	*dwell_out = dwell;
}

void LLViewerParcelMgr::getPrimInfo(S32 &sw_max, S32 &sw_total, S32 &max, S32 &total, S32 &owner, S32 &group, S32 &other, S32& selected, F32 &parcel_object_bonus, S32 &other_clean)
{
	if (mSelected && mParcel)
	{
		sw_max = mParcel->getSimWideMaxPrimCapacity();
		sw_total = mParcel->getSimWidePrimCount();
		max = llround(mParcel->getMaxPrimCapacity()*mParcel->getParcelPrimBonus());
		total = mParcel->getPrimCount();
		owner = mParcel->getOwnerPrimCount();
		group = mParcel->getGroupPrimCount();
		other = mParcel->getOtherPrimCount();
		selected = mParcel->getSelectedPrimCount();
		parcel_object_bonus = mParcel->getParcelPrimBonus();
		other_clean = mParcel->getCleanOtherTime();
	}
}

BOOL LLViewerParcelMgr::getMultipleOwners() const
{
	return mSelectedMultipleOwners;
}


BOOL LLViewerParcelMgr::getWholeParcelSelected() const
{
	return mWholeParcelSelected;
}


S32 LLViewerParcelMgr::getClaimableArea() const
{
	const S32 UNIT_AREA = S32( PARCEL_GRID_STEP_METERS * PARCEL_GRID_STEP_METERS );
	return mSelectedPublicCount * UNIT_AREA;
}

bool LLViewerParcelMgr::hasOthersSelected() const
{
	return mSelectedOtherCount != 0;
}


S32 LLViewerParcelMgr::getSelectedArea() const
{
	S32 rv = 0;
	if(mSelected && mParcel && mWholeParcelSelected)
	{
		rv = mParcel->getArea();
	}
	else if(mSelected)
	{
		F64 width = mEastNorth.mdV[VX] - mWestSouth.mdV[VX];
		F64 height = mEastNorth.mdV[VY] - mWestSouth.mdV[VY];
		F32 area = (F32)(width * height);
		rv = llround(area);
	}
	return rv;
}

void LLViewerParcelMgr::resetSegments(U8* segments)
{
	S32 i;
	S32 count = (mParcelsPerEdge+1)*(mParcelsPerEdge+1);
	for (i = 0; i < count; i++)
	{
		segments[i] = 0x0;
	}
}


void LLViewerParcelMgr::writeHighlightSegments(F32 west, F32 south, F32 east,
											   F32 north)
{
	S32 x, y;
	S32 min_x = llround( west / PARCEL_GRID_STEP_METERS );
	S32 max_x = llround( east / PARCEL_GRID_STEP_METERS );
	S32 min_y = llround( south / PARCEL_GRID_STEP_METERS );
	S32 max_y = llround( north / PARCEL_GRID_STEP_METERS );

	const S32 STRIDE = mParcelsPerEdge+1;

	// south edge
	y = min_y;
	for (x = min_x; x < max_x; x++)
	{
		// exclusive OR means that writing to this segment twice
		// will turn it off
		mHighlightSegments[x + y*STRIDE] ^= SOUTH_MASK;
	}

	// west edge
	x = min_x;
	for (y = min_y; y < max_y; y++)
	{
		mHighlightSegments[x + y*STRIDE] ^= WEST_MASK;
	}

	// north edge - draw the south border on the y+1'th cell,
	// which given C-style arrays, is item foo[max_y]
	y = max_y;
	for (x = min_x; x < max_x; x++)
	{
		mHighlightSegments[x + y*STRIDE] ^= SOUTH_MASK;
	}

	// east edge - draw west border on x+1'th cell
	x = max_x;
	for (y = min_y; y < max_y; y++)
	{
		mHighlightSegments[x + y*STRIDE] ^= WEST_MASK;
	}
}


void LLViewerParcelMgr::writeSegmentsFromBitmap(U8* bitmap, U8* segments)
{
	S32 x;
	S32 y;
	const S32 IN_STRIDE = mParcelsPerEdge;
	const S32 OUT_STRIDE = mParcelsPerEdge+1;

	for (y = 0; y < IN_STRIDE; y++)
	{
		x = 0;
		while( x < IN_STRIDE )
		{
			U8 byte = bitmap[ (x + y*IN_STRIDE) / 8 ];

			S32 bit;
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1 << bit) )
				{
					S32 out = x+y*OUT_STRIDE;

					// This and one above it
					segments[out]            ^= SOUTH_MASK;
					segments[out+OUT_STRIDE] ^= SOUTH_MASK;

					// This and one to the right
					segments[out]   ^= WEST_MASK;
					segments[out+1] ^= WEST_MASK;
				}
				x++;
			}
		}
	}
}


void LLViewerParcelMgr::writeAgentParcelFromBitmap(U8* bitmap)
{
	S32 x;
	S32 y;
	const S32 IN_STRIDE = mParcelsPerEdge;

	for (y = 0; y < IN_STRIDE; y++)
	{
		x = 0;
		while( x < IN_STRIDE )
		{
			U8 byte = bitmap[ (x + y*IN_STRIDE) / 8 ];

			S32 bit;
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1 << bit) )
				{
					mAgentParcelOverlay[x+y*IN_STRIDE] = 1;
				}
				else
				{
					mAgentParcelOverlay[x+y*IN_STRIDE] = 0;
				}
				x++;
			}
		}
	}
}


// Given a point, find the PARCEL_GRID_STEP x PARCEL_GRID_STEP block
// containing it and select that.
void LLViewerParcelMgr::selectParcelAt(const LLVector3d& pos_global)
{
	LLVector3d southwest = pos_global;
	LLVector3d northeast = pos_global;

	southwest -= LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 );
	southwest.mdV[VX] = llround( southwest.mdV[VX], (F64)PARCEL_GRID_STEP_METERS );
	southwest.mdV[VY] = llround( southwest.mdV[VY], (F64)PARCEL_GRID_STEP_METERS );

	northeast += LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 );
	northeast.mdV[VX] = llround( northeast.mdV[VX], (F64)PARCEL_GRID_STEP_METERS );
	northeast.mdV[VY] = llround( northeast.mdV[VY], (F64)PARCEL_GRID_STEP_METERS );

	// Snap to parcel
	selectLand( southwest, northeast, TRUE );
}


// Tries to select the parcel inside the rectangle
void LLViewerParcelMgr::selectParcelInRectangle()
{
	selectLand(mWestSouth, mEastNorth, TRUE);
}


void LLViewerParcelMgr::selectCollisionParcel()
{
	if (!gWorldp)
	{
		return;
	}

	// BUG: Claim to be in the agent's region
	mWestSouth = gAgent.getRegion()->getOriginGlobal();
	mEastNorth = mWestSouth;
	mEastNorth += LLVector3d(PARCEL_GRID_STEP_METERS, PARCEL_GRID_STEP_METERS, 0.0);

	// BUG: must be in the sim you are in
	LLMessageSystem *msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_ParcelPropertiesRequestByID);
	msg->nextBlockFast(_PREHASH_AgentID);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() );
	msg->nextBlockFast(_PREHASH_ParcelData);
	msg->addS32Fast(_PREHASH_SequenceID, SELECTED_PARCEL_SEQ_ID );
	msg->addS32Fast(_PREHASH_LocalID, mCollisionParcel->getLocalID() );
	gAgent.sendReliableMessage();

	mRequestResult = PARCEL_RESULT_NO_DATA;

	// Hack: Copy some data over temporarily
	mParcel->setName( mCollisionParcel->getName() );
	mParcel->setDesc( mCollisionParcel->getDesc() );
	mParcel->setPassPrice(mCollisionParcel->getPassPrice());
	mParcel->setPassHours(mCollisionParcel->getPassHours());

	// clear the list of segments to prevent flashing
	resetSegments(mHighlightSegments);

	mSelected = TRUE;
	mWholeParcelSelected = TRUE;
	notifyObservers();
	return;
}


// snap_selection = auto-select the hit parcel, if there is exactly one
void LLViewerParcelMgr::selectLand(const LLVector3d &corner1, const LLVector3d &corner2,
								   BOOL snap_selection)
{
	if (!gWorldp)
	{
		return;
	}

	sanitize_corners( corner1, corner2, mWestSouth, mEastNorth );

	// ...x isn't more than one meter away
	F32 delta_x = getSelectionWidth();
	if (delta_x * delta_x <= 1.f * 1.f)
	{
		mSelected = FALSE;
		notifyObservers();
		return;
	}

	// ...y isn't more than one meter away
	F32 delta_y = getSelectionHeight();
	if (delta_y * delta_y <= 1.f * 1.f)
	{
		mSelected = FALSE;
		notifyObservers();
		return;
	}

	// Can't select across region boundary
	// We need to pull in the upper right corner by a little bit to allow
	// selection up to the x = 256 or y = 256 edge.
	LLVector3d east_north_region_check( mEastNorth );
	east_north_region_check.mdV[VX] -= 0.5;
	east_north_region_check.mdV[VY] -= 0.5;

	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal(mWestSouth);
	LLViewerRegion *region_other = gWorldp->getRegionFromPosGlobal( east_north_region_check );

	if(!region)
	{
		// just in case they somehow selected no land.
		mSelected = FALSE;
		return;
	}

	if (region != region_other)
	{
		LLNotifyBox::showXml("CantSelectLandFromMultipleRegions");
		mSelected = FALSE;
		notifyObservers();
		return;
	}

	// Build region global copies of corners
	LLVector3 wsb_region = region->getPosRegionFromGlobal( mWestSouth );
	LLVector3 ent_region = region->getPosRegionFromGlobal( mEastNorth );

	/*
	// Check land to make sure all is either public, owned, or self
	LLViewerParcelOverlay* overlay = region->getParcelOverlay();
	if (!overlay)
	{
		llerrs << "No overlay in LLViewerParcelMgr::selectLand" << llendl;
		return;
	}

	U8 start_ownership = overlay->ownership( wsb_region );
	BOOL identical = TRUE;
	S32 x_steps = S32( getSelectionWidth() / PARCEL_GRID_STEP_METERS );
	S32 y_steps = S32( getSelectionHeight() / PARCEL_GRID_STEP_METERS );

	for (S32 x = 0; x < x_steps && identical; x++ )
	{
		for (S32 y = 0; y < y_steps && identical; y++ )
		{
			// strange recomputation each time to avoid error accumulation
			LLVector3 check = wsb_region;
			check.mV[VX] += x * PARCEL_GRID_STEP_METERS;
			check.mV[VY] += y * PARCEL_GRID_STEP_METERS;

			identical = (start_ownership == overlay->ownership(check));
		}
	}

	if (!identical)
	{
		add_chat("Can't select mix of your own, other people's and public land.", FALSE, "", FALSE, CHAT_SOURCE_SYSTEM);
		add_chat("Try selecting a smaller piece of land.", FALSE, "", FALSE, CHAT_SOURCE_SYSTEM);
		mSelected = FALSE;
		notifyObservers();
		return;
	}
	*/

	// Send request message
	LLMessageSystem *msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_ParcelPropertiesRequest);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() );
	msg->nextBlockFast(_PREHASH_ParcelData);
	msg->addS32Fast(_PREHASH_SequenceID, SELECTED_PARCEL_SEQ_ID );
	msg->addF32Fast(_PREHASH_West,  wsb_region.mV[VX] );
	msg->addF32Fast(_PREHASH_South, wsb_region.mV[VY] );
	msg->addF32Fast(_PREHASH_East,  ent_region.mV[VX] );
	msg->addF32Fast(_PREHASH_North, ent_region.mV[VY] );
	msg->addBOOL("SnapSelection", snap_selection);
	msg->sendReliable( region->getHost() );

	mRequestResult = PARCEL_RESULT_NO_DATA;

	// clear the list of segments to prevent flashing
	resetSegments(mHighlightSegments);

	mSelected = TRUE;
	mWholeParcelSelected = snap_selection;
	notifyObservers();
	return;
}


void LLViewerParcelMgr::deselectLand()
{
	if (mSelected)
	{
		mSelected = FALSE;

		// Invalidate the selected parcel
		mParcel->setLocalID(-1);
		mParcel->mAccessList.clear();
		mParcel->mBanList.clear();
		//mParcel->mRenterList.reset();

		mSelectedDwell = 0.f;

		notifyObservers();
	}
}


void LLViewerParcelMgr::addObserver(LLParcelObserver* observer)
{
	mObservers.put(observer);
}


void LLViewerParcelMgr::removeObserver(LLParcelObserver* observer)
{
	mObservers.removeObj(observer);
}


// Call this method when it's time to update everyone on a new state.
// Copy the list because an observer could respond by removing itself
// from the list.
void LLViewerParcelMgr::notifyObservers()
{
	LLDynamicArray<LLParcelObserver*> observers;
	S32 count = mObservers.count();
	S32 i;
	for(i = 0; i < count; ++i)
	{
		observers.put(mObservers.get(i));
	}
	for(i = 0; i < count; ++i)
	{
		observers.get(i)->changed();
	}
}


//
// ACCESSORS
//
BOOL LLViewerParcelMgr::selectionEmpty() const
{
	return !mSelected;
}


LLParcel *LLViewerParcelMgr::getSelectedParcel() const
{
	if (mSelected)
	{
		return mParcel;
	}
	else
	{
		return NULL;
	}
}


LLParcel *LLViewerParcelMgr::getAgentParcel() const
{
	return mAgentParcel;
}

// Return whether the agent can build on the land they are on
BOOL LLViewerParcelMgr::agentCanBuild() const
{
	if (mAgentParcel)
	{
		return gAgent.isGodlike()
			|| (mAgentParcel->getOwnerID() == gAgent.getID())
			|| (mAgentParcel->getAllowModify());
	}
	else
	{
		return gAgent.isGodlike();
	}
}

BOOL LLViewerParcelMgr::agentCanTakeDamage() const
{
	return mAgentParcel->getAllowDamage();
}

BOOL LLViewerParcelMgr::agentCanFly() const
{
	return TRUE;
}

F32 LLViewerParcelMgr::agentDrawDistance() const
{
	return 512.f;
}

BOOL LLViewerParcelMgr::isOwnedAt(const LLVector3d& pos_global) const
{
	LLViewerRegion* region = gWorldp->getRegionFromPosGlobal( pos_global );
	if (!region) return FALSE;

	LLViewerParcelOverlay* overlay = region->getParcelOverlay();
	if (!overlay) return FALSE;

	LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global );

	return overlay->isOwned( pos_region );
}

BOOL LLViewerParcelMgr::isOwnedSelfAt(const LLVector3d& pos_global) const
{
	LLViewerRegion* region = gWorldp->getRegionFromPosGlobal( pos_global );
	if (!region) return FALSE;

	LLViewerParcelOverlay* overlay = region->getParcelOverlay();
	if (!overlay) return FALSE;

	LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global );

	return overlay->isOwnedSelf( pos_region );
}

BOOL LLViewerParcelMgr::isOwnedOtherAt(const LLVector3d& pos_global) const
{
	LLViewerRegion* region = gWorldp->getRegionFromPosGlobal( pos_global );
	if (!region) return FALSE;

	LLViewerParcelOverlay* overlay = region->getParcelOverlay();
	if (!overlay) return FALSE;

	LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global );

	return overlay->isOwnedOther( pos_region );
}

BOOL LLViewerParcelMgr::isSoundLocal(const LLVector3d& pos_global) const
{
	LLViewerRegion* region = gWorldp->getRegionFromPosGlobal( pos_global );
	if (!region) return FALSE;

	LLViewerParcelOverlay* overlay = region->getParcelOverlay();
	if (!overlay) return FALSE;

	LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global );

	return overlay->isSoundLocal( pos_region );
}

BOOL LLViewerParcelMgr::canHearSound(const LLVector3d &pos_global) const
{
	BOOL in_agent_parcel = inAgentParcel(pos_global);

	if (in_agent_parcel)
	{
		// In same parcel as the agent
		return TRUE;
	}
	else
	{
		if (gParcelMgr->getAgentParcel()->getSoundLocal())
		{
			// Not in same parcel, and agent parcel only has local sound
			return FALSE;
		}
		else if (gParcelMgr->isSoundLocal(pos_global))
		{
			// Not in same parcel, and target parcel only has local sound
			return FALSE;
		}
		else
		{
			// Not in same parcel, but neither are local sound
			return TRUE;
		}
	}
}


BOOL LLViewerParcelMgr::inAgentParcel(const LLVector3d &pos_global) const
{
	LLViewerRegion* region = gWorldp->getRegionFromPosGlobal(pos_global);
	if (region != gAgent.getRegion())
	{
		// Can't be in the agent parcel if you're not in the same region.
		return FALSE;
	}

	LLVector3 pos_region = gAgent.getRegion()->getPosRegionFromGlobal(pos_global);
	S32 row =    S32(pos_region.mV[VY] / PARCEL_GRID_STEP_METERS);
	S32 column = S32(pos_region.mV[VX] / PARCEL_GRID_STEP_METERS);

	if (mAgentParcelOverlay[row*mParcelsPerEdge + column])
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

// Returns NULL when there is no valid data.
LLParcel* LLViewerParcelMgr::getHoverParcel() const
{
	if (mHoverRequestResult == PARCEL_RESULT_SUCCESS)
	{
		return mHoverParcel;
	}
	else
	{
		return NULL;
	}
}

// Returns NULL when there is no valid data.
LLParcel* LLViewerParcelMgr::getCollisionParcel() const
{
	if (mRenderCollision)
	{
		return mCollisionParcel;
	}
	else
	{
		return NULL;
	}
}

//
// UTILITIES
//

void LLViewerParcelMgr::render()
{
	if (mSelected && mRenderSelection)
	{
		// Rendering is done in agent-coordinates, so need to supply
		// an appropriate offset to the render code.
		LLViewerRegion *regionp = gWorldp->getRegionFromPosGlobal( mWestSouth );
		if (!regionp) return;

		renderHighlightSegments(mHighlightSegments, regionp);
	}
}


void LLViewerParcelMgr::renderParcelCollision()
{
	// check for expiration
	if (mCollisionTimer.getElapsedTimeF32() > PARCEL_COLLISION_DRAW_SECS)
	{
		mRenderCollision = FALSE;
	}

	if (mRenderCollision)
	{
		LLViewerRegion* regionp = gAgent.getRegion();
		BOOL use_pass = mCollisionParcel->getParcelFlag(PF_USE_PASS_LIST);
		renderCollisionSegments(mCollisionSegments, use_pass, regionp);
	}
}


void LLViewerParcelMgr::sendParcelAccessListRequest(U32 flags)
{
	if (!mSelected)
	{
		return;
	}

	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal( mWestSouth );
	if (!region) return;

	LLMessageSystem *msg = gMessageSystem;
	

	if (flags & AL_BAN) 
	{
		mParcel->mBanList.clear();
	}
	if (flags & AL_ACCESS) 
	{
		mParcel->mAccessList.clear();
	}		

	// Only the headers differ
	msg->newMessageFast(_PREHASH_ParcelAccessListRequest);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->nextBlockFast(_PREHASH_Data);
	msg->addS32Fast(_PREHASH_SequenceID, 0);
	msg->addU32Fast(_PREHASH_Flags, flags);
	msg->addS32("LocalID", mParcel->getLocalID() );
	msg->sendReliable( region->getHost() );
}


void LLViewerParcelMgr::sendParcelDwellRequest()
{
	if (!mSelected)
	{
		return;
	}

	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal( mWestSouth );
	if (!region) return;

	LLMessageSystem *msg = gMessageSystem;

	// Only the headers differ
	msg->newMessage("ParcelDwellRequest");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID", gAgent.getID() );
	msg->addUUID("SessionID", gAgent.getSessionID());
	msg->nextBlock("Data");
	msg->addS32("LocalID", mParcel->getLocalID());
	msg->addUUID("ParcelID", LLUUID::null);	// filled in on simulator
	msg->sendReliable( region->getHost() );
}


void LLViewerParcelMgr::sendParcelGodForceOwner(const LLUUID& owner_id)
{
	if (!mSelected)
	{
		gViewerWindow->alertXml("CannotSetLandOwnerNothingSelected");
		return;
	}

	llinfos << "Claiming " << mWestSouth << " to " << mEastNorth << llendl;

	// BUG: Only works for the region containing mWestSouthBottom
	LLVector3d east_north_region_check( mEastNorth );
	east_north_region_check.mdV[VX] -= 0.5;
	east_north_region_check.mdV[VY] -= 0.5;

	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal( mWestSouth );
	if (!region)
	{
		// TODO: Add a force owner version of this alert.
		gViewerWindow->alertXml("CannotContentifyNoRegion");
		return;
	}

	// BUG: Make work for cross-region selections
	LLViewerRegion *region2 = gWorldp->getRegionFromPosGlobal( east_north_region_check );
	if (region != region2)
	{
		gViewerWindow->alertXml("CannotSetLandOwnerMultipleRegions");
		return;
	}

	llinfos << "Region " << region->getOriginGlobal() << llendl;

	LLGodForceOwnerData* data = new LLGodForceOwnerData(owner_id, mParcel->getLocalID(), region->getHost());
	if(mParcel->getAuctionID())
	{
		gViewerWindow->alertXml("ForceOwnerAuctionWarning",
			callback_god_force_owner,
			(void*)data);
	}
	else
	{
		callback_god_force_owner(0, (void*)data);
	}
}

void callback_god_force_owner(S32 option, void* user_data)
{
	LLGodForceOwnerData* data = (LLGodForceOwnerData*)user_data;
	if(data && (0 == option))
	{
		LLMessageSystem* msg = gMessageSystem;
		msg->newMessage("ParcelGodForceOwner");
		msg->nextBlock("AgentData");
		msg->addUUID("AgentID", gAgent.getID());
		msg->addUUID("SessionID", gAgent.getSessionID());
		msg->nextBlock("Data");
		msg->addUUID("OwnerID", data->mOwnerID);
		msg->addS32( "LocalID", data->mLocalID);
		msg->sendReliable(data->mHost);
	}
	delete data;
}

void LLViewerParcelMgr::sendParcelGodForceToContent()
{
	if (!mSelected)
	{
		gViewerWindow->alertXml("CannotContentifyNothingSelected");
		return;
	}
	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal( mWestSouth );
	if (!region)
	{
		gViewerWindow->alertXml("CannotContentifyNoRegion");
		return;
	}

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage("ParcelGodMarkAsContent");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID", gAgent.getID());
	msg->addUUID("SessionID", gAgent.getSessionID());
	msg->nextBlock("ParcelData");
	msg->addS32("LocalID", mParcel->getLocalID());
	msg->sendReliable(region->getHost());
}

void LLViewerParcelMgr::sendParcelRelease()
{
	if (!mSelected)
	{
        gViewerWindow->alertXml("CannotReleaseLandNothingSelected");
		return;
	}

	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal( mWestSouth );
	if (!region)
	{
		gViewerWindow->alertXml("CannotReleaseLandNoRegion");
		return;
	}

	//U32 flags = PR_NONE;
	//if (god_force) flags |= PR_GOD_FORCE;

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage("ParcelRelease");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID", gAgent.getID() );
	msg->addUUID("SessionID", gAgent.getSessionID() );
	msg->nextBlock("Data");
	msg->addS32("LocalID", mParcel->getLocalID() );
	//msg->addU32("Flags", flags);
	msg->sendReliable( region->getHost() );

	// Blitz selection, since the parcel might be non-rectangular, and
	// we won't have appropriate parcel information.
	deselectLand();
}

class LLViewerParcelMgr::ParcelBuyInfo
{
public:
	LLUUID	mAgent;
	LLUUID	mSession;
	LLUUID	mGroup;
	BOOL	mIsGroupOwned;
	BOOL	mRemoveContribution;
	BOOL	mIsClaim;
	LLHost	mHost;
	
	// for parcel buys
	S32		mParcelID;
	
	// for land claims
	F32		mWest;
	F32		mSouth;
	F32		mEast;
	F32		mNorth;
};

LLViewerParcelMgr::ParcelBuyInfo* LLViewerParcelMgr::setupParcelBuy(
	const LLUUID& agent_id,
	const LLUUID& session_id,
	const LLUUID& group_id,
	BOOL is_group_owned,
	BOOL is_claim,
	BOOL remove_contribution)
{
	if (!mSelected || !mParcel)
	{
		gViewerWindow->alertXml("CannotBuyLandNothingSelected");
		return NULL;
	}

	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal( mWestSouth );
	if (!region)
	{
		gViewerWindow->alertXml("CannotBuyLandNoRegion");
		return NULL;
	}
	
	if (is_claim)
	{
		llinfos << "Claiming " << mWestSouth << " to " << mEastNorth << llendl;
		llinfos << "Region " << region->getOriginGlobal() << llendl;

		// BUG: Only works for the region containing mWestSouthBottom
		LLVector3d east_north_region_check( mEastNorth );
		east_north_region_check.mdV[VX] -= 0.5;
		east_north_region_check.mdV[VY] -= 0.5;

		LLViewerRegion *region2 = gWorldp->getRegionFromPosGlobal( east_north_region_check );

		if (region != region2)
		{
			gViewerWindow->alertXml("CantBuyLandAcrossMultipleRegions");
			return NULL;
		}
	}
	
	
	ParcelBuyInfo* info = new ParcelBuyInfo;
	
	info->mAgent = agent_id;
	info->mSession = session_id;
	info->mGroup = group_id;
	info->mIsGroupOwned = is_group_owned;
	info->mIsClaim = is_claim;
	info->mRemoveContribution = remove_contribution;
	info->mHost = region->getHost();
	
	if (!is_claim)
	{
		info->mParcelID = mParcel->getLocalID();
	}
	else
	{
		// BUG: Make work for cross-region selections
		LLVector3 west_south_bottom_region = region->getPosRegionFromGlobal( mWestSouth );
		LLVector3 east_north_top_region = region->getPosRegionFromGlobal( mEastNorth );
		
		info->mWest		= west_south_bottom_region.mV[VX];
		info->mSouth	= west_south_bottom_region.mV[VY];
		info->mEast		= east_north_top_region.mV[VX];
		info->mNorth	= east_north_top_region.mV[VY];
	}
	
	return info;
}

void LLViewerParcelMgr::sendParcelBuy(ParcelBuyInfo* info)
{
	// send the message
	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage(info->mIsClaim ? "ParcelClaim" : "ParcelBuy");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID", info->mAgent);
	msg->addUUID("SessionID", info->mSession);
	msg->nextBlock("Data");
	msg->addUUID("GroupID", info->mGroup);
	msg->addBOOL("IsGroupOwned", info->mIsGroupOwned);
	if (!info->mIsClaim)
	{
		msg->addBOOL("RemoveContribution", info->mRemoveContribution);
		msg->addS32("LocalID", info->mParcelID);
	}
	msg->addBOOL("Final", TRUE);	// don't allow escrow buys
	if (info->mIsClaim)
	{
		msg->nextBlock("ParcelData");
		msg->addF32("West",  info->mWest);
		msg->addF32("South", info->mSouth);
		msg->addF32("East",  info->mEast);
		msg->addF32("North", info->mNorth);
	}
	msg->sendReliable(info->mHost);
}

void LLViewerParcelMgr::deleteParcelBuy(ParcelBuyInfo*& info)
{
	delete info;
	info = NULL;
}

void LLViewerParcelMgr::sendParcelDeed(const LLUUID& group_id)
{
	if (!mSelected || !mParcel)
	{
		gViewerWindow->alertXml("CannotDeedLandNothingSelected");
		return;
	}
	if(group_id.isNull())
	{
		gViewerWindow->alertXml("CannotDeedLandNoGroup");
		return;
	}
	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal( mWestSouth );
	if (!region)
	{
		gViewerWindow->alertXml("CannotDeedLandNoRegion");
		return;
	}

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage("ParcelDeedToGroup");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID", gAgent.getID() );
	msg->addUUID("SessionID", gAgent.getSessionID() );
	msg->nextBlock("Data");
	msg->addUUID("GroupID", group_id );
	msg->addS32("LocalID", mParcel->getLocalID() );
	//msg->addU32("JoinNeighbors", join);
	msg->sendReliable( region->getHost() );
}


/*
// *NOTE: We cannot easily make landmarks at global positions because
// global positions probably refer to a sim/local combination which
// can move over time. We could implement this by looking up the
// region global x,y, but it's easier to take it out for now.
void LLViewerParcelMgr::makeLandmarkAtSelection()
{
	// Don't create for parcels you don't own
	if (gAgent.getID() != mParcel->getOwnerID())
	{
		return;
	}

	LLVector3d global_center(mWestSouth);
	global_center += mEastNorth;
	global_center *= 0.5f;

	LLViewerRegion* region;
	region = gWorldp->getRegionFromPosGlobal(global_center);

	LLVector3 west_south_bottom_region = region->getPosRegionFromGlobal( mWestSouth );
	LLVector3 east_north_top_region = region->getPosRegionFromGlobal( mEastNorth );

	LLString name("My Land");
	char buffer[MAX_STRING];
	S32 pos_x = (S32)floor((west_south_bottom_region.mV[VX] + east_north_top_region.mV[VX]) / 2.0f);
	S32 pos_y = (S32)floor((west_south_bottom_region.mV[VY] + east_north_top_region.mV[VY]) / 2.0f);
	sprintf(buffer, "%s in %s (%d, %d)",
			name.c_str(),
			region->getName().c_str(),
			pos_x, pos_y);
	name.assign(buffer);

	const char* desc = "Claimed land";
	create_landmark(name.c_str(), desc, global_center);
}
*/

const char* LLViewerParcelMgr::getAgentParcelName() const
{
	return mAgentParcel->getName();
}


void LLViewerParcelMgr::sendParcelPropertiesUpdate(LLParcel* parcel, BOOL want_reply_to_update)
{
	if (!parcel) return;

	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal( mWestSouth );
	if (!region) return;

	LLMessageSystem *msg = gMessageSystem;

	msg->newMessageFast(_PREHASH_ParcelPropertiesUpdate);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID,	gAgent.getID() );
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->nextBlockFast(_PREHASH_ParcelData);
	msg->addS32Fast(_PREHASH_LocalID, parcel->getLocalID() );

	U32 flags = 0x0;
	if (want_reply_to_update) flags |= 0x01;
	msg->addU32("Flags", flags);

	parcel->packMessage(msg);

	msg->sendReliable( region->getHost() );
}


void LLViewerParcelMgr::requestHoverParcelProperties(const LLVector3d& pos)
{
	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal( pos );
	if (!region)
	{
		return;
	}

	// Send a rectangle around the point.
	// This means the parcel sent back is at least a rectangle around the point,
	// which is more efficient for public land.  Fewer requests are sent.  JC
	LLVector3 wsb_region = region->getPosRegionFromGlobal( pos );

	F32 west  = PARCEL_GRID_STEP_METERS * floor( wsb_region.mV[VX] / PARCEL_GRID_STEP_METERS );
	F32 south = PARCEL_GRID_STEP_METERS * floor( wsb_region.mV[VY] / PARCEL_GRID_STEP_METERS );

	F32 east  = west  + PARCEL_GRID_STEP_METERS;
	F32 north = south + PARCEL_GRID_STEP_METERS;

	// Send request message
	LLMessageSystem *msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_ParcelPropertiesRequest);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() );
	msg->nextBlockFast(_PREHASH_ParcelData);
	msg->addS32Fast(_PREHASH_SequenceID,	HOVERED_PARCEL_SEQ_ID );
	msg->addF32Fast(_PREHASH_West,			west );
	msg->addF32Fast(_PREHASH_South,			south );
	msg->addF32Fast(_PREHASH_East,			east );
	msg->addF32Fast(_PREHASH_North,			north );
	msg->addBOOL("SnapSelection",			FALSE );
	msg->sendReliable( region->getHost() );

	mHoverRequestResult = PARCEL_RESULT_NO_DATA;
}


// static
void LLViewerParcelMgr::processParcelOverlay(LLMessageSystem *msg, void **user)
{
	if (gNoRender)
	{
		return;
	}

	// Extract the packed overlay information
	S32 packed_overlay_size = msg->getSizeFast(_PREHASH_ParcelData, _PREHASH_Data);

	if (packed_overlay_size == 0)
	{
		llwarns << "Overlay size 0" << llendl;
		return;
	}

	S32 parcels_per_edge = gParcelMgr->mParcelsPerEdge;
	S32 expected_size = parcels_per_edge * parcels_per_edge / PARCEL_OVERLAY_CHUNKS;
	if (packed_overlay_size != expected_size)
	{
		llwarns << "Got parcel overlay size " << packed_overlay_size
			<< " expecting " << expected_size << llendl;
		return;
	}

	S32 sequence_id;
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SequenceID, sequence_id);
	msg->getBinaryDataFast(
			_PREHASH_ParcelData,
			_PREHASH_Data,
			sPackedOverlay,
			expected_size);

	LLHost host = msg->getSender();
	LLViewerRegion *region = gWorldp->getRegion(host);
	if (region)
	{
		region->mParcelOverlay->uncompressLandOverlay( sequence_id, sPackedOverlay );
	}
}


// static
void LLViewerParcelMgr::processParcelProperties(LLMessageSystem *msg, void **user)
{
	S32		request_result;
	S32		sequence_id;
	BOOL	snap_selection = FALSE;
	S32		self_count = 0;
	S32		other_count = 0;
	S32		public_count = 0;
	S32		local_id;
	LLUUID	owner_id;
	BOOL	is_group_owned;
	U32 auction_id = 0;
	BOOL is_reserved = FALSE;
	S32		claim_price_per_meter = 0;
	S32		rent_price_per_meter = 0;
	S32		claim_date = 0;
	LLVector3	aabb_min;
	LLVector3	aabb_max;
	S32		area = 0;
	S32		sw_max_prims = 0;
	S32		sw_total_prims = 0;
	//LLUUID	buyer_id;
	U8 status = 0;
	S32		max_prims = 0;
	S32		total_prims = 0;
	S32		owner_prims = 0;
	S32		group_prims = 0;
	S32		other_prims = 0;
	S32		selected_prims = 0;
	F32		parcel_prim_bonus = 1.f;
	BOOL	region_push_override = false;
	BOOL	region_deny_anonymous_override = false;
	BOOL	region_deny_identified_override = false;
	BOOL	region_deny_transacted_override = false;

	S32		other_clean_time = 0;

	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_RequestResult, request_result );
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SequenceID, sequence_id );

	if (request_result == PARCEL_RESULT_NO_DATA)
	{
		// no valid parcel data
		llinfos << "no valid parcel data" << llendl;
		return;
	}

	// Decide where the data will go.
	LLParcel* parcel = NULL;
	if (sequence_id == SELECTED_PARCEL_SEQ_ID)
	{
		// ...selected parcels report this sequence id
		gParcelMgr->mRequestResult = PARCEL_RESULT_SUCCESS;
		parcel = gParcelMgr->mParcel;
	}
	else if (sequence_id == HOVERED_PARCEL_SEQ_ID)
	{
		gParcelMgr->mHoverRequestResult = PARCEL_RESULT_SUCCESS;
		parcel = gParcelMgr->mHoverParcel;
	}
	else if (sequence_id == COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID ||
			 sequence_id == COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID ||
			 sequence_id == COLLISION_BANNED_PARCEL_SEQ_ID)
	{
		gParcelMgr->mHoverRequestResult = PARCEL_RESULT_SUCCESS;
		parcel = gParcelMgr->mCollisionParcel;
	}
	else if (sequence_id == 0 || sequence_id > gParcelMgr->mAgentParcelSequenceID)
	{
		// new agent parcel
		gParcelMgr->mAgentParcelSequenceID = sequence_id;
		parcel = gParcelMgr->mAgentParcel;
	}
	else
	{
		llinfos << "out of order agent parcel sequence id " << sequence_id
			<< " last good " << gParcelMgr->mAgentParcelSequenceID
			<< llendl;
		return;
	}

	msg->getBOOL("ParcelData", "SnapSelection", snap_selection);
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SelfCount, self_count);
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_OtherCount, other_count);
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_PublicCount, public_count);
	msg->getS32Fast( _PREHASH_ParcelData, _PREHASH_LocalID,		local_id );
	msg->getUUIDFast(_PREHASH_ParcelData, _PREHASH_OwnerID,		owner_id);
	msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_IsGroupOwned, is_group_owned);
	msg->getU32Fast(_PREHASH_ParcelData, _PREHASH_AuctionID, auction_id);
	msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_ReservedNewbie, is_reserved);
	msg->getS32Fast( _PREHASH_ParcelData, _PREHASH_ClaimDate,	claim_date);
	msg->getS32Fast( _PREHASH_ParcelData, _PREHASH_ClaimPrice,	claim_price_per_meter);
	msg->getS32Fast( _PREHASH_ParcelData, _PREHASH_RentPrice,	rent_price_per_meter);
	msg->getVector3Fast(_PREHASH_ParcelData, _PREHASH_AABBMin, aabb_min);
	msg->getVector3Fast(_PREHASH_ParcelData, _PREHASH_AABBMax, aabb_max);
	msg->getS32Fast(	_PREHASH_ParcelData, _PREHASH_Area, area );
	//msg->getUUIDFast(	_PREHASH_ParcelData, _PREHASH_BuyerID, buyer_id);
	msg->getU8("ParcelData", "Status", status);
	msg->getS32("ParcelData", "SimWideMaxPrims", sw_max_prims );
	msg->getS32("ParcelData", "SimWideTotalPrims", sw_total_prims );
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_MaxPrims, max_prims );
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_TotalPrims, total_prims );
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_OwnerPrims, owner_prims );
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_GroupPrims, group_prims );
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_OtherPrims, other_prims );
	msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SelectedPrims, selected_prims );
	msg->getF32Fast(_PREHASH_ParcelData, _PREHASH_ParcelPrimBonus, parcel_prim_bonus );
	msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionPushOverride, region_push_override );
	msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionDenyAnonymous, region_deny_anonymous_override );
	msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionDenyIdentified, region_deny_identified_override );
	msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionDenyTransacted, region_deny_transacted_override );

	msg->getS32("ParcelData", "OtherCleanTime", other_clean_time );

	// Actually extract the data.
	if (parcel)
	{
		parcel->init(owner_id,
			FALSE, FALSE, FALSE,
			claim_date, claim_price_per_meter, rent_price_per_meter,
			area, other_prims, parcel_prim_bonus, is_group_owned);
		parcel->setLocalID(local_id);
		parcel->setAABBMin(aabb_min);
		parcel->setAABBMax(aabb_max);

		parcel->setAuctionID(auction_id);
		parcel->setReservedForNewbie(is_reserved);
		parcel->setOwnershipStatus((LLParcel::EOwnershipStatus)status);

		parcel->setSimWideMaxPrimCapacity(sw_max_prims);
		parcel->setSimWidePrimCount(sw_total_prims);
		parcel->setMaxPrimCapacity(max_prims);
		parcel->setOwnerPrimCount(owner_prims);
		parcel->setGroupPrimCount(group_prims);
		parcel->setOtherPrimCount(other_prims);
		parcel->setSelectedPrimCount(selected_prims);
		parcel->setParcelPrimBonus(parcel_prim_bonus);

		parcel->setCleanOtherTime(other_clean_time);
		parcel->setRegionPushOverride(region_push_override);
		parcel->setRegionDenyAnonymousOverride(region_deny_anonymous_override);
		parcel->setRegionDenyIdentifiedOverride(region_deny_identified_override);
		parcel->setRegionDenyTransactedOverride(region_deny_transacted_override);
		parcel->unpackMessage(msg);

		if (parcel == gParcelMgr->mAgentParcel)
		{
			S32 bitmap_size =	gParcelMgr->mParcelsPerEdge
								* gParcelMgr->mParcelsPerEdge
								/ 8;
			U8* bitmap = new U8[ bitmap_size ];
			msg->getBinaryDataFast(_PREHASH_ParcelData, _PREHASH_Bitmap, bitmap, bitmap_size);

			gParcelMgr->writeAgentParcelFromBitmap(bitmap);
			delete[] bitmap;
		}
	}

	// Handle updating selections, if necessary.
	if (sequence_id == SELECTED_PARCEL_SEQ_ID)
	{
		// Update selected counts
		gParcelMgr->mSelectedSelfCount = self_count;
		gParcelMgr->mSelectedOtherCount = other_count;
		gParcelMgr->mSelectedPublicCount = public_count;

		gParcelMgr->mSelectedMultipleOwners =
							(request_result == PARCEL_RESULT_MULTIPLE);

		// Select the whole parcel
		LLViewerRegion *region = gWorldp->getRegion( msg->getSender() );
		if (region)
		{
			if (!snap_selection)
			{
				// don't muck with the westsouth and eastnorth.
				// just highlight it
				LLVector3 west_south = region->getPosRegionFromGlobal(gParcelMgr->mWestSouth);
				LLVector3 east_north = region->getPosRegionFromGlobal(gParcelMgr->mEastNorth);

				gParcelMgr->resetSegments(gParcelMgr->mHighlightSegments);
				gParcelMgr->writeHighlightSegments(
								west_south.mV[VX],
								west_south.mV[VY],
								east_north.mV[VX],
								east_north.mV[VY] );
				gParcelMgr->mWholeParcelSelected = FALSE;
			}
			else if (0 == local_id)
			{
				// this is public land, just highlight the selection
				gParcelMgr->mWestSouth = region->getPosGlobalFromRegion( aabb_min );
				gParcelMgr->mEastNorth = region->getPosGlobalFromRegion( aabb_max );

				gParcelMgr->resetSegments(gParcelMgr->mHighlightSegments);
				gParcelMgr->writeHighlightSegments(
								aabb_min.mV[VX],
								aabb_min.mV[VY],
								aabb_max.mV[VX],
								aabb_max.mV[VY] );
				gParcelMgr->mWholeParcelSelected = TRUE;
			}
			else
			{
				gParcelMgr->mWestSouth = region->getPosGlobalFromRegion( aabb_min );
				gParcelMgr->mEastNorth = region->getPosGlobalFromRegion( aabb_max );

				// Owned land, highlight the boundaries
				S32 bitmap_size =	gParcelMgr->mParcelsPerEdge
									* gParcelMgr->mParcelsPerEdge
									/ 8;
				U8* bitmap = new U8[ bitmap_size ];
				msg->getBinaryDataFast(_PREHASH_ParcelData, _PREHASH_Bitmap, bitmap, bitmap_size);

				gParcelMgr->resetSegments(gParcelMgr->mHighlightSegments);
				gParcelMgr->writeSegmentsFromBitmap( bitmap, gParcelMgr->mHighlightSegments );

				delete bitmap;
				bitmap = NULL;

				gParcelMgr->mWholeParcelSelected = TRUE;
			}

			// Request access list information for this land
			gParcelMgr->sendParcelAccessListRequest(AL_ACCESS | AL_BAN);

			// Request dwell for this land, if it's not public land.
			gParcelMgr->mSelectedDwell = 0.f;
			if (0 != local_id)
			{
				gParcelMgr->sendParcelDwellRequest();
			}

			gParcelMgr->mSelected = TRUE;
			gParcelMgr->notifyObservers();
		}
	}
	else if (sequence_id == COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID ||
			 sequence_id == COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID  ||
			 sequence_id == COLLISION_BANNED_PARCEL_SEQ_ID)
	{
		// We're about to collide with this parcel
		gParcelMgr->mRenderCollision = TRUE;
		gParcelMgr->mCollisionTimer.reset();

		// Differentiate this parcel if we are banned from it.
		if (sequence_id == COLLISION_BANNED_PARCEL_SEQ_ID)
		{
			gParcelMgr->mCollisionBanned = BA_BANNED;
		}
		else if (sequence_id == COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID)
		{
			gParcelMgr->mCollisionBanned = BA_NOT_IN_GROUP;
		}
		else 
		{
			gParcelMgr->mCollisionBanned = BA_NOT_ON_LIST;

		}

		S32 bitmap_size =	gParcelMgr->mParcelsPerEdge
							* gParcelMgr->mParcelsPerEdge
							/ 8;
		U8* bitmap = new U8[ bitmap_size ];
		msg->getBinaryDataFast(_PREHASH_ParcelData, _PREHASH_Bitmap, bitmap, bitmap_size);

		gParcelMgr->resetSegments(gParcelMgr->mCollisionSegments);
		gParcelMgr->writeSegmentsFromBitmap( bitmap, gParcelMgr->mCollisionSegments );

		delete bitmap;
		bitmap = NULL;

	}
	else if (sequence_id == HOVERED_PARCEL_SEQ_ID)
	{
		LLViewerRegion *region = gWorldp->getRegion( msg->getSender() );
		if (region)
		{
			gParcelMgr->mHoverWestSouth = region->getPosGlobalFromRegion( aabb_min );
			gParcelMgr->mHoverEastNorth = region->getPosGlobalFromRegion( aabb_max );
		}
		else
		{
			gParcelMgr->mHoverWestSouth.clearVec();
			gParcelMgr->mHoverEastNorth.clearVec();
		}
	}
	else
	{
		// It's the agent parcel
		BOOL new_parcel = parcel ? FALSE : TRUE;
		if (parcel)
		{
			S32 parcelid = parcel->getLocalID();
			U64 regionid = gAgent.getRegion()->getHandle();
			if (parcelid != gParcelMgr->mMediaParcelId || regionid != gParcelMgr->mMediaRegionId)
			{
				gParcelMgr->mMediaParcelId = parcelid;
				gParcelMgr->mMediaRegionId = regionid;
				new_parcel = TRUE;
			}
		}
		// look for music.
		if (gAudiop)
		{
			if (parcel)
			{
				LLString music_url_raw = parcel->getMusicURL();

				// Trim off whitespace from front and back
				LLString music_url = music_url_raw;
				LLString::trim(music_url);

				// On entering a new parcel, stop the last stream if the
				// new parcel has a different music url.  (Empty URL counts
				// as different.)
				const char* stream_url = gAudiop->getInternetStreamURL();

				if (music_url.empty() || music_url != stream_url)
				{
					// URL is different from one currently playing.
					gAudiop->stopInternetStream();

					// If there is a new music URL and it's valid, play it.
					if (music_url.size() > 12)
					{
						if (music_url.substr(0,7) == "http://")
						{
							optionally_start_music(music_url);
						}
					}
					else if (gAudiop->getInternetStreamURL()[0])
					{
						llinfos << "Stopping parcel music" << llendl;
						gAudiop->startInternetStream(NULL);
					}
				}
			}
			else
			{
				// Public land has no music
				gAudiop->stopInternetStream();
			}
		}//if gAudiop

		// now check for video
		if (LLMediaEngine::getInstance ()->isAvailable ())
		{
			// we have a player
			if (parcel)
			{
				// we're in a parcel
				std::string mediaUrl = std::string ( parcel->getMediaURL () );
				LLString::trim(mediaUrl);

				// something changed
				LLMediaEngine* me = LLMediaEngine::getInstance();
				if (  ( me->getUrl () != mediaUrl )
					|| ( me->getImageUUID () != parcel->getMediaID () ) 
					|| ( me->isAutoScaled () != parcel->getMediaAutoScale () ) )
				{
					BOOL video_was_playing = FALSE;
					LLMediaBase* renderer = me->getMediaRenderer();
					if (renderer && (renderer->isPlaying() || renderer->isLooping()))
					{
						video_was_playing = TRUE;
					}

					stop_video();

					if ( !mediaUrl.empty () )
					{
						// Someone has "changed the channel", changing the URL of a video
						// you were already watching.  Do we want to automatically start playing? JC
						if (!new_parcel
							&& gSavedSettings.getBOOL("AudioStreamingVideo")
							&& video_was_playing)
						{
							start_video(parcel);
						}
						else
						{
							// "Prepare" the media engine, but don't auto-play. JC
							optionally_prepare_video(parcel);
						}
					}
				}
			}
			else
			{
				stop_video();
			}
		}
		else
		{
			// no audio player, do a first use dialog if their is media here
			if (parcel)
			{
				std::string mediaUrl = std::string ( parcel->getMediaURL () );
				if (!mediaUrl.empty ())
				{
					if (gSavedSettings.getWarning("QuickTimeInstalled"))
					{
						gSavedSettings.setWarning("QuickTimeInstalled", FALSE);

						LLNotifyBox::showXml("NoQuickTime" );
					};
				}
			}
		}

	};
}

void optionally_start_music(const LLString& music_url)
{
	if (gSavedSettings.getWarning("FirstStreamingMusic"))
	{
		void* data = (void*)strdup(music_url.c_str());
		gViewerWindow->alertXml("ParcelCanPlayMusic",
			callback_start_music,
			(void*)data);

	}
	else if (gSavedSettings.getBOOL("AudioStreamingMusic"))
	{
		// Make the user click the start button on the overlay bar. JC
		//		llinfos << "Starting parcel music " << music_url << llendl;

		// now only play music when you enter a new parcel if the control is in PLAY state
		// changed as part of SL-4878
		if ( gOverlayBar->getMusicRemoteControl ()->getTransportState () == LLMediaRemoteCtrl::Play )
		{
			if (gAudiop)
			{
				gAudiop->startInternetStream(music_url.c_str());
			}
		};
	}
}


void callback_start_music(S32 option, void* data)
{
	const char* music_url = (const char*)data;

	if (0 == option)
	{
		gSavedSettings.setBOOL("AudioStreamingMusic", TRUE);
		llinfos << "Starting first parcel music " << music_url << llendl;
		if (gAudiop) 
		{
			gAudiop->startInternetStream(music_url);
			LLMediaRemoteCtrl* ctrl = gOverlayBar->getMusicRemoteControl();
			ctrl->setTransportState( LLMediaRemoteCtrl::Play, FALSE );
		}
	}
	else
	{
		gSavedSettings.setBOOL("AudioStreamingMusic", FALSE);
	}

	gSavedSettings.setWarning("FirstStreamingMusic", FALSE);

	delete [] music_url;
	music_url = NULL;
}

void prepare_video(const LLParcel *parcel)
{
	std::string mediaUrl;
	if (parcel->getParcelFlag(PF_URL_RAW_HTML))
	{
		mediaUrl = std::string("data:");
		mediaUrl.append(parcel->getMediaURL());
	}
	else
	{
		mediaUrl = std::string ( parcel->getMediaURL () );
	}
	LLMediaEngine::getInstance ()->setUrl ( mediaUrl );
	LLMediaEngine::getInstance ()->setImageUUID ( parcel->getMediaID () );
	LLMediaEngine::getInstance ()->setAutoScaled ( parcel->getMediaAutoScale () ? TRUE : FALSE );  // (U8 instead of BOOL for future expansion)
}

void start_video(const LLParcel *parcel)
{
	prepare_video(parcel);
	std::string path( "" );
	LLMediaEngine::getInstance ()->convertImageAndLoadUrl ( true, false, path);
}

void stop_video()
{
	// set up remote control so stop is selected
	LLMediaEngine::getInstance ()->stop ();
	if (gOverlayBar)
	{
		gOverlayBar->refresh ();
	}

	if (LLMediaEngine::getInstance ()->isLoaded())
	{
		LLMediaEngine::getInstance ()->unload ();

		gImageList.updateMovieImage(LLUUID::null, FALSE);
		gCurrentMovieID.setNull();
	}

	LLMediaEngine::getInstance ()->setUrl ( "" );
	LLMediaEngine::getInstance ()->setImageUUID ( LLUUID::null );
	
}

void optionally_prepare_video(const LLParcel *parcelp)
{
	if (gSavedSettings.getWarning("FirstStreamingVideo"))
	{
		gViewerWindow->alertXml("ParcelCanPlayMedia",
			callback_prepare_video,
			(void*)parcelp);
	}
	else
	{
		llinfos << "Entering parcel " << parcelp->getLocalID() << " with video " <<  parcelp->getMediaURL() << llendl;
		prepare_video(parcelp);
	}
}


void callback_prepare_video(S32 option, void* data)
{
	const LLParcel *parcelp = (const LLParcel *)data;

	if (0 == option)
	{
		gSavedSettings.setBOOL("AudioStreamingVideo", TRUE);
		llinfos << "Starting parcel video " <<  parcelp->getMediaURL() << " on parcel " << parcelp->getLocalID() << llendl;
		gMessageSystem->setHandlerFunc("ParcelMediaCommandMessage", LLMediaEngine::process_parcel_media);
		gMessageSystem->setHandlerFunc ( "ParcelMediaUpdate", LLMediaEngine::process_parcel_media_update );
		prepare_video(parcelp);
	}
	else
	{
		gMessageSystem->setHandlerFunc("ParcelMediaCommandMessage", null_message_callback);
		gMessageSystem->setHandlerFunc ( "ParcelMediaUpdate", null_message_callback );
		gSavedSettings.setBOOL("AudioStreamingVideo", FALSE);
	}

	gSavedSettings.setWarning("FirstStreamingVideo", FALSE);
}

// static
void LLViewerParcelMgr::processParcelAccessListReply(LLMessageSystem *msg, void **user)
{
	LLUUID agent_id;
	S32 sequence_id = 0;
	U32 message_flags = 0x0;
	S32 parcel_id = -1;

	msg->getUUIDFast(_PREHASH_Data, _PREHASH_AgentID, agent_id);
	msg->getS32Fast( _PREHASH_Data, _PREHASH_SequenceID, sequence_id );	//ignored
	msg->getU32Fast( _PREHASH_Data, _PREHASH_Flags, message_flags);
	msg->getS32Fast( _PREHASH_Data, _PREHASH_LocalID, parcel_id);

	LLParcel* parcel = gParcelMgr->mParcel;
	if (!parcel) return;

	if (parcel_id != parcel->getLocalID())
	{
		llwarns << "processParcelAccessListReply for parcel " << parcel_id
			<< " which isn't the selected parcel " << parcel->getLocalID()<< llendl;
		return;
	}

	if (message_flags & AL_ACCESS)
	{
		parcel->unpackAccessEntries(msg, &(parcel->mAccessList) );
	}
	else if (message_flags & AL_BAN)
	{
		parcel->unpackAccessEntries(msg, &(parcel->mBanList) );
	}
	/*else if (message_flags & AL_RENTER)
	{
		parcel->unpackAccessEntries(msg, &(parcel->mRenterList) );
	}*/

	gParcelMgr->notifyObservers();
}


// static
void LLViewerParcelMgr::processParcelDwellReply(LLMessageSystem* msg, void**)
{
	LLUUID agent_id;
	msg->getUUID("AgentData", "AgentID", agent_id);

	S32 local_id;
	msg->getS32("Data", "LocalID", local_id);

	LLUUID parcel_id;
	msg->getUUID("Data", "ParcelID", parcel_id);

	F32 dwell;
	msg->getF32("Data", "Dwell", dwell);

	if (local_id == gParcelMgr->mParcel->getLocalID())
	{
		gParcelMgr->mSelectedDwell = dwell;
		gParcelMgr->notifyObservers();
	}
}


void LLViewerParcelMgr::sendParcelAccessListUpdate(U32 which)
{

	LLUUID transactionUUID;
	transactionUUID.generate();

	if (!mSelected)
	{
		return;
	}

	LLViewerRegion *region = gWorldp->getRegionFromPosGlobal( mWestSouth );
	if (!region) return;

	LLMessageSystem* msg = gMessageSystem;

	LLParcel* parcel = mParcel;
	if (!parcel) return;

	if (which & AL_ACCESS)
	{	
		S32 count = parcel->mAccessList.size();
		S32 num_sections = (S32) ceil(count/PARCEL_MAX_ENTRIES_PER_PACKET);
		S32 sequence_id = 1;
		BOOL start_message = TRUE;
		BOOL initial = TRUE;

		access_map_const_iterator cit = parcel->mAccessList.begin();
		access_map_const_iterator end = parcel->mAccessList.end();
		while ( (cit != end) || initial ) 
		{	
			if (start_message) 
			{
				msg->newMessageFast(_PREHASH_ParcelAccessListUpdate);
				msg->nextBlockFast(_PREHASH_AgentData);
				msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
				msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() );
				msg->nextBlockFast(_PREHASH_Data);
				msg->addU32Fast(_PREHASH_Flags, AL_ACCESS);
				msg->addS32(_PREHASH_LocalID, parcel->getLocalID() );
				msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID);
				msg->addS32Fast(_PREHASH_SequenceID, sequence_id);
				msg->addS32Fast(_PREHASH_Sections, num_sections);
				start_message = FALSE;

				if (initial && (cit == end))
				{
					// pack an empty block if there will be no data
					msg->nextBlockFast(_PREHASH_List);
					msg->addUUIDFast(_PREHASH_ID,  LLUUID::null );
					msg->addS32Fast(_PREHASH_Time, 0 );
					msg->addU32Fast(_PREHASH_Flags,	0 );
				}

				initial = FALSE;
				sequence_id++;

			}
			
			while ( (cit != end) && (msg->getCurrentSendTotal() < MTUBYTES)) 
			{

				const LLAccessEntry& entry = (*cit).second;
				
				msg->nextBlockFast(_PREHASH_List);
				msg->addUUIDFast(_PREHASH_ID,  entry.mID );
				msg->addS32Fast(_PREHASH_Time, entry.mTime );
				msg->addU32Fast(_PREHASH_Flags,	entry.mFlags );
				++cit;
			}

			start_message = TRUE;
			msg->sendReliable( region->getHost() );
		}
	}

	if (which & AL_BAN)
	{	
		S32 count = parcel->mBanList.size();
		S32 num_sections = (S32) ceil(count/PARCEL_MAX_ENTRIES_PER_PACKET);
		S32 sequence_id = 1;
		BOOL start_message = TRUE;
		BOOL initial = TRUE;

		access_map_const_iterator cit = parcel->mBanList.begin();
		access_map_const_iterator end = parcel->mBanList.end();
		while ( (cit != end) || initial ) 
		{
			if (start_message) 
			{
				msg->newMessageFast(_PREHASH_ParcelAccessListUpdate);
				msg->nextBlockFast(_PREHASH_AgentData);
				msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
				msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() );
				msg->nextBlockFast(_PREHASH_Data);
				msg->addU32Fast(_PREHASH_Flags, AL_BAN);
				msg->addS32(_PREHASH_LocalID, parcel->getLocalID() );
				msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID);
				msg->addS32Fast(_PREHASH_SequenceID, sequence_id);
				msg->addS32Fast(_PREHASH_Sections, num_sections);
				start_message = FALSE;

				if (initial && (cit == end))
				{
					// pack an empty block if there will be no data
					msg->nextBlockFast(_PREHASH_List);
					msg->addUUIDFast(_PREHASH_ID,  LLUUID::null );
					msg->addS32Fast(_PREHASH_Time, 0 );
					msg->addU32Fast(_PREHASH_Flags,	0 );
				}

				initial = FALSE;
				sequence_id++;

			}
			
			while ( (cit != end) && (msg->getCurrentSendTotal() < MTUBYTES)) 
			{
				const LLAccessEntry& entry = (*cit).second;
				
				msg->nextBlockFast(_PREHASH_List);
				msg->addUUIDFast(_PREHASH_ID,  entry.mID );
				msg->addS32Fast(_PREHASH_Time, entry.mTime );
				msg->addU32Fast(_PREHASH_Flags,	entry.mFlags );
				++cit;
			}

			start_message = TRUE;
			msg->sendReliable( region->getHost() );
		}
	}
}


void LLViewerParcelMgr::deedLandToGroup()
{
	char group_name[MAX_STRING];
	gCacheName->getGroupName(mParcel->getGroupID(), group_name);
	LLString::format_map_t args;
	args["[AREA]"] = llformat("%d", mParcel->getArea());
	args["[GROUP_NAME]"] = group_name;
	if(mParcel->getContributeWithDeed())
	{
		char first_name[DB_FIRST_NAME_BUF_SIZE];
		first_name[0] = '\0';
		char last_name[DB_FIRST_NAME_BUF_SIZE];
		last_name[0] = '\0';		
		gCacheName->getName(mParcel->getOwnerID(), first_name, last_name);
		args["[FIRST_NAME]"] = first_name;
		args["[LAST_NAME]"] = last_name;
		gViewerWindow->alertXml("DeedLandToGroupWithContribution",args, deedAlertCB, NULL);
	}
	else
	{
		gViewerWindow->alertXml("DeedLandToGroup",args, deedAlertCB, NULL);
	}
}

// static
void LLViewerParcelMgr::deedAlertCB(S32 option, void*)
{
	if (option == 0)
	{
		LLParcel* parcel = gParcelMgr->getSelectedParcel();
		LLUUID group_id;
		if(parcel)
		{
			group_id = parcel->getGroupID();
		}
		gParcelMgr->sendParcelDeed(group_id);
	}
}


void LLViewerParcelMgr::startReleaseLand()
{
	if (!mSelected)
	{
		gViewerWindow->alertXml("CannotReleaseLandNothingSelected");
		return;
	}

	if (mRequestResult == PARCEL_RESULT_NO_DATA)
	{
		gViewerWindow->alertXml("CannotReleaseLandWatingForServer");
		return;
	}

	if (mRequestResult == PARCEL_RESULT_MULTIPLE)
	{
		gViewerWindow->alertXml("CannotReleaseLandSelected");
		return;
	}

	if (!isParcelOwnedByAgent(mParcel, GP_LAND_RELEASE)
		&& !(gAgent.canManageEstate()))
	{
		gViewerWindow->alertXml("CannotReleaseLandDontOwn");
		return;
	}

	LLVector3d parcel_center = (mWestSouth + mEastNorth) / 2.0;
	LLViewerRegion* region = gWorldp->getRegionFromPosGlobal(parcel_center);
	if (!region)
	{
		gViewerWindow->alertXml("CannotReleaseLandRegionNotFound");
		return;
	}
/*
	if ((region->getRegionFlags() & REGION_FLAGS_BLOCK_LAND_RESELL)
		&& !gAgent.isGodlike())
	{
		LLStringBase<char>::format_map_t args;
		args["[REGION]"] = region->getName();
		gViewerWindow->alertXml("CannotReleaseLandNoTransfer", args);
		return;
	}
*/

	if (!mWholeParcelSelected)
	{
		gViewerWindow->alertXml("CannotReleaseLandPartialSelection");
		return;
	}

	// Compute claim price
	LLStringBase<char>::format_map_t args;
	args["[AREA]"] = llformat("%d",mParcel->getArea());
	gViewerWindow->alertXml("ReleaseLandWarning", args,
				releaseAlertCB, this);
}

bool LLViewerParcelMgr::canAgentBuyParcel(LLParcel* parcel, bool forGroup) const
{
	if (!parcel)
	{
		return false;
	}
	
	if (mSelected  &&  parcel == mParcel)
	{
		if (mRequestResult == PARCEL_RESULT_NO_DATA)
		{
			return false;
		}
	}
	
	const LLUUID& parcelOwner = parcel->getOwnerID();
	const LLUUID& authorizeBuyer = parcel->getAuthorizedBuyerID();

	if (parcel->isPublic())
	{
		return true;	// change this if want to make it gods only
	}
	
	bool isForSale = parcel->getForSale()
			&& ((parcel->getSalePrice() > 0) || (authorizeBuyer.notNull()));
			
	bool isEmpowered
		= forGroup ? gAgent.hasPowerInActiveGroup(GP_LAND_DEED) == TRUE : true;
		
	bool isOwner
		= parcelOwner == (forGroup ? gAgent.getGroupID() : gAgent.getID());
	
	bool isAvailable
		= parcel->getReservedForNewbie()
			? (!forGroup && gStatusBar->getSquareMetersCommitted() == 0)
			: true;
		// *TODO: should be based on never_owned_land, see SL-10728
		
	bool isAuthorized
		= (authorizeBuyer.isNull() || (gAgent.getID() == authorizeBuyer));
	
	return isForSale && !isOwner && isAuthorized && isAvailable && isEmpowered;
}


void LLViewerParcelMgr::startBuyLand(BOOL is_for_group)
{
	LLFloaterBuyLand::buyLand(getSelectionRegion(), mParcel, is_for_group == TRUE);
}

void LLViewerParcelMgr::startSellLand()
{
	LLFloaterSellLand::sellLand(getSelectionRegion(), mParcel);
}

void LLViewerParcelMgr::startDivideLand()
{
	if (!mSelected)
	{
		gViewerWindow->alertXml("CannotDivideLandNothingSelected");
		return;
	}

	if (mWholeParcelSelected)
	{
		gViewerWindow->alertXml("CannotDivideLandPartialSelection");
		return;
	}

	gViewerWindow->alertXml("LandDivideWarning", 
		callbackDivideLand,
		this);
}

// static
void LLViewerParcelMgr::callbackDivideLand(S32 option, void* data)
{
	LLViewerParcelMgr* self = (LLViewerParcelMgr*)data;

	LLVector3d parcel_center = (self->mWestSouth + self->mEastNorth) / 2.0;
	LLViewerRegion* region = gWorldp->getRegionFromPosGlobal(parcel_center);
	if (!region)
	{
		gViewerWindow->alertXml("CannotDivideLandNoRegion");
		return;
	}

	if (0 == option)
	{
		LLVector3 west_south = region->getPosRegionFromGlobal(self->mWestSouth);
		LLVector3 east_north = region->getPosRegionFromGlobal(self->mEastNorth);

		LLMessageSystem* msg = gMessageSystem;
		msg->newMessage("ParcelDivide");
		msg->nextBlock("AgentData");
		msg->addUUID("AgentID", gAgent.getID());
		msg->addUUID("SessionID", gAgent.getSessionID());
		msg->nextBlock("ParcelData");
		msg->addF32("West", west_south.mV[VX]);
		msg->addF32("South", west_south.mV[VY]);
		msg->addF32("East", east_north.mV[VX]);
		msg->addF32("North", east_north.mV[VY]);
		msg->sendReliable(region->getHost());
	}
}


void LLViewerParcelMgr::startJoinLand()
{
	if (!mSelected)
	{
		gViewerWindow->alertXml("CannotJoinLandNothingSelected");
		return;
	}

	if (mWholeParcelSelected)
	{
		gViewerWindow->alertXml("CannotJoinLandEntireParcelSelected");
		return;
	}

	if (!mSelectedMultipleOwners)
	{
		gViewerWindow->alertXml("CannotJoinLandSelection");
		return;
	}

	gViewerWindow->alertXml("JoinLandWarning",
		callbackJoinLand,
		this);
}

// static
void LLViewerParcelMgr::callbackJoinLand(S32 option, void* data)
{
	LLViewerParcelMgr* self = (LLViewerParcelMgr*)data;

	LLVector3d parcel_center = (self->mWestSouth + self->mEastNorth) / 2.0;
	LLViewerRegion* region = gWorldp->getRegionFromPosGlobal(parcel_center);
	if (!region)
	{
		gViewerWindow->alertXml("CannotJoinLandNoRegion");
		return;
	}

	if (0 == option)
	{
		LLVector3 west_south = region->getPosRegionFromGlobal(self->mWestSouth);
		LLVector3 east_north = region->getPosRegionFromGlobal(self->mEastNorth);

		LLMessageSystem* msg = gMessageSystem;
		msg->newMessage("ParcelJoin");
		msg->nextBlock("AgentData");
		msg->addUUID("AgentID", gAgent.getID());
		msg->addUUID("SessionID", gAgent.getSessionID());
		msg->nextBlock("ParcelData");
		msg->addF32("West", west_south.mV[VX]);
		msg->addF32("South", west_south.mV[VY]);
		msg->addF32("East", east_north.mV[VX]);
		msg->addF32("North", east_north.mV[VY]);
		msg->sendReliable(region->getHost());
	}
}


void LLViewerParcelMgr::startDeedLandToGroup()
{
	if (!mSelected || !mParcel)
	{
		gViewerWindow->alertXml("CannotDeedLandNothingSelected");
		return;
	}

	if (mRequestResult == PARCEL_RESULT_NO_DATA)
	{
		gViewerWindow->alertXml("CannotDeedLandWaitingForServer");
		return;
	}

	if (mRequestResult == PARCEL_RESULT_MULTIPLE)
	{
		gViewerWindow->alertXml("CannotDeedLandMultipleSelected");
		return;
	}

	LLVector3d parcel_center = (mWestSouth + mEastNorth) / 2.0;
	LLViewerRegion* region = gWorldp->getRegionFromPosGlobal(parcel_center);
	if (!region)
	{
		gViewerWindow->alertXml("CannotDeedLandNoRegion");
		return;
	}

	/*
	if(!gAgent.isGodlike())
	{
		if((region->getRegionFlags() & REGION_FLAGS_BLOCK_LAND_RESELL)
			&& (mParcel->getOwnerID() != region->getOwner()))
		{
			LLStringBase<char>::format_map_t args;
			args["[REGION]"] = region->getName();
			gViewerWindow->alertXml("CannotDeedLandNoTransfer", args);
			return;
		}
	}
	*/

	deedLandToGroup();
}
void LLViewerParcelMgr::reclaimParcel()
{
	LLParcel* parcel = gParcelMgr->getSelectedParcel();
	LLViewerRegion* regionp = gParcelMgr->getSelectionRegion();
	if(parcel && parcel->getOwnerID().notNull()
	   && (parcel->getOwnerID() != gAgent.getID())
	   && regionp && (regionp->getOwner() == gAgent.getID()))
	{
		LLMessageSystem* msg = gMessageSystem;
		msg->newMessage("ParcelReclaim");
		msg->nextBlock("AgentData");
		msg->addUUID("AgentID", gAgent.getID());
		msg->addUUID("SessionID", gAgent.getSessionID());
		msg->nextBlock("Data");
		msg->addS32("LocalID", parcel->getLocalID());
		msg->sendReliable(regionp->getHost());
	}
}

// static
void LLViewerParcelMgr::releaseAlertCB(S32 option, void *)
{
	if (option == 0)
	{
		// Send the release message, not a force
		gParcelMgr->sendParcelRelease();
	}
}

void LLViewerParcelMgr::buyPass()
{
	LLParcel* parcel = getSelectedParcel();
	if (!parcel) return;

	LLViewerRegion* region = getSelectionRegion();
	if (!region) return;

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_ParcelBuyPass);
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID", gAgent.getID());
	msg->addUUID("SessionID", gAgent.getSessionID());
	msg->nextBlockFast(_PREHASH_ParcelData);
	msg->addS32Fast(_PREHASH_LocalID, parcel->getLocalID() );
	msg->sendReliable( region->getHost() );
}

//Tells whether we are allowed to buy a pass or not
BOOL LLViewerParcelMgr::isCollisionBanned()	
{ 
	if ((mCollisionBanned == BA_ALLOWED) || (mCollisionBanned == BA_NOT_ON_LIST) || (mCollisionBanned == BA_NOT_IN_GROUP))
		return FALSE;
	else 
		return TRUE;
}

// This implementation should mirror LLSimParcelMgr::isParcelOwnedBy
// static
BOOL LLViewerParcelMgr::isParcelOwnedByAgent(const LLParcel* parcelp, U64 group_proxy_power)
{
	if (!parcelp)
	{
		return FALSE;
	}

	// Gods can always assume ownership.
	if (gAgent.isGodlike())
	{
		return TRUE;
	}

	// The owner of a parcel automatically gets all powersr.
	if (parcelp->getOwnerID() == gAgent.getID())
	{
		return TRUE;
	}

	// Only gods can assume 'ownership' of public land.
	if (parcelp->isPublic())
	{
		return FALSE;
	}

	// Return whether or not the agent has group_proxy_power powers in the
	// parcel's group.
	return gAgent.hasPowerInGroup(parcelp->getOwnerID(), group_proxy_power);
}

// This implementation should mirror llSimParcelMgr::isParcelModifiableBy
// static
BOOL LLViewerParcelMgr::isParcelModifiableByAgent(const LLParcel* parcelp, U64 group_proxy_power)
{
	// If the agent can assume ownership, it is probably modifiable.
	BOOL rv = FALSE;
	if (parcelp)
	{
		// *NOTE: This should only work for leased parcels, but group owned
		// parcels cannot be OS_LEASED yet. Phoenix 2003-12-15.
		rv = isParcelOwnedByAgent(parcelp, group_proxy_power);

		// ... except for the case that the parcel is not OS_LEASED for agent-owned parcels.
		if( (gAgent.getID() == parcelp->getOwnerID())
			&& !gAgent.isGodlike()
			&& (parcelp->getOwnershipStatus() != LLParcel::OS_LEASED) )
		{
			rv = FALSE;
		}
	}
	return rv;
}

void sanitize_corners(const LLVector3d &corner1,
										const LLVector3d &corner2,
										LLVector3d &west_south_bottom,
										LLVector3d &east_north_top)
{
	west_south_bottom.mdV[VX] = llmin( corner1.mdV[VX], corner2.mdV[VX] );
	west_south_bottom.mdV[VY] = llmin( corner1.mdV[VY], corner2.mdV[VY] );
	west_south_bottom.mdV[VZ] = llmin( corner1.mdV[VZ], corner2.mdV[VZ] );

	east_north_top.mdV[VX] = llmax( corner1.mdV[VX], corner2.mdV[VX] );
	east_north_top.mdV[VY] = llmax( corner1.mdV[VY], corner2.mdV[VY] );
	east_north_top.mdV[VZ] = llmax( corner1.mdV[VZ], corner2.mdV[VZ] );
}