// Copyright 2021 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.

#ifndef SRC_DAWN_NATIVE_TINTUTILS_H_
#define SRC_DAWN_NATIVE_TINTUTILS_H_

#include <functional>
#include <unordered_map>

#include "src/dawn/common/MatchVariant.h"
#include "src/dawn/native/BindGroupLayoutInternal.h"
#include "src/dawn/native/BindingInfo.h"
#include "src/dawn/native/IntegerTypes.h"
#include "src/dawn/native/PipelineLayout.h"
#include "src/dawn/native/ShaderModule.h"
#include "src/dawn/native/stream/Stream.h"
#include "src/utils/non_copyable.h"
#include "tint/tint.h"

namespace dawn::native {

class DeviceBase;
class PipelineLayoutBase;
struct ProgrammableStage;
class RenderPipelineBase;

tint::VertexPullingConfig BuildVertexPullingTransformConfig(
    const RenderPipelineBase& renderPipeline,
    BindGroupIndex pullingBufferBindingSet);

std::unordered_map<tint::OverrideId, double> BuildSubstituteOverridesTransformConfig(
    const ProgrammableStage& stage);

std::unordered_map<tint::OverrideId, double> BuildSubstituteOverridesTransformConfig(
    const EntryPointMetadata& metadata,
    const PipelineConstantEntries& constants);

namespace stream {
// Uses tint::ForeachField when available to implement the stream::Stream trait for types.
template <typename T>
    requires(tint::HasReflection<T>)
class Stream<T> {
  public:
    static void Write(Sink* s, const T& v) {
        tint::ForeachField(v, [&](const auto& f) { StreamIn(s, f); });
    }
    static MaybeError Read(Source* s, T* v) {
        MaybeError error = {};
        tint::ForeachField(*v, [&](auto& f) {
            if (!error.IsError()) {
                error = StreamOut(s, &f);
            }
        });
        return error;
    }
};
}  // namespace stream

constexpr tint::BindingPoint ToTint(const WGSLBindPoint& b) {
    return {static_cast<uint32_t>(b.group), static_cast<uint32_t>(b.binding)};
}

constexpr WGSLBindPoint FromTint(const tint::BindingPoint& tintBindingPoint) {
    return {BindGroupIndex(tintBindingPoint.group), BindingNumber(tintBindingPoint.binding)};
}

// Helper function to generate the binding remapping information for Tint compilation. Each backend
// remaps the group + BindingIndex to a BindingPoint differently using the `BindingPointFor`
// function passed as argument.
template <typename F>
concept ConvertsBindingIndexToBindingPoint = requires(F f, BindGroupIndex group, BindingIndex i) {
    { f(group, i) } -> std::same_as<tint::BindingPoint>;
};

template <ConvertsBindingIndexToBindingPoint F>
tint::Bindings GenerateBindingRemapping(const PipelineLayoutBase* layout,
                                        SingleShaderStage stage,
                                        F&& BindingPointFor) {
    tint::Bindings bindings;

    for (BindGroupIndex group : layout->GetBindGroupLayoutsMask()) {
        const BindGroupLayoutInternalBase* bgl = layout->GetBindGroupLayout(group);

        for (const auto& [bindingNumber, apiBindingIndex] : bgl->GetBindingMap()) {
            if (!(bgl->GetAPIBindingInfo(apiBindingIndex).visibility & StageBit(stage))) {
                continue;
            }

            tint::BindingPoint srcBindingPoint{
                .group = uint32_t(group),
                .binding = uint32_t(bindingNumber),
            };

            MatchVariant(
                bgl->GetAPIBindingInfo(apiBindingIndex).bindingLayout,
                [&](const BufferBindingInfo& bindingInfo) {
                    tint::BindingPoint dstBindingPoint =
                        BindingPointFor(group, bgl->AsBindingIndex(apiBindingIndex));
                    switch (bindingInfo.type) {
                        case wgpu::BufferBindingType::Uniform:
                            bindings.uniform.emplace(srcBindingPoint, dstBindingPoint);
                            break;
                        case kInternalStorageBufferBinding:
                        case wgpu::BufferBindingType::Storage:
                        case wgpu::BufferBindingType::ReadOnlyStorage:
                        case kInternalReadOnlyStorageBufferBinding:
                            bindings.storage.emplace(srcBindingPoint, dstBindingPoint);
                            break;
                        case wgpu::BufferBindingType::BindingNotUsed:
                        case wgpu::BufferBindingType::Undefined:
                            DAWN_UNREACHABLE();
                            break;
                    }
                },
                [&](const SamplerBindingInfo& bindingInfo) {
                    bindings.sampler.emplace(
                        srcBindingPoint,
                        BindingPointFor(group, bgl->AsBindingIndex(apiBindingIndex)));
                },
                [&](const StaticSamplerBindingInfo& bindingInfo) {
                    bindings.sampler.emplace(
                        srcBindingPoint,
                        BindingPointFor(group, bgl->AsBindingIndex(apiBindingIndex)));
                },
                [&](const TextureBindingInfo& bindingInfo) {
                    bindings.texture.emplace(
                        srcBindingPoint,
                        BindingPointFor(group, bgl->AsBindingIndex(apiBindingIndex)));
                },
                [&](const StorageTextureBindingInfo& bindingInfo) {
                    bindings.storage_texture.emplace(
                        srcBindingPoint,
                        BindingPointFor(group, bgl->AsBindingIndex(apiBindingIndex)));
                },
                [&](const InputAttachmentBindingInfo&) {
                    bindings.input_attachment.emplace(
                        srcBindingPoint,
                        BindingPointFor(group, bgl->AsBindingIndex(apiBindingIndex)));
                },
                [&](const TexelBufferBindingInfo&) {
                    bindings.texel_buffer.emplace(
                        srcBindingPoint,
                        BindingPointFor(group, bgl->AsBindingIndex(apiBindingIndex)));
                },
                [&](const ExternalTextureBindingInfo& bindingInfo) {
                    // TODO(491363837): Add YCBCR texture information as tint::ExternalYCBCRTexture
                    // data

                    bindings.external_texture.emplace(
                        srcBindingPoint,
                        tint::ExternalMultiplanarTexture{
                            .metadata = BindingPointFor(group, bindingInfo.metadata),
                            .plane0 = BindingPointFor(group, bindingInfo.plane0),
                            .plane1 = BindingPointFor(group, bindingInfo.plane1)});
                });
        }
    }

    return bindings;
}

tint::ResourceType BindingLayoutToResourceType(const BindingInfo& bi);

template <ConvertsBindingIndexToBindingPoint F>
std::unordered_map<tint::BindingPoint, tint::ResourceType> GenerateBindingToResourceType(
    const PipelineLayoutBase* layout,
    F&& BindingPointFor) {
    std::unordered_map<tint::BindingPoint, tint::ResourceType> binding_to_resource_type;

    for (BindGroupIndex group : layout->GetBindGroupLayoutsMask()) {
        const BindGroupLayoutInternalBase* bgl = layout->GetBindGroupLayout(group);

        for (const auto& [_, apiBindingIndex] : bgl->GetBindingMap()) {
            tint::BindingPoint dstBindingPoint =
                BindingPointFor(group, bgl->AsBindingIndex(apiBindingIndex));
            tint::ResourceType type =
                BindingLayoutToResourceType(bgl->GetAPIBindingInfo(apiBindingIndex));
            binding_to_resource_type.emplace(dstBindingPoint, type);
        }
    }
    return binding_to_resource_type;
}

}  // namespace dawn::native

#endif  // SRC_DAWN_NATIVE_TINTUTILS_H_
