/**
 * @file   llagentlistener.cpp
 * @author Brad Kittenbrink
 * @date   2009-07-10
 * @brief  Implementation for llagentlistener.
 *
 * $LicenseInfo:firstyear=2009&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 "llagentlistener.h"

#include "llagent.h"
#include "llvoavatar.h"
#include "llcommandhandler.h"
#include "llslurl.h"
#include "llurldispatcher.h"
#include "llviewernetwork.h"
#include "llviewerobject.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llsdutil.h"
#include "llsdutil_math.h"
#include "lltoolgrab.h"
#include "llhudeffectlookat.h"
#include "llagentcamera.h"

LLAgentListener::LLAgentListener(LLAgent &agent)
  : LLEventAPI("LLAgent",
               "LLAgent listener to (e.g.) teleport, sit, stand, etc."),
    mAgent(agent)
{
    add("requestTeleport",
        "Teleport: [\"regionname\"], [\"x\"], [\"y\"], [\"z\"]\n"
        "If [\"skip_confirmation\"] is true, use LLURLDispatcher rather than LLCommandDispatcher.",
        &LLAgentListener::requestTeleport);
    add("requestSit",
        "[\"obj_uuid\"]: id of object to sit on, use this or [\"position\"] to indicate the sit target"
        "[\"position\"]: region position {x, y, z} where to find closest object to sit on",
        &LLAgentListener::requestSit);
    add("requestStand",
        "Ask to stand up",
        &LLAgentListener::requestStand);
    add("requestTouch",
        "[\"obj_uuid\"]: id of object to touch, use this or [\"position\"] to indicate the object to touch"
        "[\"position\"]: region position {x, y, z} where to find closest object to touch"
        "[\"face\"]: optional object face number to touch[Default: 0]",
        &LLAgentListener::requestTouch);
    add("resetAxes",
        "Set the agent to a fixed orientation (optionally specify [\"lookat\"] = array of [x, y, z])",
        &LLAgentListener::resetAxes);
    add("getAxes",
        "Obsolete - use getPosition instead\n"
        "Send information about the agent's orientation on [\"reply\"]:\n"
        "[\"euler\"]: map of {roll, pitch, yaw}\n"
        "[\"quat\"]:  array of [x, y, z, w] quaternion values",
        &LLAgentListener::getAxes,
        LLSDMap("reply", LLSD()));
    add("getPosition",
        "Send information about the agent's position and orientation on [\"reply\"]:\n"
        "[\"region\"]: array of region {x, y, z} position\n"
        "[\"global\"]: array of global {x, y, z} position\n"
        "[\"euler\"]: map of {roll, pitch, yaw}\n"
        "[\"quat\"]:  array of [x, y, z, w] quaternion values",
        &LLAgentListener::getPosition,
        LLSDMap("reply", LLSD()));
    add("startAutoPilot",
        "Start the autopilot system using the following parameters:\n"
        "[\"target_global\"]: array of target global {x, y, z} position\n"
        "[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]\n"
        "[\"target_rotation\"]: array of [x, y, z, w] quaternion values [default: no target]\n"
        "[\"rotation_threshold\"]: target maximum angle from target facing rotation [default: 0.03 radians]\n"
        "[\"behavior_name\"]: name of the autopilot behavior [default: \"\"]"
        "[\"allow_flying\"]: allow flying during autopilot [default: True]",
        //"[\"callback_pump\"]: pump to send success/failure and callback data to [default: none]\n"
        //"[\"callback_data\"]: data to send back during a callback [default: none]",
        &LLAgentListener::startAutoPilot);
    add("getAutoPilot",
        "Send information about current state of the autopilot system to [\"reply\"]:\n"
        "[\"enabled\"]: boolean indicating whether or not autopilot is enabled\n"
        "[\"target_global\"]: array of target global {x, y, z} position\n"
        "[\"leader_id\"]: uuid of target autopilot is following\n"
        "[\"stop_distance\"]: target maximum distance from target\n"
        "[\"target_distance\"]: last known distance from target\n"
        "[\"use_rotation\"]: boolean indicating if autopilot has a target facing rotation\n"
        "[\"target_facing\"]: array of {x, y} target direction to face\n"
        "[\"rotation_threshold\"]: target maximum angle from target facing rotation\n"
        "[\"behavior_name\"]: name of the autopilot behavior",
        &LLAgentListener::getAutoPilot,
        LLSDMap("reply", LLSD()));
    add("startFollowPilot",
        "[\"leader_id\"]: uuid of target to follow using the autopilot system (optional with avatar_name)\n"
        "[\"avatar_name\"]: avatar name to follow using the autopilot system (optional with leader_id)\n"
        "[\"allow_flying\"]: allow flying during autopilot [default: True]\n"
        "[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]",
        &LLAgentListener::startFollowPilot);
    add("setAutoPilotTarget",
        "Update target for currently running autopilot:\n"
        "[\"target_global\"]: array of target global {x, y, z} position",
        &LLAgentListener::setAutoPilotTarget);
    add("stopAutoPilot",
        "Stop the autopilot system:\n"
        "[\"user_cancel\"] indicates whether or not to act as though user canceled autopilot [default: false]",
        &LLAgentListener::stopAutoPilot);
    add("lookAt",
        "[\"type\"]: number to indicate the lookAt type, 0 to clear\n"
        "[\"obj_uuid\"]: id of object to look at, use this or [\"position\"] to indicate the target\n"
        "[\"position\"]: region position {x, y, z} where to find closest object or avatar to look at",
        &LLAgentListener::lookAt);
    add("getGroups",
        "Send information about the agent's groups on [\"reply\"]:\n"
        "[\"groups\"]: array of group information\n"
        "[\"id\"]: group id\n"
        "[\"name\"]: group name\n"
        "[\"insignia\"]: group insignia texture id\n"
        "[\"notices\"]: boolean indicating if this user accepts notices from this group\n"
        "[\"display\"]: boolean indicating if this group is listed in the user's profile\n"
        "[\"contrib\"]: user's land contribution to this group\n",
        &LLAgentListener::getGroups,
        LLSDMap("reply", LLSD()));
}

void LLAgentListener::requestTeleport(LLSD const & event_data) const
{
    if(event_data["skip_confirmation"].asBoolean())
    {
        LLSD params(LLSD::emptyArray());
        params.append(event_data["regionname"]);
        params.append(event_data["x"]);
        params.append(event_data["y"]);
        params.append(event_data["z"]);
        LLCommandDispatcher::dispatch("teleport", params, LLSD(), LLGridManager::getInstance()->getGrid(), NULL, LLCommandHandler::NAV_TYPE_CLICKED, true);
        // *TODO - lookup other LLCommandHandlers for "agent", "classified", "event", "group", "floater", "parcel", "login", login_refresh", "balance", "chat"
        // should we just compose LLCommandHandler and LLDispatchListener?
    }
    else
    {
        std::string url = LLSLURL(event_data["regionname"],
                                  LLVector3((F32)event_data["x"].asReal(),
                                            (F32)event_data["y"].asReal(),
                                            (F32)event_data["z"].asReal())).getSLURLString();
        LLURLDispatcher::dispatch(url, LLCommandHandler::NAV_TYPE_CLICKED, NULL, false);
    }
}

void LLAgentListener::requestSit(LLSD const & event_data) const
{
    //mAgent.getAvatarObject()->sitOnObject();
    // shamelessly ripped from llviewermenu.cpp:handle_sit_or_stand()
    // *TODO - find a permanent place to share this code properly.

    LLViewerObject *object = NULL;
    if (event_data.has("obj_uuid"))
    {
        object = gObjectList.findObject(event_data["obj_uuid"]);
    }
    else if (event_data.has("position"))
    {
        LLVector3 target_position = ll_vector3_from_sd(event_data["position"]);
        object = findObjectClosestTo(target_position);
    }

    if (object && object->getPCode() == LL_PCODE_VOLUME)
    {
        gMessageSystem->newMessageFast(_PREHASH_AgentRequestSit);
        gMessageSystem->nextBlockFast(_PREHASH_AgentData);
        gMessageSystem->addUUIDFast(_PREHASH_AgentID, mAgent.getID());
        gMessageSystem->addUUIDFast(_PREHASH_SessionID, mAgent.getSessionID());
        gMessageSystem->nextBlockFast(_PREHASH_TargetObject);
        gMessageSystem->addUUIDFast(_PREHASH_TargetID, object->mID);
        gMessageSystem->addVector3Fast(_PREHASH_Offset, LLVector3(0,0,0));

        object->getRegion()->sendReliableMessage();
    }
    else
    {
        LL_WARNS() << "LLAgent requestSit could not find the sit target: "
            << event_data << LL_ENDL;
    }
}

void LLAgentListener::requestStand(LLSD const & event_data) const
{
    mAgent.setControlFlags(AGENT_CONTROL_STAND_UP);
}


LLViewerObject * LLAgentListener::findObjectClosestTo( const LLVector3 & position ) const
{
    LLViewerObject *object = NULL;

    // Find the object closest to that position
    F32 min_distance = 10000.0f;        // Start big
    S32 num_objects = gObjectList.getNumObjects();
    S32 cur_index = 0;
    while (cur_index < num_objects)
    {
        LLViewerObject * cur_object = gObjectList.getObject(cur_index++);
        if (cur_object)
        {   // Calculate distance from the target position
            LLVector3 target_diff = cur_object->getPositionRegion() - position;
            F32 distance_to_target = target_diff.length();
            if (distance_to_target < min_distance)
            {   // Found an object closer
                min_distance = distance_to_target;
                object = cur_object;
            }
        }
    }

    return object;
}


void LLAgentListener::requestTouch(LLSD const & event_data) const
{
    LLViewerObject *object = NULL;

    if (event_data.has("obj_uuid"))
    {
        object = gObjectList.findObject(event_data["obj_uuid"]);
    }
    else if (event_data.has("position"))
    {
        LLVector3 target_position = ll_vector3_from_sd(event_data["position"]);
        object = findObjectClosestTo(target_position);
    }

    S32 face = 0;
    if (event_data.has("face"))
    {
        face = event_data["face"].asInteger();
    }

    if (object && object->getPCode() == LL_PCODE_VOLUME)
    {
        // Fake enough pick info to get it to (hopefully) work
        LLPickInfo pick;
        pick.mObjectFace = face;

        /*
        These values are sent to the simulator, but face seems to be easiest to use

        pick.mUVCoords   "UVCoord"
        pick.mSTCoords  "STCoord"
        pick.mObjectFace    "FaceIndex"
        pick.mIntersection  "Position"
        pick.mNormal    "Normal"
        pick.mBinormal  "Binormal"
        */

        // A touch is a sketchy message sequence ... send a grab, immediately
        // followed by un-grabbing, crossing fingers and hoping packets arrive in
        // the correct order
        send_ObjectGrab_message(object, pick, LLVector3::zero);
        send_ObjectDeGrab_message(object, pick);
    }
    else
    {
        LL_WARNS() << "LLAgent requestTouch could not find the touch target "
            << event_data["obj_uuid"].asUUID() << LL_ENDL;
    }
}


void LLAgentListener::resetAxes(const LLSD& event_data) const
{
    if (event_data.has("lookat"))
    {
        mAgent.resetAxes(ll_vector3_from_sd(event_data["lookat"]));
    }
    else
    {
        // no "lookat", default call
        mAgent.resetAxes();
    }
}

void LLAgentListener::getAxes(const LLSD& event_data) const
{
    LLQuaternion quat(mAgent.getQuat());
    F32 roll, pitch, yaw;
    quat.getEulerAngles(&roll, &pitch, &yaw);
    // The official query API for LLQuaternion's [x, y, z, w] values is its
    // public member mQ...
    LLSD reply = LLSD::emptyMap();
    reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ));
    reply["euler"] = LLSD::emptyMap();
    reply["euler"]["roll"] = roll;
    reply["euler"]["pitch"] = pitch;
    reply["euler"]["yaw"] = yaw;
    sendReply(reply, event_data);
}

void LLAgentListener::getPosition(const LLSD& event_data) const
{
    F32 roll, pitch, yaw;
    LLQuaternion quat(mAgent.getQuat());
    quat.getEulerAngles(&roll, &pitch, &yaw);

    LLSD reply = LLSD::emptyMap();
    reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ));
    reply["euler"] = LLSD::emptyMap();
    reply["euler"]["roll"] = roll;
    reply["euler"]["pitch"] = pitch;
    reply["euler"]["yaw"] = yaw;
    reply["region"] = ll_sd_from_vector3(mAgent.getPositionAgent());
    reply["global"] = ll_sd_from_vector3d(mAgent.getPositionGlobal());

    sendReply(reply, event_data);
}


void LLAgentListener::startAutoPilot(LLSD const & event_data)
{
    LLQuaternion target_rotation_value;
    LLQuaternion* target_rotation = NULL;
    if (event_data.has("target_rotation"))
    {
        target_rotation_value = ll_quaternion_from_sd(event_data["target_rotation"]);
        target_rotation = &target_rotation_value;
    }
    // *TODO: Use callback_pump and callback_data
    F32 rotation_threshold = 0.03f;
    if (event_data.has("rotation_threshold"))
    {
        rotation_threshold = (F32)event_data["rotation_threshold"].asReal();
    }

    bool allow_flying = true;
    if (event_data.has("allow_flying"))
    {
        allow_flying = (bool) event_data["allow_flying"].asBoolean();
        mAgent.setFlying(allow_flying);
    }

    F32 stop_distance = 0.f;
    if (event_data.has("stop_distance"))
    {
        stop_distance = (F32)event_data["stop_distance"].asReal();
    }

    // Clear follow target, this is doing a path
    mFollowTarget.setNull();

    mAgent.startAutoPilotGlobal(ll_vector3d_from_sd(event_data["target_global"]),
                                event_data["behavior_name"],
                                target_rotation,
                                NULL, NULL,
                                stop_distance,
                                rotation_threshold,
                                allow_flying);
}

void LLAgentListener::getAutoPilot(const LLSD& event_data) const
{
    LLSD reply = LLSD::emptyMap();

    LLSD::Boolean enabled = mAgent.getAutoPilot();
    reply["enabled"] = enabled;

    reply["target_global"] = ll_sd_from_vector3d(mAgent.getAutoPilotTargetGlobal());

    reply["leader_id"] = mAgent.getAutoPilotLeaderID();

    reply["stop_distance"] = mAgent.getAutoPilotStopDistance();

    reply["target_distance"] = mAgent.getAutoPilotTargetDist();
    if (!enabled &&
        mFollowTarget.notNull())
    {   // Get an actual distance from the target object we were following
        LLViewerObject * target = gObjectList.findObject(mFollowTarget);
        if (target)
        {   // Found the target AV, return the actual distance to them as well as their ID
            LLVector3 difference = target->getPositionRegion() - mAgent.getPositionAgent();
            reply["target_distance"] = difference.length();
            reply["leader_id"] = mFollowTarget;
        }
    }

    reply["use_rotation"] = (LLSD::Boolean) mAgent.getAutoPilotUseRotation();
    reply["target_facing"] = ll_sd_from_vector3(mAgent.getAutoPilotTargetFacing());
    reply["rotation_threshold"] = mAgent.getAutoPilotRotationThreshold();
    reply["behavior_name"] = mAgent.getAutoPilotBehaviorName();
    reply["fly"] = (LLSD::Boolean) mAgent.getFlying();

    sendReply(reply, event_data);
}

void LLAgentListener::startFollowPilot(LLSD const & event_data)
{
    LLUUID target_id;

    bool allow_flying = true;
    if (event_data.has("allow_flying"))
    {
        allow_flying = (bool) event_data["allow_flying"].asBoolean();
    }

    if (event_data.has("leader_id"))
    {
        target_id = event_data["leader_id"];
    }
    else if (event_data.has("avatar_name"))
    {   // Find the avatar with matching name
        std::string target_name = event_data["avatar_name"].asString();

        if (target_name.length() > 0)
        {
            S32 num_objects = gObjectList.getNumObjects();
            S32 cur_index = 0;
            while (cur_index < num_objects)
            {
                LLViewerObject * cur_object = gObjectList.getObject(cur_index++);
                if (cur_object &&
                    cur_object->asAvatar() &&
                    cur_object->asAvatar()->getFullname() == target_name)
                {   // Found avatar with matching name, extract id and break out of loop
                    target_id = cur_object->getID();
                    break;
                }
            }
        }
    }

    F32 stop_distance = 0.f;
    if (event_data.has("stop_distance"))
    {
        stop_distance = (F32)event_data["stop_distance"].asReal();
    }

    if (target_id.notNull())
    {
        mAgent.setFlying(allow_flying);
        mFollowTarget = target_id;  // Save follow target so we can report distance later

        mAgent.startFollowPilot(target_id, allow_flying, stop_distance);
    }
}

void LLAgentListener::setAutoPilotTarget(LLSD const & event_data) const
{
    if (event_data.has("target_global"))
    {
        LLVector3d target_global(ll_vector3d_from_sd(event_data["target_global"]));
        mAgent.setAutoPilotTargetGlobal(target_global);
    }
}

void LLAgentListener::stopAutoPilot(LLSD const & event_data) const
{
    bool user_cancel = false;
    if (event_data.has("user_cancel"))
    {
        user_cancel = event_data["user_cancel"].asBoolean();
    }
    mAgent.stopAutoPilot(user_cancel);
}

void LLAgentListener::lookAt(LLSD const & event_data) const
{
    LLViewerObject *object = NULL;
    if (event_data.has("obj_uuid"))
    {
        object = gObjectList.findObject(event_data["obj_uuid"]);
    }
    else if (event_data.has("position"))
    {
        LLVector3 target_position = ll_vector3_from_sd(event_data["position"]);
        object = findObjectClosestTo(target_position);
    }

    S32 look_at_type = (S32) LOOKAT_TARGET_NONE;
    if (event_data.has("type"))
    {
        look_at_type = event_data["type"].asInteger();
    }
    if (look_at_type >= (S32) LOOKAT_TARGET_NONE &&
        look_at_type < (S32) LOOKAT_NUM_TARGETS)
    {
        gAgentCamera.setLookAt((ELookAtType) look_at_type, object);
    }
}

void LLAgentListener::getGroups(const LLSD& event) const
{
    LLSD reply(LLSD::emptyArray());
    for (std::vector<LLGroupData>::const_iterator
             gi(mAgent.mGroups.begin()), gend(mAgent.mGroups.end());
         gi != gend; ++gi)
    {
        reply.append(LLSDMap
                     ("id", gi->mID)
                     ("name", gi->mName)
                     ("insignia", gi->mInsigniaID)
                     ("notices", bool(gi->mAcceptNotices))
                     ("display", bool(gi->mListInProfile))
                     ("contrib", gi->mContribution));
    }
    sendReply(LLSDMap("groups", reply), event);
}