// Copyright 2021 The Emscripten Authors.  All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License.  Both these licenses can be
// found in the LICENSE file.

#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <emscripten.h>
#include <emscripten/html5.h>

#define GL_GLEXT_PROTOTYPES
#include <GLES3/gl3.h>

int result = 0;
GLuint sampleQuery = 0;

#define GL_CALL( x ) \
    { \
        x; \
        GLenum error = glGetError(); \
        if( error != GL_NO_ERROR ) { \
            printf( "GL ERROR: %d,  %s\n", (int)error, #x ); \
            result = 1; \
        } \
    } \

void getQueryResult() {
  /* Get the result. It should be nonzero. */
  GLuint any;
  GL_CALL(glGetQueryObjectuiv(sampleQuery, GL_QUERY_RESULT, &any));

  if (!any) return;

  printf("queried any samples passed: %u\n", any);
  emscripten_cancel_main_loop();

  GL_CALL(glDeleteQueries(1, &sampleQuery));

  // result == 0 signals success
  exit(result);
}

GLuint compile_shader(GLenum shaderType, const char *src) {
  GLuint shader = glCreateShader(shaderType);
  GL_CALL(glShaderSource(shader, 1, &src, NULL));
  GL_CALL(glCompileShader(shader));

  GLint isCompiled = 0;
  GL_CALL(glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled));
  assert(isCompiled);
  return shader;
}

GLuint create_program(GLuint vert, GLuint frag) {
   GLuint program = glCreateProgram();
   GL_CALL(glAttachShader(program, vert));
   GL_CALL(glAttachShader(program, frag));
   GL_CALL(glBindAttribLocation(program, 0, "position"));
   GL_CALL(glLinkProgram(program));
   return program;
}

int main() {
  EmscriptenWebGLContextAttributes attrs;
  emscripten_webgl_init_context_attributes(&attrs);

  attrs.enableExtensionsByDefault = 1;
  attrs.majorVersion = 2;
  attrs.minorVersion = 0;

  /* Skip WebGL 2 tests if not supported */
  EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context("#canvas", &attrs);
  if (!context) {
    printf("Skipped: WebGL 2 is not supported.\n");
    return 0;
  }
  emscripten_webgl_make_context_current(context);

  /* Generate a query */
  GL_CALL(glGenQueries(2, &sampleQuery));
  assert(sampleQuery);

  /* Begin it */
  GL_CALL(glBeginQuery(GL_ANY_SAMPLES_PASSED, sampleQuery));

  /* Now, `sampleQuery` should be a query */
  bool isQuery = false;
  GL_CALL(isQuery = glIsQuery(sampleQuery));
  assert(isQuery);

  /* Do a draw to actually have something in the queue */
  GLuint vert = compile_shader(GL_VERTEX_SHADER,
    "#version 300 es\n"
    "in vec4 position;\n"
    "void main() {\n"
    "  gl_Position = position;\n"
    "}\n");
  GLuint frag = compile_shader(GL_FRAGMENT_SHADER,
    "#version 300 es\n"
    "precision lowp float;\n"
    "out vec4 color;\n"
    "void main() {\n"
    "  color = vec4(1.0, 0.3, 0.1, 1.0);\n"
    "}\n");
  GLuint program = create_program(vert, frag);
  GL_CALL(glUseProgram(program));
  const float positions[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
  };
  GLuint vertices;
  GL_CALL(glGenBuffers(1, &vertices));
  GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vertices));
  GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW));
  GL_CALL(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, 0));
  GL_CALL(glEnableVertexAttribArray(0));
  GL_CALL(glClearColor(0.5f, 0.7f, 0.9f, 1.0f));
  GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
  GL_CALL(glDrawArrays(GL_TRIANGLES, 0, 3));

  /* End the query after the draw */
  GL_CALL(glEndQuery(GL_ANY_SAMPLES_PASSED));

  /* In WebGL, query results are available only in the next event loop
     iteration, so this should return false */
  GLuint isAvailable;
  GL_CALL(glGetQueryObjectuiv(sampleQuery, GL_QUERY_RESULT_AVAILABLE, &isAvailable));
  assert(!isAvailable);

  /* Run the main loop to get the result */
  emscripten_set_main_loop(getQueryResult, 0, 0);

  return 99;
}
