/*     Copyright 2015-2019 Egor Yusov
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS.
 *
 *  In no event and under no legal theory, whether in tort (including negligence), 
 *  contract, or otherwise, unless required by applicable law (such as deliberate 
 *  and grossly negligent acts) or agreed to in writing, shall any Contributor be
 *  liable for any damages, including any direct, indirect, special, incidental, 
 *  or consequential damages of any character arising as a result of this License or 
 *  out of the use or inability to use the software (including but not limited to damages 
 *  for loss of goodwill, work stoppage, computer failure or malfunction, or any and 
 *  all other commercial damages or losses), even if such Contributor has been advised 
 *  of the possibility of such damages.
 */

#pragma once

/// \file
/// Declaration of Diligent::ShaderResourceLayoutD3D11 class

#include "ShaderResources.h"
#include "ShaderBase.h"
#include "ShaderResourceCacheD3D11.h"
#include "EngineD3D11Defines.h"
#include "STDAllocator.h"
#include "ShaderVariableD3DBase.h"
#include "ShaderResourcesD3D11.h"

namespace Diligent
{

class IMemoryAllocator;

/// Diligent::ShaderResourceLayoutD3D11 class
/// http://diligentgraphics.com/diligent-engine/architecture/d3d11/shader-resource-layout/
// sizeof(ShaderResourceLayoutD3D11) == 64 (x64)
class ShaderResourceLayoutD3D11
{
public:
    ShaderResourceLayoutD3D11(IObject& Owner,
                              std::shared_ptr<const ShaderResourcesD3D11> pSrcResources,
                              const PipelineResourceLayoutDesc&           ResourceLayout,
                              const SHADER_RESOURCE_VARIABLE_TYPE*        VarTypes, 
                              Uint32                                      NumVarTypes, 
                              ShaderResourceCacheD3D11&                   ResourceCache,
                              IMemoryAllocator&                           ResCacheDataAllocator,
                              IMemoryAllocator&                           ResLayoutDataAllocator);
    ~ShaderResourceLayoutD3D11();

    // No copies, only moves are allowed
    ShaderResourceLayoutD3D11             (const ShaderResourceLayoutD3D11&)  = delete;
    ShaderResourceLayoutD3D11& operator = (const ShaderResourceLayoutD3D11&)  = delete;
    ShaderResourceLayoutD3D11             (      ShaderResourceLayoutD3D11&&) = default;
    ShaderResourceLayoutD3D11& operator = (      ShaderResourceLayoutD3D11&&) = delete;

    static size_t GetRequiredMemorySize(const ShaderResourcesD3D11&          SrcResources, 
                                        const PipelineResourceLayoutDesc&    ResourceLayout,
                                        const SHADER_RESOURCE_VARIABLE_TYPE* AllowedVarTypes,
                                        Uint32                               NumAllowedTypes);

    void CopyResources(ShaderResourceCacheD3D11& DstCache)const;

    using ShaderVariableD3D11Base = ShaderVariableD3DBase<ShaderResourceLayoutD3D11>;

    struct ConstBuffBindInfo final : ShaderVariableD3D11Base
    {
        ConstBuffBindInfo( const D3DShaderResourceAttribs& ResourceAttribs,
                           ShaderResourceLayoutD3D11&      ParentResLayout,
                           SHADER_RESOURCE_VARIABLE_TYPE   VariableType) :
            ShaderVariableD3D11Base(ParentResLayout, ResourceAttribs, VariableType)
        {}
        // Non-virtual function
        __forceinline void BindResource(IDeviceObject* pObject, Uint32 ArrayIndex);
        virtual void Set(IDeviceObject* pObject)override final{ BindResource(pObject, 0); }

        virtual void SetArray(IDeviceObject* const* ppObjects, Uint32 FirstElement, Uint32 NumElements)override final
        {
            for(Uint32 elem=0; elem < NumElements; ++elem)
                BindResource(ppObjects[elem], FirstElement+elem);
        }

        virtual bool IsBound(Uint32 ArrayIndex)const override final
        {
            VERIFY_EXPR(ArrayIndex < m_Attribs.BindCount);
            return m_ParentResLayout.m_ResourceCache.IsCBBound(m_Attribs.BindPoint + ArrayIndex);
        }
    };
    
    struct TexSRVBindInfo final : ShaderVariableD3D11Base
    {
        TexSRVBindInfo( const D3DShaderResourceAttribs& _TextureAttribs, 
                        Uint32                          _SamplerIndex,
                        ShaderResourceLayoutD3D11&      ParentResLayout,
                        SHADER_RESOURCE_VARIABLE_TYPE   VariableType) :
            ShaderVariableD3D11Base(ParentResLayout, _TextureAttribs, VariableType),
            SamplerIndex(_SamplerIndex)
        {}

        // Non-virtual function
        __forceinline void BindResource(IDeviceObject* pObject, Uint32 ArrayIndex);
        virtual void Set(IDeviceObject* pObject)override final{ BindResource(pObject, 0); }

        virtual void SetArray(IDeviceObject* const* ppObjects, Uint32 FirstElement, Uint32 NumElements)override final
        {
            for(Uint32 elem=0; elem < NumElements; ++elem)
                BindResource(ppObjects[elem], FirstElement+elem);
        }

        virtual bool IsBound(Uint32 ArrayIndex)const override final
        {
            VERIFY_EXPR(ArrayIndex < m_Attribs.BindCount);
            return m_ParentResLayout.m_ResourceCache.IsSRVBound(m_Attribs.BindPoint + ArrayIndex, true);
        }

        
        bool ValidSamplerAssigned() const {return SamplerIndex != InvalidSamplerIndex;}

        static constexpr Uint32 InvalidSamplerIndex = static_cast<Uint32>(-1);
        const Uint32 SamplerIndex;
    };

    struct TexUAVBindInfo final : ShaderVariableD3D11Base
    {
        TexUAVBindInfo( const D3DShaderResourceAttribs& ResourceAttribs,
                        ShaderResourceLayoutD3D11&      ParentResLayout,
                        SHADER_RESOURCE_VARIABLE_TYPE   VariableType ) :
            ShaderVariableD3D11Base(ParentResLayout, ResourceAttribs, VariableType)
        {}

        // Provide non-virtual function
        __forceinline void BindResource(IDeviceObject* pObject, Uint32 ArrayIndex);
        virtual void Set(IDeviceObject* pObject)override final{ BindResource(pObject, 0); }

        virtual void SetArray(IDeviceObject* const* ppObjects, Uint32 FirstElement, Uint32 NumElements)override final
        {
            for(Uint32 elem=0; elem < NumElements; ++elem)
                BindResource(ppObjects[elem], FirstElement+elem);
        }

        __forceinline virtual bool IsBound(Uint32 ArrayIndex)const override final
        {
            VERIFY_EXPR(ArrayIndex < m_Attribs.BindCount);
            return m_ParentResLayout.m_ResourceCache.IsUAVBound(m_Attribs.BindPoint + ArrayIndex, true);
        }
    };

    struct BuffUAVBindInfo final : ShaderVariableD3D11Base
    {
        BuffUAVBindInfo( const D3DShaderResourceAttribs& ResourceAttribs,
                         ShaderResourceLayoutD3D11&      ParentResLayout,
                         SHADER_RESOURCE_VARIABLE_TYPE   VariableType ) :
            ShaderVariableD3D11Base(ParentResLayout, ResourceAttribs, VariableType)
        {}

        // Non-virtual function
        __forceinline void BindResource(IDeviceObject* pObject, Uint32 ArrayIndex);
        virtual void Set(IDeviceObject* pObject)override final{ BindResource(pObject, 0); }

        virtual void SetArray(IDeviceObject* const* ppObjects, Uint32 FirstElement, Uint32 NumElements)override final
        {
            for(Uint32 elem=0; elem < NumElements; ++elem)
                BindResource(ppObjects[elem], FirstElement+elem);
        }

        virtual bool IsBound(Uint32 ArrayIndex)const override final
        {
            VERIFY_EXPR(ArrayIndex < m_Attribs.BindCount);
            return m_ParentResLayout.m_ResourceCache.IsUAVBound(m_Attribs.BindPoint + ArrayIndex, false);
        }

    };

    struct BuffSRVBindInfo final : ShaderVariableD3D11Base
    {
        BuffSRVBindInfo( const D3DShaderResourceAttribs& ResourceAttribs,
                         ShaderResourceLayoutD3D11&      ParentResLayout,
                         SHADER_RESOURCE_VARIABLE_TYPE   VariableType ) :
            ShaderVariableD3D11Base(ParentResLayout, ResourceAttribs, VariableType)
        {}

        // Non-virtual function
        __forceinline void BindResource(IDeviceObject* pObject, Uint32 ArrayIndex);
        virtual void Set(IDeviceObject* pObject)override final{ BindResource(pObject, 0); }

        virtual void SetArray(IDeviceObject* const* ppObjects, Uint32 FirstElement, Uint32 NumElements)override final
        {
            for(Uint32 elem=0; elem < NumElements; ++elem)
                BindResource(ppObjects[elem], FirstElement+elem);
        }

        virtual bool IsBound(Uint32 ArrayIndex)const override final
        {
            VERIFY_EXPR(ArrayIndex < m_Attribs.BindCount);
            return m_ParentResLayout.m_ResourceCache.IsSRVBound(m_Attribs.BindPoint + ArrayIndex, false);
        }
    };

    struct SamplerBindInfo final : ShaderVariableD3D11Base
    {
        SamplerBindInfo( const D3DShaderResourceAttribs& ResourceAttribs,
                         ShaderResourceLayoutD3D11&      ParentResLayout,
                         SHADER_RESOURCE_VARIABLE_TYPE   VariableType) :
            ShaderVariableD3D11Base(ParentResLayout, ResourceAttribs, VariableType)
        {}

        // Non-virtual function
        __forceinline void BindResource(IDeviceObject* pObject, Uint32 ArrayIndex);
        virtual void Set(IDeviceObject* pObject)override final{ BindResource(pObject, 0); }

        virtual void SetArray(IDeviceObject* const* ppObjects, Uint32 FirstElement, Uint32 NumElements)override final
        {
            for(Uint32 elem=0; elem < NumElements; ++elem)
                BindResource(ppObjects[elem], FirstElement+elem);
        }

        virtual bool IsBound(Uint32 ArrayIndex)const override final
        {
            VERIFY_EXPR(ArrayIndex < m_Attribs.BindCount);
            return m_ParentResLayout.m_ResourceCache.IsSamplerBound(m_Attribs.BindPoint + ArrayIndex);
        }
    };

    // dbgResourceCache is only used for sanity check and as a remainder that the resource cache must be alive
    // while Layout is alive
    void BindResources( IResourceMapping* pResourceMapping, Uint32 Flags, const ShaderResourceCacheD3D11& dbgResourceCache );
    
#ifdef DEVELOPMENT
    bool dvpVerifyBindings()const;
#endif

    IShaderResourceVariable* GetShaderVariable( const Char* Name );
    IShaderResourceVariable* GetShaderVariable( Uint32 Index );
    __forceinline SHADER_TYPE GetShaderType()const{return m_pResources->GetShaderType();}

    IObject& GetOwner(){return m_Owner;}

    Uint32 GetVariableIndex(const ShaderVariableD3D11Base& Variable)const;
    Uint32 GetTotalResourceCount()const
    {
        auto ResourceCount = GetNumCBs() + GetNumTexSRVs() + GetNumTexUAVs() + GetNumBufUAVs() + GetNumBufSRVs();
        // Do not expose sampler variables when using combined texture samplers
        if (!m_pResources->IsUsingCombinedTextureSamplers())
            ResourceCount += GetNumSamplers();
        return ResourceCount;
    }

    Uint32 GetNumCBs()      const { return (m_TexSRVsOffset  - 0               ) / sizeof(ConstBuffBindInfo);}
    Uint32 GetNumTexSRVs()  const { return (m_TexUAVsOffset  - m_TexSRVsOffset ) / sizeof(TexSRVBindInfo);   }
    Uint32 GetNumTexUAVs()  const { return (m_BuffSRVsOffset - m_TexUAVsOffset ) / sizeof(TexUAVBindInfo) ;  }
    Uint32 GetNumBufSRVs()  const { return (m_BuffUAVsOffset - m_BuffSRVsOffset) / sizeof(BuffSRVBindInfo);  }
    Uint32 GetNumBufUAVs()  const { return (m_SamplerOffset  - m_BuffUAVsOffset) / sizeof(BuffUAVBindInfo);  }
    Uint32 GetNumSamplers() const { return (m_MemorySize     - m_SamplerOffset ) / sizeof(SamplerBindInfo);  }

    template<typename ResourceType> Uint32 GetNumResources()const;
    template<> Uint32 GetNumResources<ConstBuffBindInfo>() const { return GetNumCBs();      }
    template<> Uint32 GetNumResources<TexSRVBindInfo>   () const { return GetNumTexSRVs();  }
    template<> Uint32 GetNumResources<TexUAVBindInfo>   () const { return GetNumTexUAVs();  }
    template<> Uint32 GetNumResources<BuffSRVBindInfo>  () const { return GetNumBufSRVs();  }
    template<> Uint32 GetNumResources<BuffUAVBindInfo>  () const { return GetNumBufUAVs();  }
    template<> Uint32 GetNumResources<SamplerBindInfo>  () const { return GetNumSamplers(); }

    const Char* GetShaderName()const
    {
        return m_pResources->GetShaderName();
    }

private:

/* 0 */ IObject&                                       m_Owner;
/* 8 */ std::shared_ptr<const ShaderResourcesD3D11>    m_pResources;

       // No need to use shared pointer, as the resource cache is either part of the same
       // ShaderD3D11Impl object, or ShaderResourceBindingD3D11Impl object
/*24*/ ShaderResourceCacheD3D11&                      m_ResourceCache;

/*32*/ std::unique_ptr<void, STDDeleterRawMem<void> > m_ResourceBuffer;
    
       // Offsets in bytes
       using OffsetType = Uint16;
/*48*/ OffsetType m_TexSRVsOffset  = 0;
/*50*/ OffsetType m_TexUAVsOffset  = 0;
/*52*/ OffsetType m_BuffSRVsOffset = 0;
/*54*/ OffsetType m_BuffUAVsOffset = 0;
/*56*/ OffsetType m_SamplerOffset  = 0;
/*58*/ OffsetType m_MemorySize     = 0;
/*60 - 64*/    
/*64*/ // End of data


    template<typename ResourceType> OffsetType GetResourceOffset()const;
    template<> OffsetType GetResourceOffset<ConstBuffBindInfo>() const { return 0;                }
    template<> OffsetType GetResourceOffset<TexSRVBindInfo>   () const { return m_TexSRVsOffset;  }
    template<> OffsetType GetResourceOffset<TexUAVBindInfo>   () const { return m_TexUAVsOffset;  }
    template<> OffsetType GetResourceOffset<BuffSRVBindInfo>  () const { return m_BuffSRVsOffset; }
    template<> OffsetType GetResourceOffset<BuffUAVBindInfo>  () const { return m_BuffUAVsOffset; }
    template<> OffsetType GetResourceOffset<SamplerBindInfo>  () const { return m_SamplerOffset;  }

    template<typename ResourceType>
    ResourceType& GetResource(Uint32 ResIndex)
    {
        VERIFY(ResIndex < GetNumResources<ResourceType>(), "Resource index (", ResIndex, ") exceeds max allowed value (", GetNumResources<ResourceType>(), ")");
        auto Offset = GetResourceOffset<ResourceType>();
        return reinterpret_cast<ResourceType*>( reinterpret_cast<Uint8*>(m_ResourceBuffer.get()) + Offset)[ResIndex];
    }
    
    template<typename ResourceType>
    const ResourceType& GetConstResource(Uint32 ResIndex)const
    {
        VERIFY(ResIndex < GetNumResources<ResourceType>(), "Resource index (", ResIndex, ") exceeds max allowed value (", GetNumResources<ResourceType>(), ")");
        auto Offset = GetResourceOffset<ResourceType>();
        return reinterpret_cast<const ResourceType*>( reinterpret_cast<const Uint8*>(m_ResourceBuffer.get()) + Offset)[ResIndex];
    }
    
    template<typename ResourceType>
    IShaderResourceVariable* GetResourceByName( const Char* Name );

    template<typename THandleCB,
             typename THandleTexSRV,
             typename THandleTexUAV,
             typename THandleBufSRV,
             typename THandleBufUAV,
             typename THandleSampler>
    void HandleResources(THandleCB      HandleCB,
                         THandleTexSRV  HandleTexSRV,
                         THandleTexUAV  HandleTexUAV,
                         THandleBufSRV  HandleBufSRV,
                         THandleBufUAV  HandleBufUAV,
                         THandleSampler HandleSampler)
    {
        for (Uint32 cb = 0; cb < GetNumResources<ConstBuffBindInfo>(); ++cb)
            HandleCB(GetResource<ConstBuffBindInfo>(cb));

        for (Uint32 t = 0; t < GetNumResources<TexSRVBindInfo>(); ++t)
            HandleTexSRV(GetResource<TexSRVBindInfo>(t));

        for (Uint32 u = 0; u < GetNumResources<TexUAVBindInfo>(); ++u)
            HandleTexUAV(GetResource<TexUAVBindInfo>(u));

        for (Uint32 s = 0; s < GetNumResources<BuffSRVBindInfo>(); ++s)
            HandleBufSRV(GetResource<BuffSRVBindInfo>(s));

        for (Uint32 u = 0; u < GetNumResources<BuffUAVBindInfo>(); ++u)
            HandleBufUAV(GetResource<BuffUAVBindInfo>(u));

        for (Uint32 s = 0; s < GetNumResources<SamplerBindInfo>(); ++s)
            HandleSampler(GetResource<SamplerBindInfo>(s));
    }

    template<typename THandleCB,
             typename THandleTexSRV,
             typename THandleTexUAV,
             typename THandleBufSRV,
             typename THandleBufUAV,
             typename THandleSampler>
    void HandleConstResources(THandleCB      HandleCB,
                              THandleTexSRV  HandleTexSRV,
                              THandleTexUAV  HandleTexUAV,
                              THandleBufSRV  HandleBufSRV,
                              THandleBufUAV  HandleBufUAV,
                              THandleSampler HandleSampler)const
    {
        for (Uint32 cb = 0; cb < GetNumResources<ConstBuffBindInfo>(); ++cb)
            HandleCB(GetConstResource<ConstBuffBindInfo>(cb));

        for (Uint32 t = 0; t < GetNumResources<TexSRVBindInfo>(); ++t)
            HandleTexSRV(GetConstResource<TexSRVBindInfo>(t));

        for (Uint32 u = 0; u < GetNumResources<TexUAVBindInfo>(); ++u)
            HandleTexUAV(GetConstResource<TexUAVBindInfo>(u));

        for (Uint32 s = 0; s < GetNumResources<BuffSRVBindInfo>(); ++s)
            HandleBufSRV(GetConstResource<BuffSRVBindInfo>(s));

        for (Uint32 u = 0; u < GetNumResources<BuffUAVBindInfo>(); ++u)
            HandleBufUAV(GetConstResource<BuffUAVBindInfo>(u));

        for (Uint32 s = 0; s < GetNumResources<SamplerBindInfo>(); ++s)
            HandleSampler(GetConstResource<SamplerBindInfo>(s));
    }

    friend class ShaderVariableIndexLocator;
    friend class ShaderVariableLocator;
};

}
