// Copyright 2026 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "src/dawn/native/ResourceTableDefaultResources.h"

#include "src/dawn/common/ityp_array.h"
#include "src/dawn/native/Device.h"
#include "src/dawn/native/ResourceTable.h"
#include "src/dawn/native/Texture.h"
#include "tint/tint.h"

namespace dawn::native {

namespace {
constexpr auto kDefaults = ityp::array<ResourceTableSlot, tint::ResourceType, 35>{
    tint::ResourceType::kTexture1d_f32_filterable,
    tint::ResourceType::kTexture2d_f32_filterable,
    tint::ResourceType::kTexture2dArray_f32_filterable,
    tint::ResourceType::kTextureCube_f32_filterable,
    tint::ResourceType::kTextureCubeArray_f32_filterable,
    tint::ResourceType::kTexture3d_f32_filterable,
    tint::ResourceType::kTexture1d_f32_unfilterable,
    tint::ResourceType::kTexture2d_f32_unfilterable,
    tint::ResourceType::kTexture2dArray_f32_unfilterable,
    tint::ResourceType::kTextureCube_f32_unfilterable,
    tint::ResourceType::kTextureCubeArray_f32_unfilterable,
    tint::ResourceType::kTexture3d_f32_unfilterable,

    tint::ResourceType::kTexture1d_u32,
    tint::ResourceType::kTexture2d_u32,
    tint::ResourceType::kTexture2dArray_u32,
    tint::ResourceType::kTextureCube_u32,
    tint::ResourceType::kTextureCubeArray_u32,
    tint::ResourceType::kTexture3d_u32,

    tint::ResourceType::kTexture1d_i32,
    tint::ResourceType::kTexture2d_i32,
    tint::ResourceType::kTexture2dArray_i32,
    tint::ResourceType::kTextureCube_i32,
    tint::ResourceType::kTextureCubeArray_i32,
    tint::ResourceType::kTexture3d_i32,

    tint::ResourceType::kTextureMultisampled2d_f32,
    tint::ResourceType::kTextureMultisampled2d_u32,
    tint::ResourceType::kTextureMultisampled2d_i32,

    tint::ResourceType::kTextureDepth2d,
    tint::ResourceType::kTextureDepth2dArray,
    tint::ResourceType::kTextureDepthCube,
    tint::ResourceType::kTextureDepthCubeArray,
    tint::ResourceType::kTextureDepthMultisampled2d,

    // If more samplers are added, update kNumDefaultSamplers.
    tint::ResourceType::kSampler_filtering,
    tint::ResourceType::kSampler_non_filtering,
    tint::ResourceType::kSampler_comparison,
};
constexpr auto kNumDefaultSamplers = ResourceTableSlot{3};

// This helper function is used in ASSERTs to check that the default resources are compatible with
// the typeIds that they will be used as defaults for.
[[maybe_unused]] bool AreTypeIDCompatible(tint::ResourceType resourceTypeId,
                                          tint::ResourceType slotTypeId) {
    if (resourceTypeId == slotTypeId) {
        return true;
    }

    // Cases where conversions are allowed:
    switch (slotTypeId) {
        case tint::ResourceType::kTexture1d_f32_unfilterable:
            return resourceTypeId == tint::ResourceType::kTexture1d_f32_filterable;

        case tint::ResourceType::kTexture2d_f32_unfilterable:
            return resourceTypeId == tint::ResourceType::kTexture2d_f32_filterable ||
                   resourceTypeId == tint::ResourceType::kTextureDepth2d;

        case tint::ResourceType::kTexture2dArray_f32_unfilterable:
            return resourceTypeId == tint::ResourceType::kTexture2dArray_f32_filterable ||
                   resourceTypeId == tint::ResourceType::kTextureDepth2dArray;

        case tint::ResourceType::kTextureCube_f32_unfilterable:
            return resourceTypeId == tint::ResourceType::kTextureCube_f32_filterable ||
                   resourceTypeId == tint::ResourceType::kTextureDepthCube;

        case tint::ResourceType::kTextureCubeArray_f32_unfilterable:
            return resourceTypeId == tint::ResourceType::kTextureCubeArray_f32_filterable ||
                   resourceTypeId == tint::ResourceType::kTextureDepthCubeArray;

        case tint::ResourceType::kTexture3d_f32_unfilterable:
            return resourceTypeId == tint::ResourceType::kTexture3d_f32_filterable;

        case tint::ResourceType::kSampler_filtering:
            return resourceTypeId == tint::ResourceType::kSampler_non_filtering;

        default:
            return false;
    }
}
}  // namespace

// static
ResultOrError<std::unique_ptr<ResourceTableDefaultResources>> ResourceTableDefaultResources::Create(
    DeviceBase* device) {
    auto defaults = std::make_unique<ResourceTableDefaultResources>();
    DAWN_TRY(defaults->Initialize(device));
    return defaults;
}

// static
ityp::span<ResourceTableSlot, const tint::ResourceType> ResourceTableDefaultResources::GetOrder() {
    return {kDefaults.data(), ResourceTableSlot(uint32_t(kDefaults.size()))};
}

// static
ResourceTableSlot ResourceTableDefaultResources::GetCount() {
    return GetOrder().size();
}

// static
ResourceTableSlot ResourceTableDefaultResources::GetSamplerCount() {
    return kNumDefaultSamplers;
}

// static
ResourceTableSlot ResourceTableDefaultResources::GetNonSamplerCount() {
    return GetCount() - GetSamplerCount();
}

TextureViewBase* ResourceTableDefaultResources::GetPlaceholderSampleableTexture() const {
    return mPlaceholderSampleableTexture.Get();
}

SamplerBase* ResourceTableDefaultResources::GetPlaceholderSampler() const {
    return mPlaceholderSampler.Get();
}

ityp::span<ResourceTableSlot, const ResourceTableDefaultResources::Resource>
ResourceTableDefaultResources::GetResources() const {
    return {mDefaultResources.data(), mDefaultResources.size()};
}

MaybeError ResourceTableDefaultResources::Initialize(DeviceBase* device) {
    // Each resource is added in the order specified by GetOrder()
    auto AddDefaultTextureView = [&](TextureBase* texture, const TextureViewDescriptor* viewDesc =
                                                               nullptr) -> MaybeError {
        DAWN_TRY(texture->Pin(wgpu::TextureUsage::TextureBinding));

        Ref<TextureViewBase> view;
        DAWN_TRY_ASSIGN(view, device->CreateTextureView(texture, viewDesc));

        // Check that the resource we will have will match the order of default textures that we
        // will give to the shader compilation.
        DAWN_ASSERT(AreTypeIDCompatible(ResourceTableBase::ComputeTypeId(view),
                                        GetOrder()[mDefaultResources.size()]));
        mDefaultResources.push_back(view);

        return {};
    };

    auto AddDefaultSampler = [&](Ref<SamplerBase> sampler) {
        // Check that the resource we will have will match the order of default textures that we
        // will give to the shader compilation.
        DAWN_ASSERT(AreTypeIDCompatible(ResourceTableBase::ComputeTypeId(sampler),
                                        GetOrder()[mDefaultResources.size()]));
        mDefaultResources.push_back(sampler);
    };

    // Create the color format single-sampled views.
    for (auto format :
         {wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Uint, wgpu::TextureFormat::R8Sint}) {
        // Create the necessary 1/2/3D textures.
        TextureDescriptor tDesc{
            .label = "default SampledTexture resource",
            .usage = wgpu::TextureUsage::TextureBinding,
            .size = {1},
            .format = format,
        };

        tDesc.size = {1};
        tDesc.dimension = wgpu::TextureDimension::e1D;
        Ref<TextureBase> t1D;
        DAWN_TRY_ASSIGN(t1D, device->CreateTexture(&tDesc));

        tDesc.size = {1, 1, 6};
        tDesc.dimension = wgpu::TextureDimension::e2D;
        Ref<TextureBase> t2D;
        DAWN_TRY_ASSIGN(t2D, device->CreateTexture(&tDesc));

        tDesc.size = {1, 1, 1};
        tDesc.dimension = wgpu::TextureDimension::e3D;
        Ref<TextureBase> t3D;
        DAWN_TRY_ASSIGN(t3D, device->CreateTexture(&tDesc));

        // There are two different sets of default resources for R8Unorm because we first add all
        // the filterable f32 sample types, then all the unfilterable f32 sample types.
        uint32_t timesToAdd = format == wgpu::TextureFormat::R8Unorm ? 2 : 1;
        for (uint32_t i = 0; i < timesToAdd; i++) {
            // Create all the default binding view, reusing the 2D texture between
            // 2D/2DArray/Cube/CubeArray.
            DAWN_TRY(AddDefaultTextureView(t1D.Get()));

            TextureViewDescriptor vDesc{
                .label = "default SampledTexture resource",
            };
            vDesc.arrayLayerCount = 1;
            vDesc.dimension = wgpu::TextureViewDimension::e2D;
            DAWN_TRY(AddDefaultTextureView(t2D.Get(), &vDesc));
            vDesc.dimension = wgpu::TextureViewDimension::e2DArray;
            DAWN_TRY(AddDefaultTextureView(t2D.Get(), &vDesc));
            vDesc.arrayLayerCount = 6;
            vDesc.dimension = wgpu::TextureViewDimension::Cube;
            DAWN_TRY(AddDefaultTextureView(t2D.Get(), &vDesc));
            vDesc.dimension = wgpu::TextureViewDimension::CubeArray;
            DAWN_TRY(AddDefaultTextureView(t2D.Get(), &vDesc));

            DAWN_TRY(AddDefaultTextureView(t3D.Get()));
        }

        if (mPlaceholderSampleableTexture == nullptr) {
            DAWN_TRY_ASSIGN(mPlaceholderSampleableTexture,
                            device->CreateTextureView(t2D.Get(), nullptr));
        }
    }

    // Create the color format multi-sampled views.
    for (auto format :
         {wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Uint, wgpu::TextureFormat::R8Sint}) {
        TextureDescriptor tDesc{
            .label = "default SampledTexture resource",
            .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment,
            .dimension = wgpu::TextureDimension::e2D,
            .size = {1, 1},
            .format = format,
            .sampleCount = 4,
        };

        Ref<TextureBase> t;
        DAWN_TRY_ASSIGN(t, device->CreateTexture(&tDesc));
        DAWN_TRY(AddDefaultTextureView(t.Get()));
    }

    // Create the single-sampled depth texture default resource.
    {
        TextureDescriptor tDesc{
            .label = "default SampledTexture resource",
            .usage = wgpu::TextureUsage::TextureBinding,
            .dimension = wgpu::TextureDimension::e2D,
            .size = {1, 1, 6},
            .format = wgpu::TextureFormat::Depth16Unorm,
        };
        Ref<TextureBase> t;
        DAWN_TRY_ASSIGN(t, device->CreateTexture(&tDesc));

        TextureViewDescriptor vDesc{
            .label = "default SampledTexture resource",
        };
        vDesc.arrayLayerCount = 1;
        vDesc.dimension = wgpu::TextureViewDimension::e2D;
        DAWN_TRY(AddDefaultTextureView(t.Get(), &vDesc));
        vDesc.dimension = wgpu::TextureViewDimension::e2DArray;
        DAWN_TRY(AddDefaultTextureView(t.Get(), &vDesc));
        vDesc.arrayLayerCount = 6;
        vDesc.dimension = wgpu::TextureViewDimension::Cube;
        DAWN_TRY(AddDefaultTextureView(t.Get(), &vDesc));
        vDesc.dimension = wgpu::TextureViewDimension::CubeArray;
        DAWN_TRY(AddDefaultTextureView(t.Get(), &vDesc));
    }

    // Create the multi-sampled depth texture default resource.
    {
        TextureDescriptor tDesc{
            .label = "default SampledTexture resource",
            .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment,
            .dimension = wgpu::TextureDimension::e2D,
            .size = {1, 1},
            .format = wgpu::TextureFormat::Depth16Unorm,
            .sampleCount = 4,
        };

        Ref<TextureBase> t;
        DAWN_TRY_ASSIGN(t, device->CreateTexture(&tDesc));
        DAWN_TRY(AddDefaultTextureView(t.Get()));
    }

    // Create the samplers.
    {
        // Filtering and non-filtering both use a non-filtering sampler
        {
            SamplerDescriptor sDesc{
                .label = "default Sampler resource",
                .magFilter = wgpu::FilterMode::Nearest,
                .minFilter = wgpu::FilterMode::Nearest,
                .mipmapFilter = wgpu::MipmapFilterMode::Nearest,
            };
            Ref<SamplerBase> s;
            DAWN_TRY_ASSIGN(s, device->CreateSampler(&sDesc));
            AddDefaultSampler(s);  // Filtering
            AddDefaultSampler(s);  // Non-filtering

            mPlaceholderSampler = s;
        }
        // Comparison
        {
            SamplerDescriptor sDesc{
                .label = "default Sampler resource",
                .compare = wgpu::CompareFunction::Always,
            };
            Ref<SamplerBase> s;
            DAWN_TRY_ASSIGN(s, device->CreateSampler(&sDesc));
            AddDefaultSampler(s);
        }
    }

    return {};
}

}  // namespace dawn::native
