/******************************************************************************
 * 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_State_Trashing, OpenGLGraphicsTest)
{
  static constexpr const char *Description =
      "Ensures that implicit shadowed GL state isn't trashed by initial states or capture overlay.";

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

#define v2f v2f_block \
{                     \
	vec4 pos;           \
	vec4 col;           \
	vec4 uv;            \
}

in v2f vertIn;

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

layout(binding = 0, std140) uniform constsbuf
{
  vec4 tint;
};

uniform vec4 tint2;

void main()
{
	Color = vertIn.col * tint * tint2;
}

)EOSHADER";

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

    GLuint vao = MakeVAO();
    // only time we bind the VAO, to ensure VAO state isn't trashed
    glBindVertexArray(vao);

    GLuint vb = MakeBuffer();
    // only time we bind the array buffer
    glBindBuffer(GL_ARRAY_BUFFER, vb);
    glBufferStorage(GL_ARRAY_BUFFER, sizeof(DefaultTri), NULL, GL_DYNAMIC_STORAGE_BIT);

    GLuint ubo = MakeBuffer();
    // only time we bind the uniform buffer
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    glBufferStorage(GL_UNIFORM_BUFFER, sizeof(DefaultTri), NULL, GL_DYNAMIC_STORAGE_BIT);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo);
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);

    GLuint program = MakeProgram(GLDefaultVertex, pixel);
    glUseProgram(program);

    GLint loc = glGetUniformLocation(program, "tint2");

    uint32_t empty[1024] = {};

    GLuint fbo = MakeFBO();
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // create some things to force different types of initial states
    GLuint texs[] = {MakeTexture(), MakeTexture(), MakeTexture()};
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texs[0]);
    glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGB16F, screenWidth, screenHeight,
                            false);

    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, texs[1]);
    glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, 4, GL_DEPTH_COMPONENT24, screenWidth,
                            screenHeight, 6, false);

    glBindTexture(GL_TEXTURE_3D, texs[2]);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA16F, 64, 48, 16, 0, GL_RGBA, GL_FLOAT, NULL);

    GLuint samp = 0;
    glGenSamplers(1, &samp);

    GLuint pipe = MakePipeline();
    GLuint sepprog = MakeProgram("", GLDefaultPixel);

    // force things to be dirty
    for(int i = 0; i < 100; i++)
    {
      glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(DefaultTri), DefaultTri);
      glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(DefaultTri), DefaultTri);

      glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texs[0], 0);
      glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texs[1], 0, 0);

      glUseProgramStages(pipe, GL_FRAGMENT_SHADER_BIT, sepprog);

      glSamplerParameterf(samp, GL_TEXTURE_LOD_BIAS, 0.0f);

      glEnableVertexAttribArray(0);
    }

    while(Running())
    {
      // forcibly reference all objects to ensure we prepare AND serialise their initial contents
      glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texs[0]);
      glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, texs[1]);
      glBindTexture(GL_TEXTURE_3D, texs[2]);

      glBindFramebuffer(GL_FRAMEBUFFER, fbo);

      glBindProgramPipeline(pipe);
      glBindProgramPipeline(0);

      glBindSampler(6, samp);

      glBindFramebuffer(GL_FRAMEBUFFER, 0);

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

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

      // configure the VAO. If state tracking has been corrupted this won't modify the right VAO
      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(0));
      glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(sizeof(Vec3f)));
      glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V),
                            (void *)(sizeof(Vec3f) + sizeof(Vec4f)));

      glEnableVertexAttribArray(0);
      glEnableVertexAttribArray(1);
      glEnableVertexAttribArray(2);

      // upload the data to the implicit buffer binding - same thing as above this won't modify the
      // right buffer.
      Vec4f tint = Vec4f(1.0f, 1.0f, 1.0f, 1.0f);
      glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(DefaultTri), DefaultTri);
      glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(Vec4f), &tint);

      // set the bare uniform
      glUniform4f(loc, 1.0f, 1.0f, 1.0f, 1.0f);

      glDrawArrays(GL_TRIANGLES, 0, 3);

      // trash everything so we don't get the state saved as initial contents
      glUniform4f(loc, 0.0f, 0.0f, 0.0f, 0.0f);

      glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(DefaultTri), empty);
      glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(DefaultTri), empty);

      glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 64, (void *)(0));
      glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 64, (void *)(0));
      glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 64, (void *)(0));

      glDisableVertexAttribArray(0);
      glDisableVertexAttribArray(1);
      glDisableVertexAttribArray(2);

      Present();
    }

    glDeleteSamplers(1, &samp);

    return 0;
  }
};

REGISTER_TEST();
