/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2019-2022 Baldur Karlsson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include "gl_test.h"

RD_TEST(GL_Vertex_Attr_Zoo, OpenGLGraphicsTest)
{
  static constexpr const char *Description =
      "Draws a triangle but using different kinds of vertex attributes, including doubles, arrays, "
      "and matrices.";

  struct vertin
  {
    int16_t i16[4];
    uint16_t u16[4];
    double df[2];
    float arr0[2];
    float arr1[2];
    float arr2[2];
    float mat0[2];
    float mat1[2];
  };

  std::string vertex = R"EOSHADER(
#version 450 core

layout(location = 0) in vec4 InSNorm;
layout(location = 1) in vec4 InUNorm;
layout(location = 2) in uvec4 InUInt;
layout(location = 3) in dvec2 InDouble;
layout(location = 4) in vec2 InArray[3];
layout(location = 7) in mat2x2 InMatrix;

layout(location = 0) out vec4 OutSNorm;
layout(location = 1) out vec4 OutUNorm;
layout(location = 2) out uvec4 OutUInt;
layout(location = 3) out dvec2 OutDouble;
layout(location = 4) out vec2 OutArray[3];
layout(location = 7) out mat2x2 OutMatrix;

void main()
{
  const vec4 verts[3] = vec4[3](vec4(-0.5, 0.5, 0.0, 1.0), vec4(0.0, -0.5, 0.0, 1.0),
                                vec4(0.5, 0.5, 0.0, 1.0));

  gl_Position = verts[gl_VertexID];

  OutSNorm = InSNorm;
  OutDouble = InDouble;
  OutUInt = InUInt;
  OutUNorm = InUNorm;
  OutArray = InArray;
  OutMatrix = InMatrix;
}

)EOSHADER";

  std::string pixel = R"EOSHADER(
#version 450 core

layout(location = 0) in vec4 InSNorm;
layout(location = 1) in vec4 InUNorm;
layout(location = 2) flat in uvec4 InUInt;
layout(location = 3) flat in dvec2 InDouble;
layout(location = 4) in vec2 InArray[3];
layout(location = 7) in mat2x2 InMatrix;

layout(location = 0, index = 0) out vec4 Color;

void main()
{
  Color = vec4(0, 1.0f, 0, 1);

  // ensure all values are used
  Color.g += abs(InSNorm.x);
  Color.g += InUNorm.x;
  Color.g += float(InUInt.x);
  Color.g += float(InDouble.x);
  Color.g += float(InArray[0].x);
  Color.g += float(InArray[1].x);
  Color.g += float(InArray[2].x);
  Color.g += float(InMatrix[0][0]);
  Color.g += float(InMatrix[1][1]);

  // check values came through correctly

  // SNorm should be in [-1, 1]
  if(clamp(InSNorm, -1.0, 1.0) != InSNorm)
    Color = vec4(0.1f, 0, 0, 1);

  // UNorm should be in [0, 1]
  if(clamp(InUNorm, 0.0, 1.0) != InUNorm)
    Color = vec4(0.2f, 0, 0, 1);

  // Similar for UInt
  if(InUInt.x > 65535 || InUInt.y > 65535 || InUInt.z > 65535 || InUInt.w > 65535)
    Color = vec4(0.3f, 0, 0, 1);

  // doubles are all in range [-10, 10]
  if(clamp(InDouble, -10.0, 10.0) != InDouble)
    Color = vec4(0.4f, 0, 0, 1);
}

)EOSHADER";

  std::string geom = R"EOSHADER(
#version 450 core

layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;

layout(location = 0) in vec4 InSNorm[3];
layout(location = 1) in vec4 InUNorm[3];
layout(location = 2) in uvec4 InUInt[3];
layout(location = 3) in dvec2 InDouble[3];
layout(location = 4) in vec2 InArray[3][3];
layout(location = 7) in mat2x2 InMatrix[3];

layout(location = 0) out vec4 OutSNorm;
layout(location = 1) out vec4 OutUNorm;
layout(location = 2) out uvec4 OutUInt;
layout(location = 3) out dvec2 OutDouble;
layout(location = 4) out vec2 OutArray[3];
layout(location = 7) out mat2x2 OutMatrix;

void main()
{
  for(int i = 0; i < 3; i++)
  {
    gl_Position = vec4(gl_in[i].gl_Position.yx, 0.4f, 1.2f);

    OutSNorm = InSNorm[i];
    OutDouble = InDouble[i];
    OutUInt = InUInt[i];
    OutUNorm = InUNorm[i];
    OutArray = InArray[i];
    OutMatrix = InMatrix[i];

    EmitVertex();
  }
  EndPrimitive();
}

)EOSHADER";

  int main()
  {
    // initialise, create window, create context, etc
    if(!Init())
      return 3;

    GLuint vao = MakeVAO();
    glBindVertexArray(vao);

    vertin triangle[] = {
        {
            {32767, -32768, 32767, -32767},
            {12345, 6789, 1234, 567},
            {9.8765432109, -5.6789012345},
            {1.0f, 2.0f},
            {3.0f, 4.0f},
            {5.0f, 6.0f},
            {7.0, 8.0f},
            {9.0f, 10.0f},
        },
        {
            {32766, -32766, 16000, -16000},
            {56, 7890, 123, 4567},
            {-7.89012345678, 6.54321098765},
            {11.0f, 12.0f},
            {13.0f, 14.0f},
            {15.0f, 16.0f},
            {17.0, 18.0f},
            {19.0f, 20.0f},
        },
        {
            {5, -5, 0, 0},
            {8765, 43210, 987, 65432},
            {0.1234567890123, 4.5678901234},
            {21.0f, 22.0f},
            {23.0f, 24.0f},
            {25.0f, 26.0f},
            {27.0, 28.0f},
            {29.0f, 30.0f},
        },
    };

    GLuint vb = MakeBuffer();
    glBindBuffer(GL_ARRAY_BUFFER, vb);
    glBufferStorage(GL_ARRAY_BUFFER, sizeof(triangle), triangle, 0);

    // R16G16B16A16_SNORM
    glVertexAttribPointer(0, 4, GL_SHORT, GL_TRUE, sizeof(vertin), (void *)offsetof(vertin, i16));
    // R16G16B16A16_UNORM
    glVertexAttribPointer(1, 4, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(vertin),
                          (void *)offsetof(vertin, u16));
    // R16G16B16A16_UINT
    glVertexAttribIPointer(2, 4, GL_UNSIGNED_SHORT, sizeof(vertin), (void *)offsetof(vertin, u16));

    // R64G64_FLOAT
    glVertexAttribLPointer(3, 2, GL_DOUBLE, sizeof(vertin), (void *)offsetof(vertin, df));

    // Array[]
    glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE, sizeof(vertin), (void *)offsetof(vertin, arr0));
    glVertexAttribPointer(5, 2, GL_FLOAT, GL_FALSE, sizeof(vertin), (void *)offsetof(vertin, arr1));
    glVertexAttribPointer(6, 2, GL_FLOAT, GL_FALSE, sizeof(vertin), (void *)offsetof(vertin, arr2));

    // Matrix
    glVertexAttribPointer(7, 2, GL_FLOAT, GL_FALSE, sizeof(vertin), (void *)offsetof(vertin, mat0));
    glVertexAttribPointer(8, 2, GL_FLOAT, GL_FALSE, sizeof(vertin), (void *)offsetof(vertin, mat1));

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);
    glEnableVertexAttribArray(3);
    glEnableVertexAttribArray(4);
    glEnableVertexAttribArray(5);
    glEnableVertexAttribArray(6);
    glEnableVertexAttribArray(7);
    glEnableVertexAttribArray(8);

    GLuint program = MakeProgram(vertex, pixel, geom);

    while(Running())
    {
      float col[] = {0.2f, 0.2f, 0.2f, 1.0f};
      glClearBufferfv(GL_COLOR, 0, col);

      glBindVertexArray(vao);

      glUseProgram(program);

      glViewport(0, 0, GLsizei(screenWidth), GLsizei(screenHeight));

      glDrawArrays(GL_TRIANGLES, 0, 3);

      Present();
    }

    return 0;
  }
};

REGISTER_TEST();
