/**
* @file llpathfindingmanager.cpp
* @brief Implementation of llpathfindingmanager
* @author Stinson@lindenlab.com
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2012, 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 "llpathfindingmanager.h"

#include <string>
#include <map>

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/signals2.hpp>

#include "llagent.h"
#include "llhttpnode.h"
#include "llnotificationsutil.h"
#include "llpathfindingcharacterlist.h"
#include "llpathfindinglinkset.h"
#include "llpathfindinglinksetlist.h"
#include "llpathfindingnavmesh.h"
#include "llpathfindingnavmeshstatus.h"
#include "llpathfindingobject.h"
#include "llpathinglib.h"
#include "llsingleton.h"
#include "llsd.h"
#include "lltrans.h"
#include "lluuid.h"
#include "llviewerregion.h"
#include "llweb.h"
#include "llcorehttputil.h"
#include "llworld.h"

#define CAP_SERVICE_RETRIEVE_NAVMESH        "RetrieveNavMeshSrc"

#define CAP_SERVICE_NAVMESH_STATUS          "NavMeshGenerationStatus"

#define CAP_SERVICE_GET_OBJECT_LINKSETS     "RegionObjects"
#define CAP_SERVICE_SET_OBJECT_LINKSETS     "ObjectNavMeshProperties"
#define CAP_SERVICE_TERRAIN_LINKSETS        "TerrainNavMeshProperties"

#define CAP_SERVICE_CHARACTERS              "CharacterProperties"

#define SIM_MESSAGE_NAVMESH_STATUS_UPDATE   "/message/NavMeshStatusUpdate"
#define SIM_MESSAGE_AGENT_STATE_UPDATE      "/message/AgentStateUpdate"
#define SIM_MESSAGE_BODY_FIELD              "body"

#define CAP_SERVICE_AGENT_STATE             "AgentState"

#define AGENT_STATE_CAN_REBAKE_REGION_FIELD "can_modify_navmesh"

//---------------------------------------------------------------------------
// LLNavMeshSimStateChangeNode
//---------------------------------------------------------------------------

class LLNavMeshSimStateChangeNode : public LLHTTPNode
{
public:
    virtual void post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const;
};

LLHTTPRegistration<LLNavMeshSimStateChangeNode> gHTTPRegistrationNavMeshSimStateChangeNode(SIM_MESSAGE_NAVMESH_STATUS_UPDATE);


//---------------------------------------------------------------------------
// LLAgentStateChangeNode
//---------------------------------------------------------------------------
class LLAgentStateChangeNode : public LLHTTPNode
{
public:
    virtual void post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const;
};

LLHTTPRegistration<LLAgentStateChangeNode> gHTTPRegistrationAgentStateChangeNode(SIM_MESSAGE_AGENT_STATE_UPDATE);

//---------------------------------------------------------------------------
// LinksetsResponder
//---------------------------------------------------------------------------

class LinksetsResponder
{
public:
    LinksetsResponder(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::object_request_callback_t pLinksetsCallback, bool pIsObjectRequested, bool pIsTerrainRequested);
    virtual ~LinksetsResponder();

    void handleObjectLinksetsResult(const LLSD &pContent);
    void handleObjectLinksetsError();
    void handleTerrainLinksetsResult(const LLSD &pContent);
    void handleTerrainLinksetsError();

    typedef std::shared_ptr<LinksetsResponder> ptr_t;

protected:

private:
    void sendCallback();

    typedef enum
    {
        kNotRequested,
        kWaiting,
        kReceivedGood,
        kReceivedError
    } EMessagingState;

    LLPathfindingManager::request_id_t              mRequestId;
    LLPathfindingManager::object_request_callback_t mLinksetsCallback;

    EMessagingState                                 mObjectMessagingState;
    EMessagingState                                 mTerrainMessagingState;

    LLPathfindingObjectListPtr                      mObjectLinksetListPtr;
    LLPathfindingObjectPtr                          mTerrainLinksetPtr;
};

typedef std::shared_ptr<LinksetsResponder> LinksetsResponderPtr;

//---------------------------------------------------------------------------
// LLPathfindingManager
//---------------------------------------------------------------------------

LLPathfindingManager::LLPathfindingManager():
    mNavMeshMap(),
    mAgentStateSignal()
{
}

LLPathfindingManager::~LLPathfindingManager()
{
    quitSystem();
}

void LLPathfindingManager::initSystem()
{
    if (LLPathingLib::getInstance() == NULL)
    {
        LLPathingLib::initSystem();
    }
}

void LLPathfindingManager::quitSystem()
{
    if (LLPathingLib::getInstance() != NULL)
    {
        LLPathingLib::quitSystem();
    }
}

bool LLPathfindingManager::isPathfindingViewEnabled() const
{
    return (LLPathingLib::getInstance() != NULL);
}

bool LLPathfindingManager::isPathfindingEnabledForCurrentRegion() const
{
    return isPathfindingEnabledForRegion(getCurrentRegion());
}

bool LLPathfindingManager::isPathfindingEnabledForRegion(LLViewerRegion *pRegion) const
{
    std::string retrieveNavMeshURL = getRetrieveNavMeshURLForRegion(pRegion);
    return !retrieveNavMeshURL.empty();
}

bool LLPathfindingManager::isAllowViewTerrainProperties() const
{
    LLViewerRegion* region = getCurrentRegion();
    return (gAgent.isGodlike() || ((region != NULL) && region->canManageEstate()));
}

LLPathfindingNavMesh::navmesh_slot_t LLPathfindingManager::registerNavMeshListenerForRegion(LLViewerRegion *pRegion, LLPathfindingNavMesh::navmesh_callback_t pNavMeshCallback)
{
    LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(pRegion);
    return navMeshPtr->registerNavMeshListener(pNavMeshCallback);
}

void LLPathfindingManager::requestGetNavMeshForRegion(LLViewerRegion *pRegion, bool pIsGetStatusOnly)
{
    LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(pRegion);

    if (pRegion == NULL)
    {
        navMeshPtr->handleNavMeshNotEnabled();
    }
    else if (!pRegion->capabilitiesReceived())
    {
        navMeshPtr->handleNavMeshWaitForRegionLoad();
        pRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetNavMeshForRegion, this, _1, pIsGetStatusOnly));
    }
    else if (!isPathfindingEnabledForRegion(pRegion))
    {
        navMeshPtr->handleNavMeshNotEnabled();
    }
    else
    {
        std::string navMeshStatusURL = getNavMeshStatusURLForRegion(pRegion);
        llassert(!navMeshStatusURL.empty());
        navMeshPtr->handleNavMeshCheckVersion();

        U64 regionHandle = pRegion->getHandle();
        std::string coroname = LLCoros::instance().launch("LLPathfindingManager::navMeshStatusRequestCoro",
            boost::bind(&LLPathfindingManager::navMeshStatusRequestCoro, this, navMeshStatusURL, regionHandle, pIsGetStatusOnly));
    }
}

void LLPathfindingManager::requestGetLinksets(request_id_t pRequestId, object_request_callback_t pLinksetsCallback) const
{
    LLPathfindingObjectListPtr emptyLinksetListPtr;
    LLViewerRegion *currentRegion = getCurrentRegion();

    if (currentRegion == NULL)
    {
        pLinksetsCallback(pRequestId, kRequestNotEnabled, emptyLinksetListPtr);
    }
    else if (!currentRegion->capabilitiesReceived())
    {
        pLinksetsCallback(pRequestId, kRequestStarted, emptyLinksetListPtr);
        currentRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetLinksetsForRegion, this, _1, pRequestId, pLinksetsCallback));
    }
    else
    {
        std::string objectLinksetsURL = getRetrieveObjectLinksetsURLForCurrentRegion();
        std::string terrainLinksetsURL = getTerrainLinksetsURLForCurrentRegion();
        if (objectLinksetsURL.empty() || terrainLinksetsURL.empty())
        {
            pLinksetsCallback(pRequestId, kRequestNotEnabled, emptyLinksetListPtr);
        }
        else
        {
            pLinksetsCallback(pRequestId, kRequestStarted, emptyLinksetListPtr);

            bool doRequestTerrain = isAllowViewTerrainProperties();
            LinksetsResponder::ptr_t linksetsResponderPtr(new LinksetsResponder(pRequestId, pLinksetsCallback, true, doRequestTerrain));

            std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetObjectsCoro",
                boost::bind(&LLPathfindingManager::linksetObjectsCoro, this, objectLinksetsURL, linksetsResponderPtr, LLSD()));

            if (doRequestTerrain)
            {
                std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetTerrainCoro",
                    boost::bind(&LLPathfindingManager::linksetTerrainCoro, this, terrainLinksetsURL, linksetsResponderPtr, LLSD()));
            }
        }
    }
}

void LLPathfindingManager::requestSetLinksets(request_id_t pRequestId, const LLPathfindingObjectListPtr &pLinksetListPtr, LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD, object_request_callback_t pLinksetsCallback) const
{
    LLPathfindingObjectListPtr emptyLinksetListPtr;

    std::string objectLinksetsURL = getChangeObjectLinksetsURLForCurrentRegion();
    std::string terrainLinksetsURL = getTerrainLinksetsURLForCurrentRegion();
    if (objectLinksetsURL.empty() || terrainLinksetsURL.empty())
    {
        pLinksetsCallback(pRequestId, kRequestNotEnabled, emptyLinksetListPtr);
    }
    else if ((pLinksetListPtr == NULL) || pLinksetListPtr->isEmpty())
    {
        pLinksetsCallback(pRequestId, kRequestCompleted, emptyLinksetListPtr);
    }
    else
    {
        const LLPathfindingLinksetList *linksetList = dynamic_cast<const LLPathfindingLinksetList *>(pLinksetListPtr.get());

        LLSD objectPostData = linksetList->encodeObjectFields(pLinksetUse, pA, pB, pC, pD);
        LLSD terrainPostData;
        if (isAllowViewTerrainProperties())
        {
            terrainPostData = linksetList->encodeTerrainFields(pLinksetUse, pA, pB, pC, pD);
        }

        if (objectPostData.isUndefined() && terrainPostData.isUndefined())
        {
            pLinksetsCallback(pRequestId, kRequestCompleted, emptyLinksetListPtr);
        }
        else
        {
            pLinksetsCallback(pRequestId, kRequestStarted, emptyLinksetListPtr);

            LinksetsResponder::ptr_t linksetsResponderPtr(new LinksetsResponder(pRequestId, pLinksetsCallback, !objectPostData.isUndefined(), !terrainPostData.isUndefined()));

            if (!objectPostData.isUndefined())
            {
                std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetObjectsCoro",
                    boost::bind(&LLPathfindingManager::linksetObjectsCoro, this, objectLinksetsURL, linksetsResponderPtr, objectPostData));
            }

            if (!terrainPostData.isUndefined())
            {
                std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetTerrainCoro",
                    boost::bind(&LLPathfindingManager::linksetTerrainCoro, this, terrainLinksetsURL, linksetsResponderPtr, terrainPostData));
            }
        }
    }
}

void LLPathfindingManager::requestGetCharacters(request_id_t pRequestId, object_request_callback_t pCharactersCallback) const
{
    LLPathfindingObjectListPtr emptyCharacterListPtr;

    LLViewerRegion *currentRegion = getCurrentRegion();

    if (currentRegion == NULL)
    {
        pCharactersCallback(pRequestId, kRequestNotEnabled, emptyCharacterListPtr);
    }
    else if (!currentRegion->capabilitiesReceived())
    {
        pCharactersCallback(pRequestId, kRequestStarted, emptyCharacterListPtr);
        currentRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetCharactersForRegion, this, _1, pRequestId, pCharactersCallback));
    }
    else
    {
        std::string charactersURL = getCharactersURLForCurrentRegion();
        if (charactersURL.empty())
        {
            pCharactersCallback(pRequestId, kRequestNotEnabled, emptyCharacterListPtr);
        }
        else
        {
            pCharactersCallback(pRequestId, kRequestStarted, emptyCharacterListPtr);

            std::string coroname = LLCoros::instance().launch("LLPathfindingManager::charactersCoro",
                boost::bind(&LLPathfindingManager::charactersCoro, this, charactersURL, pRequestId, pCharactersCallback));
        }
    }
}

LLPathfindingManager::agent_state_slot_t LLPathfindingManager::registerAgentStateListener(agent_state_callback_t pAgentStateCallback)
{
    return mAgentStateSignal.connect(pAgentStateCallback);
}

void LLPathfindingManager::requestGetAgentState()
{
    LLViewerRegion *currentRegion = getCurrentRegion();

    if (currentRegion == NULL)
    {
        mAgentStateSignal(false);
    }
    else
    {
        if (!currentRegion->capabilitiesReceived())
        {
            currentRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetAgentStateForRegion, this, _1));
        }
        else if (!isPathfindingEnabledForRegion(currentRegion))
        {
            mAgentStateSignal(false);
        }
        else
        {
            std::string agentStateURL = getAgentStateURLForRegion(currentRegion);
            llassert(!agentStateURL.empty());

            std::string coroname = LLCoros::instance().launch("LLPathfindingManager::navAgentStateRequestCoro",
                boost::bind(&LLPathfindingManager::navAgentStateRequestCoro, this, agentStateURL));
        }
    }
}

void LLPathfindingManager::requestRebakeNavMesh(rebake_navmesh_callback_t pRebakeNavMeshCallback)
{
    LLViewerRegion *currentRegion = getCurrentRegion();

    if (currentRegion == NULL)
    {
        pRebakeNavMeshCallback(false);
    }
    else if (!isPathfindingEnabledForRegion(currentRegion))
    {
        pRebakeNavMeshCallback(false);
    }
    else
    {
        std::string navMeshStatusURL = getNavMeshStatusURLForCurrentRegion();
        llassert(!navMeshStatusURL.empty());

        std::string coroname = LLCoros::instance().launch("LLPathfindingManager::navMeshRebakeCoro",
                boost::bind(&LLPathfindingManager::navMeshRebakeCoro, this, navMeshStatusURL, pRebakeNavMeshCallback));
    }
}

void LLPathfindingManager::handleDeferredGetAgentStateForRegion(const LLUUID &pRegionUUID)
{
    LLViewerRegion *currentRegion = getCurrentRegion();

    if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID))
    {
        requestGetAgentState();
    }
}

void LLPathfindingManager::handleDeferredGetNavMeshForRegion(const LLUUID &pRegionUUID, bool pIsGetStatusOnly)
{
    LLViewerRegion *currentRegion = getCurrentRegion();

    if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID))
    {
        requestGetNavMeshForRegion(currentRegion, pIsGetStatusOnly);
    }
}

void LLPathfindingManager::handleDeferredGetLinksetsForRegion(const LLUUID &pRegionUUID, request_id_t pRequestId, object_request_callback_t pLinksetsCallback) const
{
    LLViewerRegion *currentRegion = getCurrentRegion();

    if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID))
    {
        requestGetLinksets(pRequestId, pLinksetsCallback);
    }
}

void LLPathfindingManager::handleDeferredGetCharactersForRegion(const LLUUID &pRegionUUID, request_id_t pRequestId, object_request_callback_t pCharactersCallback) const
{
    LLViewerRegion *currentRegion = getCurrentRegion();

    if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID))
    {
        requestGetCharacters(pRequestId, pCharactersCallback);
    }
}

void LLPathfindingManager::navMeshStatusRequestCoro(std::string url, U64 regionHandle, bool isGetStatusOnly)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("NavMeshStatusRequest", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    LLViewerRegion *region = LLWorld::getInstance()->getRegionFromHandle(regionHandle);
    if (!region)
    {
        LL_WARNS("PathfindingManager") << "Attempting to retrieve navmesh status for region that has gone away." << LL_ENDL;
        return;
    }
    LLUUID regionUUID = region->getRegionID();

    region = NULL;
    LLSD result = httpAdapter->getAndSuspend(httpRequest, url);

    region = LLWorld::getInstance()->getRegionFromHandle(regionHandle);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    LLPathfindingNavMeshStatus navMeshStatus(regionUUID);
    if (!status)
    {
        LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() <<
            ". Building using empty status." << LL_ENDL;
    }
    else
    {
        result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
        navMeshStatus = LLPathfindingNavMeshStatus(regionUUID, result);
    }

    LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(regionUUID);

    if (!navMeshStatus.isValid())
    {
        navMeshPtr->handleNavMeshError();
        return;
    }
    else if (navMeshPtr->hasNavMeshVersion(navMeshStatus))
    {
        navMeshPtr->handleRefresh(navMeshStatus);
        return;
    }
    else if (isGetStatusOnly)
    {
        navMeshPtr->handleNavMeshNewVersion(navMeshStatus);
        return;
    }

    if ((!region) || !region->isAlive())
    {
        LL_WARNS("PathfindingManager") << "About to update navmesh status for region that has gone away." << LL_ENDL;
        navMeshPtr->handleNavMeshNotEnabled();
        return;
    }

    std::string navMeshURL = getRetrieveNavMeshURLForRegion(region);

    if (navMeshURL.empty())
    {
        navMeshPtr->handleNavMeshNotEnabled();
        return;
    }

    navMeshPtr->handleNavMeshStart(navMeshStatus);

    LLSD postData;
    result = httpAdapter->postAndSuspend(httpRequest, navMeshURL, postData);

    U32 navMeshVersion = navMeshStatus.getVersion();

    if (!status)
    {
        LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() <<
            ". reporting error." << LL_ENDL;
        navMeshPtr->handleNavMeshError(navMeshVersion);
    }
    else
    {
        result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
        navMeshPtr->handleNavMeshResult(result, navMeshVersion);

    }

}

void LLPathfindingManager::navAgentStateRequestCoro(std::string url)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("NavAgentStateRequest", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    LLSD result = httpAdapter->getAndSuspend(httpRequest, url);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    bool canRebake = false;
    if (!status)
    {
        LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() <<
            ". Building using empty status." << LL_ENDL;
    }
    else
    {
        llassert(result.has(AGENT_STATE_CAN_REBAKE_REGION_FIELD));
        llassert(result.get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).isBoolean());
        canRebake = result.get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).asBoolean();
    }

    handleAgentState(canRebake);
}

void LLPathfindingManager::navMeshRebakeCoro(std::string url, rebake_navmesh_callback_t rebakeNavMeshCallback)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("NavMeshRebake", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);


    LLSD postData = LLSD::emptyMap();
    postData["command"] = "rebuild";

    LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    bool success = true;
    if (!status)
    {
        LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() <<
            ". Rebake failed." << LL_ENDL;
        success = false;
    }

    rebakeNavMeshCallback(success);
}

// If called with putData undefined this coroutine will issue a get.  If there
// is data in putData it will be PUT to the URL.
void LLPathfindingManager::linksetObjectsCoro(std::string url, LinksetsResponder::ptr_t linksetsResponsderPtr, LLSD putData) const
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("LinksetObjects", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    LLSD result;

    if (putData.isUndefined())
    {
        result = httpAdapter->getAndSuspend(httpRequest, url);
    }
    else
    {
        result = httpAdapter->putAndSuspend(httpRequest, url, putData);
    }

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (!status)
    {
        LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() <<
            ". linksetObjects failed." << LL_ENDL;
        linksetsResponsderPtr->handleObjectLinksetsError();
    }
    else
    {
        result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
        linksetsResponsderPtr->handleObjectLinksetsResult(result);
    }
}

// If called with putData undefined this coroutine will issue a GET.  If there
// is data in putData it will be PUT to the URL.
void LLPathfindingManager::linksetTerrainCoro(std::string url, LinksetsResponder::ptr_t linksetsResponsderPtr, LLSD putData) const
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("LinksetTerrain", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    LLSD result;

    if (putData.isUndefined())
    {
        result = httpAdapter->getAndSuspend(httpRequest, url);
    }
    else
    {
        result = httpAdapter->putAndSuspend(httpRequest, url, putData);
    }

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (!status)
    {
        LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() <<
            ". linksetTerrain failed." << LL_ENDL;
        linksetsResponsderPtr->handleTerrainLinksetsError();
    }
    else
    {
        result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
        linksetsResponsderPtr->handleTerrainLinksetsResult(result);
    }

}

void LLPathfindingManager::charactersCoro(std::string url, request_id_t requestId, object_request_callback_t callback) const
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("LinksetTerrain", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    LLSD result = httpAdapter->getAndSuspend(httpRequest, url);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (!status)
    {
        LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() <<
            ". characters failed." << LL_ENDL;

        LLPathfindingObjectListPtr characterListPtr = LLPathfindingObjectListPtr(new LLPathfindingCharacterList());
        callback(requestId, LLPathfindingManager::kRequestError, characterListPtr);
    }
    else
    {
        result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
        LLPathfindingObjectListPtr characterListPtr = LLPathfindingObjectListPtr(new LLPathfindingCharacterList(result));
        callback(requestId, LLPathfindingManager::kRequestCompleted, characterListPtr);
    }
}

void LLPathfindingManager::handleNavMeshStatusUpdate(const LLPathfindingNavMeshStatus &pNavMeshStatus)
{
    LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(pNavMeshStatus.getRegionUUID());

    if (!pNavMeshStatus.isValid())
    {
        navMeshPtr->handleNavMeshError();
    }
    else
    {
        navMeshPtr->handleNavMeshNewVersion(pNavMeshStatus);
    }
}

void LLPathfindingManager::handleAgentState(bool pCanRebakeRegion)
{
    mAgentStateSignal(pCanRebakeRegion);
}

LLPathfindingNavMeshPtr LLPathfindingManager::getNavMeshForRegion(const LLUUID &pRegionUUID)
{
    LLPathfindingNavMeshPtr navMeshPtr;
    NavMeshMap::iterator navMeshIter = mNavMeshMap.find(pRegionUUID);
    if (navMeshIter == mNavMeshMap.end())
    {
        navMeshPtr = LLPathfindingNavMeshPtr(new LLPathfindingNavMesh(pRegionUUID));
        mNavMeshMap.insert(std::pair<LLUUID, LLPathfindingNavMeshPtr>(pRegionUUID, navMeshPtr));
    }
    else
    {
        navMeshPtr = navMeshIter->second;
    }

    return navMeshPtr;
}

LLPathfindingNavMeshPtr LLPathfindingManager::getNavMeshForRegion(LLViewerRegion *pRegion)
{
    LLUUID regionUUID;
    if (pRegion != NULL)
    {
        regionUUID = pRegion->getRegionID();
    }

    return getNavMeshForRegion(regionUUID);
}

std::string LLPathfindingManager::getNavMeshStatusURLForCurrentRegion() const
{
    return getNavMeshStatusURLForRegion(getCurrentRegion());
}

std::string LLPathfindingManager::getNavMeshStatusURLForRegion(LLViewerRegion *pRegion) const
{
    return getCapabilityURLForRegion(pRegion, CAP_SERVICE_NAVMESH_STATUS);
}

std::string LLPathfindingManager::getRetrieveNavMeshURLForRegion(LLViewerRegion *pRegion) const
{
    return getCapabilityURLForRegion(pRegion, CAP_SERVICE_RETRIEVE_NAVMESH);
}

std::string LLPathfindingManager::getRetrieveObjectLinksetsURLForCurrentRegion() const
{
    return getCapabilityURLForCurrentRegion(CAP_SERVICE_GET_OBJECT_LINKSETS);
}

std::string LLPathfindingManager::getChangeObjectLinksetsURLForCurrentRegion() const
{
    return getCapabilityURLForCurrentRegion(CAP_SERVICE_SET_OBJECT_LINKSETS);
}

std::string LLPathfindingManager::getTerrainLinksetsURLForCurrentRegion() const
{
    return getCapabilityURLForCurrentRegion(CAP_SERVICE_TERRAIN_LINKSETS);
}

std::string LLPathfindingManager::getCharactersURLForCurrentRegion() const
{
    return getCapabilityURLForCurrentRegion(CAP_SERVICE_CHARACTERS);
}

std::string LLPathfindingManager::getAgentStateURLForRegion(LLViewerRegion *pRegion) const
{
    return getCapabilityURLForRegion(pRegion, CAP_SERVICE_AGENT_STATE);
}

std::string LLPathfindingManager::getCapabilityURLForCurrentRegion(const std::string &pCapabilityName) const
{
    return getCapabilityURLForRegion(getCurrentRegion(), pCapabilityName);
}

std::string LLPathfindingManager::getCapabilityURLForRegion(LLViewerRegion *pRegion, const std::string &pCapabilityName) const
{
    std::string capabilityURL("");

    if (pRegion != NULL)
    {
        capabilityURL = pRegion->getCapability(pCapabilityName);
    }

    if (capabilityURL.empty())
    {
        LL_WARNS() << "cannot find capability '" << pCapabilityName << "' for current region '"
            << ((pRegion != NULL) ? pRegion->getName() : "<null>") << "'" << LL_ENDL;
    }

    return capabilityURL;
}

LLViewerRegion *LLPathfindingManager::getCurrentRegion() const
{
    return gAgent.getRegion();
}

//---------------------------------------------------------------------------
// LLNavMeshSimStateChangeNode
//---------------------------------------------------------------------------

void LLNavMeshSimStateChangeNode::post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const
{
    llassert(pInput.has(SIM_MESSAGE_BODY_FIELD));
    llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).isMap());
    LLPathfindingNavMeshStatus navMeshStatus(pInput.get(SIM_MESSAGE_BODY_FIELD));
    LLPathfindingManager::getInstance()->handleNavMeshStatusUpdate(navMeshStatus);
}

//---------------------------------------------------------------------------
// LLAgentStateChangeNode
//---------------------------------------------------------------------------

void LLAgentStateChangeNode::post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const
{
    llassert(pInput.has(SIM_MESSAGE_BODY_FIELD));
    llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).isMap());
    llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).has(AGENT_STATE_CAN_REBAKE_REGION_FIELD));
    llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).isBoolean());
    bool canRebakeRegion = pInput.get(SIM_MESSAGE_BODY_FIELD).get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).asBoolean();

    LLPathfindingManager::getInstance()->handleAgentState(canRebakeRegion);
}

//---------------------------------------------------------------------------
// LinksetsResponder
//---------------------------------------------------------------------------
LinksetsResponder::LinksetsResponder(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::object_request_callback_t pLinksetsCallback, bool pIsObjectRequested, bool pIsTerrainRequested)
    : mRequestId(pRequestId),
    mLinksetsCallback(pLinksetsCallback),
    mObjectMessagingState(pIsObjectRequested ? kWaiting : kNotRequested),
    mTerrainMessagingState(pIsTerrainRequested ? kWaiting : kNotRequested),
    mObjectLinksetListPtr(),
    mTerrainLinksetPtr()
{
}

LinksetsResponder::~LinksetsResponder()
{
}

void LinksetsResponder::handleObjectLinksetsResult(const LLSD &pContent)
{
    mObjectLinksetListPtr = LLPathfindingObjectListPtr(new LLPathfindingLinksetList(pContent));

    mObjectMessagingState = kReceivedGood;
    if (mTerrainMessagingState != kWaiting)
    {
        sendCallback();
    }
}

void LinksetsResponder::handleObjectLinksetsError()
{
    LL_WARNS() << "LinksetsResponder object linksets error" << LL_ENDL;
    mObjectMessagingState = kReceivedError;
    if (mTerrainMessagingState != kWaiting)
    {
        sendCallback();
    }
}

void LinksetsResponder::handleTerrainLinksetsResult(const LLSD &pContent)
{
    mTerrainLinksetPtr = LLPathfindingObjectPtr(new LLPathfindingLinkset(pContent));

    mTerrainMessagingState = kReceivedGood;
    if (mObjectMessagingState != kWaiting)
    {
        sendCallback();
    }
}

void LinksetsResponder::handleTerrainLinksetsError()
{
    LL_WARNS() << "LinksetsResponder terrain linksets error" << LL_ENDL;
    mTerrainMessagingState = kReceivedError;
    if (mObjectMessagingState != kWaiting)
    {
        sendCallback();
    }
}

void LinksetsResponder::sendCallback()
{
    llassert(mObjectMessagingState != kWaiting);
    llassert(mTerrainMessagingState != kWaiting);
    LLPathfindingManager::ERequestStatus requestStatus =
        ((((mObjectMessagingState == kReceivedGood) || (mObjectMessagingState == kNotRequested)) &&
          ((mTerrainMessagingState == kReceivedGood) || (mTerrainMessagingState == kNotRequested))) ?
         LLPathfindingManager::kRequestCompleted : LLPathfindingManager::kRequestError);

    if (mObjectMessagingState != kReceivedGood)
    {
        mObjectLinksetListPtr = LLPathfindingObjectListPtr(new LLPathfindingLinksetList());
    }

    if (mTerrainMessagingState == kReceivedGood)
    {
        mObjectLinksetListPtr->update(mTerrainLinksetPtr);
    }

    mLinksetsCallback(mRequestId, requestStatus, mObjectLinksetListPtr);
}