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

#include <pixelflinger2/pixelflinger2_interface.h>

int ApproximatelyEqual(const Vector4 lhs, const Vector4 rhs, const float t)
{
   if (fabs(lhs.x - rhs.x) > t)
      return 0;
   if (fabs(lhs.y - rhs.y) > t)
      return 0;
   if (fabs(lhs.z - rhs.z) > t)
      return 0;
   if (fabs(lhs.w - rhs.w) > t)
      return 0;
   return 1;
}

extern void * llvmCtx;

void contextless_test()
{
   static const char vsGLSL [] =
      "uniform vec4 uVec4; \n"
      "uniform sampler2D sampler2d; \n"
      "attribute vec4 aPosition; \n"
      "attribute vec4 aTexCoord; \n"
      "varying vec4 vTexCoord; \n"
      "varying vec4 vTexColor; \n"
      "void main() { \n"
      "   gl_Position = aPosition; \n"
      "   vTexCoord = aTexCoord; \n"
      "   vTexColor = texture2D(sampler2d, aTexCoord.zw); \n"
      "   gl_PointSize = 432.0; \n"
      "}";
   gl_shader_t * vs = GGLShaderCreate(GL_VERTEX_SHADER);
   const char * infoLog = NULL;
   if (!GGLShaderCompile(vs, vsGLSL, &infoLog)) {
      printf("GGLShaderCompile vs failed:\n%s\n", infoLog);
      assert(0);
   }
   static const char fsGLSL [] =
      "uniform vec4 uVec4; \n"
      "uniform sampler2D sampler2d; \n"
      "varying vec4 vTexCoord; \n"
      "varying vec4 vTexColor; \n"
      "void main() { \n"
      "   gl_FragColor = texture2D(sampler2d, vTexCoord.zw); \n"
      "}";
   gl_shader_t * fs = GGLShaderCreate(GL_FRAGMENT_SHADER);
   if (!GGLShaderCompile(fs, fsGLSL, &infoLog)) {
      printf("GGLShaderCompile fs failed:\n%s\n", infoLog);
      assert(0);
   }
   gl_shader_program_t * prog = GGLShaderProgramCreate();
   unsigned glError = GL_NO_ERROR;
   glError = GGLShaderAttach(prog, vs);
   assert(GL_NO_ERROR == glError);
   glError = GGLShaderAttach(prog, fs);
   assert(GL_NO_ERROR == glError);
   GGLShaderAttributeBind(prog, 4, "aPosition");
   GGLShaderAttributeBind(prog, 5, "aTexCoord");
   if (!GGLShaderProgramLink(prog, &infoLog)) {
      printf("GGLShaderProgramLink failed:\n%s\n", infoLog);
      assert(0);
   }
//   llvm::LLVMContext * llvmCtx = new llvm::LLVMContext();
   GGLState_t gglState = {0};
   unsigned texels0 [] = {0xff10ffff, 0x22222222, 0x66666666, 0xffffffff};
   GGLTexture_t texture0 = {GL_TEXTURE_2D, GGL_PIXEL_FORMAT_RGBA_8888,
                            2, 2, 1, // width, height, levelCount
                            texels0, GGL_CLAMP_TO_EDGE, GGL_CLAMP_TO_EDGE,
                            GGL_NEAREST, GGL_NEAREST
                           };
   gglState.textureState.textures[0] = texture0;
   gglState.textureState.textureData[0] = gglState.textureState.textures[0].levels;
   gglState.textureState.textureDimensions[0 * 2 + 0] = gglState.textureState.textures[0].width;
   gglState.textureState.textureDimensions[0 * 2 + 1] = gglState.textureState.textures[0].height;
   GGLShaderUse(llvmCtx, &gglState, prog);

   VertexInput_t input = {0, 0, 0, 0};
   input.attributes[4] = VECTOR4_CTR(0,0,0,1);
   input.attributes[5] = VECTOR4_CTR(0,0,0,0);
   VertexOutput_t output = {0};
   GGLProcessVertex(prog, &input, &output, NULL);
   int vTexColor = -1;
   GGLShaderVaryingLocation(prog, "vTexColor", &vTexColor);
   if (vTexColor >= 0) {
      if (memcmp(((Vector4 *)&output) + vTexColor, &VECTOR4_CTR(1,1,16/255.0f,1), sizeof(Vector4))) {
         puts("((Vector4 *)&output)[vTexColor] != Vector4(1,1,0,1)");
         assert(0);
      }
   } else {
      puts("vTexColor < 0");
      assert(0);
   }

   static const char fsGLSL1 [] =
      "uniform vec4 uVec4; \n"
      "uniform sampler2D sampler2d; \n"
      "varying vec4 vTexCoord; \n"
      "varying vec4 vTexColor; \n"
      "void main() { \n"
      "   gl_FragColor = vTexColor; \n"
      "}";
   gl_shader_t * fs1 = GGLShaderCreate(GL_FRAGMENT_SHADER);
   if (!GGLShaderCompile(fs1, fsGLSL1, &infoLog)) {
      printf("GGLShaderCompile fs failed:\n%s\n", infoLog);
      assert(0);
   }
   gl_shader_program_t * prog1 = GGLShaderProgramCreate();
   glError = GGLShaderAttach(prog1, vs);
   assert(GL_NO_ERROR == glError);
   glError = GGLShaderAttach(prog1, fs1);
   assert(GL_NO_ERROR == glError);
   GGLShaderAttributeBind(prog1, 1, "aPosition");
   GGLShaderAttributeBind(prog1, 2, "aTexCoord");
   if (!GGLShaderProgramLink(prog1, &infoLog)) {
      printf("GGLShaderProgramLink failed:\n%s\n", infoLog);
      assert(0);
   }

   GGLShaderUse(llvmCtx, &gglState, prog1);
   VertexInput_t input1 = {0};
   input1.attributes[1] = VECTOR4_CTR(1,1,0,1);
   input1.attributes[2] = VECTOR4_CTR(1,1,0,0);
   VertexOutput_t output1 = {0};
   GGLProcessVertex(prog1, &input1, &output1, NULL);
   int vTexCoord = -1;
   assert(2 == GGLShaderAttributeLocation(prog1, "aTexCoord"));
   GGLShaderVaryingLocation(prog1, "vTexCoord", &vTexCoord);
   if (vTexCoord >= 0) {
      if (memcmp(((Vector4 *)&output1) + vTexCoord, input1.attributes + 2, sizeof(Vector4))) {
         puts("((Vector4 *)&output1)[vTexCoord] != input1.attributes[1]");
         assert(0);
      }
   } else {
      puts("vTexCoord < 0");
      assert(0);
   }

   puts("***\n finished contextless_test \n***");

   GGLShaderProgramDelete(prog);
   GGLShaderProgramDelete(prog1);
   
   GLContextDctr();
}

int cmain(int argc, char **argv)
{
   contextless_test();
   
   const char * infoLog = NULL;

   GGLInterface_t * ggl = CreateGGLInterface();

   gl_shader_t * shader0 = ggl->ShaderCreate(ggl, GL_VERTEX_SHADER);
   assert(shader0);
   const char * glsl0 =
      "uniform vec4 uVec4; \n"
      "uniform sampler2D sampler2d; \n"
      "attribute vec4 aPosition; \n"
      "attribute vec4 aTexCoord; \n"
      "varying vec4 vTexCoord; \n"
      "varying vec4 vTexColor; \n"
      "void main() { \n"
      "   gl_Position = aPosition; \n"
      "   vTexCoord = aTexCoord + uVec4; \n"
      "   vTexColor = texture2D(sampler2d, aTexCoord.zw); \n"
      "   gl_PointSize = 432; \n"
      "}";
   puts(glsl0);
   GLboolean compileStatus = ggl->ShaderCompile(ggl, shader0, glsl0, &infoLog);
   if (!compileStatus)
      fprintf(stderr, "failed to compile vertex shader 0, infoLog: \n %s \n", infoLog);
   assert(compileStatus);

   gl_shader_t * shader1 = ggl->ShaderCreate(ggl, GL_FRAGMENT_SHADER);
   assert(shader1);
   const char * glsl1 =
      "uniform vec4 uVec4; \n"
      "uniform sampler2D sampler2d; \n"
      "varying vec4 vTexCoord; \n"
      "varying vec4 vTexColor; \n"
      "void main() { \n"
      "   gl_FragColor = vTexCoord + vTexColor; \n"
      "}";
   puts(glsl1);
   compileStatus = ggl->ShaderCompile(ggl, shader1, glsl1, &infoLog);
   if (!compileStatus)
      fprintf(stderr, "failed to compile fragment shader 0, infoLog: \n %s \n", infoLog);
   assert(compileStatus);

   gl_shader_program_t * program0 = ggl->ShaderProgramCreate(ggl);
   assert(program0);

   ggl->ShaderAttach(ggl, program0, shader0);
   ggl->ShaderAttach(ggl, program0, shader1);
   ggl->ShaderAttributeBind(program0, 2, "aTexCoord");
   ggl->ShaderAttributeBind(program0, 3, "aPosition");

   GLboolean linkStatus = ggl->ShaderProgramLink(program0, &infoLog);
   if (!linkStatus)
      fprintf(stderr, "failed to link program 0, infoLog: \n %s \n", infoLog);
   assert(linkStatus);

   ggl->ShaderUse(ggl, program0);

   unsigned texels0 [] = {0xffffffff, 0x22222222, 0x66666666, 0xffffffff};
   GGLTexture_t texture0 = {GL_TEXTURE_2D, GGL_PIXEL_FORMAT_RGBA_8888,
                            2, 2, 1, // width, height, levelCount
                            texels0, GGL_CLAMP_TO_EDGE, GGL_MIRRORED_REPEAT, GGL_LINEAR, GGL_LINEAR
                           }; // levels, wrapS, wrapT, minFilter, magFilter

   int sampler2dLoc = ggl->ShaderUniformLocation(program0, "sampler2d");
   if (0 <= sampler2dLoc) {
      int samplerUnit = -1;
      //ggl->ShaderUniformGetiv(ggl, program0, sampler2dLoc, &samplerUnit);
      samplerUnit = sampler2dLoc;
      ggl->SetSampler(ggl, samplerUnit, &texture0);
   }

   Vector4 uVec4 = {1.125f, 1.5f, 1.75f, 1.75f};
   int uVec4Loc = ggl->ShaderUniformLocation(program0, "uVec4");
   ggl->ShaderUniform(program0, uVec4Loc, 1, &uVec4, GL_FLOAT_VEC4);

   VertexInput_t v0 = {0};
   v0.attributes[2] = VECTOR4_CTR(0,0,1,1); // aTexCoord
   v0.attributes[3] = VECTOR4_CTR(0.25f, 0.25f, 0.5f,1); // aPosition

   VertexOutput_t vout0 = {0};
   ggl->ProcessVertex(ggl, &v0, &vout0);
   if (memcmp(&vout0.position,&v0.attributes[3],sizeof(vout0.position))) {
      fprintf(stderr, "gl_Position != aPosition \n");
      assert(0);
   }

   int vTexCoordIndex = ggl->ShaderVaryingLocation(program0, "vTexCoord", NULL) - 2;
   VECTOR4_OP_UNARY(vout0.varyings[vTexCoordIndex],-=,uVec4);
   if (memcmp(&vout0.varyings[vTexCoordIndex],&v0.attributes[2],sizeof uVec4)) {
      fprintf(stderr, "vTexCoord != aTexCoord + uVec4 \n");
      assert(0);
   }
   Vector4 ones = {1,1,1,1};
   int vTexColorIndex = ggl->ShaderVaryingLocation(program0, "vTexColor", NULL) - 2;
   if (memcmp(&vout0.varyings[vTexColorIndex],&ones,sizeof ones)) { // should be the last texel color
      fprintf(stderr, "vTexColor != Vector4(1,1,1,1) \n");
      assert(0);
   }
   if (vout0.pointSize.x != 432) {
      fprintf(stderr, "gl_PointSize != 432 \n");
      assert(0);
   }

   v0.attributes[2] = VECTOR4_CTR(0,0, 1.5f, 1.5f);
   texture0.wrapS = GGL_REPEAT;
   texture0.wrapT = GGL_REPEAT;

   sampler2dLoc = ggl->ShaderUniformLocation(program0, "sampler2d");
   if (0 <= sampler2dLoc) {
      int samplerUnit = -1;
      //ggl->ShaderUniformGetiv(ggl, program0, sampler2dLoc, &samplerUnit);
      samplerUnit = sampler2dLoc;
      ggl->SetSampler(ggl, samplerUnit, &texture0);
   }
   ggl->ShaderUse(ggl, program0);
   ggl->ProcessVertex(ggl, &v0, &vout0);
   const float filtered = (float)(0xff + 0x22 + 0x66 + 0xff) / (4 * 0xff);
   if (!ApproximatelyEqual(vout0.varyings[vTexColorIndex],
                           VECTOR4_CTR(filtered, filtered, filtered, filtered), 1.0f / 255)) {
      fprintf(stderr, "failed linear filter and/or wrapS and wrapT test");
      assert(0);
   }

   const unsigned width = 60, height = 100;

   GGLSurface_t colorSurface = {width, height, GGL_PIXEL_FORMAT_RGBA_8888, malloc(width * height * 4)};
   assert(colorSurface.data);
   ggl->SetBuffer(ggl, GL_COLOR_BUFFER_BIT, &colorSurface);

   GGLSurface_t depthSurface = {width, height, GGL_PIXEL_FORMAT_Z_32, malloc(width * height * 4)};
   assert(depthSurface.data);
   ggl->SetBuffer(ggl, GL_DEPTH_BUFFER_BIT, &depthSurface);

   GGLSurface_t stencilSurface = {width, height, GGL_PIXEL_FORMAT_S_8, malloc(width * height * 1)};
   assert(stencilSurface.data);
   ggl->SetBuffer(ggl, GL_STENCIL_BUFFER_BIT, &stencilSurface);

   ggl->ClearColor(ggl, 0.1f, 0.1f, 0.1f, 1.0f);
   ggl->ClearDepthf(ggl, 0.5f);

// TODO DXL test scanline and fs

   free(colorSurface.data);
   colorSurface.data = NULL;
   free(depthSurface.data);
   depthSurface.data = NULL;
   free(stencilSurface.data);
   stencilSurface.data = NULL;

   ggl->ShaderProgramDelete(ggl, program0);

   puts("*******************");
   puts("*** end of test ***");
   puts("*******************");

   DestroyGGLInterface(ggl);
   return 0;
}