// Copyright 2020 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 <array>

#include "gmock/gmock.h"

#include "src/tint/lang/core/type/depth_multisampled_texture.h"
#include "src/tint/lang/core/type/depth_texture.h"
#include "src/tint/lang/core/type/multisampled_texture.h"
#include "src/tint/lang/core/type/sampled_texture.h"
#include "src/tint/lang/core/type/sampler.h"
#include "src/tint/lang/core/type/storage_texture.h"
#include "src/tint/lang/core/type/texture_dimension.h"
#include "src/tint/lang/msl/writer/ast_printer/helper_test.h"
#include "src/tint/utils/text/string_stream.h"

// Done except for two questions on:
// TEST_F(MslASTPrinterTest, EmitType_Struct_WithAttribute) {
// TEST_F(MslASTPrinterTest, EmitType_Pointer) {

namespace tint::msl::writer {
namespace {

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

void FormatMSLField(StringStream& out,
                    const char* addr,
                    const char* type,
                    size_t array_count,
                    const char* name) {
    out << "  /* " << std::string(addr) << " */ ";
    if (array_count == 0) {
        out << type << " ";
    } else {
        out << "tint_array<" << type << ", " << std::to_string(array_count) << "> ";
    }
    out << name << ";\n";
}

#define CHECK_TYPE_SIZE_AND_ALIGN(TYPE, SIZE, ALIGN)      \
    static_assert(sizeof(TYPE) == SIZE, "Bad type size"); \
    static_assert(alignof(TYPE) == ALIGN, "Bad type alignment")

// Declare C++ types that match the size and alignment of the types of the same
// name in MSL.
#define DECLARE_TYPE(NAME, SIZE, ALIGN) \
    struct alignas(ALIGN) NAME {        \
        uint8_t _[SIZE];                \
    };                                  \
    CHECK_TYPE_SIZE_AND_ALIGN(NAME, SIZE, ALIGN)

// Size and alignments taken from the MSL spec:
// https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
DECLARE_TYPE(float2, 8, 8);
DECLARE_TYPE(float3, 12, 4);
DECLARE_TYPE(float4, 16, 16);
DECLARE_TYPE(float2x2, 16, 8);
DECLARE_TYPE(float2x3, 32, 16);
DECLARE_TYPE(float2x4, 32, 16);
DECLARE_TYPE(float3x2, 24, 8);
DECLARE_TYPE(float3x3, 48, 16);
DECLARE_TYPE(float3x4, 48, 16);
DECLARE_TYPE(float4x2, 32, 8);
DECLARE_TYPE(float4x3, 64, 16);
DECLARE_TYPE(float4x4, 64, 16);
DECLARE_TYPE(half2, 4, 4);
DECLARE_TYPE(packed_half3, 6, 2);
DECLARE_TYPE(half4, 8, 8);
DECLARE_TYPE(half2x2, 8, 4);
DECLARE_TYPE(half2x3, 16, 8);
DECLARE_TYPE(half2x4, 16, 8);
DECLARE_TYPE(half3x2, 12, 4);
DECLARE_TYPE(half3x3, 24, 8);
DECLARE_TYPE(half3x4, 24, 8);
DECLARE_TYPE(half4x2, 16, 4);
DECLARE_TYPE(half4x3, 32, 8);
DECLARE_TYPE(half4x4, 32, 8);
using uint = unsigned int;

using MslASTPrinterTest = TestHelper;

// MslPrinterTest.EmitType_Array
TEST_F(MslASTPrinterTest, EmitType_Array) {
    auto arr = ty.array<bool, 4>();
    ast::Type type = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type;

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type))) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "tint_array<bool, 4>");
}

// MslPrinterTest.EmitType_ArrayOfArray
TEST_F(MslASTPrinterTest, EmitType_ArrayOfArray) {
    auto a = ty.array<bool, 4>();
    auto b = ty.array(a, 5_u);
    ast::Type type = GlobalVar("G", b, core::AddressSpace::kPrivate)->type;

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type))) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "tint_array<tint_array<bool, 4>, 5>");
}

// MslPrinterTest.EmitType_ArrayOfArrayOfArray
TEST_F(MslASTPrinterTest, EmitType_ArrayOfArrayOfArray) {
    auto a = ty.array<bool, 4>();
    auto b = ty.array(a, 5_u);
    auto c = ty.array(b, 6_u);
    ast::Type type = GlobalVar("G", c, core::AddressSpace::kPrivate)->type;

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type))) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "tint_array<tint_array<tint_array<bool, 4>, 5>, 6>");
}

// TODO(dsinclair): Port? Not sure if this is relevant ...
TEST_F(MslASTPrinterTest, EmitType_Array_WithoutName) {
    auto arr = ty.array<bool, 4>();
    ast::Type type = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type;

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type))) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "tint_array<bool, 4>");
}

// MslPrinterTest.EmitType_RuntimeArray
TEST_F(MslASTPrinterTest, EmitType_RuntimeArray) {
    auto arr = ty.array<bool, 1>();
    ast::Type type = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type;

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type))) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "tint_array<bool, 1>");
}

// MSLPrinterTest.EmitType_Bool
TEST_F(MslASTPrinterTest, EmitType_Bool) {
    auto* bool_ = create<core::type::Bool>();

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, bool_)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "bool");
}

// MSLPrintertest.EmitType_F32
TEST_F(MslASTPrinterTest, EmitType_F32) {
    auto* f32 = create<core::type::F32>();

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, f32)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "float");
}

// MSLPrinterTest.EmitType_F16
TEST_F(MslASTPrinterTest, EmitType_F16) {
    auto* f16 = create<core::type::F16>();

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, f16)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "half");
}

// MSLPrinterTest.EmitType_I32
TEST_F(MslASTPrinterTest, EmitType_I32) {
    auto* i32 = create<core::type::I32>();

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, i32)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "int");
}

// MSLPrinterTest.EmitType_Matrix_F32
TEST_F(MslASTPrinterTest, EmitType_Matrix_F32) {
    auto* f32 = create<core::type::F32>();
    auto* vec3 = create<core::type::Vector>(f32, 3u);
    auto* mat2x3 = create<core::type::Matrix>(vec3, 2u);

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, mat2x3)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "float2x3");
}

// MSLPrinterTest.EmitType_Matrix_F16
TEST_F(MslASTPrinterTest, EmitType_Matrix_F16) {
    auto* f16 = create<core::type::F16>();
    auto* vec3 = create<core::type::Vector>(f16, 3u);
    auto* mat2x3 = create<core::type::Matrix>(vec3, 2u);

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, mat2x3)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "half2x3");
}

// TODO(dsinclair): Not sure if this is relevant? MslPrinterTest.EmitType_Pointer_Workgroup
TEST_F(MslASTPrinterTest, EmitType_Pointer) {
    auto* f32 = create<core::type::F32>();
    auto* p =
        create<core::type::Pointer>(core::AddressSpace::kWorkgroup, f32, core::Access::kReadWrite);

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, p)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "threadgroup float*");
}

TEST_F(MslASTPrinterTest, EmitType_Struct) {
    auto* s = Structure("S", Vector{
                                 Member("a", ty.i32()),
                                 Member("b", ty.f32()),
                             });

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(s))) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "S");
}

// MSLPrinterTest.EmitType_Struct
TEST_F(MslASTPrinterTest, EmitType_StructDecl) {
    auto* s = Structure("S", Vector{
                                 Member("a", ty.i32()),
                                 Member("b", ty.f32()),
                             });

    ASTPrinter& gen = Build();

    tint::TextGenerator::TextBuffer buf;
    auto* str = program->TypeOf(s)->As<core::type::Struct>();
    ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
    EXPECT_EQ(buf.String(), R"(struct S {
  int a;
  float b;
};
)");
}

// MSLPrinterTest.EmitType_Struct_Layout_NonComposites
TEST_F(MslASTPrinterTest, EmitType_Struct_Layout_NonComposites) {
    auto* s =
        Structure("S", Vector{
                           Member("a", ty.i32(), Vector{MemberSize(32_a)}),
                           Member("b", ty.f32(), Vector{MemberAlign(128_i), MemberSize(128_a)}),
                           Member("c", ty.vec2<f32>()),
                           Member("d", ty.u32()),
                           Member("e", ty.vec3<f32>()),
                           Member("f", ty.u32()),
                           Member("g", ty.vec4<f32>()),
                           Member("h", ty.u32()),
                           Member("i", ty.mat2x2<f32>()),
                           Member("j", ty.u32()),
                           Member("k", ty.mat2x3<f32>()),
                           Member("l", ty.u32()),
                           Member("m", ty.mat2x4<f32>()),
                           Member("n", ty.u32()),
                           Member("o", ty.mat3x2<f32>()),
                           Member("p", ty.u32()),
                           Member("q", ty.mat3x3<f32>()),
                           Member("r", ty.u32()),
                           Member("s", ty.mat3x4<f32>()),
                           Member("t", ty.u32()),
                           Member("u", ty.mat4x2<f32>()),
                           Member("v", ty.u32()),
                           Member("w", ty.mat4x3<f32>()),
                           Member("x", ty.u32()),
                           Member("y", ty.mat4x4<f32>()),
                           Member("z", ty.f32()),
                       });

    ast::Type type = GlobalVar("G", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead,
                               Binding(0_a), Group(0_a))
                         ->type;

    ASTPrinter& gen = Build();

    tint::TextGenerator::TextBuffer buf;
    auto* str = program->TypeOf(type)->As<core::type::Struct>();
    ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();

    // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
    // for each field of the structure s.
#define ALL_FIELDS()                       \
    FIELD(0x0000, int, 0, a)               \
    FIELD(0x0004, int8_t, 124, tint_pad)   \
    FIELD(0x0080, float, 0, b)             \
    FIELD(0x0084, int8_t, 124, tint_pad_1) \
    FIELD(0x0100, float2, 0, c)            \
    FIELD(0x0108, uint, 0, d)              \
    FIELD(0x010c, int8_t, 4, tint_pad_2)   \
    FIELD(0x0110, float3, 0, e)            \
    FIELD(0x011c, uint, 0, f)              \
    FIELD(0x0120, float4, 0, g)            \
    FIELD(0x0130, uint, 0, h)              \
    FIELD(0x0134, int8_t, 4, tint_pad_3)   \
    FIELD(0x0138, float2x2, 0, i)          \
    FIELD(0x0148, uint, 0, j)              \
    FIELD(0x014c, int8_t, 4, tint_pad_4)   \
    FIELD(0x0150, float2x3, 0, k)          \
    FIELD(0x0170, uint, 0, l)              \
    FIELD(0x0174, int8_t, 12, tint_pad_5)  \
    FIELD(0x0180, float2x4, 0, m)          \
    FIELD(0x01a0, uint, 0, n)              \
    FIELD(0x01a4, int8_t, 4, tint_pad_6)   \
    FIELD(0x01a8, float3x2, 0, o)          \
    FIELD(0x01c0, uint, 0, p)              \
    FIELD(0x01c4, int8_t, 12, tint_pad_7)  \
    FIELD(0x01d0, float3x3, 0, q)          \
    FIELD(0x0200, uint, 0, r)              \
    FIELD(0x0204, int8_t, 12, tint_pad_8)  \
    FIELD(0x0210, float3x4, 0, s)          \
    FIELD(0x0240, uint, 0, t)              \
    FIELD(0x0244, int8_t, 4, tint_pad_9)   \
    FIELD(0x0248, float4x2, 0, u)          \
    FIELD(0x0268, uint, 0, v)              \
    FIELD(0x026c, int8_t, 4, tint_pad_10)  \
    FIELD(0x0270, float4x3, 0, w)          \
    FIELD(0x02b0, uint, 0, x)              \
    FIELD(0x02b4, int8_t, 12, tint_pad_11) \
    FIELD(0x02c0, float4x4, 0, y)          \
    FIELD(0x0300, float, 0, z)             \
    FIELD(0x0304, int8_t, 124, tint_pad_12)

    // Check that the generated string is as expected.
    StringStream expect;
    expect << "struct S {\n";
#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
    FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
    ALL_FIELDS()
#undef FIELD
    expect << "};\n";
    EXPECT_EQ(buf.String(), expect.str());

    // 1.4 Metal and C++14
    // The Metal programming language is a C++14-based Specification with
    // extensions and restrictions. Refer to the C++14 Specification (also known
    // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
    // description of the language grammar.
    //
    // Tint is written in C++14, so use the compiler to verify the generated
    // layout is as expected for C++14 / MSL.
    {
        struct S {
#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1> NAME;
            ALL_FIELDS()
#undef FIELD
        };

#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
    EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
        ALL_FIELDS()
#undef FIELD
    }
#undef ALL_FIELDS
}

// MSLPrinterTest.EmitType_Struct_Layout_Structures
TEST_F(MslASTPrinterTest, EmitType_Struct_Layout_Structures) {
    // inner_x: size(1024), align(512)
    auto* inner_x = Structure("inner_x", Vector{
                                             Member("a", ty.i32()),
                                             Member("b", ty.f32(), Vector{MemberAlign(512_i)}),
                                         });

    // inner_y: size(516), align(4)
    auto* inner_y = Structure("inner_y", Vector{
                                             Member("a", ty.i32(), Vector{MemberSize(512_a)}),
                                             Member("b", ty.f32()),
                                         });

    auto* s = Structure("S", Vector{
                                 Member("a", ty.i32()),
                                 Member("b", ty.Of(inner_x)),
                                 Member("c", ty.f32()),
                                 Member("d", ty.Of(inner_y)),
                                 Member("e", ty.f32()),
                             });

    ast::Type type = GlobalVar("G", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead,
                               Binding(0_a), Group(0_a))
                         ->type;

    ASTPrinter& gen = Build();

    tint::TextGenerator::TextBuffer buf;
    auto* str = program->TypeOf(type)->As<core::type::Struct>();
    ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();

    // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
    // for each field of the structure s.
#define ALL_FIELDS()                     \
    FIELD(0x0000, int, 0, a)             \
    FIELD(0x0004, int8_t, 508, tint_pad) \
    FIELD(0x0200, inner_x, 0, b)         \
    FIELD(0x0600, float, 0, c)           \
    FIELD(0x0604, inner_y, 0, d)         \
    FIELD(0x0808, float, 0, e)           \
    FIELD(0x080c, int8_t, 500, tint_pad_1)

    // Check that the generated string is as expected.
    StringStream expect;
    expect << "struct S {\n";
#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
    FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
    ALL_FIELDS()
#undef FIELD
    expect << "};\n";
    EXPECT_EQ(buf.String(), expect.str());

    // 1.4 Metal and C++14
    // The Metal programming language is a C++14-based Specification with
    // extensions and restrictions. Refer to the C++14 Specification (also known
    // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
    // description of the language grammar.
    //
    // Tint is written in C++14, so use the compiler to verify the generated
    // layout is as expected for C++14 / MSL.
    {
        struct inner_x {
            uint32_t a;
            alignas(512) float b;
        };
        CHECK_TYPE_SIZE_AND_ALIGN(inner_x, 1024, 512);

        struct inner_y {
            uint32_t a[128];
            float b;
        };
        CHECK_TYPE_SIZE_AND_ALIGN(inner_y, 516, 4);

        struct S {
#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1> NAME;
            ALL_FIELDS()
#undef FIELD
        };

#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
    EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
        ALL_FIELDS()
#undef FIELD
    }

#undef ALL_FIELDS
}

// MSLPrinterTest.EmitType_Struct_Layout_ArrayDefaultStride
TEST_F(MslASTPrinterTest, EmitType_Struct_Layout_ArrayDefaultStride) {
    // inner: size(1024), align(512)
    auto* inner = Structure("inner", Vector{
                                         Member("a", ty.i32()),
                                         Member("b", ty.f32(), Vector{MemberAlign(512_i)}),
                                     });

    // array_x: size(28), align(4)
    auto array_x = ty.array<f32, 7>();

    // array_y: size(4096), align(512)
    auto array_y = ty.array(ty.Of(inner), 4_u);

    // array_z: size(4), align(4)
    auto array_z = ty.array<f32>();

    auto* s = Structure("S", Vector{
                                 Member("a", ty.i32()),
                                 Member("b", array_x),
                                 Member("c", ty.f32()),
                                 Member("d", array_y),
                                 Member("e", ty.f32()),
                                 Member("f", array_z),
                             });

    ast::Type type = GlobalVar("G", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead,
                               Binding(0_a), Group(0_a))
                         ->type;

    ASTPrinter& gen = Build();

    tint::TextGenerator::TextBuffer buf;
    auto* str = program->TypeOf(type)->As<core::type::Struct>();
    ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();

    // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
    // for each field of the structure s.
#define ALL_FIELDS()                     \
    FIELD(0x0000, int, 0, a)             \
    FIELD(0x0004, float, 7, b)           \
    FIELD(0x0020, float, 0, c)           \
    FIELD(0x0024, int8_t, 476, tint_pad) \
    FIELD(0x0200, inner, 4, d)           \
    FIELD(0x1200, float, 0, e)           \
    FIELD(0x1204, float, 1, f)           \
    FIELD(0x1208, int8_t, 504, tint_pad_1)

    // Check that the generated string is as expected.
    StringStream expect;
    expect << "struct S {\n";
#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
    FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
    ALL_FIELDS()
#undef FIELD
    expect << "};\n";
    EXPECT_EQ(buf.String(), expect.str());

    // 1.4 Metal and C++14
    // The Metal programming language is a C++14-based Specification with
    // extensions and restrictions. Refer to the C++14 Specification (also known
    // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
    // description of the language grammar.
    //
    // Tint is written in C++14, so use the compiler to verify the generated
    // layout is as expected for C++14 / MSL.
    {
        struct inner {
            uint32_t a;
            alignas(512) float b;
        };
        CHECK_TYPE_SIZE_AND_ALIGN(inner, 1024, 512);

        // array_x: size(28), align(4)
        using array_x = std::array<float, 7>;
        CHECK_TYPE_SIZE_AND_ALIGN(array_x, 28, 4);

        // array_y: size(4096), align(512)
        using array_y = std::array<inner, 4>;
        CHECK_TYPE_SIZE_AND_ALIGN(array_y, 4096, 512);

        // array_z: size(4), align(4)
        using array_z = std::array<float, 1>;
        CHECK_TYPE_SIZE_AND_ALIGN(array_z, 4, 4);

        struct S {
#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1> NAME;
            ALL_FIELDS()
#undef FIELD
        };

#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
    EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
        ALL_FIELDS()
#undef FIELD
    }

#undef ALL_FIELDS
}

// MslPrinterTest.EmitType_Struct_Layout_ArrayVec3DefaultStride
TEST_F(MslASTPrinterTest, EmitType_Struct_Layout_ArrayVec3DefaultStride) {
    // array: size(64), align(16)
    auto array = ty.array<vec3<f32>, 4>();

    auto* s = Structure("S", Vector{
                                 Member("a", ty.i32()),
                                 Member("b", array),
                                 Member("c", ty.i32()),
                             });

    ast::Type type = GlobalVar("G", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead,
                               Binding(0_a), Group(0_a))
                         ->type;

    ASTPrinter& gen = Build();

    tint::TextGenerator::TextBuffer buf;
    auto* str = program->TypeOf(type)->As<core::type::Struct>();
    ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();

    // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
    // for each field of the structure s.
#define ALL_FIELDS()                    \
    FIELD(0x0000, int, 0, a)            \
    FIELD(0x0004, int8_t, 12, tint_pad) \
    FIELD(0x0010, float3, 4, b)         \
    FIELD(0x0050, int, 0, c)            \
    FIELD(0x0054, int8_t, 12, tint_pad_1)

    // Check that the generated string is as expected.
    StringStream expect;
    expect << "struct S {\n";
#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
    FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
    ALL_FIELDS()
#undef FIELD
    expect << "};\n";
    EXPECT_EQ(buf.String(), expect.str());
}

// MslPrinterTest.AttemptTintPadSymbolCollision
TEST_F(MslASTPrinterTest, AttemptTintPadSymbolCollision) {
    auto* s = Structure(
        "S", Vector{
                 // uses symbols tint_pad_[0..9] and tint_pad_[20..35]
                 Member("tint_pad_2", ty.i32(), Vector{MemberSize(32_a)}),
                 Member("tint_pad_20", ty.f32(), Vector{MemberAlign(128_i), MemberSize(128_u)}),
                 Member("tint_pad_33", ty.vec2<f32>()),
                 Member("tint_pad_1", ty.u32()),
                 Member("tint_pad_3", ty.vec3<f32>()),
                 Member("tint_pad_7", ty.u32()),
                 Member("tint_pad_25", ty.vec4<f32>()),
                 Member("tint_pad_5", ty.u32()),
                 Member("tint_pad_27", ty.mat2x2<f32>()),
                 Member("tint_pad_24", ty.u32()),
                 Member("tint_pad_23", ty.mat2x3<f32>()),
                 Member("tint_pad", ty.u32()),
                 Member("tint_pad_8", ty.mat2x4<f32>()),
                 Member("tint_pad_26", ty.u32()),
                 Member("tint_pad_29", ty.mat3x2<f32>()),
                 Member("tint_pad_6", ty.u32()),
                 Member("tint_pad_22", ty.mat3x3<f32>()),
                 Member("tint_pad_32", ty.u32()),
                 Member("tint_pad_34", ty.mat3x4<f32>()),
                 Member("tint_pad_35", ty.u32()),
                 Member("tint_pad_30", ty.mat4x2<f32>()),
                 Member("tint_pad_9", ty.u32()),
                 Member("tint_pad_31", ty.mat4x3<f32>()),
                 Member("tint_pad_28", ty.u32()),
                 Member("tint_pad_4", ty.mat4x4<f32>()),
                 Member("tint_pad_21", ty.f32()),
             });

    ast::Type type = GlobalVar("G", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead,
                               Binding(0_a), Group(0_a))
                         ->type;

    ASTPrinter& gen = Build();

    tint::TextGenerator::TextBuffer buf;
    auto* str = program->TypeOf(type)->As<core::type::Struct>();
    ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
    EXPECT_EQ(buf.String(), R"(struct S {
  /* 0x0000 */ int tint_pad_2;
  /* 0x0004 */ tint_array<int8_t, 124> tint_pad_10;
  /* 0x0080 */ float tint_pad_20;
  /* 0x0084 */ tint_array<int8_t, 124> tint_pad_11;
  /* 0x0100 */ float2 tint_pad_33;
  /* 0x0108 */ uint tint_pad_1;
  /* 0x010c */ tint_array<int8_t, 4> tint_pad_12;
  /* 0x0110 */ float3 tint_pad_3;
  /* 0x011c */ uint tint_pad_7;
  /* 0x0120 */ float4 tint_pad_25;
  /* 0x0130 */ uint tint_pad_5;
  /* 0x0134 */ tint_array<int8_t, 4> tint_pad_13;
  /* 0x0138 */ float2x2 tint_pad_27;
  /* 0x0148 */ uint tint_pad_24;
  /* 0x014c */ tint_array<int8_t, 4> tint_pad_14;
  /* 0x0150 */ float2x3 tint_pad_23;
  /* 0x0170 */ uint tint_pad;
  /* 0x0174 */ tint_array<int8_t, 12> tint_pad_15;
  /* 0x0180 */ float2x4 tint_pad_8;
  /* 0x01a0 */ uint tint_pad_26;
  /* 0x01a4 */ tint_array<int8_t, 4> tint_pad_16;
  /* 0x01a8 */ float3x2 tint_pad_29;
  /* 0x01c0 */ uint tint_pad_6;
  /* 0x01c4 */ tint_array<int8_t, 12> tint_pad_17;
  /* 0x01d0 */ float3x3 tint_pad_22;
  /* 0x0200 */ uint tint_pad_32;
  /* 0x0204 */ tint_array<int8_t, 12> tint_pad_18;
  /* 0x0210 */ float3x4 tint_pad_34;
  /* 0x0240 */ uint tint_pad_35;
  /* 0x0244 */ tint_array<int8_t, 4> tint_pad_19;
  /* 0x0248 */ float4x2 tint_pad_30;
  /* 0x0268 */ uint tint_pad_9;
  /* 0x026c */ tint_array<int8_t, 4> tint_pad_36;
  /* 0x0270 */ float4x3 tint_pad_31;
  /* 0x02b0 */ uint tint_pad_28;
  /* 0x02b4 */ tint_array<int8_t, 12> tint_pad_37;
  /* 0x02c0 */ float4x4 tint_pad_4;
  /* 0x0300 */ float tint_pad_21;
  /* 0x0304 */ tint_array<int8_t, 124> tint_pad_38;
};
)");
}

// TODO(dsinclair): Missing from IR tests, but test also doesn't look right.
TEST_F(MslASTPrinterTest, EmitType_Struct_WithAttribute) {
    auto* s = Structure("S", Vector{
                                 Member("a", ty.i32()),
                                 Member("b", ty.f32()),
                             });

    ast::Type type = GlobalVar("G", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead,
                               Binding(0_a), Group(0_a))
                         ->type;

    ASTPrinter& gen = Build();

    tint::TextGenerator::TextBuffer buf;
    auto* str = program->TypeOf(type)->As<core::type::Struct>();
    ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
    EXPECT_EQ(buf.String(), R"(struct S {
  /* 0x0000 */ int a;
  /* 0x0004 */ float b;
};
)");
}

// MSLPrinterTest.EmitType_U32
TEST_F(MslASTPrinterTest, EmitType_U32) {
    auto* u32 = create<core::type::U32>();

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, u32)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "uint");
}

// MSLPrinterTest.EmitType_Vector
TEST_F(MslASTPrinterTest, EmitType_Vector) {
    auto* f32 = create<core::type::F32>();
    auto* vec3 = create<core::type::Vector>(f32, 3u);

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, vec3)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "float3");
}

// MSLPrinterTest.EmitType_Void
TEST_F(MslASTPrinterTest, EmitType_Void) {
    auto* void_ = create<core::type::Void>();

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, void_)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "void");
}

// MSLPrinterTest.EmitType_Sampler
TEST_F(MslASTPrinterTest, EmitType_Sampler) {
    auto* sampler = create<core::type::Sampler>(core::type::SamplerKind::kSampler);

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, sampler)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "sampler");
}

// MSLPrinterTest.EmitType_SamplerComparison
TEST_F(MslASTPrinterTest, EmitType_SamplerComparison) {
    auto* sampler = create<core::type::Sampler>(core::type::SamplerKind::kComparisonSampler);

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, sampler)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "sampler");
}

struct MslDepthTextureData {
    core::type::TextureDimension dim;
    std::string result;
};
inline std::ostream& operator<<(std::ostream& out, MslDepthTextureData data) {
    StringStream str;
    str << data.dim;
    out << str.str();
    return out;
}
using MslDepthTexturesTest = TestParamHelper<MslDepthTextureData>;
// MslPrinterDepthTexturesTest.Emit
TEST_P(MslDepthTexturesTest, Emit) {
    auto params = GetParam();

    core::type::DepthTexture s(params.dim);

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, &s)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), params.result);
}
INSTANTIATE_TEST_SUITE_P(
    MslASTPrinterTest,
    MslDepthTexturesTest,
    testing::Values(MslDepthTextureData{core::type::TextureDimension::k2d,
                                        "depth2d<float, access::sample>"},
                    MslDepthTextureData{core::type::TextureDimension::k2dArray,
                                        "depth2d_array<float, access::sample>"},
                    MslDepthTextureData{core::type::TextureDimension::kCube,
                                        "depthcube<float, access::sample>"},
                    MslDepthTextureData{core::type::TextureDimension::kCubeArray,
                                        "depthcube_array<float, access::sample>"}));

using MslDepthMultisampledTexturesTest = TestHelper;
// MSLPrinterTest.EmitType_DepthMultisampledTexture
TEST_F(MslDepthMultisampledTexturesTest, Emit) {
    core::type::DepthMultisampledTexture s(core::type::TextureDimension::k2d);

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, &s)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "depth2d_ms<float, access::read>");
}

struct MslTextureData {
    core::type::TextureDimension dim;
    std::string result;
};
inline std::ostream& operator<<(std::ostream& out, MslTextureData data) {
    StringStream str;
    str << data.dim;
    out << str.str();
    return out;
}
using MslSampledtexturesTest = TestParamHelper<MslTextureData>;
// MslPrinterSampledTexturesTest.Emit
TEST_P(MslSampledtexturesTest, Emit) {
    auto params = GetParam();

    auto* f32 = create<core::type::F32>();
    auto* s = create<core::type::SampledTexture>(params.dim, f32);

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, s)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), params.result);
}
INSTANTIATE_TEST_SUITE_P(
    MslASTPrinterTest,
    MslSampledtexturesTest,
    testing::Values(
        MslTextureData{core::type::TextureDimension::k1d, "texture1d<float, access::sample>"},
        MslTextureData{core::type::TextureDimension::k2d, "texture2d<float, access::sample>"},
        MslTextureData{core::type::TextureDimension::k2dArray,
                       "texture2d_array<float, access::sample>"},
        MslTextureData{core::type::TextureDimension::k3d, "texture3d<float, access::sample>"},
        MslTextureData{core::type::TextureDimension::kCube, "texturecube<float, access::sample>"},
        MslTextureData{core::type::TextureDimension::kCubeArray,
                       "texturecube_array<float, access::sample>"}));

// MslPrinterTest.Emit_TypeMultisampledTexture
TEST_F(MslASTPrinterTest, Emit_TypeMultisampledTexture) {
    auto* u32 = create<core::type::U32>();
    auto* ms = create<core::type::MultisampledTexture>(core::type::TextureDimension::k2d, u32);

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, ms)) << gen.Diagnostics();
    EXPECT_EQ(out.str(), "texture2d_ms<uint, access::read>");
}

struct MslStorageTextureData {
    core::type::TextureDimension dim;
    std::string result;
};
inline std::ostream& operator<<(std::ostream& out, MslStorageTextureData data) {
    StringStream str;
    str << data.dim;
    return out << str.str();
}
using MslStorageTexturesTest = TestParamHelper<MslStorageTextureData>;
// MslPrinterStorageTexturesTest.Emit
TEST_P(MslStorageTexturesTest, Emit) {
    auto params = GetParam();

    auto s = ty.storage_texture(params.dim, core::TexelFormat::kR32Float, core::Access::kWrite);
    ast::Type type = GlobalVar("test_var", s, Binding(0_a), Group(0_a))->type;

    ASTPrinter& gen = Build();

    StringStream out;
    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type))) << gen.Diagnostics();
    EXPECT_EQ(out.str(), params.result);
}
INSTANTIATE_TEST_SUITE_P(MslASTPrinterTest,
                         MslStorageTexturesTest,
                         testing::Values(MslStorageTextureData{core::type::TextureDimension::k1d,
                                                               "texture1d<float, access::write>"},
                                         MslStorageTextureData{core::type::TextureDimension::k2d,
                                                               "texture2d<float, access::write>"},
                                         MslStorageTextureData{
                                             core::type::TextureDimension::k2dArray,
                                             "texture2d_array<float, access::write>"},
                                         MslStorageTextureData{core::type::TextureDimension::k3d,
                                                               "texture3d<float, access::write>"}));

}  // namespace
}  // namespace tint::msl::writer
