/*
 * Copyright 2014 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.
 */

// Built from glbook/hello triange and sdl_ogl, see details there

#include "SDL/SDL.h"
#include "SDL/SDL_image.h"
#include "SDL/SDL_opengl.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

GLuint programObject;
int width = 512;
int height = 256;

GLuint LoadShader ( GLenum type, const char *shaderSrc )
{
   GLuint shader;
   GLint compiled;

   shader = glCreateShader ( type );
   if ( shader == 0 )
   	return 0;

   glShaderSource ( shader, 1, &shaderSrc, NULL );
   glCompileShader ( shader );
   glGetShaderiv ( shader, GL_COMPILE_STATUS, &compiled );
   if ( !compiled )
   {
      GLint infoLen = 0;
      glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen );
      if ( infoLen > 1 )
      {
         char* infoLog = malloc (sizeof(char) * infoLen );
         glGetShaderInfoLog ( shader, infoLen, NULL, infoLog );
         printf ( "Error compiling shader:\n%s\n", infoLog );
         free ( infoLog );
      }
      glDeleteShader ( shader );
      return 0;
   }
   return shader;
}

int Init ()
{
   GLbyte vShaderStr[] =
      "attribute vec4 vPosition;    \n"
      "void main()                  \n"
      "{                            \n"
      "   gl_Position = vPosition;  \n"
      "}                            \n";

   GLbyte fShaderStr[] =
      "precision mediump float;\n"\
      "void main()                                  \n"
      "{                                            \n"
      "  gl_FragColor = vec4 ( 0.0, 0.0, 1.0, 1.0 );\n"
      "}                                            \n";

   GLuint vertexShader;
   GLuint fragmentShader;
   GLint linked;

   vertexShader = LoadShader ( GL_VERTEX_SHADER, (const char *)vShaderStr );
   fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, (const char *)fShaderStr );

   programObject = glCreateProgram ( );
   if ( programObject == 0 )
      return 0;

   glAttachShader ( programObject, vertexShader );
   glAttachShader ( programObject, fragmentShader );
   glBindAttribLocation ( programObject, 0, "vPosition" );
   glLinkProgram ( programObject );
   glGetProgramiv ( programObject, GL_LINK_STATUS, &linked );
   if ( !linked )
   {
      GLint infoLen = 0;
      glGetProgramiv ( programObject, GL_INFO_LOG_LENGTH, &infoLen );
      if ( infoLen > 1 )
      {
         char* infoLog = malloc (sizeof(char) * infoLen );
         glGetProgramInfoLog ( programObject, infoLen, NULL, infoLog );
         printf ( "Error linking program:\n%s\n", infoLog );
         free ( infoLog );
      }
      glDeleteProgram ( programObject );
      return GL_FALSE;
   }

   glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
   return GL_TRUE;
}

static const int kNegativeTest = 0;
static const int kPositiveTest = 1;

///
// Draw a triangle using the shader pair created in Init()
//
void Draw (GLenum accessBits, int testMode)
{
   void *buffer;
   GLfloat vVertices[] = {  0.0f,  0.5f, 0.0f,
                           -0.5f, -0.5f, 0.0f,
                            0.5f, -0.5f, 0.0f };

   // No clientside arrays, so do this in a webgl-friendly manner
   GLuint vertexPosObject;
   glGenBuffers(1, &vertexPosObject);
   glBindBuffer(GL_ARRAY_BUFFER, vertexPosObject);
   glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices), NULL, GL_STATIC_DRAW);
   buffer = glMapBufferRange(
     GL_ARRAY_BUFFER,
     0,
     sizeof(vVertices),
     accessBits
   );
   if (testMode == kPositiveTest && buffer == NULL) {
     fprintf(stderr, "Could not map buffer: %x\n", glGetError());
     return;
   }
   if (testMode == kNegativeTest) {
     // When negative testing, stop after the call to glMapBufferRange.
     return;
   }
   memcpy(buffer, vVertices, sizeof(vVertices));
   if (accessBits & GL_MAP_FLUSH_EXPLICIT_BIT)
   {
      glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, sizeof(vVertices));
   }
   glUnmapBuffer(GL_ARRAY_BUFFER);

   glViewport ( 0, 0, width, height );
   glClear ( GL_COLOR_BUFFER_BIT );
   glUseProgram ( programObject );

   glBindBuffer(GL_ARRAY_BUFFER, vertexPosObject);
   glVertexAttribPointer(0 /* ? */, 3, GL_FLOAT, 0, 0, 0);
   glEnableVertexAttribArray(0);

   glDrawArrays ( GL_TRIANGLES, 0, 3 );
}

void Verify() {
  unsigned char *data = malloc(width*height*4 + 16);
  int *last = (int*)(data + width*height*4 - 4);
  int *after = (int*)(data + width*height*4);
  *last = 0xdeadbeef;
  *after = 0x12345678;
  glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
  assert(*last != 0xdeadbeef); // should overwrite the buffer to the end
  assert(*after == 0x12345678); // nothing should be written afterwards!
  // Should see some blue, and nothing else
  int seen = 0;
  int ok = 1;
  for (int x = 0; x < width*height; x++) {
    seen = seen || data[x*4+2] != 0;
    ok = ok && (data[x*4+0] == 0);
    ok = ok && (data[x*4+1] == 0);
  }
  assert(seen);
  assert(ok);
}

int main(int argc, char *argv[])
{
  SDL_Surface *screen;
  if ( SDL_Init(SDL_INIT_VIDEO) != 0 ) {
    printf("Unable to initialize SDL: %s\n", SDL_GetError());
    return 1;
  }

  screen = SDL_SetVideoMode(width, height, 32, SDL_OPENGL);
  if (!screen) {
    printf("Unable to set video mode: %s\n", SDL_GetError());
    return 1;
  }

  Init();

  // Positive Tests.

  // Test both invalidate modes and write.
  Draw(GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_WRITE_BIT, kPositiveTest);
  Verify();

  Draw(GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_WRITE_BIT, kPositiveTest);
  Verify();

  // Test both invalidate modes and write with flush explicit.
  Draw(GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_WRITE_BIT, kPositiveTest);
  Verify();

  Draw(GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_WRITE_BIT, kPositiveTest);
  Verify();


  // Negative tests.

  // Test write bit alone is unsupported.
  Draw(GL_MAP_WRITE_BIT, kNegativeTest);

  // Test read bit is unsupported.
  Draw(GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_WRITE_BIT | GL_MAP_READ_BIT, kNegativeTest);

  // Test unsynchronized bit is unsupported.
  Draw(GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, kNegativeTest);

  return 0;
}

