// Copyright 2025 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/tint/lang/wgsl/resolver/resolver.h"
#include "src/tint/lang/wgsl/resolver/resolver_helper_test.h"

namespace tint::resolver {
namespace {

using namespace tint::core::fluent_types;     // NOLINT
using namespace tint::core::number_suffixes;  // NOLINT

using ResolverTexelBufferTest = ResolverTest;

struct FormatParams {
    core::TexelFormat format;
    bool is_valid;
};

#define VALID_FORMAT_CASE(FMT)       \
    FormatParams {                   \
        core::TexelFormat::FMT, true \
    }

static constexpr FormatParams kFormatCases[] = {
    VALID_FORMAT_CASE(kBgra8Unorm), VALID_FORMAT_CASE(kR32Float),   VALID_FORMAT_CASE(kR32Sint),
    VALID_FORMAT_CASE(kR32Uint),    VALID_FORMAT_CASE(kR8Unorm),    VALID_FORMAT_CASE(kRg32Float),
    VALID_FORMAT_CASE(kRg32Sint),   VALID_FORMAT_CASE(kRg32Uint),   VALID_FORMAT_CASE(kRgba16Float),
    VALID_FORMAT_CASE(kRgba16Sint), VALID_FORMAT_CASE(kRgba16Uint), VALID_FORMAT_CASE(kRgba32Float),
    VALID_FORMAT_CASE(kRgba32Sint), VALID_FORMAT_CASE(kRgba32Uint), VALID_FORMAT_CASE(kRgba8Sint),
    VALID_FORMAT_CASE(kRgba8Snorm), VALID_FORMAT_CASE(kRgba8Uint),  VALID_FORMAT_CASE(kRgba8Unorm),
};
#undef VALID_FORMAT_CASE

using TexelBufferFormatTest = ResolverTestWithParam<FormatParams>;

// Tests that the texel_buffer format is valid or invalid based on the
// format parameter.
TEST_P(TexelBufferFormatTest, Usage) {
    auto& params = GetParam();
    Require(wgsl::LanguageFeature::kTexelBuffers);

    auto tb = ty.AsType(Source{{12, 34}}, "texel_buffer", tint::ToString(params.format),
                        tint::ToString(core::Access::kRead));
    GlobalVar("a", tb, Group(0_a), Binding(0_a));

    if (params.is_valid) {
        EXPECT_TRUE(r()->Resolve()) << r()->error();
    } else {
        EXPECT_FALSE(r()->Resolve());
        EXPECT_EQ(r()->error(),
                  "12:34 error: image format must be one of the texel formats "
                  "specified for texel buffers in https://gpuweb.github.io/"
                  "gpuweb/wgsl/#texel-formats");
    }
}
INSTANTIATE_TEST_SUITE_P(ResolverTexelBufferTest,
                         TexelBufferFormatTest,
                         testing::ValuesIn(kFormatCases));

// Requires 2 template arguments: format and access
TEST_F(ResolverTexelBufferTest, MissingAccess) {
    Require(wgsl::LanguageFeature::kTexelBuffers);
    auto tb = ty.AsType(Source{{12, 34}}, "texel_buffer", "rgba32float");
    GlobalVar("a", tb, Group(0_a), Binding(0_a));

    EXPECT_FALSE(r()->Resolve());
    EXPECT_EQ(r()->error(), R"(12:34 error: 'texel_buffer' requires 2 template arguments)");
}

// Access must be read or read_write
TEST_F(ResolverTexelBufferTest, InvalidAccess) {
    Require(wgsl::LanguageFeature::kTexelBuffers);
    auto tb = ty.AsType(Source{{12, 34}}, "texel_buffer", "rgba32float",
                        Expr(Source{{12, 34}}, "read_only"));
    GlobalVar("a", tb, Group(0_a), Binding(0_a));

    EXPECT_FALSE(r()->Resolve());
    EXPECT_EQ(r()->error(),
              R"(12:34 error: unresolved access 'read_only'
12:34 note: Possible values: 'read', 'read_write', 'write')");
}

// Access must be read or read_write, not write
TEST_F(ResolverTexelBufferTest, WriteOnlyAccess) {
    Require(wgsl::LanguageFeature::kTexelBuffers);
    auto tb =
        ty.texel_buffer(Source{{12, 34}}, core::TexelFormat::kRgba32Float, core::Access::kWrite);
    GlobalVar("a", tb, Group(0_a), Binding(0_a));

    EXPECT_FALSE(r()->Resolve());
    EXPECT_EQ(r()->error(), R"(12:34 error: texel_buffer access must be read or read_write)");
}

// Requires the texel_buffer language feature to be enabled
TEST_F(ResolverTexelBufferTest, FeatureRequired) {
    auto tb =
        ty.texel_buffer(Source{{12, 34}}, core::TexelFormat::kRgba32Float, core::Access::kRead);
    GlobalVar("a", tb, Group(0_a), Binding(0_a));

    Resolver resolver{this, wgsl::AllowedFeatures{}};
    EXPECT_FALSE(resolver.Resolve());
    EXPECT_EQ(resolver.error(),
              "12:34 error: use of 'texel_buffer' requires the texel_buffer language "
              "feature, which is not allowed in the current environment");
}

enum class SubtypeKind {
    kF16,
    kI8,
    kU8,
};

struct SubtypeParams {
    SubtypeKind kind;
};

static constexpr SubtypeParams kInvalidSubtypeCases[] = {
    {SubtypeKind::kF16},
    {SubtypeKind::kI8},
    {SubtypeKind::kU8},
};

using TexelBufferSubtypeTest = ResolverTestWithParam<SubtypeParams>;

// Storage subtype must be f32, i32 or u32
TEST_P(TexelBufferSubtypeTest, InvalidSubtype) {
    auto& params = GetParam();
    Require(wgsl::LanguageFeature::kTexelBuffers);

    const core::type::Type* subtype = nullptr;
    switch (params.kind) {
        case SubtypeKind::kF16:
            subtype = create<core::type::F16>();
            break;
        case SubtypeKind::kI8:
            subtype = create<core::type::I8>();
            break;
        case SubtypeKind::kU8:
            subtype = create<core::type::U8>();
            break;
    }

    auto* tb_ty = create<core::type::TexelBuffer>(core::TexelFormat::kRgba32Float,
                                                  core::Access::kRead, subtype);

    EXPECT_FALSE(v()->TexelBuffer(tb_ty, Source{{12, 34}}));
    EXPECT_EQ(r()->error(),
              "12:34 error: texel_buffer<format, access>: storage subtype must "
              "be f32, i32 or u32");
}

INSTANTIATE_TEST_SUITE_P(ResolverTexelBufferTest,
                         TexelBufferSubtypeTest,
                         testing::ValuesIn(kInvalidSubtypeCases));

// texel_buffer used as struct member
TEST_F(ResolverTexelBufferTest, StructMember) {
    Require(wgsl::LanguageFeature::kTexelBuffers);

    auto* s = Structure(
        "S", Vector{
                 Member("buf", ty.texel_buffer(Source{{12, 34}}, core::TexelFormat::kRgba32Float,
                                               core::Access::kRead)),
             });

    GlobalVar("g", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead, Binding(0_a),
              Group(0_a));

    EXPECT_FALSE(r()->Resolve());
    EXPECT_EQ(r()->error(),
              "error: texel_buffer<rgba32float, read> cannot be used as the "
              "type of a structure member");
}

// texel_buffer used as function parameter
TEST_F(ResolverTexelBufferTest, FunctionParameter) {
    Require(wgsl::LanguageFeature::kTexelBuffers);

    Func("f",
         Vector{Param("p", ty.texel_buffer(Source{{12, 34}}, core::TexelFormat::kRgba32Float,
                                           core::Access::kReadWrite))},
         ty.void_(), Empty);

    EXPECT_TRUE(r()->Resolve()) << r()->error();
}

// Global variable missing binding or group attributes
TEST_F(ResolverTexelBufferTest, MissingBinding) {
    Require(wgsl::LanguageFeature::kTexelBuffers);
    GlobalVar(Source{{12, 34}}, "G",
              ty.texel_buffer(core::TexelFormat::kRgba32Float, core::Access::kRead));

    EXPECT_FALSE(r()->Resolve());
    EXPECT_EQ(r()->error(),
              "12:34 error: resource variables require '@group' and "
              "'@binding' attributes");
}

}  // namespace
}  // namespace tint::resolver
