/**
 * @file llviewerjointattachment.cpp
 * @brief Implementation of LLViewerJointAttachment class
 *
 * $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 "llviewerjointattachment.h"

#include "llviewercontrol.h"
#include "lldrawable.h"
#include "llgl.h"
#include "llhudtext.h"
#include "llrender.h"
#include "llvoavatarself.h"
#include "llvolume.h"
#include "pipeline.h"
#include "llspatialpartition.h"
#include "llinventorymodel.h"
#include "llviewerobjectlist.h"
#include "llface.h"
#include "llvoavatar.h"

#include "llglheaders.h"

extern LLPipeline gPipeline;
constexpr F32 MAX_ATTACHMENT_DIST = 3.5f; // meters

//-----------------------------------------------------------------------------
// LLViewerJointAttachment()
//-----------------------------------------------------------------------------
LLViewerJointAttachment::LLViewerJointAttachment() :
    mVisibleInFirst(false),
    mGroup(0),
    mIsHUDAttachment(false),
    mPieSlice(-1)
{
    mValid = false;
    mUpdateXform = false;
    mAttachedObjects.clear();
}

//-----------------------------------------------------------------------------
// ~LLViewerJointAttachment()
//-----------------------------------------------------------------------------
LLViewerJointAttachment::~LLViewerJointAttachment()
{
}

//-----------------------------------------------------------------------------
// isTransparent()
//-----------------------------------------------------------------------------
bool LLViewerJointAttachment::isTransparent()
{
    return false;
}

//-----------------------------------------------------------------------------
// drawShape()
//-----------------------------------------------------------------------------
U32 LLViewerJointAttachment::drawShape( F32 pixelArea, bool first_pass, bool is_dummy )
{
    if (LLVOAvatar::sShowAttachmentPoints)
    {
        LLGLDisable cull_face(GL_CULL_FACE);

        gGL.color4f(1.f, 1.f, 1.f, 1.f);
        gGL.begin(LLRender::TRIANGLES);
        {
            gGL.vertex3f(-0.1f, 0.1f, 0.f);
            gGL.vertex3f(-0.1f, -0.1f, 0.f);
            gGL.vertex3f(0.1f, -0.1f, 0.f);

            gGL.vertex3f(-0.1f, 0.1f, 0.f);
            gGL.vertex3f(0.1f, -0.1f, 0.f);
            gGL.vertex3f(0.1f, 0.1f, 0.f);
        }
        gGL.end();
    }
    return 0;
}

void LLViewerJointAttachment::setupDrawable(LLViewerObject *object)
{
    if (!object->mDrawable)
        return;
    if (object->mDrawable->isActive())
    {
        object->mDrawable->makeStatic(false);
    }

    object->mDrawable->mXform.setParent(getXform()); // LLViewerJointAttachment::lazyAttach
    object->mDrawable->makeActive();
    LLVector3 current_pos = object->getRenderPosition();
    LLQuaternion current_rot = object->getRenderRotation();
    LLQuaternion attachment_pt_inv_rot = ~(getWorldRotation());

    current_pos -= getWorldPosition();
    current_pos.rotVec(attachment_pt_inv_rot);

    current_rot = current_rot * attachment_pt_inv_rot;

    object->mDrawable->mXform.setPosition(current_pos);
    object->mDrawable->mXform.setRotation(current_rot);
    gPipeline.markMoved(object->mDrawable);
    gPipeline.markTextured(object->mDrawable); // face may need to change draw pool to/from POOL_HUD

    if(mIsHUDAttachment)
    {
        for (S32 face_num = 0; face_num < object->mDrawable->getNumFaces(); face_num++)
        {
            LLFace *face = object->mDrawable->getFace(face_num);
            if (face)
            {
                face->setState(LLFace::HUD_RENDER);
            }
        }
    }

    LLViewerObject::const_child_list_t& child_list = object->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);

            if(mIsHUDAttachment)
            {
                for (S32 face_num = 0; face_num < childp->mDrawable->getNumFaces(); face_num++)
                {
                    LLFace * face = childp->mDrawable->getFace(face_num);
                    if (face)
                    {
                        face->setState(LLFace::HUD_RENDER);
                    }
                }
            }
        }
    }
}

//-----------------------------------------------------------------------------
// addObject()
//-----------------------------------------------------------------------------
bool LLViewerJointAttachment::addObject(LLViewerObject* object)
{
    object->extractAttachmentItemID();

    // Same object reattached
    if (isObjectAttached(object))
    {
        LL_INFOS() << "(same object re-attached)" << LL_ENDL;
        removeObject(object);
        // Pass through anyway to let setupDrawable()
        // re-connect object to the joint correctly
    }

    // Two instances of the same inventory item attached --
    // Request detach, and kill the object in the meantime.
    if (getAttachedObject(object->getAttachmentItemID()))
    {
        LL_INFOS() << "(same object re-attached)" << LL_ENDL;
        object->markDead();

        // If this happens to be attached to self, then detach.
        LLVOAvatarSelf::detachAttachmentIntoInventory(object->getAttachmentItemID());
        return false;
    }

    mAttachedObjects.push_back(object);
    setupDrawable(object);

    if (mIsHUDAttachment)
    {
        if (object->mText.notNull())
        {
            object->mText->setOnHUDAttachment(true);
        }
        LLViewerObject::const_child_list_t& child_list = object->getChildren();
        for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
             iter != child_list.end(); ++iter)
        {
            LLViewerObject* childp = *iter;
            if (childp && childp->mText.notNull())
            {
                childp->mText->setOnHUDAttachment(true);
            }
        }
    }
    calcLOD();
    mUpdateXform = true;

    return true;
}

//-----------------------------------------------------------------------------
// removeObject()
//-----------------------------------------------------------------------------
void LLViewerJointAttachment::removeObject(LLViewerObject *object)
{
    attachedobjs_vec_t::iterator iter;
    for (iter = mAttachedObjects.begin();
         iter != mAttachedObjects.end();
         ++iter)
    {
        LLViewerObject *attached_object = iter->get();
        if (attached_object == object)
        {
            break;
        }
    }
    if (iter == mAttachedObjects.end())
    {
        LL_WARNS() << "Could not find object to detach" << LL_ENDL;
        return;
    }

    // force object visibile
    setAttachmentVisibility(true);

    mAttachedObjects.erase(iter);
    if (object->mDrawable.notNull())
    {
        //if object is active, make it static
        if(object->mDrawable->isActive())
        {
            object->mDrawable->makeStatic(false);
        }

        LLVector3 cur_position = object->getRenderPosition();
        LLQuaternion cur_rotation = object->getRenderRotation();

        object->mDrawable->mXform.setPosition(cur_position);
        object->mDrawable->mXform.setRotation(cur_rotation);
        gPipeline.markMoved(object->mDrawable, true);
        gPipeline.markTextured(object->mDrawable); // face may need to change draw pool to/from POOL_HUD

        if (mIsHUDAttachment)
        {
            for (S32 face_num = 0; face_num < object->mDrawable->getNumFaces(); face_num++)
            {
                LLFace * face = object->mDrawable->getFace(face_num);
                if (face)
                {
                    face->clearState(LLFace::HUD_RENDER);
                }
            }
        }
    }

    LLViewerObject::const_child_list_t& child_list = object->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
            if (mIsHUDAttachment)
            {
                for (S32 face_num = 0; face_num < childp->mDrawable->getNumFaces(); face_num++)
                {
                    LLFace * face = childp->mDrawable->getFace(face_num);
                    if (face)
                    {
                        face->clearState(LLFace::HUD_RENDER);
                    }
                }
            }
        }
    }

    if (mIsHUDAttachment)
    {
        if (object->mText.notNull())
        {
            object->mText->setOnHUDAttachment(false);
        }
        LLViewerObject::const_child_list_t& child_list = object->getChildren();
        for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
             iter != child_list.end(); ++iter)
        {
            LLViewerObject* childp = *iter;
            if (childp->mText.notNull())
            {
                childp->mText->setOnHUDAttachment(false);
            }
        }
    }
    if (mAttachedObjects.size() == 0)
    {
        mUpdateXform = false;
    }
    object->setAttachmentItemID(LLUUID::null);
}

//-----------------------------------------------------------------------------
// setAttachmentVisibility()
//-----------------------------------------------------------------------------
void LLViewerJointAttachment::setAttachmentVisibility(bool visible)
{
    for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
         iter != mAttachedObjects.end();
         ++iter)
    {
        LLViewerObject *attached_obj = iter->get();
        if (!attached_obj || attached_obj->mDrawable.isNull() ||
            !(attached_obj->mDrawable->getSpatialBridge()))
            continue;

        if (visible)
        {
            // Hack to make attachments not visible by disabling their type mask!
            // This will break if you can ever attach non-volumes! - djs 02/14/03
            attached_obj->mDrawable->getSpatialBridge()->mDrawableType =
                attached_obj->isHUDAttachment() ? LLPipeline::RENDER_TYPE_HUD : LLPipeline::RENDER_TYPE_VOLUME;
        }
        else
        {
            attached_obj->mDrawable->getSpatialBridge()->mDrawableType = 0;
        }
    }
}

//-----------------------------------------------------------------------------
// setOriginalPosition()
//-----------------------------------------------------------------------------
void LLViewerJointAttachment::setOriginalPosition(LLVector3& position)
{
    mOriginalPos = position;
    // SL-315
    setPosition(position);
}

//-----------------------------------------------------------------------------
// getNumAnimatedObjects()
//-----------------------------------------------------------------------------
S32 LLViewerJointAttachment::getNumAnimatedObjects() const
{
    S32 count = 0;
    for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
         iter != mAttachedObjects.end();
         ++iter)
    {
        const LLViewerObject *attached_object = iter->get();
        if (attached_object->isAnimatedObject())
        {
            count++;
        }
    }
    return count;
}

//-----------------------------------------------------------------------------
// clampObjectPosition()
//-----------------------------------------------------------------------------
void LLViewerJointAttachment::clampObjectPosition()
{
    for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
         iter != mAttachedObjects.end();
         ++iter)
    {
        if (LLViewerObject *attached_object = iter->get())
        {
            // *NOTE: object can drift when hitting maximum radius
            LLVector3 attachmentPos = attached_object->getPosition();
            F32 dist = attachmentPos.normVec();
            dist = llmin(dist, MAX_ATTACHMENT_DIST);
            attachmentPos *= dist;
            attached_object->setPosition(attachmentPos);
        }
    }
}

//-----------------------------------------------------------------------------
// calcLOD()
//-----------------------------------------------------------------------------
void LLViewerJointAttachment::calcLOD()
{
    F32 maxarea = 0;
    for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
         iter != mAttachedObjects.end();
         ++iter)
    {
        if (LLViewerObject *attached_object = iter->get())
        {
            maxarea = llmax(maxarea,attached_object->getMaxScale() * attached_object->getMidScale());
            LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
            for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
                 iter != child_list.end(); ++iter)
            {
                LLViewerObject* childp = *iter;
                F32 area = childp->getMaxScale() * childp->getMidScale();
                maxarea = llmax(maxarea, area);
            }
        }
    }
    maxarea = llclamp(maxarea, .01f*.01f, 1.f);
    F32 avatar_area = (4.f * 4.f); // pixels for an avatar sized attachment
    F32 min_pixel_area = avatar_area / maxarea;
    setLOD(min_pixel_area);
}

//-----------------------------------------------------------------------------
// updateLOD()
//-----------------------------------------------------------------------------
bool LLViewerJointAttachment::updateLOD(F32 pixel_area, bool activate)
{
    bool res{ false };
    if (!mValid)
    {
        setValid(true, true);
        res = true;
    }
    return res;
}

bool LLViewerJointAttachment::isObjectAttached(const LLViewerObject *viewer_object) const
{
    for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
         iter != mAttachedObjects.end();
         ++iter)
    {
        const LLViewerObject* attached_object = iter->get();
        if (attached_object == viewer_object)
        {
            return true;
        }
    }
    return false;
}

const LLViewerObject *LLViewerJointAttachment::getAttachedObject(const LLUUID &object_id) const
{
    for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
         iter != mAttachedObjects.end();
         ++iter)
    {
        const LLViewerObject* attached_object = iter->get();
        if (attached_object->getAttachmentItemID() == object_id)
        {
            return attached_object;
        }
    }
    return nullptr;
}

LLViewerObject *LLViewerJointAttachment::getAttachedObject(const LLUUID &object_id)
{
    for (attachedobjs_vec_t::iterator iter = mAttachedObjects.begin();
         iter != mAttachedObjects.end();
         ++iter)
    {
        LLViewerObject* attached_object = iter->get();
        if (attached_object->getAttachmentItemID() == object_id)
        {
            return attached_object;
        }
    }
    return nullptr;
}