//
// Copyright 2017 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// MSLOutput_test.cpp:
//   Tests for MSL output.
//

#include <regex>
#include "GLSLANG/ShaderLang.h"
#include "angle_gl.h"
#include "gtest/gtest.h"
#include "tests/test_utils/compiler_test.h"

using namespace sh;

class MSLVertexOutputTest : public MatchOutputCodeTest
{
  public:
    MSLVertexOutputTest() : MatchOutputCodeTest(GL_VERTEX_SHADER, SH_MSL_METAL_OUTPUT)
    {
        ShCompileOptions defaultCompileOptions = {};
        setDefaultCompileOptions(defaultCompileOptions);
    }
};

class MSLOutputTest : public MatchOutputCodeTest
{
  public:
    MSLOutputTest() : MatchOutputCodeTest(GL_FRAGMENT_SHADER, SH_MSL_METAL_OUTPUT)
    {
        ShCompileOptions defaultCompileOptions       = {};
        defaultCompileOptions.rescopeGlobalVariables = true;
        setDefaultCompileOptions(defaultCompileOptions);
    }
};

// Test that having dynamic indexing of a vector inside the right hand side of logical or doesn't
// trigger asserts in MSL output.
TEST_F(MSLOutputTest, DynamicIndexingOfVectorOnRightSideOfLogicalOr)
{
    const std::string &shaderString =
        "#version 300 es\n"
        "precision highp float;\n"
        "out vec4 my_FragColor;\n"
        "uniform int u1;\n"
        "void main() {\n"
        "   bvec4 v = bvec4(true, true, true, false);\n"
        "   my_FragColor = vec4(v[u1 + 1] || v[u1]);\n"
        "}\n";
    compile(shaderString);
}

// Test that having an array constructor as a statement doesn't trigger an assert in MSL output.
TEST_F(MSLOutputTest, ArrayConstructorStatement)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;
        out vec4 outColor;
        void main()
        {
            outColor = vec4(0.0, 0.0, 0.0, 1.0);
            float[1](outColor[1]++);
        })";
    compile(shaderString);
}

// Test an array of arrays constructor as a statement.
TEST_F(MSLOutputTest, ArrayOfArraysStatement)
{
    const std::string &shaderString =
        R"(#version 310 es
        precision mediump float;
        out vec4 outColor;
        void main()
        {
            outColor = vec4(0.0, 0.0, 0.0, 1.0);
            float[2][2](float[2](outColor[1]++, 0.0), float[2](1.0, 2.0));
        })";
    compile(shaderString);
}

// Test dynamic indexing of a vector. This makes sure that helper functions added for dynamic
// indexing have correct data that subsequent traversal steps rely on.
TEST_F(MSLOutputTest, VectorDynamicIndexing)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;
        out vec4 outColor;
        uniform int i;
        void main()
        {
            vec4 foo = vec4(0.0, 0.0, 0.0, 1.0);
            foo[i] = foo[i + 1];
            outColor = foo;
        })";
    compile(shaderString);
}

// Test returning an array from a user-defined function. This makes sure that function symbols are
// changed consistently when the user-defined function is changed to have an array out parameter.
TEST_F(MSLOutputTest, ArrayReturnValue)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;
        uniform float u;
        out vec4 outColor;

        float[2] getArray(float f)
        {
            return float[2](f, f + 1.0);
        }

        void main()
        {
            float[2] arr = getArray(u);
            outColor = vec4(arr[0], arr[1], 0.0, 1.0);
        })";
    compile(shaderString);
}

// Test that writing parameters without a name doesn't assert.
TEST_F(MSLOutputTest, ParameterWithNoName)
{
    const std::string &shaderString =
        R"(precision mediump float;

        uniform vec4 v;

        vec4 s(vec4)
        {
            return v;
        }
        void main()
        {
            gl_FragColor = s(v);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, Macro)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;

        #define FOO vec4

        out vec4 outColor;

        void main()
        {
            outColor = FOO(1.0, 2.0, 3.0, 4.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, UniformSimple)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;

        out vec4 outColor;
        uniform float x;

        void main()
        {
            outColor = vec4(x, x, x, x);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, FragmentOutSimple)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;

        out vec4 outColor;

        void main()
        {
            outColor = vec4(1.0, 2.0, 3.0, 4.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, FragmentOutIndirect1)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;

        out vec4 outColor;

        void foo()
        {
            outColor = vec4(1.0, 2.0, 3.0, 4.0);
        }

        void bar()
        {
            foo();
        }

        void main()
        {
            bar();
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, FragmentOutIndirect2)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;

        out vec4 outColor;

        void foo();

        void bar()
        {
            foo();
        }

        void foo()
        {
            outColor = vec4(1.0, 2.0, 3.0, 4.0);
        }

        void main()
        {
            bar();
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, FragmentOutIndirect3)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;

        out vec4 outColor;

        float foo(float x, float y)
        {
            outColor = vec4(x, y, 3.0, 4.0);
            return 7.0;
        }

        float bar(float x)
        {
            return foo(x, 2.0);
        }

        float baz()
        {
            return 13.0;
        }

        float identity(float x)
        {
            return x;
        }

        void main()
        {
            identity(bar(baz()));
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, VertexInOut)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;
        in float in0;
        out float out0;
        void main()
        {
            out0 = in0;
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, SymbolSharing)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;

        out vec4 outColor;

        struct Foo {
            float x;
            float y;
        };

        void doFoo(Foo foo, float zw);

        void doFoo(Foo foo, float zw)
        {
            foo.x = foo.y;
            outColor = vec4(foo.x, foo.y, zw, zw);
        }

        void main()
        {
            Foo foo;
            foo.x = 2.0;
            foo.y = 2.0;
            doFoo(foo, 3.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, StructDecl)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;

        out float out0;

        struct Foo {
            float value;
        };

        void main()
        {
            Foo foo;
            out0 = foo.value;
        }
        )";
    compile(shaderString);
}

TEST_F(MSLOutputTest, Structs)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;

        struct Foo {
            float value;
        };

        out vec4 out0;

        struct Bar {
            Foo foo;
        };

        void go();

        uniform UniInstance {
            Bar bar;
            float instance;
        } uniInstance;

        uniform UniGlobal {
            Foo foo;
            float global;
        };

        void main()
        {
            go();
        }

        struct Baz {
            Bar bar;
        } baz;

        void go()
        {
            out0.x = baz.bar.foo.value;
            out0.y = global;
            out0.z = uniInstance.instance;
            out0.w = 0.0;
        }

        )";
    compile(shaderString);
}

TEST_F(MSLOutputTest, KeywordConflict)
{
    const std::string &shaderString =
        R"(#version 300 es
            precision highp float;

        struct fragment {
            float kernel;
        } device;

        struct Foo {
            fragment frag;
        } foo;

        out float vertex;
        float kernel;

        float stage_in(float x)
        {
            return x;
        }

        void metal(float metal, float fragment);
        void metal(float metal, float fragment)
        {
            vertex = metal * fragment * foo.frag.kernel;
        }

        void main()
        {
            metal(stage_in(stage_in(kernel * device.kernel)), foo.frag.kernel);
        })";
    compile(shaderString);
}

TEST_F(MSLVertexOutputTest, Vertex)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;
        void main()
        {
            gl_Position = vec4(1.0,1.0,1.0,1.0);
        })";
    compile(shaderString);
}

TEST_F(MSLVertexOutputTest, LastReturn)
{
    const std::string &shaderString =
        R"(#version 300 es
        in highp vec4 a_position;
        in highp vec4 a_coords;
        out highp vec4 v_color;

        void main (void)
        {
            gl_Position = a_position;
            v_color = vec4(a_coords.xyz, 1.0);
            return;
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, LastReturn)
{
    const std::string &shaderString =
        R"(#version 300 es
        in mediump vec4 v_coords;
        layout(location = 0) out mediump vec4 o_color;

        void main (void)
        {
            o_color = vec4(v_coords.xyz, 1.0);
            return;
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, FragColor)
{
    const std::string &shaderString = R"(
        void main ()
        {
            gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, MatrixIn)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision highp float;

        in mat4 mat;
        out float out0;

        void main()
        {
            out0 = mat[0][0];
        }
        )";
    compile(shaderString);
}

TEST_F(MSLOutputTest, WhileTrue)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        uniform float uf;
        out vec4 my_FragColor;

        void main()
        {
            my_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
            while (true)
            {
                break;
            }
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, ForTrue)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        uniform float uf;
        out vec4 my_FragColor;

        void main()
        {
            my_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
            for (;true;)
            {
                break;
            }
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, ForEmpty)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        uniform float uf;
        out vec4 my_FragColor;

        void main()
        {
            my_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
            for (;;)
            {
                break;
            }
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, ForComplex)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        uniform float uf;
        out vec4 my_FragColor;

        void main()
        {
            my_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
            for (int i = 0, j = 2; i < j; ++i) {
                if (i == 0) continue;
                if (i == 42) break;
                my_FragColor.x += float(i);
            }
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, ForSymbol)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        uniform float uf;
        out vec4 my_FragColor;

        void main()
        {
            bool cond = true;
            for (;cond;)
            {
                my_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
                cond = false;
            }
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, DoWhileSymbol)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        uniform float uf;
        out vec4 my_FragColor;

        void main()
        {
            bool cond = false;
            do
            {
                my_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
            } while (cond);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, AnonymousStruct)
{
    const std::string &shaderString =
        R"(
        precision mediump float;
        struct { vec4 v; } anonStruct;
        void main() {
            anonStruct.v = vec4(0.0,1.0,0.0,1.0);
            gl_FragColor = anonStruct.v;
        })";
    compile(shaderString);
    // TODO(anglebug.com/6395): This success condition is expected to fail now.
    // When WebKit build is able to run the tests, this should be changed to something else.
    //    ASSERT_TRUE(foundInCode(SH_MSL_METAL_OUTPUT, "__unnamed"));
}

TEST_F(MSLOutputTest, GlobalRescopingSimple)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        // Should rescope uf into main

        float uf;
        out vec4 my_FragColor;

        void main()
        {
            uf += 1.0f;
            my_FragColor = vec4(uf, 0.0, 0.0, 1.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, GlobalRescopingNoRescope)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        // Should not rescope any variable

        float uf;
        out vec4 my_FragColor;
        void modifyGlobal()
        {
            uf = 1.0f;
        }
        void main()
        {
            modifyGlobal();
            my_FragColor = vec4(uf, 0.0, 0.0, 1.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, GlobalRescopingInitializer)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        // Should rescope uf into main

        float uf = 1.0f;
        out vec4 my_FragColor;

        void main()
        {
            uf += 1.0;
            my_FragColor = vec4(uf, 0.0, 0.0, 1.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, GlobalRescopingInitializerNoRescope)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        // Should not rescope any variable

        float uf = 1.0f;
        out vec4 my_FragColor;

        void modifyGlobal()
        {
            uf =+ 1.0f;
        }
        void main()
        {
            modifyGlobal();
            my_FragColor = vec4(uf, 0.0, 0.0, 1.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, GlobalRescopingNestedFunction)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        // Should rescope a info modifyGlobal

        float a = 1.0f;
        float uf = 1.0f;
        out vec4 my_FragColor;

        void modifyGlobal()
        {
            uf =+ a;
        }
        void main()
        {
            modifyGlobal();
            my_FragColor = vec4(uf, 0.0, 0.0, 1.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, GlobalRescopingMultipleUses)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        // Should rescope uf into main

        float uf = 1.0f;
        out vec4 my_FragColor;

        void main()
        {
            uf =+ 1.0;
            if (uf > 0.0)
            {
                uf =- 0.5;
            }
            my_FragColor = vec4(uf, 0.0, 0.0, 1.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, GlobalRescopingGloballyReferencedVar)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        // Should rescope uf into main

        const float a = 1.0f;
        float uf = a;
        out vec4 my_FragColor;

        void main()
        {
            my_FragColor = vec4(uf, 0.0, a, 0.0);
        })";
    compile(shaderString);
}

TEST_F(MSLOutputTest, GlobalRescopingDeclarationAfterFunction)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        // Should rescope c and b into main

        float a = 1.0f;
        float c = 1.0f;
        out vec4 my_FragColor;

        void modifyGlobal()
        {
            a =+ 1.0f;
        }

        float b = 1.0f;

        void main()
        {
            modifyGlobal();
            my_FragColor = vec4(a, b, c, 0.0);
        }

        )";
    compile(shaderString);
}

TEST_F(MSLOutputTest, ReusedOutVarName)
{
    const std::string &shaderString =
        R"(#version 300 es
        precision mediump float;

        out vec4 my_FragColor;

        void funcWith1Out(
        out float outC) {
            outC = 1.0;
        }

        void funcWith4Outs(
        out float outA,
        out float outB,
        out float outC,
        out float outD) {
            outA = 1.0;
            outB = 1.0;
            outD = 1.0;
        }


        void main()
        {
            funcWith1Out(my_FragColor.g);
            funcWith4Outs(my_FragColor.r, my_FragColor.g, my_FragColor.b, my_FragColor.a);
        }

        )";
    compile(shaderString);
}