// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

#include "Joint.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Core/Log.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/PhysicsBackend.h"
#include "Engine/Physics/PhysicsScene.h"
#include "Engine/Physics/Actors/IPhysicsActor.h"
#if USE_EDITOR
#include "Engine/Level/Scene/SceneRendering.h"
#endif

Joint::Joint(const SpawnParams& params)
    : Actor(params)
    , _joint(nullptr)
    , _breakForce(MAX_float)
    , _breakTorque(MAX_float)
    , _targetAnchor(Vector3::Zero)
    , _targetAnchorRotation(Quaternion::Identity)
{
    Target.Changed.Bind<Joint, &Joint::OnTargetChanged>(this);
}

void Joint::SetBreakForce(float value)
{
    if (Math::NearEqual(value, _breakForce))
        return;
    _breakForce = value;
    if (_joint)
        PhysicsBackend::SetJointBreakForce(_joint, _breakForce, _breakTorque);
}

void Joint::SetBreakTorque(float value)
{
    if (Math::NearEqual(value, _breakTorque))
        return;
    _breakTorque = value;
    if (_joint)
        PhysicsBackend::SetJointBreakForce(_joint, _breakForce, _breakTorque);
}

void Joint::SetEnableCollision(bool value)
{
    if (value == GetEnableCollision())
        return;
    _enableCollision = value;
    if (_joint)
        PhysicsBackend::SetJointFlags(_joint, _enableCollision ? PhysicsBackend::JointFlags::Collision : PhysicsBackend::JointFlags::None);
}

bool Joint::GetEnableAutoAnchor() const
{
    return _enableAutoAnchor;
}

void Joint::SetEnableAutoAnchor(bool value)
{
    _enableAutoAnchor = value;
}

void Joint::SetTargetAnchor(const Vector3& value)
{
    if (Vector3::NearEqual(value, _targetAnchor))
        return;
    _targetAnchor = value;
    if (_joint && !_enableAutoAnchor)
        PhysicsBackend::SetJointActorPose(_joint, _targetAnchor, _targetAnchorRotation, 1);
}

void Joint::SetTargetAnchorRotation(const Quaternion& value)
{
    if (Quaternion::NearEqual(value, _targetAnchorRotation))
        return;
    _targetAnchorRotation = value;
    if (_joint && !_enableAutoAnchor)
        PhysicsBackend::SetJointActorPose(_joint, _targetAnchor, _targetAnchorRotation, 1);
}

void* Joint::GetPhysicsImpl() const
{
    return _joint;
}

void Joint::SetJointLocation(const Vector3& location)
{
    if (GetParent())
    {
        SetLocalPosition(GetParent()->GetTransform().WorldToLocal(location));
    }
    if (Target)
    {
        SetTargetAnchor(Target->GetTransform().WorldToLocal(location));
    }
}

FORCE_INLINE Quaternion WorldToLocal(const Quaternion& world, const Quaternion& orientation)
{
    Quaternion rot;
    const Quaternion invRotation = world.Conjugated();
    Quaternion::Multiply(invRotation, orientation, rot);
    rot.Normalize();
    return rot;
}

void Joint::SetJointOrientation(const Quaternion& orientation)
{
    if (GetParent())
    {
        SetLocalOrientation(WorldToLocal(GetParent()->GetOrientation(), orientation));
    }
    if (Target)
    {
        SetTargetAnchorRotation(WorldToLocal(Target->GetOrientation(), orientation));
    }
}

void Joint::GetCurrentForce(Vector3& linear, Vector3& angular) const
{
    linear = angular = Vector3::Zero;
    if (_joint)
        PhysicsBackend::GetJointForce(_joint, linear, angular);
}

void Joint::Create()
{
    ASSERT(_joint == nullptr);

    auto parent = dynamic_cast<IPhysicsActor*>(GetParent());
    auto target = dynamic_cast<IPhysicsActor*>(Target.Get());
    if (parent == nullptr)
    {
        // Skip creation if joint is link to the not supported actor
        return;
    }

    // Create joint object
    PhysicsJointDesc desc;
    desc.Joint = this;
    desc.Actor0 = parent->GetPhysicsActor();
    desc.Actor1 = target ? target->GetPhysicsActor() : nullptr;
    desc.Pos0 = _localTransform.Translation;
    desc.Rot0 = _localTransform.Orientation;
    desc.Pos1 = _targetAnchor;
    desc.Rot1 = _targetAnchorRotation;
    if (_enableAutoAnchor && target)
    {
        // Place target anchor at the joint location
        desc.Pos1 = Target->GetTransform().WorldToLocal(GetPosition());
        desc.Rot1 = WorldToLocal(Target->GetOrientation(), GetOrientation());
    }
    _joint = CreateJoint(desc);

    // Setup joint properties
    PhysicsBackend::SetJointBreakForce(_joint, _breakForce, _breakTorque);
    PhysicsBackend::SetJointFlags(_joint, _enableCollision ? PhysicsBackend::JointFlags::Collision : PhysicsBackend::JointFlags::None);
}

void Joint::OnJointBreak()
{
    JointBreak();
}

void Joint::Delete()
{
    PhysicsBackend::RemoveJoint(this);
    PhysicsBackend::DestroyJoint(_joint);
    _joint = nullptr;
}

void Joint::SetActors()
{
    auto parent = dynamic_cast<IPhysicsActor*>(GetParent());
    auto target = dynamic_cast<IPhysicsActor*>(Target.Get());
    ASSERT(parent != nullptr);
    PhysicsBackend::SetJointActors(_joint, parent->GetPhysicsActor(), target ? target->GetPhysicsActor() : nullptr);
}

void Joint::OnTargetChanged()
{
    // Validate type
    const auto target = dynamic_cast<IPhysicsActor*>(Target.Get());
    if (Target && target == nullptr)
    {
        LOG(Error, "Invalid actor. Cannot use it as joint target. Rigidbodies and character controllers are supported. Object: {0}", Target.Get()->ToString());
        Target = nullptr;
    }
    else if (_joint)
    {
        SetActors();
    }
}

Vector3 Joint::GetTargetPosition() const
{
    Vector3 position = _targetAnchor;
    if (Target)
    {
        if (_enableAutoAnchor)
            position = Target->GetTransform().WorldToLocal(GetPosition());
        position = Target->GetOrientation() * position + Target->GetPosition();
    }
    return position;
}

Quaternion Joint::GetTargetOrientation() const
{
    Quaternion rotation = _targetAnchorRotation;
    if (Target)
    {
        if (_enableAutoAnchor)
            rotation = WorldToLocal(Target->GetOrientation(), GetOrientation());
        rotation = Target->GetOrientation() * rotation;
    }
    return rotation;
}

#if USE_EDITOR

#include "Engine/Debug/DebugDraw.h"
#include "Engine/Graphics/RenderView.h"

void Joint::DrawPhysicsDebug(RenderView& view)
{
    if (view.Mode == ViewMode::PhysicsColliders)
    {
        DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(GetPosition(), 3.0f), Color::BlueViolet * 0.8f, 0, true);
        DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(GetTargetPosition(), 4.0f), Color::AliceBlue * 0.8f, 0, true);
    }
}

void Joint::OnDebugDrawSelected()
{
    DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(GetPosition(), 3.0f), Color::BlueViolet * 0.8f, 0, false);
    DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(GetTargetPosition(), 4.0f), Color::AliceBlue * 0.8f, 0, false);

    // Base
    Actor::OnDebugDrawSelected();
}

#endif

void Joint::Serialize(SerializeStream& stream, const void* otherObj)
{
    // Base
    Actor::Serialize(stream, otherObj);

    SERIALIZE_GET_OTHER_OBJ(Joint);

    SERIALIZE(Target);
    SERIALIZE_MEMBER(BreakForce, _breakForce);
    SERIALIZE_MEMBER(BreakTorque, _breakTorque);
    SERIALIZE_MEMBER(TargetAnchor, _targetAnchor);
    SERIALIZE_MEMBER(TargetAnchorRotation, _targetAnchorRotation);
    SERIALIZE_MEMBER(EnableCollision, _enableCollision);
    SERIALIZE_MEMBER(EnableAutoAnchor, _enableAutoAnchor);
}

void Joint::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
    // Base
    Actor::Deserialize(stream, modifier);

    DESERIALIZE(Target);
    DESERIALIZE_MEMBER(BreakForce, _breakForce);
    DESERIALIZE_MEMBER(BreakTorque, _breakTorque);
    DESERIALIZE_MEMBER(TargetAnchor, _targetAnchor);
    DESERIALIZE_MEMBER(TargetAnchorRotation, _targetAnchorRotation);
    DESERIALIZE_MEMBER(EnableCollision, _enableCollision);
    DESERIALIZE_MEMBER(EnableAutoAnchor, _enableAutoAnchor);
}

void Joint::BeginPlay(SceneBeginData* data)
{
    // Base
    Actor::BeginPlay(data);

    // Create joint object only if it's enabled (otherwise it will be created in OnActiveInTreeChanged)
    if (IsActiveInHierarchy() && !_joint)
    {
        // Register for later init (joints are created after full beginPlay so all rigidbodies are created and can be linked)
        data->JointsToCreate.Add(this);
    }
}

void Joint::EndPlay()
{
    if (_joint)
    {
        Delete();
    }

    // Base
    Actor::EndPlay();
}

#if USE_EDITOR

void Joint::OnEnable()
{
    GetSceneRendering()->AddPhysicsDebug<Joint, &Joint::DrawPhysicsDebug>(this);

    // Base
    Actor::OnEnable();
}

void Joint::OnDisable()
{
    GetSceneRendering()->RemovePhysicsDebug<Joint, &Joint::DrawPhysicsDebug>(this);

    // Base
    Actor::OnDisable();
}

#endif

void Joint::OnActiveInTreeChanged()
{
    // Base
    Actor::OnActiveInTreeChanged();

    if (_joint)
    {
        // Enable/disable joint
        if (IsActiveInHierarchy())
            SetActors();
        else
            Delete();
    }
    // Joint object may not be created if actor is disabled on play mode begin (late init feature)
    else if (IsDuringPlay())
    {
        Create();
    }
}

void Joint::OnParentChanged()
{
    // Base
    Actor::OnParentChanged();

    if (!IsDuringPlay())
        return;

    // Check reparenting Joint case
    const auto parent = dynamic_cast<IPhysicsActor*>(GetParent());
    if (parent == nullptr)
    {
        if (_joint)
        {
            // Remove joint
            Delete();
        }
    }
    else if (_joint)
    {
        // Change target actor
        SetActors();
    }
    else
    {
        // Create joint
        Create();
    }
}

void Joint::OnTransformChanged()
{
    // Base
    Actor::OnTransformChanged();

    // TODO: this could track only local transform changed

    _box = BoundingBox(_transform.Translation);
    _sphere = BoundingSphere(_transform.Translation, 0.0f);
    if (_joint)
        PhysicsBackend::SetJointActorPose(_joint, _localTransform.Translation, _localTransform.Orientation, 0);
}
