/**
* @file llpathfindingpathtool.cpp
* @brief Implementation of llpathfindingpathtool
* @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 "llpathfindingpathtool.h"

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

#include "llagent.h"
#include "llpathfindingmanager.h"
#include "llviewercamera.h"
#include "llviewerregion.h"
#include "llviewerwindow.h"

#define PATH_TOOL_NAME "PathfindingPathTool"

LLPathfindingPathTool::LLPathfindingPathTool()
    : LLTool(PATH_TOOL_NAME),
    mFinalPathData(),
    mTempPathData(),
    mPathResult(LLPathingLib::LLPL_NO_PATH),
    mCharacterType(kCharacterTypeNone),
    mPathEventSignal(),
    mIsLeftMouseButtonHeld(false),
    mIsMiddleMouseButtonHeld(false),
    mIsRightMouseButtonHeld(false)
{
    setCharacterWidth(1.0f);
    setCharacterType(mCharacterType);
}

LLPathfindingPathTool::~LLPathfindingPathTool()
{
}

bool LLPathfindingPathTool::handleMouseDown(S32 pX, S32 pY, MASK pMask)
{
    bool returnVal = false;

    if (!mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld)
    {
        if (isAnyPathToolModKeys(pMask))
        {
            gViewerWindow->setCursor(isPointAModKeys(pMask)
                ? UI_CURSOR_TOOLPATHFINDING_PATH_START_ADD
                : UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD);
            computeFinalPoints(pX, pY, pMask);
            mIsLeftMouseButtonHeld = true;
            setMouseCapture(true);
            returnVal = true;
        }
        else if (!isCameraModKeys(pMask))
        {
            gViewerWindow->setCursor(UI_CURSOR_TOOLNO);
            mIsLeftMouseButtonHeld = true;
            setMouseCapture(true);
            returnVal = true;
        }
    }
    mIsLeftMouseButtonHeld = true;

    return returnVal;
}

bool LLPathfindingPathTool::handleMouseUp(S32 pX, S32 pY, MASK pMask)
{
    bool returnVal = false;

    if (mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld)
    {
        computeFinalPoints(pX, pY, pMask);
        setMouseCapture(false);
        returnVal = true;
    }
    mIsLeftMouseButtonHeld = false;

    return returnVal;
}

bool LLPathfindingPathTool::handleMiddleMouseDown(S32 pX, S32 pY, MASK pMask)
{
    setMouseCapture(true);
    mIsMiddleMouseButtonHeld = true;
    gViewerWindow->setCursor(UI_CURSOR_TOOLNO);

    return true;
}

bool LLPathfindingPathTool::handleMiddleMouseUp(S32 pX, S32 pY, MASK pMask)
{
    if (!mIsLeftMouseButtonHeld && mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld)
    {
        setMouseCapture(false);
    }
    mIsMiddleMouseButtonHeld = false;

    return true;
}

bool LLPathfindingPathTool::handleRightMouseDown(S32 pX, S32 pY, MASK pMask)
{
    setMouseCapture(true);
    mIsRightMouseButtonHeld = true;
    gViewerWindow->setCursor(UI_CURSOR_TOOLNO);

    return true;
}

bool LLPathfindingPathTool::handleRightMouseUp(S32 pX, S32 pY, MASK pMask)
{
    if (!mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && mIsRightMouseButtonHeld)
    {
        setMouseCapture(false);
    }
    mIsRightMouseButtonHeld = false;

    return true;
}

bool LLPathfindingPathTool::handleDoubleClick(S32 pX, S32 pY, MASK pMask)
{
    return true;
}

bool LLPathfindingPathTool::handleHover(S32 pX, S32 pY, MASK pMask)
{
    bool returnVal = false;

    if (!mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld && !isAnyPathToolModKeys(pMask))
    {
        gViewerWindow->setCursor(UI_CURSOR_TOOLPATHFINDING);
    }

    if (!mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld && isAnyPathToolModKeys(pMask))
    {
        gViewerWindow->setCursor(isPointAModKeys(pMask)
            ? (mIsLeftMouseButtonHeld ? UI_CURSOR_TOOLPATHFINDING_PATH_START_ADD : UI_CURSOR_TOOLPATHFINDING_PATH_START)
            : (mIsLeftMouseButtonHeld ? UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD : UI_CURSOR_TOOLPATHFINDING_PATH_END));
        computeTempPoints(pX, pY, pMask);
        returnVal = true;
    }
    else
    {
        clearTemp();
        computeFinalPath();
    }

    return returnVal;
}

bool LLPathfindingPathTool::handleKey(KEY pKey, MASK pMask)
{
    // Eat the escape key or else the camera tool will pick up and reset to default view.  This,
    // in turn, will cause some other methods to get called.  And one of those methods will reset
    // the current toolset back to the basic toolset.  This means that the pathfinding path toolset
    // will no longer be active, but typically with pathfinding path elements on screen.
    return (pKey == KEY_ESCAPE);
}

LLPathfindingPathTool::EPathStatus LLPathfindingPathTool::getPathStatus() const
{
    EPathStatus status = kPathStatusUnknown;

    if (LLPathingLib::getInstance() == NULL)
    {
        status = kPathStatusNotImplemented;
    }
    else if ((gAgent.getRegion() != NULL) && !gAgent.getRegion()->capabilitiesReceived())
    {
        status = kPathStatusUnknown;
    }
    else if (!LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion())
    {
        status = kPathStatusNotEnabled;
    }
    else if (!hasFinalA() && !hasFinalB())
    {
        status = kPathStatusChooseStartAndEndPoints;
    }
    else if (!hasFinalA())
    {
        status = kPathStatusChooseStartPoint;
    }
    else if (!hasFinalB())
    {
        status = kPathStatusChooseEndPoint;
    }
    else if (mPathResult == LLPathingLib::LLPL_PATH_GENERATED_OK)
    {
        status = kPathStatusHasValidPath;
    }
    else if (mPathResult == LLPathingLib::LLPL_NO_PATH)
    {
        status = kPathStatusHasInvalidPath;
    }
    else
    {
        status = kPathStatusError;
    }

    return status;
}

F32 LLPathfindingPathTool::getCharacterWidth() const
{
    return mFinalPathData.mCharacterWidth;
}

void LLPathfindingPathTool::setCharacterWidth(F32 pCharacterWidth)
{
    mFinalPathData.mCharacterWidth = pCharacterWidth;
    mTempPathData.mCharacterWidth = pCharacterWidth;
    computeFinalPath();
}

LLPathfindingPathTool::ECharacterType LLPathfindingPathTool::getCharacterType() const
{
    return mCharacterType;
}

void LLPathfindingPathTool::setCharacterType(ECharacterType pCharacterType)
{
    mCharacterType = pCharacterType;

    LLPathingLib::LLPLCharacterType characterType;
    switch (pCharacterType)
    {
    case kCharacterTypeNone :
        characterType = LLPathingLib::LLPL_CHARACTER_TYPE_NONE;
        break;
    case kCharacterTypeA :
        characterType = LLPathingLib::LLPL_CHARACTER_TYPE_A;
        break;
    case kCharacterTypeB :
        characterType = LLPathingLib::LLPL_CHARACTER_TYPE_B;
        break;
    case kCharacterTypeC :
        characterType = LLPathingLib::LLPL_CHARACTER_TYPE_C;
        break;
    case kCharacterTypeD :
        characterType = LLPathingLib::LLPL_CHARACTER_TYPE_D;
        break;
    default :
        characterType = LLPathingLib::LLPL_CHARACTER_TYPE_NONE;
        llassert(0);
        break;
    }
    mFinalPathData.mCharacterType = characterType;
    mTempPathData.mCharacterType = characterType;
    computeFinalPath();
}

bool LLPathfindingPathTool::isRenderPath() const
{
    return (hasFinalA() || hasFinalB() || hasTempA() || hasTempB());
}

void LLPathfindingPathTool::clearPath()
{
    clearFinal();
    clearTemp();
    computeFinalPath();
}

LLPathfindingPathTool::path_event_slot_t LLPathfindingPathTool::registerPathEventListener(path_event_callback_t pPathEventCallback)
{
    return mPathEventSignal.connect(pPathEventCallback);
}

bool LLPathfindingPathTool::isAnyPathToolModKeys(MASK pMask) const
{
    return ((pMask & (MASK_CONTROL|MASK_SHIFT)) != 0);
}

bool LLPathfindingPathTool::isPointAModKeys(MASK pMask) const
{
    return ((pMask & MASK_CONTROL) != 0);
}

bool LLPathfindingPathTool::isPointBModKeys(MASK pMask) const
{
    return ((pMask & MASK_SHIFT) != 0);
}

bool LLPathfindingPathTool::isCameraModKeys(MASK pMask) const
{
    return ((pMask & MASK_ALT) != 0);
}

void LLPathfindingPathTool::getRayPoints(S32 pX, S32 pY, LLVector3 &pRayStart, LLVector3 &pRayEnd) const
{
    LLVector3 dv = gViewerWindow->mouseDirectionGlobal(pX, pY);
    LLVector3 mousePos = LLViewerCamera::getInstance()->getOrigin();
    pRayStart = mousePos;
    pRayEnd = mousePos + dv * 150;
}

void LLPathfindingPathTool::computeFinalPoints(S32 pX, S32 pY, MASK pMask)
{
    LLVector3 rayStart, rayEnd;
    getRayPoints(pX, pY, rayStart, rayEnd);

    if (isPointAModKeys(pMask))
    {
        setFinalA(rayStart, rayEnd);
    }
    else if (isPointBModKeys(pMask))
    {
        setFinalB(rayStart, rayEnd);
    }
    computeFinalPath();
}

void LLPathfindingPathTool::computeTempPoints(S32 pX, S32 pY, MASK pMask)
{
    LLVector3 rayStart, rayEnd;
    getRayPoints(pX, pY, rayStart, rayEnd);

    if (isPointAModKeys(pMask))
    {
        setTempA(rayStart, rayEnd);
        if (hasFinalB())
        {
            setTempB(getFinalBStart(), getFinalBEnd());
        }
    }
    else if (isPointBModKeys(pMask))
    {
        if (hasFinalA())
        {
            setTempA(getFinalAStart(), getFinalAEnd());
        }
        setTempB(rayStart, rayEnd);
    }
    computeTempPath();
}

void LLPathfindingPathTool::setFinalA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint)
{
    mFinalPathData.mStartPointA = pStartPoint;
    mFinalPathData.mEndPointA = pEndPoint;
    mFinalPathData.mHasPointA = true;
}

bool LLPathfindingPathTool::hasFinalA() const
{
    return mFinalPathData.mHasPointA;
}

const LLVector3 &LLPathfindingPathTool::getFinalAStart() const
{
    return mFinalPathData.mStartPointA;
}

const LLVector3 &LLPathfindingPathTool::getFinalAEnd() const
{
    return mFinalPathData.mEndPointA;
}

void LLPathfindingPathTool::setTempA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint)
{
    mTempPathData.mStartPointA = pStartPoint;
    mTempPathData.mEndPointA = pEndPoint;
    mTempPathData.mHasPointA = true;
}

bool LLPathfindingPathTool::hasTempA() const
{
    return mTempPathData.mHasPointA;
}

void LLPathfindingPathTool::setFinalB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint)
{
    mFinalPathData.mStartPointB = pStartPoint;
    mFinalPathData.mEndPointB = pEndPoint;
    mFinalPathData.mHasPointB = true;
}

bool LLPathfindingPathTool::hasFinalB() const
{
    return mFinalPathData.mHasPointB;
}

const LLVector3 &LLPathfindingPathTool::getFinalBStart() const
{
    return mFinalPathData.mStartPointB;
}

const LLVector3 &LLPathfindingPathTool::getFinalBEnd() const
{
    return mFinalPathData.mEndPointB;
}

void LLPathfindingPathTool::setTempB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint)
{
    mTempPathData.mStartPointB = pStartPoint;
    mTempPathData.mEndPointB = pEndPoint;
    mTempPathData.mHasPointB = true;
}

bool LLPathfindingPathTool::hasTempB() const
{
    return mTempPathData.mHasPointB;
}

void LLPathfindingPathTool::clearFinal()
{
    mFinalPathData.mHasPointA = false;
    mFinalPathData.mHasPointB = false;
}

void LLPathfindingPathTool::clearTemp()
{
    mTempPathData.mHasPointA = false;
    mTempPathData.mHasPointB = false;
}

void LLPathfindingPathTool::computeFinalPath()
{
    mPathResult = LLPathingLib::LLPL_NO_PATH;
    if (LLPathingLib::getInstance() != NULL)
    {
        mPathResult = LLPathingLib::getInstance()->generatePath(mFinalPathData);
    }
    mPathEventSignal();
}

void LLPathfindingPathTool::computeTempPath()
{
    mPathResult = LLPathingLib::LLPL_NO_PATH;
    if (LLPathingLib::getInstance() != NULL)
    {
        mPathResult = LLPathingLib::getInstance()->generatePath(mTempPathData);
    }
    mPathEventSignal();
}