// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.

#include "GPUTextureDescription.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Graphics/Config.h"

namespace
{
    int32 CalculateMipMapCount(int32 requestedLevel, int32 width)
    {
        //int32 size = width;
        //int32 maxMipMap = 1 + Math::CeilToInt(Math::Log(size) / Math::Log(2.0));
        //return requestedLevel == 0 ? maxMipMap : Math::Min(requestedLevel, maxMipMap);

        if (requestedLevel == 0)
            requestedLevel = GPU_MAX_TEXTURE_MIP_LEVELS;

        int32 maxMipMap = 1;
        while (width > 1)
        {
            width >>= 1;
            maxMipMap++;
        }

        return Math::Min(requestedLevel, maxMipMap);
    }
}

const Char* ToString(TextureDimensions value)
{
    const Char* result;
    switch (value)
    {
    case TextureDimensions::Texture:
        result = TEXT("Texture");
        break;
    case TextureDimensions::VolumeTexture:
        result = TEXT("VolumeTexture");
        break;
    case TextureDimensions::CubeTexture:
        result = TEXT("CubeTexture");
        break;
    default:
        result = TEXT("");
    }
    return result;
}

void GPUTextureDescription::Clear()
{
    Platform::MemoryClear(this, sizeof(GPUTextureDescription));
    MultiSampleLevel = MSAALevel::None;
}

GPUTextureDescription GPUTextureDescription::New1D(int32 width, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount, int32 arraySize)
{
    GPUTextureDescription desc;
    desc.Dimensions = TextureDimensions::Texture;
    desc.Width = width;
    desc.Height = 1;
    desc.Depth = 1;
    desc.ArraySize = arraySize;
    desc.MipLevels = CalculateMipMapCount(mipCount, width);
    desc.Format = format;
    desc.MultiSampleLevel = MSAALevel::None;
    desc.Flags = textureFlags;
    desc.Usage = GPUResourceUsage::Default;
    desc.DefaultClearColor = Color::Black;
    return desc;
}

GPUTextureDescription GPUTextureDescription::New2D(int32 width, int32 height, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount, int32 arraySize, MSAALevel msaaLevel)
{
    GPUTextureDescription desc;
    desc.Dimensions = TextureDimensions::Texture;
    desc.Width = width;
    desc.Height = height;
    desc.Depth = 1;
    desc.ArraySize = arraySize;
    desc.MipLevels = CalculateMipMapCount(mipCount, Math::Max(width, height));
    desc.Format = format;
    desc.MultiSampleLevel = msaaLevel;
    desc.Flags = textureFlags;
    desc.DefaultClearColor = Color::Black;
    desc.Usage = GPUResourceUsage::Default;
    return desc;
}

GPUTextureDescription GPUTextureDescription::New3D(const Vector3& size, PixelFormat format, GPUTextureFlags textureFlags)
{
    return New3D((int32)size.X, (int32)size.Y, (int32)size.Z, 1, format, textureFlags);
}

GPUTextureDescription GPUTextureDescription::New3D(int32 width, int32 height, int32 depth, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount)
{
    GPUTextureDescription desc;
    desc.Dimensions = TextureDimensions::VolumeTexture;
    desc.Width = width;
    desc.Height = height;
    desc.Depth = depth;
    desc.ArraySize = 1;
    desc.MipLevels = CalculateMipMapCount(mipCount, Math::Max(width, height, depth));
    desc.Format = format;
    desc.MultiSampleLevel = MSAALevel::None;
    desc.Flags = textureFlags;
    desc.DefaultClearColor = Color::Black;
    desc.Usage = GPUResourceUsage::Default;
    return desc;
}

GPUTextureDescription GPUTextureDescription::NewCube(int32 size, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount)
{
    auto desc = New2D(size, size, format, textureFlags, mipCount, 6, MSAALevel::None);
    desc.Dimensions = TextureDimensions::CubeTexture;
    return desc;
}

bool GPUTextureDescription::Equals(const GPUTextureDescription& other) const
{
    return Dimensions == other.Dimensions
            && Width == other.Width
            && Height == other.Height
            && Depth == other.Depth
            && ArraySize == other.ArraySize
            && MipLevels == other.MipLevels
            && Format == other.Format
            && MultiSampleLevel == other.MultiSampleLevel
            && Flags == other.Flags
            && Usage == other.Usage
            && Color::NearEqual(DefaultClearColor, other.DefaultClearColor);
}

String GPUTextureDescription::ToString() const
{
    // TODO: add tool to Format to string

    String flags;
    if (Flags == GPUTextureFlags::None)
    {
        flags = TEXT("None");
    }
    else
    {
        // TODO: create tool to auto convert flag enums to string

#define CONVERT_FLAGS_FLAGS_2_STR(value) if(Flags & GPUTextureFlags::value) { if (flags.HasChars()) flags += TEXT('|'); flags += TEXT(#value); }
        CONVERT_FLAGS_FLAGS_2_STR(ShaderResource);
        CONVERT_FLAGS_FLAGS_2_STR(RenderTarget);
        CONVERT_FLAGS_FLAGS_2_STR(UnorderedAccess);
        CONVERT_FLAGS_FLAGS_2_STR(DepthStencil);
        CONVERT_FLAGS_FLAGS_2_STR(PerMipViews);
        CONVERT_FLAGS_FLAGS_2_STR(PerSliceViews);
        CONVERT_FLAGS_FLAGS_2_STR(ReadOnlyDepthView);
        CONVERT_FLAGS_FLAGS_2_STR(BackBuffer);
#undef CONVERT_FLAGS_FLAGS_2_STR
    }

    return String::Format(TEXT("Size: {0}x{1}x{2}[{3}], Type: {4}, Mips: {5}, Format: {6}, MSAA: {7}, Flags: {8}, Usage: {9}"),
                          Width,
                          Height,
                          Depth,
                          ArraySize,
                          ::ToString(Dimensions),
                          MipLevels,
                          (int32)Format,
                          ::ToString(MultiSampleLevel),
                          flags,
                          (int32)Usage);
}

uint32 GetHash(const GPUTextureDescription& key)
{
    uint32 hashCode = key.Width;
    hashCode = (hashCode * 397) ^ key.Height;
    hashCode = (hashCode * 397) ^ key.Depth;
    hashCode = (hashCode * 397) ^ key.ArraySize;
    hashCode = (hashCode * 397) ^ (uint32)key.Dimensions;
    hashCode = (hashCode * 397) ^ key.MipLevels;
    hashCode = (hashCode * 397) ^ (uint32)key.Format;
    hashCode = (hashCode * 397) ^ (uint32)key.MultiSampleLevel;
    hashCode = (hashCode * 397) ^ (uint32)key.Flags;
    hashCode = (hashCode * 397) ^ (uint32)key.Usage;
    hashCode = (hashCode * 397) ^ key.DefaultClearColor.GetHashCode();
    return hashCode;
}
