/*
* 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/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/smart_ptr/intrusive_ptr.h>

#include <MCore/Source/Vector.h>
#include <MCore/Source/MemoryObject.h>

#include <EMotionFX/Source/Transform.h>

namespace AZStd
{
    /**
     * Intrusive ptr for EMotionFX-owned objects (uses EMotionFX's internal ref-counting MCore::Destroy()).
     */
    template<>
    struct IntrusivePtrCountPolicy<MCore::MemoryObject>
    {
        static AZ_FORCE_INLINE void add_ref(MCore::MemoryObject* ptr) 
        { 
            ptr->IncreaseReferenceCount();
        }
        static AZ_FORCE_INLINE void release(MCore::MemoryObject* ptr) 
        { 
            MCore::Destroy(ptr); // Calls DecreaseReferenceCount.
        }
    };
}

namespace EMotionFX
{
    namespace Integration
    {
        /**
         * System allocator to be used for all EMotionFX and EMotionFXAnimation gem persistent allocations.
         */
        class EMotionFXAllocator
            : public AZ::SimpleSchemaAllocator<AZ::ChildAllocatorSchema<AZ::SystemAllocator>>
        {
        public:
            AZ_TYPE_INFO(EMotionFXAllocator, "{00AEC34F-4A00-4ECB-BC9C-7221E76337D6}");
            using Base = AZ::SimpleSchemaAllocator<AZ::ChildAllocatorSchema<AZ::SystemAllocator>>;
            using Descriptor = Base::Descriptor;

            EMotionFXAllocator() : Base("EMotion FX System Allocator", "EMotion FX general memory allocator") 
            {
            }
        };

        /**
         * Intrusive ptr for EMotionFX-owned objects.
         * Uses EMotionFX's internal ref-counting.
         */
        template <typename ObjectType>
        class EMotionFXPtr
        {
        public:

            /// Use only to initialize a new EMotionFXPtr<> given an EMotionFX SDK object not currently owned by an EMotionFXPtr<>.
            /// This is generally only appropriate for use when an EMotionFX object has just been constructed.
            static EMotionFXPtr<ObjectType> MakeFromNew(ObjectType* object)
            {
                AZ_Assert(object, "CreateFromNew called with invalid object.");
                AZ_Assert(object && object->GetReferenceCount() == 1, "Newly constructed EMotion FX objects are expected to have a referene count initialized to 1.");

                // EMotionFX initializes objects with a ref count already at 1. So for newly-constructed objects that we're
                // managing through smart pointers, it's not necessary to increment ref count during initial acquisition.
                EMotionFXPtr<ObjectType> ptr;
                ptr.m_ptr = object;
                return ptr;
            }

            AZ_FORCE_INLINE explicit EMotionFXPtr(ObjectType* object = nullptr)
            {
                *this = object;
            }

            AZ_FORCE_INLINE EMotionFXPtr(const EMotionFXPtr<ObjectType>& rhs)
            {
                *this = rhs;
            }

            AZ_FORCE_INLINE ~EMotionFXPtr()
            {
                if (m_ptr)
                {
                    MCore::Destroy(m_ptr); // Calls DecreaseReferenceCount.
                }
            }

            AZ_FORCE_INLINE void reset(ObjectType* object = nullptr)
            {
                *this = object;
            }

            AZ_FORCE_INLINE void operator=(ObjectType* object)
            {
                if (m_ptr)
                {
                    MCore::Destroy(m_ptr); // Calls DecreaseReferenceCount.
                    m_ptr = nullptr;
                }

                m_ptr = object;

                if (m_ptr)
                {
                    m_ptr->IncreaseReferenceCount();
                }
            }

            AZ_FORCE_INLINE void operator=(const EMotionFXPtr<ObjectType>& rhs)
            {
                reset(rhs ? rhs.get() : nullptr);
            }

            AZ_FORCE_INLINE ObjectType* operator ->() const
            {
                AZ_Assert(m_ptr, "Attempting to dereference a null EMotion FX object pointer.");
                return m_ptr;
            }

            AZ_FORCE_INLINE ObjectType* get() const
            {
                return m_ptr;
            }

            AZ_FORCE_INLINE operator bool() const
            {
                return m_ptr != nullptr;
            }

            AZ_FORCE_INLINE bool operator==(const EMotionFXPtr<ObjectType>& rhs) const
            {
                return (m_ptr == rhs.m_ptr);
            }

            AZ_FORCE_INLINE bool operator==(const ObjectType* rhs) const
            {
                return (m_ptr == rhs);
            }

            AZ_FORCE_INLINE bool operator!=(const EMotionFXPtr<ObjectType>& rhs) const
            {
                return !(m_ptr == rhs.m_ptr);
            }

            AZ_FORCE_INLINE bool operator!=(const ObjectType* rhs) const
            {
                return !(m_ptr == rhs);
            }

        private:

            ObjectType* m_ptr = nullptr;
        };

        /**
         * EMotionFX memory hooks
         */
        AZ_FORCE_INLINE void* EMotionFXAlloc(size_t numBytes, AZ::u16 categoryID, AZ::u16 blockID, const char* filename, AZ::u32 lineNr)
        {
            (void)categoryID;
            (void)blockID;
            (void)lineNr;
            return AZ::AllocatorInstance<EMotionFXAllocator>::Get().Allocate(numBytes, 8, 0, "EMotionFX", filename, lineNr);
        }

        AZ_FORCE_INLINE void* EMotionFXRealloc(void* memory, size_t numBytes, AZ::u16 categoryID, AZ::u16 blockID, const char* filename, AZ::u32 lineNr)
        {
            (void)categoryID;
            (void)blockID;
            (void)filename;
            (void)lineNr;
            return AZ::AllocatorInstance<EMotionFXAllocator>::Get().ReAllocate(memory, numBytes, 8);
        }

        AZ_FORCE_INLINE void EMotionFXFree(void* memory)
        {
            AZ::AllocatorInstance<EMotionFXAllocator>::Get().DeAllocate(memory);
        }

    } //namespace Integration
} // namespace EMotionFX