// Copyright (c) Wojciech Figat. All rights reserved.

#include "GPUShader.h"
#include "GPUConstantBuffer.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Types/Span.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Profiler/ProfilerMemory.h"

static FORCE_INLINE uint32 HashPermutation(const StringAnsiView& name, int32 permutationIndex)
{
    return GetHash(name) * 37 + permutationIndex;
}

void GPUShaderProgram::Init(const GPUShaderProgramInitializer& initializer)
{
    _name = initializer.Name;
    _bindings = initializer.Bindings;
    _flags = initializer.Flags;
#if !BUILD_RELEASE
    _owner = initializer.Owner;
#endif
}

#if !BUILD_RELEASE

void GPUShaderProgram::GetDebugName(DebugName& name) const
{
    StringView ownerName = StringUtils::GetFileNameWithoutExtension(_owner->GetName());
    name.AddUninitialized(ownerName.Length() + _name.Length() + 2);
    char* dst = name.Get();
    for (int32 i = 0; i < ownerName.Length(); i++)
        dst[i] = (char)ownerName.Get()[i];
    dst += ownerName.Length();
    *dst = ':';
    dst++;
    for (int32 i = 0; i < _name.Length(); i++)
        dst[i] = _name.Get()[i];
    dst[_name.Length()] = 0;
}

#endif

GPUShader::GPUShader()
    : GPUResource(SpawnParams(Guid::New(), TypeInitializer))
{
    Platform::MemoryClear(_constantBuffers, sizeof(_constantBuffers));
}

bool GPUShader::Create(MemoryReadStream& stream)
{
    ReleaseGPU();
    _memoryUsage = sizeof(GPUShader);

    // Version
    int32 version;
    stream.ReadInt32(&version);
    if (version != GPU_SHADER_CACHE_VERSION)
    {
        LOG(Warning, "Unsupported shader version {0}. The current version is {1}.", version, GPU_SHADER_CACHE_VERSION);
        return true;
    }

    // Additional data start
    int32 additionalDataStart;
    stream.ReadInt32(&additionalDataStart);

    // Shaders count
    int32 shadersCount;
    stream.ReadInt32(&shadersCount);
    GPUShaderProgramInitializer initializer;
#if !BUILD_RELEASE
    initializer.Owner = this;
    const StringView name = GetName();
#else
    const StringView name;
#endif
    const bool hasCompute = GPUDevice::Instance->Limits.HasCompute;
    for (int32 i = 0; i < shadersCount; i++)
    {
        const ShaderStage type = static_cast<ShaderStage>(stream.ReadByte());
        const int32 permutationsCount = stream.ReadByte();
        ASSERT(Math::IsInRange(permutationsCount, 1, SHADER_PERMUTATIONS_MAX_COUNT));

        // Load shader name
        stream.Read(initializer.Name, 11);
        ASSERT(initializer.Name.HasChars());

        // Load shader flags
        stream.ReadUint32((uint32*)&initializer.Flags);

        for (int32 permutationIndex = 0; permutationIndex < permutationsCount; permutationIndex++)
        {
            // Load bytecode
            uint32 bytecodeSize;
            stream.ReadUint32(&bytecodeSize);
            if (bytecodeSize > stream.GetLength() - stream.GetPosition())
            {
                LOG(Warning, "Invalid shader bytecode size.");
                return true;
            }
            byte* bytecode = stream.Move<byte>(bytecodeSize);

            // Read bindings
            stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings));

            // Create shader program
            if (type == ShaderStage::Compute && !hasCompute)
            {
                LOG(Warning, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
                continue;
            }
            GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, Span<byte>(bytecode, bytecodeSize), stream);
            if (shader == nullptr)
            {
#if !GPU_ALLOW_TESSELLATION_SHADERS
                if (type == ShaderStage::Hull || type == ShaderStage::Domain)
                    continue;
#endif
#if !GPU_ALLOW_GEOMETRY_SHADERS
                if (type == ShaderStage::Geometry)
                    continue;
#endif
                LOG(Error, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
                return true;
            }

            // Add to the collection
            const uint32 hash = HashPermutation(shader->GetName(), permutationIndex);
            ASSERT_LOW_LAYER(!_shaders.ContainsKey(hash));
            _shaders.Add(hash, shader);
            _memoryUsage += sizeof(GPUShaderProgram) + bytecodeSize;
        }
    }

    // Constant Buffers
    const byte constantBuffersCount = stream.ReadByte();
    for (int32 i = 0; i < constantBuffersCount; i++)
    {
        // Load info
        const byte slotIndex = stream.ReadByte();
        if (slotIndex >= GPU_MAX_CB_BINDED)
        {
            LOG(Warning, "Failed to create shader constant buffer.");
            return true;
        }
        uint32 size;
        stream.ReadUint32(&size);

        // Create CB
#if GPU_ENABLE_RESOURCE_NAMING
        String cbName = String::Format(TEXT("{}.CB{}"), ToString(), i);
#else
		String cbName;
#endif
        ASSERT(_constantBuffers[slotIndex] == nullptr);
        const auto cb = GPUDevice::Instance->CreateConstantBuffer(size, cbName);
        if (cb == nullptr)
        {
            LOG(Warning, "Failed to create shader constant buffer.");
            return true;
        }
        _constantBuffers[slotIndex] = cb;
        _memoryUsage += sizeof(GPUConstantBuffer);
    }

    // Don't read additional data

    PROFILE_MEM_INC(GraphicsShaders, _memoryUsage);
    return false;
}

bool GPUShader::HasShader(const StringAnsiView& name, int32 permutationIndex) const
{
    const uint32 hash = HashPermutation(name, permutationIndex);
    return _shaders.ContainsKey(hash);
}

GPUShaderProgram* GPUShader::GetShader(ShaderStage stage, const StringAnsiView& name, int32 permutationIndex) const
{
    GPUShaderProgram* shader = nullptr;
    const uint32 hash = HashPermutation(name, permutationIndex);
    _shaders.TryGet(hash, shader);
#if BUILD_RELEASE
	// Release build is more critical on that
	ASSERT(shader != nullptr && shader->GetStage() == stage);
#else
    if (shader == nullptr)
    {
        LOG(Error, "Missing {0} shader \'{1}\'[{2}]. Object: {3}.", ::ToString(stage), String(name), permutationIndex, ToString());
    }
    else if (shader->GetStage() != stage)
    {
        LOG(Error, "Invalid shader stage \'{1}\'[{2}]. Expected: {0}. Actual: {4}. Object: {3}.", ::ToString(stage), String(name), permutationIndex, ToString(), ::ToString(shader->GetStage()));
    }
#endif
    return shader;
}

void GPUShader::ReadVertexLayout(MemoryReadStream& stream, GPUVertexLayout*& inputLayout, GPUVertexLayout*& vertexLayout)
{
    inputLayout = vertexLayout = nullptr;

    // Read input layout (based on shader reflection)
    GPUVertexLayout::Elements elements;
    stream.Read(elements);
    inputLayout = GPUVertexLayout::Get(elements);

    // [Deprecated in v1.10]
    byte inputLayoutSize;
    stream.ReadByte(&inputLayoutSize);
    if (inputLayoutSize == 0)
        return;
    void* elementsData = stream.Move(sizeof(VertexElement) * inputLayoutSize);
    if (inputLayoutSize > GPU_MAX_VS_ELEMENTS)
    {
        LOG(Error, "Incorrect input layout size.");
        return;
    }
    elements.Set((VertexElement*)elementsData, inputLayoutSize);
    vertexLayout = GPUVertexLayout::Get(elements);
}

GPUResourceType GPUShader::GetResourceType() const
{
    return GPUResourceType::Shader;
}

void GPUShader::OnReleaseGPU()
{
    PROFILE_MEM_DEC(GraphicsShaders, _memoryUsage);
    for (GPUConstantBuffer*& cb : _constantBuffers)
    {
        if (cb)
        {
            SAFE_DELETE_GPU_RESOURCE(cb);
            cb = nullptr;
        }
    }
    _memoryUsage = 0;
    _shaders.ClearDelete();
}
