/**
 * @file llcontrolavatar.cpp
 * @brief Implementation for special dummy avatar used to drive rigged meshes.
 *
 * $LicenseInfo:firstyear=2017&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2017, 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 "llcontrolavatar.h"
#include "llagent.h" //  Get state values from here
#include "llviewerobjectlist.h"
#include "pipeline.h"
#include "llanimationstates.h"
#include "llviewercontrol.h"
#include "llmeshrepository.h"
#include "llviewerregion.h"
#include "llskinningutil.h"

const F32 LLControlAvatar::MAX_LEGAL_OFFSET = 3.0f;
const F32 LLControlAvatar::MAX_LEGAL_SIZE = 64.0f;

//static
boost::signals2::connection LLControlAvatar::sRegionChangedSlot;

LLControlAvatar::LLControlAvatar(const LLUUID& id, const LLPCode pcode, LLViewerRegion* regionp) :
    LLVOAvatar(id, pcode, regionp),
    mPlaying(false),
    mGlobalScale(1.0f),
    mMarkedForDeath(false),
    mRootVolp(NULL),
    mControlAVBridge(NULL),
    mScaleConstraintFixup(1.0),
    mRegionChanged(false)
{
    mIsDummy = true;
    mIsControlAvatar = true;
    mEnableDefaultMotions = false;
}

// virtual
LLControlAvatar::~LLControlAvatar()
{
    // Should already have been unlinked before destruction
    llassert(!mRootVolp);
}

// virtual
void LLControlAvatar::initInstance()
{
    // Potential optimizations here: avoid creating system
    // avatar mesh content since it's not used. For now we just clean some
    // things up after the fact in releaseMeshData().
    LLVOAvatar::initInstance();

    createDrawable(&gPipeline);
    updateJointLODs();
    updateGeometry(mDrawable);
    hideSkirt();

    mInitFlags |= 1<<4;
}

const LLVOAvatar *LLControlAvatar::getAttachedAvatar() const
{
    if (mRootVolp && mRootVolp->isAttachment())
    {
        return mRootVolp->getAvatarAncestor();
    }
    return NULL;
}

LLVOAvatar *LLControlAvatar::getAttachedAvatar()
{
    if (mRootVolp && mRootVolp->isAttachment())
    {
        return mRootVolp->getAvatarAncestor();
    }
    return NULL;
}

void LLControlAvatar::getNewConstraintFixups(LLVector3& new_pos_fixup, F32& new_scale_fixup) const
{
    static LLCachedControl<F32> anim_max_legal_offset(gSavedSettings, "AnimatedObjectsMaxLegalOffset", MAX_LEGAL_OFFSET);
    F32 max_legal_offset = llmax(anim_max_legal_offset(), 0.f);

    static LLCachedControl<F32> anim_max_legal_size(gSavedSettings, "AnimatedObjectsMaxLegalSize", MAX_LEGAL_SIZE);
    F32 max_legal_size = llmax(anim_max_legal_size(), 1.f);

    new_pos_fixup = LLVector3();
    new_scale_fixup = 1.0f;
    LLVector3 vol_pos = mRootVolp->getRenderPosition();

    // Fix up position if needed to prevent visual encroachment
    if (box_valid_and_non_zero(getLastAnimExtents())) // wait for state to settle down
    {
        // The goal here is to ensure that the extent of the avatar's
        // bounding box does not wander too far from the
        // official position of the corresponding volume. We
        // do this by tracking the distance and applying a
        // correction to the control avatar position if
        // needed.
        const LLVector3 *extents = getLastAnimExtents();
        LLVector3 unshift_extents[2];
        unshift_extents[0] = extents[0] - mPositionConstraintFixup;
        unshift_extents[1] = extents[1] - mPositionConstraintFixup;
        LLVector3 box_dims = extents[1]-extents[0];
        F32 box_size = llmax(box_dims[0],box_dims[1],box_dims[2]);

        if (!mRootVolp->isAttachment())
        {
            LLVector3 pos_box_offset = point_to_box_offset(vol_pos, unshift_extents);
            F32 offset_dist = pos_box_offset.length();
            if (offset_dist > MAX_LEGAL_OFFSET && offset_dist > 0.f)
            {
                F32 target_dist = (offset_dist - MAX_LEGAL_OFFSET);
                new_pos_fixup = (target_dist/offset_dist)*pos_box_offset;
            }
            if (new_pos_fixup != mPositionConstraintFixup)
            {
                LL_DEBUGS("ConstraintFix") << getFullname() << " pos fix, offset_dist " << offset_dist << " pos fixup "
                                           << new_pos_fixup << " was " << mPositionConstraintFixup << LL_ENDL;
                LL_DEBUGS("ConstraintFix") << "vol_pos " << vol_pos << LL_ENDL;
                LL_DEBUGS("ConstraintFix") << "extents " << extents[0] << " " << extents[1] << LL_ENDL;
                LL_DEBUGS("ConstraintFix") << "unshift_extents " << unshift_extents[0] << " " << unshift_extents[1] << LL_ENDL;

            }
        }
        if (box_size/mScaleConstraintFixup > MAX_LEGAL_SIZE)
        {
            new_scale_fixup = mScaleConstraintFixup* MAX_LEGAL_SIZE /box_size;
            LL_DEBUGS("ConstraintFix") << getFullname() << " scale fix, box_size " << box_size << " fixup "
                                       << mScaleConstraintFixup << " max legal " << MAX_LEGAL_SIZE
                                       << " -> new scale " << new_scale_fixup << LL_ENDL;
        }
    }
}

void LLControlAvatar::matchVolumeTransform()
{
    if (mRootVolp)
    {
        LLVector3 new_pos_fixup;
        F32 new_scale_fixup;
        if (mRegionChanged)
        {
            new_scale_fixup = mScaleConstraintFixup;
            new_pos_fixup = mPositionConstraintFixup;
            mRegionChanged = false;
        }
        else
        {
            getNewConstraintFixups(new_pos_fixup, new_scale_fixup);
        }
        mPositionConstraintFixup = new_pos_fixup;
        mScaleConstraintFixup = new_scale_fixup;

        if (mRootVolp->isAttachment())
        {
            LLVOAvatar *attached_av = getAttachedAvatar();
            if (attached_av)
            {
                LLViewerJointAttachment *attach = attached_av->getTargetAttachmentPoint(mRootVolp);
                if (getRegion() && !isDead())
                {
                    setPositionAgent(mRootVolp->getRenderPosition());
                }
                attach->updateWorldPRSParent();
                LLVector3 joint_pos = attach->getWorldPosition();
                LLQuaternion joint_rot = attach->getWorldRotation();
                LLVector3 obj_pos = mRootVolp->mDrawable->getPosition();
                LLQuaternion obj_rot = mRootVolp->mDrawable->getRotation();
                obj_pos.rotVec(joint_rot);
                mRoot->setWorldPosition(obj_pos + joint_pos);
                mRoot->setWorldRotation(obj_rot * joint_rot);
                setRotation(mRoot->getRotation());

                setGlobalScale(mScaleConstraintFixup);
            }
            else
            {
                LL_WARNS_ONCE() << "can't find attached av!" << LL_ENDL;
            }
        }
        else
        {
            LLVector3 vol_pos = mRootVolp->getRenderPosition();

            // FIXME: Currently if you're doing something like playing an
            // animation that moves the pelvis (on an avatar or
            // animated object), the name tag and debug text will be
            // left behind. Ideally setPosition() would follow the
            // skeleton around in a smarter way, so name tags,
            // complexity info and such line up better. Should defer
            // this until avatars also get fixed.

            LLQuaternion obj_rot;
            if (mRootVolp->mDrawable)
            {
                obj_rot = mRootVolp->mDrawable->getRotation();
            }
            else
            {
                obj_rot = mRootVolp->getRotation();
            }

            LLMatrix3 bind_mat;

            LLQuaternion bind_rot;
#define MATCH_BIND_SHAPE
#ifdef MATCH_BIND_SHAPE
            // MAINT-8671 - based on a patch from Beq Janus
            const LLMeshSkinInfo* skin_info = mRootVolp->getSkinInfo();
            if (skin_info)
            {
                LL_DEBUGS("BindShape") << getFullname() << " bind shape " << skin_info->mBindShapeMatrix << LL_ENDL;
                bind_rot = LLSkinningUtil::getUnscaledQuaternion(LLMatrix4(skin_info->mBindShapeMatrix));
            }
#endif
            setRotation(bind_rot*obj_rot);
            mRoot->setWorldRotation(bind_rot*obj_rot);
            if (getRegion() && !isDead())
            {
                setPositionAgent(vol_pos);
            }
            mRoot->setPosition(vol_pos + mPositionConstraintFixup);

            setGlobalScale(mScaleConstraintFixup);
        }
    }
}

void LLControlAvatar::setGlobalScale(F32 scale)
{
    if (scale <= 0.0)
    {
        LL_WARNS() << "invalid global scale " << scale << LL_ENDL;
        return;
    }
    if (scale != mGlobalScale)
    {
        F32 adjust_scale = scale/mGlobalScale;
        LL_INFOS() << "scale " << scale << " adjustment " << adjust_scale << LL_ENDL;
        // should we be scaling from the pelvis or the root?
        recursiveScaleJoint(mPelvisp,adjust_scale);
        mGlobalScale = scale;
    }
}

void LLControlAvatar::recursiveScaleJoint(LLJoint* joint, F32 factor)
{
    joint->setScale(factor * joint->getScale());

    for (LLJoint::joints_t::iterator iter = joint->mChildren.begin();
         iter != joint->mChildren.end(); ++iter)
    {
        LLJoint* child = *iter;
        recursiveScaleJoint(child, factor);
    }
}

// Based on LLViewerJointAttachment::setupDrawable(), without the attaching part.
void LLControlAvatar::updateVolumeGeom()
{
    if (!mRootVolp->mDrawable)
        return;
    if (mRootVolp->mDrawable->isActive())
    {
        mRootVolp->mDrawable->makeStatic(false);
    }
    mRootVolp->mDrawable->makeActive();
    gPipeline.markMoved(mRootVolp->mDrawable);
    gPipeline.markTextured(mRootVolp->mDrawable); // face may need to change draw pool to/from POOL_HUD

    LLViewerObject::const_child_list_t& child_list = mRootVolp->getChildren();
    for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
         iter != child_list.end(); ++iter)
    {
        LLViewerObject* childp = *iter;
        if (childp && childp->mDrawable.notNull())
        {
            gPipeline.markTextured(childp->mDrawable); // face may need to change draw pool to/from POOL_HUD
            gPipeline.markMoved(childp->mDrawable);
        }
    }

    gPipeline.markRebuild(mRootVolp->mDrawable, LLDrawable::REBUILD_ALL);
    mRootVolp->markForUpdate();

    // Note that attachment overrides aren't needed here, have already
    // been applied at the time the mControlAvatar was created, in
    // llvovolume.cpp.

    matchVolumeTransform();

    // Initial exploration of allowing scaling skeleton to match root
    // prim bounding box. If enabled, would probably be controlled by
    // an additional checkbox and default to off. Not enabled for
    // initial release.

    // What should the scale be? What we really want is the ratio
    // between the scale at which the object was originally designed
    // and rigged, and the scale to which it has been subsequently
    // modified - for example, if the object has been scaled down by a
    // factor of 2 then we should use 0.5 as the global scale. But we
    // don't have the original scale stored anywhere, just the current
    // scale. Possibilities - 1) remember the original scale
    // somewhere, 2) add another field to let the user specify the
    // global scale, 3) approximate the original scale by looking at
    // the proportions of the skeleton after joint positions have
    // been applied

    //LLVector3 obj_scale = obj->getScale();
    //F32 obj_scale_z = llmax(obj_scale[2],0.1f);
    //setGlobalScale(obj_scale_z/2.0f); // roughly fit avatar height range (2m) into object height
}

LLControlAvatar *LLControlAvatar::createControlAvatar(LLVOVolume *obj)
{
    LLControlAvatar *cav = (LLControlAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), CO_FLAG_CONTROL_AVATAR);

    if (cav)
    {
        cav->mRootVolp = obj;

        // Sync up position/rotation with object
        cav->matchVolumeTransform();
    }

    return cav;
}

void LLControlAvatar::markForDeath()
{
    mMarkedForDeath = true;
    // object unlinked cav and might be dead already
    // might need to clean mControlAVBridge here as well
    mRootVolp = NULL;
}

void LLControlAvatar::idleUpdate(LLAgent &agent, const F64 &time)
{
    if (mMarkedForDeath)
    {
        markDead();
        mMarkedForDeath = false;
    }
    else
    {
        LLVOAvatar::idleUpdate(agent,time);
    }
}

void LLControlAvatar::markDead()
{
    mRootVolp = NULL;
    super::markDead();
    mControlAVBridge = NULL;
}

bool LLControlAvatar::computeNeedsUpdate()
{
    computeUpdatePeriod();

    // Animesh attachments are a special case. Should have the same update cadence as their attached parent avatar.
    LLVOAvatar *attached_av = getAttachedAvatar();
    if (attached_av)
    {
        // Have to run computeNeedsUpdate() for attached av in
        // case it hasn't run updateCharacter() already this
        // frame.  Note this means that the attached av will
        // run computeNeedsUpdate() multiple times per frame
        // if it has animesh attachments. Results will be
        // consistent except for the corner case of exceeding
        // MAX_IMPOSTOR_INTERVAL in one call but not another,
        // which should be rare.
        attached_av->computeNeedsUpdate();
        mNeedsImpostorUpdate = attached_av->mNeedsImpostorUpdate;
        if (mNeedsImpostorUpdate)
        {
            mLastImpostorUpdateReason = 12;
        }
        return mNeedsImpostorUpdate;
    }
    return LLVOAvatar::computeNeedsUpdate();
}

bool LLControlAvatar::updateCharacter(LLAgent &agent)
{
    return LLVOAvatar::updateCharacter(agent);
}

//virtual
void LLControlAvatar::updateDebugText()
{
    static LLCachedControl<bool> debug_animated_objects(gSavedSettings, "DebugAnimatedObjects");
    if (debug_animated_objects)
    {
        S32 total_linkset_count = 0;
        if (mRootVolp)
        {
            total_linkset_count = 1 + static_cast<S32>(mRootVolp->getChildren().size());
        }
        std::vector<LLVOVolume*> volumes;
        getAnimatedVolumes(volumes);
        S32 animated_volume_count = static_cast<S32>(volumes.size());
        std::string active_string;
        std::string type_string;
        std::string lod_string;
        std::string animated_object_flag_string;
        S32 total_tris = 0;
        S32 total_verts = 0;
        F32 est_tris = 0.f;
        F32 est_streaming_tris = 0.f;
        F32 streaming_cost = 0.f;
        std::string cam_dist_string = "";
        S32 cam_dist_count = 0;
        F32 lod_radius = mRootVolp ? mRootVolp->mLODRadius : 0.f;

        for (std::vector<LLVOVolume*>::iterator it = volumes.begin();
             it != volumes.end(); ++it)
        {
            LLVOVolume *volp = *it;
            S32 verts = 0;
            total_tris += volp->getTriangleCount(&verts);
            total_verts += verts;
            est_tris += volp->getEstTrianglesMax();
            est_streaming_tris += volp->getEstTrianglesStreamingCost();
            streaming_cost += volp->getStreamingCost();
            lod_string += llformat("%d",volp->getLOD());
            if (volp && volp->mDrawable)
            {
                bool is_animated_flag = volp->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG;
                if (is_animated_flag)
                {
                    animated_object_flag_string += "1";
                }
                else
                {
                    animated_object_flag_string += "0";
                }
                if (volp->mDrawable->isActive())
                {
                    active_string += "A";
                }
                else
                {
                    active_string += "S";
                }
                if (volp->isRiggedMesh())
                {
                    // Rigged/animatable mesh
                    type_string += "R";
                    lod_radius = volp->mLODRadius;
                }
                else if (volp->isMesh())
                {
                    // Static mesh
                    type_string += "M";
                }
                else
                {
                    // Any other prim
                    type_string += "P";
                }
                if (cam_dist_count < 4)
                {
                    cam_dist_string += LLStringOps::getReadableNumber(volp->mLODDistance) + "/" +
                        LLStringOps::getReadableNumber(volp->mLODAdjustedDistance) + " ";
                    cam_dist_count++;
                }
            }
            else
            {
                active_string += "-";
                type_string += "-";
            }
        }
        addDebugText(llformat("CAV obj %d anim %d active %s impost %d upprd %d strcst %f",
                              total_linkset_count, animated_volume_count,
                              active_string.c_str(), (S32) isImpostor(), getUpdatePeriod(), streaming_cost));
        addDebugText(llformat("types %s lods %s", type_string.c_str(), lod_string.c_str()));
        addDebugText(llformat("flags %s", animated_object_flag_string.c_str()));
        addDebugText(llformat("tris %d (est %.1f, streaming %.1f), verts %d", total_tris, est_tris, est_streaming_tris, total_verts));
        addDebugText(llformat("pxarea %s rank %d", LLStringOps::getReadableNumber(getPixelArea()).c_str(), getVisibilityRank()));
        addDebugText(llformat("lod_radius %s dists %s", LLStringOps::getReadableNumber(lod_radius).c_str(),cam_dist_string.c_str()));
        if (mPositionConstraintFixup.length() > 0.0f || mScaleConstraintFixup != 1.0f)
        {
            addDebugText(llformat("pos fix (%.1f %.1f %.1f) scale %f",
                                  mPositionConstraintFixup[0],
                                  mPositionConstraintFixup[1],
                                  mPositionConstraintFixup[2],
                                  mScaleConstraintFixup));
        }

#if 0
        std::string region_name = "no region";
        if (mRootVolp->getRegion())
        {
            region_name = mRootVolp->getRegion()->getName();
        }
        std::string skel_region_name = "skel no region";
        if (getRegion())
        {
            skel_region_name = getRegion()->getName();
        }
        addDebugText(llformat("region %x %s skel %x %s",
                              mRootVolp->getRegion(), region_name.c_str(),
                              getRegion(), skel_region_name.c_str()));
#endif

    }
    LLVOAvatar::updateDebugText();
}

void LLControlAvatar::getAnimatedVolumes(std::vector<LLVOVolume*>& volumes)
{
    if (!mRootVolp)
    {
        return;
    }

    volumes.push_back(mRootVolp);

    LLViewerObject::const_child_list_t& child_list = mRootVolp->getChildren();
    for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin();
         iter != child_list.end(); ++iter)
    {
        LLViewerObject* childp = *iter;
        LLVOVolume *child_volp = dynamic_cast<LLVOVolume*>(childp);
        if (child_volp && child_volp->isAnimatedObject())
        {
            volumes.push_back(child_volp);
        }
    }
}

// This is called after an associated object receives an animation
// message. Combine the signaled animations for all associated objects
// and process any resulting state changes.
void LLControlAvatar::updateAnimations()
{
    if (!mRootVolp)
    {
        LL_WARNS_ONCE("AnimatedObjectsNotify") << "No root vol" << LL_ENDL;
        return;
    }

    std::vector<LLVOVolume*> volumes;
    getAnimatedVolumes(volumes);

    // Rebuild mSignaledAnimations from the associated volumes.
    std::map<LLUUID, S32> anims;
    for (std::vector<LLVOVolume*>::iterator vol_it = volumes.begin(); vol_it != volumes.end(); ++vol_it)
    {
        LLVOVolume *volp = *vol_it;
        //LL_INFOS("AnimatedObjects") << "updating anim for vol " << volp->getID() << " root " << mRootVolp->getID() << LL_ENDL;
        signaled_animation_map_t& signaled_animations = LLObjectSignaledAnimationMap::instance().getMap()[volp->getID()];
        for (std::map<LLUUID,S32>::iterator anim_it = signaled_animations.begin();
             anim_it != signaled_animations.end();
             ++anim_it)
        {
            std::map<LLUUID,S32>::iterator found_anim_it = anims.find(anim_it->first);
            if (found_anim_it != anims.end())
            {
                // Animation already present, use the larger sequence id
                anims[anim_it->first] = llmax(found_anim_it->second, anim_it->second);
            }
            else
            {
                // Animation not already present, use this sequence id.
                anims[anim_it->first] = anim_it->second;
            }
            LL_DEBUGS("AnimatedObjectsNotify") << "found anim for vol " << volp->getID() << " anim " << anim_it->first << " root " << mRootVolp->getID() << LL_ENDL;
        }
    }
    if (!mPlaying)
    {
        mPlaying = true;
        //if (!mRootVolp->isAnySelected())
        {
            updateVolumeGeom();
            mRootVolp->recursiveMarkForUpdate();
        }
    }

    mSignaledAnimations = anims;
    processAnimationStateChanges();
}

// virtual
LLViewerObject* LLControlAvatar::lineSegmentIntersectRiggedAttachments(const LLVector4a& start, const LLVector4a& end,
                                      S32 face,
                                      bool pick_transparent,
                                      bool pick_rigged,
                                      bool pick_unselectable,
                                      S32* face_hit,
                                      LLVector4a* intersection,
                                      LLVector2* tex_coord,
                                      LLVector4a* normal,
                                      LLVector4a* tangent)
{
    if (!mRootVolp)
    {
        return NULL;
    }

    LLViewerObject* hit = NULL;

    if (lineSegmentBoundingBox(start, end))
    {
        LLVector4a local_end = end;
        LLVector4a local_intersection;
        if (mRootVolp->lineSegmentIntersect(start, local_end, face, pick_transparent, pick_rigged, pick_unselectable, face_hit, &local_intersection, tex_coord, normal, tangent))
        {
            local_end = local_intersection;
            if (intersection)
            {
                *intersection = local_intersection;
            }
            hit = mRootVolp;
        }
        else
        {
            std::vector<LLVOVolume*> volumes;
            getAnimatedVolumes(volumes);

            for (std::vector<LLVOVolume*>::iterator vol_it = volumes.begin(); vol_it != volumes.end(); ++vol_it)
            {
                LLVOVolume *volp = *vol_it;
                if (mRootVolp != volp && volp->lineSegmentIntersect(start, local_end, face, pick_transparent, pick_rigged, pick_unselectable, face_hit, &local_intersection, tex_coord, normal, tangent))
        {
            local_end = local_intersection;
            if (intersection)
            {
                *intersection = local_intersection;
            }
                    hit = volp;
                    break;
                }
            }
        }
    }

    return hit;
}

// virtual
std::string LLControlAvatar::getFullname() const
{
    if (mRootVolp)
    {
        return "AO_" + mRootVolp->getID().getString();
    }
    else
    {
        return "AO_no_root_vol";
    }
}

// virtual
bool LLControlAvatar::shouldRenderRigged() const
{
    const LLVOAvatar *attached_av = getAttachedAvatar();
    if (attached_av)
    {
        return attached_av->shouldRenderRigged();
    }
    return true;
}

// virtual
bool LLControlAvatar::isImpostor()
{
    // Attached animated objects should match state of their attached av.
    LLVOAvatar *attached_av = getAttachedAvatar();
    if (attached_av)
    {
        return attached_av->isImpostor();
    }
    return LLVOAvatar::isImpostor();
}

// static
void LLControlAvatar::onRegionChanged()
{
    for (LLCharacter* character : LLCharacter::sInstances)
    {
        if (LLControlAvatar* cav = dynamic_cast<LLControlAvatar*>(character))
        {
            cav->mRegionChanged = true;
        }
    }
}