/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/

#pragma once

#include <AzCore/Math/Spline.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <PhysX/ForceRegionComponentBus.h>

namespace PhysX
{
    /// Parameters of an entity in the force region.
    /// Used to calculate final force.
    struct EntityParams
    {
        AZ::EntityId m_id;
        AZ::Vector3 m_position;
        AZ::Vector3 m_velocity;
        AZ::Aabb m_aabb;
        float m_mass;
    };

    /// Parameters of the force region.
    /// Used to calculate final force.
    struct RegionParams
    {
        AZ::EntityId m_id;
        AZ::Vector3 m_position;
        AZ::Quaternion m_rotation;
        AZ::Vector3 m_scale;
        AZ::SplinePtr m_spline;
        AZ::Aabb m_aabb;
    };

    /// Requests serviced by all forces used by force regions.
    class BaseForce
    {
    public:
        AZ_CLASS_ALLOCATOR(BaseForce, AZ::SystemAllocator, 0);
        AZ_RTTI(BaseForce, "{0D1DFFE1-16C1-425B-972B-DC70FDC61B56}");
        static void Reflect(AZ::SerializeContext& context);

        virtual ~BaseForce() = default;

        /// Connect to any buses.
        virtual void Activate(AZ::EntityId entityId)
        {
            m_entityId = entityId;
        }

        /// Disconnect from any buses.
        virtual void Deactivate()
        {
            m_entityId.SetInvalid();
        }

        /// Calculate the size and direction the force.
        virtual AZ::Vector3 CalculateForce(const EntityParams& entityParams
            , const RegionParams& volumeParams) const = 0;

    protected:
        void NotifyChanged();

        AZ::EntityId m_entityId;
    };

    /// Class for a world space force exerted on bodies in a force region.
    class ForceWorldSpace final
        : public BaseForce
        , private ForceWorldSpaceRequestBus::Handler
    {
    public:
        AZ_CLASS_ALLOCATOR(ForceWorldSpace, AZ::SystemAllocator, 0);
        AZ_RTTI(ForceWorldSpace, "{A6C17DD3-7A09-4BC7-8ACC-C0BD04EA8F7C}", BaseForce);
        ForceWorldSpace() = default;
        ForceWorldSpace(const AZ::Vector3& direction, float magnitude);
        static void Reflect(AZ::ReflectContext* context);
        AZ::Vector3 CalculateForce(const EntityParams& entity, const RegionParams& region) const override;

        // BaseForce
        void Activate(AZ::EntityId entityId) override 
        { 
            BaseForce::Activate(entityId);
            ForceWorldSpaceRequestBus::Handler::BusConnect(entityId);
        }
        void Deactivate() override 
        { 
            ForceWorldSpaceRequestBus::Handler::BusDisconnect();
            BaseForce::Deactivate();
        }

    private:
        // ForceWorldSpaceRequestBus
        void SetDirection(const AZ::Vector3& direction) override;
        AZ::Vector3 GetDirection() const override;
        void SetMagnitude(float magnitude) override;
        float GetMagnitude() const override;

        AZ::Vector3 m_direction = AZ::Vector3::CreateAxisZ();
        float m_magnitude = 10.f;
    };

    /// Class for a local space force exerted on bodies in a force region.
    class ForceLocalSpace final
        : public BaseForce
        , private ForceLocalSpaceRequestBus::Handler
    {
    public:
        AZ_CLASS_ALLOCATOR(ForceLocalSpace, AZ::SystemAllocator, 0);
        AZ_RTTI(ForceLocalSpace, "{F0EAFB7C-1BC7-4497-99AE-ECBF7169AB81}", BaseForce);
        ForceLocalSpace() = default;
        ForceLocalSpace(const AZ::Vector3& direction, float magnitude);
        static void Reflect(AZ::ReflectContext* context);
        AZ::Vector3 CalculateForce(const EntityParams& entity, const RegionParams& region) const override;

        // BaseForce
        void Activate(AZ::EntityId entityId) override
        {
            BaseForce::Activate(entityId);
            ForceLocalSpaceRequestBus::Handler::BusConnect(entityId);
        }
        void Deactivate() override
        {
            ForceLocalSpaceRequestBus::Handler::BusDisconnect();
            BaseForce::Deactivate();
        }

    private:
        // ForceLocalSpaceRequestBus
        void SetDirection(const AZ::Vector3& direction) override;
        AZ::Vector3 GetDirection() const override;
        void SetMagnitude(float magnitude) override;
        float GetMagnitude() const override;

        AZ::Vector3 m_direction = AZ::Vector3::CreateAxisZ();
        float m_magnitude = 10.0f;
    };

    /// Class for a point force exerted on bodies in a force region. 
    /// Bodies in a force region with a point force are repelled away from the center of the force region.
    class ForcePoint final
        : public BaseForce
        , private ForcePointRequestBus::Handler
    {
    public:
        AZ_CLASS_ALLOCATOR(ForcePoint, AZ::SystemAllocator, 0);
        AZ_RTTI(ForcePoint, "{3F8ABEAC-6972-4845-A131-EA9831029E68}", BaseForce);
        ForcePoint() = default;
        explicit ForcePoint(float magnitude);
        static void Reflect(AZ::ReflectContext* context);
        AZ::Vector3 CalculateForce(const EntityParams& entity, const RegionParams& region) const override;

        // BaseForce
        void Activate(AZ::EntityId entityId) override
        {
            BaseForce::Activate(entityId);
            ForcePointRequestBus::Handler::BusConnect(entityId);
        }
        void Deactivate() override
        {
            ForcePointRequestBus::Handler::BusDisconnect();
            BaseForce::Deactivate();
        }

    private:
        // ForcePointRequestBus
        void SetMagnitude(float magnitude) override;
        float GetMagnitude() const override;

        float m_magnitude = 1.0f;
    };

    /// Class for a spline follow force.
    /// Bodies in a force region with a spline follow force tend to follow the path of the spline.
    class ForceSplineFollow final
        : public BaseForce
        , private ForceSplineFollowRequestBus::Handler
    {
    public:
        AZ_CLASS_ALLOCATOR(ForceSplineFollow, AZ::SystemAllocator, 0);
        AZ_RTTI(ForceSplineFollow, "{AB397D4C-62DA-43F0-8CF1-9BD9013129BB}", BaseForce);
        ForceSplineFollow() = default;
        ForceSplineFollow(float dampingRatio
            , float frequency
            , float targetSpeed
            , float lookAhead);
        static void Reflect(AZ::ReflectContext* context);
        AZ::Vector3 CalculateForce(const EntityParams& entity, const RegionParams& region) const override;

        // BaseForce
        void Activate(AZ::EntityId entityId) override;
        void Deactivate() override
        {
            ForceSplineFollowRequestBus::Handler::BusDisconnect();
            BaseForce::Deactivate();
        }

    private:
        // ForceSplineFollowRequestBus
        void SetDampingRatio(float ratio) override;
        float GetDampingRatio() const override;
        void SetFrequency(float frequency) override;
        float GetFrequency() const override;
        void SetTargetSpeed(float targetSpeed) override;
        float GetTargetSpeed() const override;
        void SetLookAhead(float lookAhead) override;
        float GetLookAhead() const override;

        float m_dampingRatio = 1.0f;
        float m_frequency = 3.0f;
        float m_targetSpeed = 1.0f;
        float m_lookAhead = 0.0f;
        bool m_loggedMissingSplineWarning = false;
    };

    /// Class for a simple drag force.
    class ForceSimpleDrag final
        : public BaseForce
        , private ForceSimpleDragRequestBus::Handler
    {
    public:
        AZ_CLASS_ALLOCATOR(ForceSimpleDrag, AZ::SystemAllocator, 0);
        AZ_RTTI(ForceSimpleDrag, "{56A4E393-4724-4486-B4C0-E02C4EF1534C}", BaseForce);
        ForceSimpleDrag() = default;
        ForceSimpleDrag(float dragCoefficient, float volumeDensity);
        static void Reflect(AZ::ReflectContext* context);
        AZ::Vector3 CalculateForce(const EntityParams& entity, const RegionParams& region) const override;

        // BaseForce
        void Activate(AZ::EntityId entityId) override
        {
            BaseForce::Activate(entityId);
            ForceSimpleDragRequestBus::Handler::BusConnect(entityId);
        }
        void Deactivate() override
        {
            ForceSimpleDragRequestBus::Handler::BusDisconnect();
            BaseForce::Deactivate();
        }

    private:
        // ForceSimpleDragRequests
        void SetDensity(float density) override;
        float GetDensity() const override;

        //Wikipedia: https://en.wikipedia.org/wiki/Drag_coefficient
        float m_dragCoefficient = 0.47f;
        float m_volumeDensity = 1.0f;
    };

    /// Class for a linear damping force.
    class ForceLinearDamping final
        : public BaseForce
        , private ForceLinearDampingRequestBus::Handler
    {
    public:
        AZ_CLASS_ALLOCATOR(ForceLinearDamping, AZ::SystemAllocator, 0);
        AZ_RTTI(ForceLinearDamping, "{7EECFBD7-0942-4960-A54A-7582159CFFA3}", BaseForce);
        ForceLinearDamping() = default;
        explicit ForceLinearDamping(float damping);
        static void Reflect(AZ::ReflectContext* context);
        AZ::Vector3 CalculateForce(const EntityParams& entity, const RegionParams& region) const override;

        // BaseForce
        void Activate(AZ::EntityId entityId) override 
        { 
            BaseForce::Activate(entityId);
            ForceLinearDampingRequestBus::Handler::BusConnect(entityId);
        }
        void Deactivate() override 
        {
            ForceLinearDampingRequestBus::Handler::BusDisconnect();
            BaseForce::Deactivate();
        }

    private:
        // ForceLinearDampingRequests
        void SetDamping(float damping) override;
        float GetDamping() const override;

        float m_damping = 1.0f;
    };
}