/*
 *  Copyright 2019-2021 Diligent Graphics LLC
 *  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
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  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.
 */

#include "Buffer.h"
#include "GraphicsAccessories.hpp"

namespace Diligent
{

#define LOG_BUFFER_ERROR_AND_THROW(...) LOG_ERROR_AND_THROW("Description of buffer '", (Desc.Name ? Desc.Name : ""), "' is invalid: ", ##__VA_ARGS__)
#define VERIFY_BUFFER(Expr, ...)                     \
    do                                               \
    {                                                \
        if (!(Expr))                                 \
        {                                            \
            LOG_BUFFER_ERROR_AND_THROW(__VA_ARGS__); \
        }                                            \
    } while (false)


void ValidateBufferDesc(const BufferDesc& Desc, const DeviceCaps& deviceCaps) noexcept(false)
{
    static_assert(BIND_FLAGS_LAST == 0x400L, "Please update this function to handle the new bind flags");

    constexpr Uint32 AllowedBindFlags =
        BIND_VERTEX_BUFFER |
        BIND_INDEX_BUFFER |
        BIND_UNIFORM_BUFFER |
        BIND_SHADER_RESOURCE |
        BIND_STREAM_OUTPUT |
        BIND_UNORDERED_ACCESS |
        BIND_INDIRECT_DRAW_ARGS |
        BIND_RAY_TRACING;

    VERIFY_BUFFER((Desc.BindFlags & ~AllowedBindFlags) == 0, "the following bind flags are not allowed for a buffer: ", GetBindFlagsString(Desc.BindFlags & ~AllowedBindFlags, ", "), '.');

    if ((Desc.BindFlags & BIND_UNORDERED_ACCESS) ||
        (Desc.BindFlags & BIND_SHADER_RESOURCE))
    {
        VERIFY_BUFFER(Desc.Mode > BUFFER_MODE_UNDEFINED && Desc.Mode < BUFFER_MODE_NUM_MODES, GetBufferModeString(Desc.Mode), " is not a valid mode for a buffer created with BIND_SHADER_RESOURCE or BIND_UNORDERED_ACCESS flags.");
        if (Desc.Mode == BUFFER_MODE_STRUCTURED || Desc.Mode == BUFFER_MODE_FORMATTED)
        {
            VERIFY_BUFFER(Desc.ElementByteStride != 0, "element stride must not be zero for structured and formatted buffers.");
        }
        else if (Desc.Mode == BUFFER_MODE_RAW)
        {
        }
    }

    switch (Desc.Usage)
    {
        case USAGE_IMMUTABLE:
        case USAGE_DEFAULT:
            VERIFY_BUFFER(Desc.CPUAccessFlags == CPU_ACCESS_NONE, "static and default buffers can't have any CPU access flags set.");
            break;

        case USAGE_DYNAMIC:
            VERIFY_BUFFER(Desc.CPUAccessFlags == CPU_ACCESS_WRITE, "dynamic buffers require CPU_ACCESS_WRITE flag.");
            break;

        case USAGE_STAGING:
            VERIFY_BUFFER(Desc.CPUAccessFlags == CPU_ACCESS_WRITE || Desc.CPUAccessFlags == CPU_ACCESS_READ,
                          "exactly one of CPU_ACCESS_WRITE or CPU_ACCESS_READ flags must be specified for a staging buffer.");
            VERIFY_BUFFER(Desc.BindFlags == BIND_NONE,
                          "staging buffers cannot be bound to any part of the graphics pipeline and can't have any bind flags set.");
            break;

        case USAGE_UNIFIED:
            VERIFY_BUFFER(deviceCaps.AdapterInfo.UnifiedMemory != 0,
                          "Unified memory is not present on this device. Check the amount of available unified memory "
                          "in the device caps before creating unified buffers.");
            VERIFY_BUFFER(Desc.CPUAccessFlags != CPU_ACCESS_NONE,
                          "at least one of CPU_ACCESS_WRITE or CPU_ACCESS_READ flags must be specified for a unified buffer.");
            if (Desc.CPUAccessFlags & CPU_ACCESS_WRITE)
            {
                VERIFY_BUFFER(deviceCaps.AdapterInfo.UnifiedMemoryCPUAccess & CPU_ACCESS_WRITE,
                              "Unified memory on this device does not support write access. Check the available access flags "
                              "in the device caps before creating unified buffers.");
            }
            if (Desc.CPUAccessFlags & CPU_ACCESS_READ)
            {
                VERIFY_BUFFER(deviceCaps.AdapterInfo.UnifiedMemoryCPUAccess & CPU_ACCESS_READ,
                              "Unified memory on this device does not support read access. Check the available access flags "
                              "in the device caps before creating unified buffers.");
            }
            break;

        default:
            UNEXPECTED("Unknown usage");
    }
}

void ValidateBufferInitData(const BufferDesc& Desc, const BufferData* pBuffData) noexcept(false)
{
    if (Desc.Usage == USAGE_IMMUTABLE && (pBuffData == nullptr || pBuffData->pData == nullptr))
        LOG_BUFFER_ERROR_AND_THROW("initial data must not be null as immutable buffers must be initialized at creation time.");

    if (Desc.Usage == USAGE_DYNAMIC && pBuffData != nullptr && pBuffData->pData != nullptr)
        LOG_BUFFER_ERROR_AND_THROW("initial data must be null for dynamic buffers.");

    if (Desc.Usage == USAGE_STAGING)
    {
        if (Desc.CPUAccessFlags == CPU_ACCESS_WRITE)
        {
            VERIFY_BUFFER(pBuffData == nullptr || pBuffData->pData == nullptr,
                          "CPU-writable staging buffers must be updated via map.");
        }
    }
    else if (Desc.Usage == USAGE_UNIFIED)
    {
        if (pBuffData != nullptr && pBuffData->pData != nullptr && (Desc.CPUAccessFlags & CPU_ACCESS_WRITE) == 0)
        {
            LOG_BUFFER_ERROR_AND_THROW("CPU_ACCESS_WRITE flag is required to intialize a unified buffer.");
        }
    }
}

#undef VERIFY_BUFFER
#undef LOG_BUFFER_ERROR_AND_THROW

void ValidateAndCorrectBufferViewDesc(const BufferDesc& BuffDesc, BufferViewDesc& ViewDesc) noexcept(false)
{
    if (ViewDesc.ByteWidth == 0)
    {
        DEV_CHECK_ERR(BuffDesc.uiSizeInBytes > ViewDesc.ByteOffset, "Byte offset (", ViewDesc.ByteOffset, ") exceeds buffer size (", BuffDesc.uiSizeInBytes, ")");
        ViewDesc.ByteWidth = BuffDesc.uiSizeInBytes - ViewDesc.ByteOffset;
    }

    if (ViewDesc.ByteOffset + ViewDesc.ByteWidth > BuffDesc.uiSizeInBytes)
        LOG_ERROR_AND_THROW("Buffer view range [", ViewDesc.ByteOffset, ", ", ViewDesc.ByteOffset + ViewDesc.ByteWidth, ") is out of the buffer boundaries [0, ", BuffDesc.uiSizeInBytes, ").");

    if ((BuffDesc.BindFlags & BIND_UNORDERED_ACCESS) ||
        (BuffDesc.BindFlags & BIND_SHADER_RESOURCE))
    {
        if (BuffDesc.Mode == BUFFER_MODE_STRUCTURED || BuffDesc.Mode == BUFFER_MODE_FORMATTED)
        {
            VERIFY(BuffDesc.ElementByteStride != 0, "Element byte stride is zero");
            if ((ViewDesc.ByteOffset % BuffDesc.ElementByteStride) != 0)
                LOG_ERROR_AND_THROW("Buffer view byte offset (", ViewDesc.ByteOffset, ") is not a multiple of element byte stride (", BuffDesc.ElementByteStride, ").");
            if ((ViewDesc.ByteWidth % BuffDesc.ElementByteStride) != 0)
                LOG_ERROR_AND_THROW("Buffer view byte width (", ViewDesc.ByteWidth, ") is not a multiple of element byte stride (", BuffDesc.ElementByteStride, ").");
        }

        if (BuffDesc.Mode == BUFFER_MODE_FORMATTED && ViewDesc.Format.ValueType == VT_UNDEFINED)
            LOG_ERROR_AND_THROW("Format must be specified when creating a view of a formatted buffer");

        if (BuffDesc.Mode == BUFFER_MODE_FORMATTED || (BuffDesc.Mode == BUFFER_MODE_RAW && ViewDesc.Format.ValueType != VT_UNDEFINED))
        {
            if (ViewDesc.Format.NumComponents <= 0 || ViewDesc.Format.NumComponents > 4)
                LOG_ERROR_AND_THROW("Incorrect number of components (", Uint32{ViewDesc.Format.NumComponents}, "). 1, 2, 3, or 4 are allowed values");
            if (ViewDesc.Format.ValueType == VT_FLOAT32 || ViewDesc.Format.ValueType == VT_FLOAT16)
                ViewDesc.Format.IsNormalized = false;
            auto ViewElementStride = GetValueSize(ViewDesc.Format.ValueType) * Uint32{ViewDesc.Format.NumComponents};
            if (BuffDesc.Mode == BUFFER_MODE_RAW && BuffDesc.ElementByteStride == 0)
                LOG_ERROR_AND_THROW("To enable formatted views of a raw buffer, element byte must be specified during buffer initialization");
            if (ViewElementStride != BuffDesc.ElementByteStride)
                LOG_ERROR_AND_THROW("Buffer element byte stride (", BuffDesc.ElementByteStride, ") is not consistent with the size (", ViewElementStride, ") defined by the format of the view (", GetBufferFormatString(ViewDesc.Format), ')');
        }

        if (BuffDesc.Mode == BUFFER_MODE_RAW && ViewDesc.Format.ValueType == VT_UNDEFINED)
        {
            if ((ViewDesc.ByteOffset % 16) != 0)
                LOG_ERROR_AND_THROW("When creating a RAW view, the offset of the first element from the start of the buffer (", ViewDesc.ByteOffset, ") must be a multiple of 16 bytes");
        }
    }
}

} // namespace Diligent
