/*     Copyright 2015-2018 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

#include <mutex>
#include <map>
#include <deque>
#include <atomic>

namespace Diligent
{

class RenderDeviceD3D12Impl;

struct D3D12DynamicAllocation
{
    D3D12DynamicAllocation()noexcept{}
    D3D12DynamicAllocation(ID3D12Resource*           pBuff, 
                           Uint64                    _Offset, 
                           Uint64                    _Size,
                           void*                     _CPUAddress,
                           D3D12_GPU_VIRTUAL_ADDRESS _GPUAddress
#ifdef DEVELOPMENT
                         , Uint64                    _DvpCtxFrameNumber
#endif
    )noexcept :
        pBuffer          (pBuff), 
        Offset           (_Offset),
        Size             (_Size),
        CPUAddress       (_CPUAddress),
        GPUAddress       (_GPUAddress)
#ifdef DEVELOPMENT
      , DvpCtxFrameNumber(_DvpCtxFrameNumber)
#endif
    {}

    ID3D12Resource*           pBuffer    = nullptr;	// The D3D buffer associated with this memory.
    Uint64                    Offset     = 0;			// Offset from start of buffer resource
    Uint64                    Size       = 0;			// Reserved size of this allocation
    void*                     CPUAddress = nullptr;   // The CPU-writeable address
    D3D12_GPU_VIRTUAL_ADDRESS GPUAddress = 0;	// The GPU-visible address
#ifdef DEVELOPMENT
    Uint64 DvpCtxFrameNumber = static_cast<Uint64>(-1);
#endif
};

    
class D3D12DynamicPage
{
public:
    D3D12DynamicPage(ID3D12Device* pd3d12Device, Uint64 Size);
    
    D3D12DynamicPage            (const D3D12DynamicPage&)  = delete;
    D3D12DynamicPage            (      D3D12DynamicPage&&) = default;
    D3D12DynamicPage& operator= (const D3D12DynamicPage&)  = delete;
    D3D12DynamicPage& operator= (      D3D12DynamicPage&&) = delete;

    void* GetCPUAddress(Uint64 Offset)
    {
        VERIFY_EXPR(m_pd3d12Buffer);
        VERIFY(Offset < GetSize(), "Offset (", Offset, ") exceeds buffer size (", GetSize(), ")");
        return reinterpret_cast<Uint8*>(m_CPUVirtualAddress) + Offset;
    }

    D3D12_GPU_VIRTUAL_ADDRESS GetGPUAddress(Uint64 Offset)
    {
        VERIFY_EXPR(m_pd3d12Buffer);
        VERIFY(Offset < GetSize(), "Offset (", Offset, ") exceeds buffer size (", GetSize(), ")");
        return m_GPUVirtualAddress + Offset;
    }

    ID3D12Resource* GetD3D12Buffer()
    {
        return m_pd3d12Buffer;
    }

    Uint64 GetSize()const
    {
        VERIFY_EXPR(m_pd3d12Buffer);
        return m_pd3d12Buffer->GetDesc().Width;
    }

    bool IsValid()const { return m_pd3d12Buffer != nullptr; }

private:
    CComPtr<ID3D12Resource>   m_pd3d12Buffer;
    void*                     m_CPUVirtualAddress = nullptr; // The CPU-writeable address
    D3D12_GPU_VIRTUAL_ADDRESS m_GPUVirtualAddress = 0;	     // The GPU-visible address
};


class D3D12DynamicMemoryManager
{
public:
    D3D12DynamicMemoryManager(IMemoryAllocator&      Allocator, 
                              RenderDeviceD3D12Impl& DeviceD3D12Impl,
                              Uint32                 NumPagesToReserve,
                              Uint64                 PageSize);
    ~D3D12DynamicMemoryManager();

    D3D12DynamicMemoryManager            (const D3D12DynamicMemoryManager&)  = delete;
    D3D12DynamicMemoryManager            (      D3D12DynamicMemoryManager&&) = delete;
    D3D12DynamicMemoryManager& operator= (const D3D12DynamicMemoryManager&)  = delete;
    D3D12DynamicMemoryManager& operator= (      D3D12DynamicMemoryManager&&) = delete;

    void ReleasePages(std::vector<D3D12DynamicPage>& Pages, Uint64 QueueMask);

    void Destroy();

    D3D12DynamicPage AllocatePage(Uint64 SizeInBytes);

#ifdef DEVELOPMENT
    int32_t GetAllocatedPageCounter()const{return m_AllocatedPageCounter;}
#endif

private:
    RenderDeviceD3D12Impl& m_DeviceD3D12Impl;
    
    std::mutex m_AvailablePagesMtx;
    using AvailablePagesMapElemType = std::pair<Uint64, D3D12DynamicPage>;
    std::multimap<Uint64, D3D12DynamicPage, std::less<Uint64>, STDAllocatorRawMem<AvailablePagesMapElemType> > m_AvailablePages;

#ifdef DEVELOPMENT
    std::atomic_int32_t m_AllocatedPageCounter = 0;
#endif
};


class D3D12DynamicHeap
{
public:
    D3D12DynamicHeap(D3D12DynamicMemoryManager& DynamicMemMgr, std::string HeapName, Uint64 PageSize) :
        m_GlobalDynamicMemMgr (DynamicMemMgr),
        m_HeapName            (std::move(HeapName)),
        m_PageSize            (PageSize)
    {}

    D3D12DynamicHeap            (const D3D12DynamicHeap&) = delete;
    D3D12DynamicHeap            (D3D12DynamicHeap&&)      = delete;
    D3D12DynamicHeap& operator= (const D3D12DynamicHeap&) = delete;
    D3D12DynamicHeap& operator= (D3D12DynamicHeap&&)      = delete;
    
    ~D3D12DynamicHeap();

    D3D12DynamicAllocation Allocate(Uint64 SizeInBytes, Uint64 Alignment, Uint64 DvpCtxFrameNumber);
    void ReleaseAllocatedPages(Uint64 QueueMask);

    static constexpr Uint64 InvalidOffset = static_cast<Uint64>(-1);

    size_t GetAllocatedPagesCount() const {return m_AllocatedPages.size();}

private:
    D3D12DynamicMemoryManager& m_GlobalDynamicMemMgr;
    const std::string m_HeapName;

    std::vector<D3D12DynamicPage> m_AllocatedPages;
    
    const Uint64 m_PageSize;

    Uint64 m_CurrOffset        = InvalidOffset;
    Uint64 m_AvailableSize     = 0;

    Uint64 m_CurrAllocatedSize = 0;
    Uint64 m_CurrUsedSize      = 0;
    Uint64 m_CurrAlignedSize   = 0;
    Uint64 m_PeakAllocatedSize = 0;
    Uint64 m_PeakUsedSize      = 0;
    Uint64 m_PeakAlignedSize   = 0;
};

}
