/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.

#ifndef CRYINCLUDE_CRYENGINE_RENDERDLL_XRENDERD3D9_DEVICEMANAGER_TEMPDYNBUFFER_H
#define CRYINCLUDE_CRYENGINE_RENDERDLL_XRENDERD3D9_DEVICEMANAGER_TEMPDYNBUFFER_H
#pragma once
#include "../Common/DevBuffer.h"

namespace TempDynBuffer
{
    enum State
    {
        Default,
        Allocated,
        Locked,
        Filled
    };

    struct ValidatorNull
    {
        void Check(State) {}
        void Check2(State, State) {}
        void Set(State) {}
    };

    struct ValidatorDbgBreak
    {
        ValidatorDbgBreak()
            : m_state(Default) {}

        void Check(State expectedState)
        {
            if (m_state != expectedState)
            {
                __debugbreak();
            }
        }

        void Check2(State expectedState0, State expectedState1)
        {
            if (m_state != expectedState0 && m_state != expectedState1)
            {
                __debugbreak();
            }
        }

        void Set(State newState)
        {
            m_state = newState;
        }

        State m_state;
    };

#if !defined(_RELEASE)
    typedef ValidatorDbgBreak ValidatorDefault;
#else
    typedef ValidatorNull ValidatorDefault;
#endif

    //////////////////////////////////////////////////////////////////////////
    template<typename T, BUFFER_BIND_TYPE bindType, class Validator>
    class TempDynBufferBase
    {
    public:
        static const buffer_handle_t invalidHandle = ~0u;

    protected:
        TempDynBufferBase(IRenderer* renderer)
            : m_handle(invalidHandle)
            , m_renderer(renderer)
            , m_numElements(0)
            , m_DevBufMan(renderer->GetDeviceBufferManager())
            , m_validator() {}
        ~TempDynBufferBase() { m_validator.Check(Default); }

        void Allocate(size_t numElements) { AllocateInternal(numElements, sizeof(T)); }
        bool IsAllocated() const { return m_handle != invalidHandle; }
        void Release();

        void Update(const T* pData) { UpdateInternal(pData, sizeof(T)); }
        T* Lock();
        void Unlock();

    protected:
        void AllocateInternal(size_t numElements, size_t elementSize);
        void UpdateInternal(const void* pData, size_t elementSize);

    protected:
        IRenderer* m_renderer;
        size_t m_handle;
        size_t m_numElements;
        CGuardedDeviceBufferManager m_DevBufMan;
        Validator m_validator;
    };

    template<typename T, BUFFER_BIND_TYPE bindType, class Validator>
    void TempDynBufferBase<T, bindType, Validator>::AllocateInternal(size_t numElements, size_t elementSize)
    {
        m_validator.Check(Default);
        BUFFER_USAGE usageType = m_renderer->IsVideoThreadModeEnabled()
            ? BU_WHEN_LOADINGTHREAD_ACTIVE // use a separate pool for everything in video rendering mode
            : BU_TRANSIENT_RT; // default to transient_RT
        m_handle = m_DevBufMan.Create(bindType, usageType, numElements * elementSize);
        IF (m_handle != invalidHandle, 1)
        {
            m_numElements = numElements;
            m_validator.Set(Allocated);
        }
    }

    template<typename T, BUFFER_BIND_TYPE bindType, class Validator>
    void TempDynBufferBase<T, bindType, Validator>::Release()
    {
        IF (m_handle != invalidHandle, 1)
        {
            m_validator.Check2(Allocated, Filled);
            m_DevBufMan.Destroy(m_handle);
            m_handle = invalidHandle;
            m_numElements = 0;
        }
        m_validator.Set(Default);
    }

    template<typename T, BUFFER_BIND_TYPE bindType, class Validator>
    void TempDynBufferBase<T, bindType, Validator>::UpdateInternal(const void* pData, size_t elementSize)
    {
        m_validator.Check(Allocated);
        m_DevBufMan.UpdateBuffer(m_handle, pData, m_numElements * elementSize);
        m_validator.Set(Filled);
    }

    template<typename T, BUFFER_BIND_TYPE bindType, class Validator>
    T* TempDynBufferBase<T, bindType, Validator>::Lock()
    {
        m_validator.Check(Allocated);
        T* p = (T*) m_DevBufMan.BeginWrite(m_handle);
        m_validator.Set(Locked);
        return p;
    }

    template<typename T, BUFFER_BIND_TYPE bindType, class Validator>
    void TempDynBufferBase<T, bindType, Validator>::Unlock()
    {
        m_validator.Check(Locked);
        m_DevBufMan.EndReadWrite(m_handle);
        m_validator.Set(Filled);
    }

    //////////////////////////////////////////////////////////////////////////
    template<typename T, class Validator = ValidatorDefault>
    class TempDynVBBase
        : public TempDynBufferBase<T, BBT_VERTEX_BUFFER, Validator>
    {
    public:
        typedef TempDynBufferBase<T, BBT_VERTEX_BUFFER, Validator> Base;

    public:
        using Base::IsAllocated;
        using Base::Release;

        using Base::Update;
        using Base::Lock;
        using Base::Unlock;

    protected:
        TempDynVBBase(IRenderer* renderer)
            : TempDynBufferBase<T, BBT_VERTEX_BUFFER, Validator>(renderer),
            m_renderer(renderer){}
        ~TempDynVBBase() {}

        void BindInternal(uint32 streamID, size_t stride);

    protected:
        IRenderer* m_renderer = nullptr;
        using Base::m_handle;
        using Base::m_validator;
    };

    template<typename T, class Validator>
    void TempDynVBBase<T, Validator>::BindInternal(uint32 streamID, size_t stride)
    {
        m_validator.Check(Filled);
        size_t bufferOffset = ~0;
        D3DBuffer* pVB = this->m_DevBufMan.GetD3D(m_handle, &bufferOffset);
        m_renderer->FX_SetVStream(streamID, pVB, bufferOffset, stride);
    }

    //////////////////////////////////////////////////////////////////////////
    template<typename T, class Validator = ValidatorDefault>
    class TempDynVB
        : public TempDynVBBase<T, Validator>
    {
        friend class TempDynVBAny;

    public:
        typedef TempDynVBBase<T, Validator> Base;

    public:
        static void CreateFillAndBind(const T* pData, size_t numElements, uint32 streamID) { CreateFillAndBindInternal(pData, numElements, streamID, sizeof(T)); }

    public:
        TempDynVB(IRenderer* renderer)
            : TempDynVBBase<T, Validator>(renderer)
            , m_renderer(renderer){}

        using Base::Allocate;
        void Bind(uint32 streamID) { BindInternal(streamID, sizeof(T)); }

    protected:
        static void CreateFillAndBindInternal(const void* pData, size_t numElements, uint32 streamID, size_t stride);

    protected:
        IRenderer* m_renderer = nullptr;
        using Base::BindInternal;

    protected:
        TempDynVB(const TempDynVB&);
        TempDynVB& operator=(const TempDynVB&);
    };

    template<typename T, class Validator>
    void TempDynVB<T, Validator>::CreateFillAndBindInternal(const void* pData, size_t numElements, uint32 streamID, size_t stride)
    {
        TempDynVB<T, Validator> vb(gRenDev);
        vb.AllocateInternal(numElements, stride);
        vb.UpdateInternal(pData, stride);
        vb.BindInternal(streamID, stride);
        vb.Release();
    }

    //////////////////////////////////////////////////////////////////////////
    template<class Validator = ValidatorDefault>
    class TempDynInstVB
        : public TempDynVBBase<void, Validator>
    {
    public:
        typedef TempDynVBBase<void, Validator> Base;

    public:
        TempDynInstVB(IRenderer* renderer)
            : TempDynVBBase<void, Validator>(renderer),
            m_renderer(renderer)
        {}

        void Allocate(size_t numElements, size_t elementSize) { AllocateInternal(numElements, elementSize); }
        void Bind(uint32 streamID, uint32 stride) { BindInternal(streamID, stride); }

    protected:
        using Base::AllocateInternal;
        using Base::BindInternal;

    protected:
        IRenderer* m_renderer = nullptr;

        TempDynInstVB(const TempDynInstVB&);
        TempDynInstVB& operator=(const TempDynInstVB&);
    };

    //////////////////////////////////////////////////////////////////////////
    class TempDynVBAny
    {
    public:
        static void CreateFillAndBind(const void* pData, size_t numElements, uint32 streamID, size_t stride) { TempDynVB<void>::CreateFillAndBindInternal(pData, numElements, streamID, stride); }

    private:
        IRenderer* m_renderer = nullptr;
        TempDynVBAny(IRenderer* renderer);
    };

    //////////////////////////////////////////////////////////////////////////
    template <RenderIndexType idxType>
    struct MapIndexType {};

    template <>
    struct MapIndexType<Index16>
    {
        typedef uint16 Type;
        enum
        {
            Size = sizeof(Type)
        };
    };

    template <>
    struct MapIndexType<Index32>
    {
        typedef uint32 Type;
        enum
        {
            Size = sizeof(Type)
        };
    };

    template<RenderIndexType idxType, class Validator = ValidatorDefault>
    class TempDynIB
        : public TempDynBufferBase<typename MapIndexType<idxType>::Type, BBT_INDEX_BUFFER, Validator>
    {
    public:
        typedef typename MapIndexType<idxType>::Type IndexDataType;
        typedef TempDynBufferBase<IndexDataType, BBT_INDEX_BUFFER, Validator> Base;

    public:
        static void CreateFillAndBind(const IndexDataType* pData, size_t numElements);

    public:
        TempDynIB(IRenderer* renderer)
            : TempDynBufferBase<IndexDataType, BBT_INDEX_BUFFER, Validator>(renderer),
            m_renderer(renderer)
        {}

        void Allocate(size_t numElements);
        using Base::IsAllocated;
        using Base::Release;

        using Base::Update;
        using Base::Lock;
        using Base::Unlock;

        void Bind();

    protected:
        IRenderer* m_renderer = nullptr;

        TempDynIB(const TempDynIB&);
        TempDynIB& operator=(const TempDynIB&);

    protected:
        using Base::m_handle;
        using Base::m_validator;
    };

    template<RenderIndexType idxType, class Validator>
    void TempDynIB<idxType, Validator>::Allocate(size_t numElements)
    {
        Base::Allocate(numElements);
    }

    template<RenderIndexType idxType, class Validator>
    void TempDynIB<idxType, Validator>::Bind()
    {
        m_validator.Check(Filled);
        size_t bufferOffset = ~0;
        D3DBuffer* pIB = this->m_DevBufMan.GetD3D(m_handle, &bufferOffset);
        m_renderer->FX_SetIStream(pIB, bufferOffset, idxType);
    }

    template<RenderIndexType idxType, class Validator>
    void TempDynIB<idxType, Validator>::CreateFillAndBind(const IndexDataType* pData, size_t numElements)
    {
        TempDynIB<idxType, Validator> ib(gcpRendD3D);
        ib.Allocate(numElements);
        ib.Update(pData);
        ib.Bind();
        ib.Release();
    }
} // namespace TempDynBuffer

using TempDynBuffer::TempDynVB;
using TempDynBuffer::TempDynVBAny;
typedef TempDynBuffer::TempDynInstVB<> TempDynInstVB;

typedef TempDynBuffer::TempDynIB<Index16> TempDynIB16;
typedef TempDynBuffer::TempDynIB<Index32> TempDynIB32;

#endif // CRYINCLUDE_CRYENGINE_RENDERDLL_XRENDERD3D9_DEVICEMANAGER_TEMPDYNBUFFER_H