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

#include "llviewerprecompiledheaders.h"
#include "llsd.h"
#include "lluuid.h"
#include "llagent.h"
#include "llviewerregion.h"
#include "llpathfindingnavmesh.h"
#include "llpathfindingnavmeshzone.h"
#include "llpathfindingmanager.h"
#include "llviewercontrol.h"

#include "llpathinglib.h"

#include <string>
#include <vector>

#include <boost/bind.hpp>

#define CENTER_REGION 99

//---------------------------------------------------------------------------
// LLPathfindingNavMeshZone
//---------------------------------------------------------------------------

LLPathfindingNavMeshZone::LLPathfindingNavMeshZone()
	: mNavMeshLocationPtrs(),
	mNavMeshZoneRequestStatus(kNavMeshZoneRequestUnknown),
	mNavMeshZoneSignal()
{
}

LLPathfindingNavMeshZone::~LLPathfindingNavMeshZone()
{
}

LLPathfindingNavMeshZone::navmesh_zone_slot_t LLPathfindingNavMeshZone::registerNavMeshZoneListener(navmesh_zone_callback_t pNavMeshZoneCallback)
{
	return mNavMeshZoneSignal.connect(pNavMeshZoneCallback);
}

void LLPathfindingNavMeshZone::initialize()
{
	mNavMeshLocationPtrs.clear();

#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
	LLViewerRegion *currentRegion = gAgent.getRegion();
	if (currentRegion != NULL)
	{
		llinfos << "STINSON DEBUG: currentRegion: '" << currentRegion->getName() << "' (" << currentRegion->getRegionID().asString() << ")" << llendl;
		std::vector<S32> availableRegions;
		currentRegion->getNeighboringRegionsStatus( availableRegions );
		std::vector<LLViewerRegion*> neighborRegionsPtrs;
		currentRegion->getNeighboringRegions( neighborRegionsPtrs );
		for (std::vector<S32>::const_iterator statusIter = availableRegions.begin();
			statusIter != availableRegions.end(); ++statusIter)
		{
			LLViewerRegion *region = neighborRegionsPtrs[statusIter - availableRegions.begin()];
			llinfos << "STINSON DEBUG: region #" << *statusIter << ": '" << region->getName() << "' (" << region->getRegionID().asString() << ")" << llendl;
		}
 	}

#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
	NavMeshLocationPtr centerNavMeshPtr(new NavMeshLocation(CENTER_REGION, boost::bind(&LLPathfindingNavMeshZone::handleNavMeshLocation, this)));
	mNavMeshLocationPtrs.push_back(centerNavMeshPtr);

	U32 neighborRegionDir = gSavedSettings.getU32("RetrieveNeighboringRegion");
	if (neighborRegionDir != CENTER_REGION)
	{
		NavMeshLocationPtr neighborNavMeshPtr(new NavMeshLocation(neighborRegionDir, boost::bind(&LLPathfindingNavMeshZone::handleNavMeshLocation, this)));
		mNavMeshLocationPtrs.push_back(neighborNavMeshPtr);
	}
}

void LLPathfindingNavMeshZone::enable()
{
	for (NavMeshLocationPtrs::iterator navMeshLocationPtrIter = mNavMeshLocationPtrs.begin();
		navMeshLocationPtrIter != mNavMeshLocationPtrs.end(); ++navMeshLocationPtrIter)
	{
		NavMeshLocationPtr navMeshLocationPtr = *navMeshLocationPtrIter;
		navMeshLocationPtr->enable();
	}
}

void LLPathfindingNavMeshZone::disable()
{
	for (NavMeshLocationPtrs::iterator navMeshLocationPtrIter = mNavMeshLocationPtrs.begin();
		navMeshLocationPtrIter != mNavMeshLocationPtrs.end(); ++navMeshLocationPtrIter)
	{
		NavMeshLocationPtr navMeshLocationPtr = *navMeshLocationPtrIter;
		navMeshLocationPtr->disable();
	}
}

void LLPathfindingNavMeshZone::refresh()
{
	llassert(LLPathingLib::getInstance() != NULL);
	if (LLPathingLib::getInstance() != NULL)
	{
		LLPathingLib::getInstance()->cleanupResidual();
	}

	for (NavMeshLocationPtrs::iterator navMeshLocationPtrIter = mNavMeshLocationPtrs.begin();
		navMeshLocationPtrIter != mNavMeshLocationPtrs.end(); ++navMeshLocationPtrIter)
	{
		NavMeshLocationPtr navMeshLocationPtr = *navMeshLocationPtrIter;
		navMeshLocationPtr->refresh();
	}
}

LLPathfindingNavMeshZone::ENavMeshZoneStatus LLPathfindingNavMeshZone::getNavMeshZoneStatus() const
{
	bool hasPending = false;
	bool hasBuilding = false;
	bool hasComplete = false;
	bool hasRepending = false;

	for (NavMeshLocationPtrs::const_iterator navMeshLocationPtrIter = mNavMeshLocationPtrs.begin();
		navMeshLocationPtrIter != mNavMeshLocationPtrs.end(); ++navMeshLocationPtrIter)
	{
		const NavMeshLocationPtr navMeshLocationPtr = *navMeshLocationPtrIter;

		switch (navMeshLocationPtr->getNavMeshStatus())
		{
		case LLPathfindingNavMeshStatus::kPending :
			hasPending = true;
			break;
		case LLPathfindingNavMeshStatus::kBuilding :
			hasBuilding = true;
			break;
		case LLPathfindingNavMeshStatus::kComplete :
			hasComplete = true;
			break;
		case LLPathfindingNavMeshStatus::kRepending :
			hasRepending = true;
			break;
		default :
			hasPending = true;
			llassert(0);
			break;
		}
	}

	ENavMeshZoneStatus zoneStatus = kNavMeshZoneComplete;
	if (hasRepending || (hasPending && hasBuilding))
	{
		zoneStatus = kNavMeshZonePendingAndBuilding;
	}
	else if (hasComplete)
	{
		if (hasPending)
		{
			zoneStatus = kNavMeshZoneSomePending;
		}
		else if (hasBuilding)
		{
			zoneStatus = kNavMeshZoneSomeBuilding;
		}
		else
		{
			zoneStatus = kNavMeshZoneComplete;
		}
	}
	else if (hasPending)
	{
		zoneStatus = kNavMeshZonePending;
	}
	else if (hasBuilding)
	{
		zoneStatus = kNavMeshZoneBuilding;
	}

	return zoneStatus;
}

void LLPathfindingNavMeshZone::handleNavMeshLocation()
{
	updateStatus();
}

void LLPathfindingNavMeshZone::updateStatus()
{
	bool hasRequestUnknown = false;
	bool hasRequestChecking = false;
	bool hasRequestNeedsUpdate = false;
	bool hasRequestStarted = false;
	bool hasRequestCompleted = false;
	bool hasRequestNotEnabled = false;
	bool hasRequestError = false;

#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
	llinfos << "STINSON DEBUG: Navmesh zone update BEGIN" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
	for (NavMeshLocationPtrs::const_iterator navMeshLocationPtrIter = mNavMeshLocationPtrs.begin();
		navMeshLocationPtrIter != mNavMeshLocationPtrs.end(); ++navMeshLocationPtrIter)
	{
		const NavMeshLocationPtr navMeshLocationPtr = *navMeshLocationPtrIter;
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG:    region #" << navMeshLocationPtr->getDirection() << ": region(" << navMeshLocationPtr->getRegionUUID().asString() << ") status:" << navMeshLocationPtr->getRequestStatus() << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
		switch (navMeshLocationPtr->getRequestStatus())
		{
		case LLPathfindingNavMesh::kNavMeshRequestUnknown :
			hasRequestUnknown = true;
			break;
		case LLPathfindingNavMesh::kNavMeshRequestChecking :
			hasRequestChecking = true;
			break;
		case LLPathfindingNavMesh::kNavMeshRequestNeedsUpdate :
			hasRequestNeedsUpdate = true;
			break;
		case LLPathfindingNavMesh::kNavMeshRequestStarted :
			hasRequestStarted = true;
			break;
		case LLPathfindingNavMesh::kNavMeshRequestCompleted :
			hasRequestCompleted = true;
			break;
		case LLPathfindingNavMesh::kNavMeshRequestNotEnabled :
			hasRequestNotEnabled = true;
			break;
		case LLPathfindingNavMesh::kNavMeshRequestError :
			hasRequestError = true;
			break;
		default :
			hasRequestError = true;
			llassert(0);
			break;
		}
	}

	ENavMeshZoneRequestStatus zoneRequestStatus = kNavMeshZoneRequestUnknown;
	if (hasRequestNeedsUpdate)
	{
		zoneRequestStatus = kNavMeshZoneRequestNeedsUpdate;
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG: Navmesh zone update is NEEDS UPDATE" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
	}
	else if (hasRequestChecking)
	{
		zoneRequestStatus = kNavMeshZoneRequestChecking;
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG: Navmesh zone update is CHECKING" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
	}
	else if (hasRequestStarted)
	{
		zoneRequestStatus = kNavMeshZoneRequestStarted;
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG: Navmesh zone update is STARTED" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
	}
	else if (hasRequestError)
	{
		zoneRequestStatus = kNavMeshZoneRequestError;
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG: Navmesh zone update is ERROR" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
	}
	else if (hasRequestUnknown)
	{
		zoneRequestStatus = kNavMeshZoneRequestUnknown;
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG: Navmesh zone update is UNKNOWN" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
	}
	else if (hasRequestCompleted)
	{
		zoneRequestStatus = kNavMeshZoneRequestCompleted;
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG: Navmesh zone update is COMPLETED" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
	}
	else if (hasRequestNotEnabled)
	{
		zoneRequestStatus = kNavMeshZoneRequestNotEnabled;
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG: Navmesh zone update is NOT ENABLED" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
	}
	else
	{
		zoneRequestStatus = kNavMeshZoneRequestError;
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG: Navmesh zone update is BAD ERROR" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
		llassert(0);
	}

	if ((mNavMeshZoneRequestStatus != kNavMeshZoneRequestCompleted) &&
		(zoneRequestStatus == kNavMeshZoneRequestCompleted))
	{
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG: Navmesh zone update is stitching" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
		llassert(LLPathingLib::getInstance() != NULL);
		if (LLPathingLib::getInstance() != NULL)
		{
			LLPathingLib::getInstance()->stitchNavMeshes();
		}
#ifdef XXX_STINSON_DEBUG_NAVMESH_ZONE
		llinfos << "STINSON DEBUG: Navmesh zone update stitching is done" << llendl;
#endif // XXX_STINSON_DEBUG_NAVMESH_ZONE
	}

	mNavMeshZoneRequestStatus = zoneRequestStatus;
	mNavMeshZoneSignal(mNavMeshZoneRequestStatus);
}

//---------------------------------------------------------------------------
// LLPathfindingNavMeshZone::NavMeshLocation
//---------------------------------------------------------------------------

LLPathfindingNavMeshZone::NavMeshLocation::NavMeshLocation(S32 pDirection, navmesh_location_callback_t pLocationCallback)
	: mDirection(pDirection),
	mRegionUUID(),
	mHasNavMesh(false),
	mNavMeshVersion(0U),
	mNavMeshStatus(LLPathfindingNavMeshStatus::kComplete),
	mLocationCallback(pLocationCallback),
	mRequestStatus(LLPathfindingNavMesh::kNavMeshRequestUnknown),
	mNavMeshSlot()
{
}

LLPathfindingNavMeshZone::NavMeshLocation::~NavMeshLocation()
{
}

void LLPathfindingNavMeshZone::NavMeshLocation::enable()
{
	clear();

	LLViewerRegion *region = getRegion();
	if (region == NULL)
	{
		mRegionUUID.setNull();
	}
	else
	{
		mRegionUUID = region->getRegionID();
		mNavMeshSlot = LLPathfindingManager::getInstance()->registerNavMeshListenerForRegion(region, boost::bind(&LLPathfindingNavMeshZone::NavMeshLocation::handleNavMesh, this, _1, _2, _3));
	}
}

void LLPathfindingNavMeshZone::NavMeshLocation::refresh()
{
	LLViewerRegion *region = getRegion();

	if (region == NULL)
	{
		llassert(mRegionUUID.isNull());
		LLPathfindingNavMeshStatus newNavMeshStatus(mRegionUUID);
		LLSD::Binary nullData;
		handleNavMesh(LLPathfindingNavMesh::kNavMeshRequestNotEnabled, newNavMeshStatus, nullData);
	}
	else
	{
		llassert(mRegionUUID == region->getRegionID());
		LLPathfindingManager::getInstance()->requestGetNavMeshForRegion(region);
	}
}

void LLPathfindingNavMeshZone::NavMeshLocation::disable()
{
	clear();
}

LLPathfindingNavMesh::ENavMeshRequestStatus LLPathfindingNavMeshZone::NavMeshLocation::getRequestStatus() const
{
	return mRequestStatus;
}

LLPathfindingNavMeshStatus::ENavMeshStatus LLPathfindingNavMeshZone::NavMeshLocation::getNavMeshStatus() const
{
	return mNavMeshStatus;
}

void LLPathfindingNavMeshZone::NavMeshLocation::handleNavMesh(LLPathfindingNavMesh::ENavMeshRequestStatus pNavMeshRequestStatus, const LLPathfindingNavMeshStatus &pNavMeshStatus, const LLSD::Binary &pNavMeshData)
{
	llassert(mRegionUUID == pNavMeshStatus.getRegionUUID());

	if ((pNavMeshRequestStatus == LLPathfindingNavMesh::kNavMeshRequestCompleted) &&
		(!mHasNavMesh || (mNavMeshVersion != pNavMeshStatus.getVersion())))
	{
		llassert(!pNavMeshData.empty());
		mHasNavMesh = true;
		mNavMeshVersion = pNavMeshStatus.getVersion();
		llassert(LLPathingLib::getInstance() != NULL);
		if (LLPathingLib::getInstance() != NULL)
		{
			LLPathingLib::getInstance()->extractNavMeshSrcFromLLSD(pNavMeshData, mDirection);
		}
	}

	mRequestStatus = pNavMeshRequestStatus;
	mNavMeshStatus = pNavMeshStatus.getStatus();
	mLocationCallback();
}

void LLPathfindingNavMeshZone::NavMeshLocation::clear()
{
	mHasNavMesh = false;
	mRequestStatus = LLPathfindingNavMesh::kNavMeshRequestUnknown;
	mNavMeshStatus = LLPathfindingNavMeshStatus::kComplete;
	if (mNavMeshSlot.connected())
	{
		mNavMeshSlot.disconnect();
	}
}

LLViewerRegion *LLPathfindingNavMeshZone::NavMeshLocation::getRegion() const
{
	LLViewerRegion *region = NULL;

	LLViewerRegion *currentRegion = gAgent.getRegion();
	if (currentRegion != NULL)
	{
		if (mDirection == CENTER_REGION)
		{
			region = currentRegion;
		}
		else
		{
			//User wants to pull in a neighboring region
			std::vector<S32> availableRegions;
			currentRegion->getNeighboringRegionsStatus( availableRegions );
			//Is the desired region in the available list
			std::vector<S32>::iterator foundElem = std::find(availableRegions.begin(),availableRegions.end(),mDirection); 
			if ( foundElem != availableRegions.end() )
			{
				std::vector<LLViewerRegion*> neighborRegionsPtrs;
				currentRegion->getNeighboringRegions( neighborRegionsPtrs );
				region = neighborRegionsPtrs[foundElem - availableRegions.begin()];
			}
		}
	}

	return region;
}