#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <SDL2/SDL_opengles2.h>

GLfloat light_diffuse[] = {1.0, 1.0, 1.0, 1.0};  /* Red diffuse light. */
GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0};  /* Infinite light location. */
GLfloat n[6][3] = {  /* Normals for the 6 faces of a cube. */
  {-1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0},
  {0.0, -1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, -1.0} };
GLushort faces[6][4] = {  /* Vertex indices for the 6 faces of a cube. */
  {0, 1, 2, 3}, {3, 2, 6, 7}, {7, 6, 5, 4},
  {4, 5, 1, 0}, {5, 6, 2, 1}, {7, 4, 0, 3} };
GLfloat v[8][3];  /* Will be filled in with X,Y,Z vertexes. */

GLfloat angle = 0.0;

GLuint vs, fs, prog; // vertex and fragment shader, and program
GLint att_normal, att_vertex, att_color;
GLfloat matrix[16]; // the matrix used
GLfloat savedmat[16];   // matrix saved (simulating push / pop)
GLfloat nmat[9]; // the normal matrix
GLfloat light_position_transformed[4];

// Matrix code taken from gl4es...
void set_identity(float* mat) {
    memset(mat, 0, 16*sizeof(GLfloat));
    mat[0] = mat[1+4] = mat[2+8] = mat[3+12] = 1.0f;
}
void matrix_mul(const float *a, const float *b, float *c) {
   float a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];

    float b0  = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
    c[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    c[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    c[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    c[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
    c[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    c[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    c[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    c[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
    c[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    c[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    c[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    c[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
    c[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    c[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    c[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    c[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
}
void matrix_inverse3_transpose(const float *m, float *r) {
    
    r[0] = m[4+1]*m[8+2] - m[4+2]*m[8+1];
    r[1] = m[4+2]*m[8+0] - m[4+0]*m[8+2];
    r[2] = m[4+0]*m[8+1] - m[4+1]*m[8+0];

    r[3] = m[0+2]*m[8+1] - m[0+1]*m[8+2];
    r[4] = m[0+0]*m[8+2] - m[0+2]*m[8+0];
    r[5] = m[0+1]*m[8+0] - m[0+0]*m[8+1];

    r[6] = m[0+1]*m[4+2] - m[0+2]*m[4+1];
    r[7] = m[0+2]*m[4+0] - m[0+0]*m[4+2];
    r[8] = m[0+0]*m[4+1] - m[0+1]*m[4+0];

    float det = 1.0f/(m[0]*r[0] + m[4+0]*r[3] + m[8+0]*r[6]);
    for (int i = 0; i < 9; i++) r[i] *= det;
}
void vector_matrix(const float *a, const float *b, float *c) {
    const float a0=a[0], a1=a[1], a2=a[2], a3=a[3];
    c[0] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12];
    c[1] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13];
    c[2] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14];
    c[3] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15];
}
void Rotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) {
	// create a rotation matrix than multiply it...
	GLfloat tmp[16];
	memset(tmp, 0, 16*sizeof(GLfloat));
	if((x==0 && y==0 && z==0) || angle==0)
		return;	// nothing to do
	// normalize x y z
	GLfloat l = 1.0f/sqrtf(x*x+y*y+z*z);
	x=x*l; y=y*l; z=z*l;
	// calculate sin/cos
	angle*=3.1415926535f/180.f;
	const GLfloat s=sinf(angle); 
	const GLfloat c=cosf(angle);
	const GLfloat c1 = 1-c;
	//build the matrix
	tmp[0+0] = x*x*c1+c;   tmp[0+4] = x*y*c1-z*s; tmp[0+8] = x*z*c1+y*s;
	tmp[1+0] = y*x*c1+z*s; tmp[1+4] = y*y*c1+c;   tmp[1+8] = y*z*c1-x*s;
	tmp[2+0] = x*z*c1-y*s; tmp[2+4] = y*z*c1+x*s; tmp[2+8] = z*z*c1+c;

	tmp[3+12] = 1.0f;
	// done...
	matrix_mul(matrix, tmp, matrix);
}
void Translatef(GLfloat x, GLfloat y, GLfloat z) {
	// create a translation matrix than multiply it...
	GLfloat tmp[16];
	set_identity(tmp);
	tmp[12+0] = x;
	tmp[12+1] = y;
	tmp[12+2] = z;
    matrix_mul(matrix, tmp, matrix);
}

void
drawBox(void)
{
  int i;
  
  static float colors[6][4] =   // switch to 4-sized, it's better for colors (and it's done in gl4es)
  {
  	{1.0f, 0.0f, 0.0f, 1.0f},
  	{0.0f, 1.0f, 0.0f, 1.0f},
  	{0.0f, 0.0f, 1.0f, 1.0f},
  	{1.0f, 0.0f, 1.0f, 1.0f},
  	{0.0f, 1.0f, 1.0f, 1.0f},
  	{1.0f, 1.0f, 1.0f, 1.0f}
  };

  // apply transformation first
  glUniformMatrix4fv(glGetUniformLocation(prog, "_gl4es_ModelViewProjectionMatrix"), 1, GL_FALSE, matrix);
  //Normal matrix (mat3 version of transpose(inverse(gl_ModelViewMatrix))) !!!
  matrix_inverse3_transpose(matrix, nmat);
  glUniformMatrix3fv(glGetUniformLocation(prog, "_gl4es_NormalMatrix"), 1, GL_FALSE, nmat);

  for (i = 0; i < 6; i++) {

    glVertexAttrib3fv(att_normal, &n[i][0]);
    glVertexAttribPointer(att_vertex, 3, GL_FLOAT, 0, 0, v);
    glVertexAttrib4fv(att_color, &colors[i][0]);	
    glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_SHORT, faces[i]);
  }
}

void
display(void)
{
  glClearColor(0.4, 0.0, 0.0, 1.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  vector_matrix(light_position, matrix, light_position_transformed);
  glUniform4fv(glGetUniformLocation(prog, "_gl4es_LightSource_0.position"), 1, light_position_transformed);// glLightfv(GL_LIGHT0, GL_POSITION, light_position);
  memcpy(savedmat, matrix, 16*4); //glPushMatrix();
  Rotatef(angle, 1.0, 0.0, 0.0); //glRotatef(angle, 1.0, 0.0, 0.0);
  drawBox();
  memcpy(matrix, savedmat, 16*4); //glPopMatrix();
}

GLfloat light_ambient[4] = {0.2f, 0.2f, 0.2f, 1.0f}; // default value (for light0)
GLfloat light_specular[4] = {1.0f, 1.0f, 1.0f, 1.0f}; // default value (for light0)
GLfloat frontlightprod_specular[4] = {0.0f, 0.0f, 0.0f, 1.0f}; // default for for light0
GLfloat frontlightprod_diffuse[4] = {0.8f, 0.8f, 0.8f, 1.0f}; // default for for light0
void
init(void)
{
  /* Setup cube vertex data. */
  v[0][0] = v[1][0] = v[2][0] = v[3][0] = -1;
  v[4][0] = v[5][0] = v[6][0] = v[7][0] = 1;
  v[0][1] = v[1][1] = v[4][1] = v[5][1] = -1;
  v[2][1] = v[3][1] = v[6][1] = v[7][1] = 1;
  v[0][2] = v[3][2] = v[4][2] = v[7][2] = 1;
  v[1][2] = v[2][2] = v[5][2] = v[6][2] = -1;

  /* Enable a single OpenGL light. */
  //glEnable(GL_LIGHT0);
  //glEnable(GL_LIGHTING);
    //emission is 0, no need to update it, default value is 0 in driver (or should be at least)
    glUniform4fv(glGetUniformLocation(prog, "_gl4es_LightSource_0.ambient"), 1, light_ambient);
    glUniform4fv(glGetUniformLocation(prog, "_gl4es_LightSource_0.diffuse"), 1, light_diffuse);
    glUniform4fv(glGetUniformLocation(prog, "_gl4es_LightSource_0.specular"), 1, light_specular);
    glUniform4fv(glGetUniformLocation(prog, "_gl4es_LightSource_0.position"), 1, light_position);
    //spotDirection and other value not used in shader
    glUniform4fv(glGetUniformLocation(prog, "_gl4es_FrontLightProduct_0.diffuse"), 1, frontlightprod_diffuse); // not used in the shader
    glUniform4fv(glGetUniformLocation(prog, "_gl4es_FrontLightProduct_0.specular"), 1, frontlightprod_specular);
  //glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
  //glLightfv(GL_LIGHT0, GL_POSITION, light_position);

	//glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	//glEnable(GL_COLOR_MATERIAL);
	
  /* Use depth buffering for hidden surface elimination. */
  glEnable(GL_DEPTH_TEST);

  /* Setup the view of the cube. */
  //glMatrixMode(GL_PROJECTION);
  
  //gluPerspective( /* field of view in degree */ 40.0,
   // /* aspect ratio */ 1.0,
   // /* Z near */ 1.0, /* Z far */ 10.0);
	
  //glMatrixMode(GL_MODELVIEW);
  set_identity(matrix);
  
  /* Adjust cube position to be asthetic angle. */  
  Translatef(0.0, 0.0, -1.0); //glTranslatef(0.0, 0.0, -1.0);
  Rotatef(60.0, 1.0, 0.0, 0.0); //glRotatef(60.0, 1.0, 0.0, 0.0);
  Rotatef(-20, 0.0, 0.0, 1.0); //glRotatef(-20, 0.0, 0.0, 1.0);
}

void 
idle(void)
{ 	
    angle += 0.1;
    if (angle > 360.0)
       angle = 0.0;
  
}


const char* vertex_shader[] = {
"#version 100\n"
"precision mediump float;\n"
"precision mediump int;\n"
"uniform highp mat4 _gl4es_ModelViewProjectionMatrix;\n"
"uniform highp mat3 _gl4es_NormalMatrix;\n"
"attribute highp vec4 _gl4es_Vertex;\n"
"attribute lowp vec4 _gl4es_Color;\n"
"attribute highp vec3 _gl4es_Normal;\n"
"struct _gl4es_LightModelParameters {\n"
"  vec4 ambient;\n"
"};\n"
"uniform _gl4es_LightModelParameters _gl4es_LightModel;\n"
"struct _gl4es_MaterialParameters\n"
"{\n"
"   vec4 emission;\n"
"   vec4 ambient;\n"
"   vec4 diffuse;\n"
"   vec4 specular;\n"
"   float shininess;\n"
"};\n"
"uniform _gl4es_MaterialParameters _gl4es_FrontMaterial;\n"
"uniform _gl4es_MaterialParameters _gl4es_BackMaterial;\n"
"// FPE_Shader generated\n"
"// ** Vertex Shader **\n"
"// ligthting=1 (twosided=0, separate=0, color_material=1)\n"
"// secondary=0, planes=000000\n"
"// texture=00000000, point=0\n"
"varying vec4 Color;\n"
"struct _gl4es_FPELightSourceParameters1\n"
"{\n"
"   highp vec4 ambient;\n"
"   highp vec4 diffuse;\n"
"   highp vec4 specular;\n"
"   highp vec4 position;\n"
"   highp vec3 spotDirection;\n"
"   highp float spotExponent;\n"
"   highp float spotCosCutoff;\n"
"   highp float constantAttenuation;\n"
"   highp float linearAttenuation;\n"
"   highp float quadraticAttenuation;\n"
"};\n"
"struct _gl4es_FPELightSourceParameters0\n"
"{\n"
"   highp vec4 ambient;\n"
"   highp vec4 diffuse;\n"
"   highp vec4 specular;\n"
"   highp vec4 position;\n"
"   highp vec3 spotDirection;\n"
"   highp float spotExponent;\n"
"   highp float spotCosCutoff;\n"
"};\n"
"struct _gl4es_LightProducts\n"
"{\n"
"   highp vec4 ambient;\n"
"   highp vec4 diffuse;\n"
"   highp vec4 specular;\n"
"};\n"
"uniform _gl4es_FPELightSourceParameters0 _gl4es_LightSource_0;\n"
"uniform _gl4es_LightProducts _gl4es_FrontLightProduct_0;\n"
"\n"
"void main() {\n"
"vec3 normal = _gl4es_NormalMatrix * _gl4es_Normal;\n"
"gl_Position = _gl4es_ModelViewProjectionMatrix * _gl4es_Vertex;\n"
"// ColorMaterial On/Off=1 Front = 0 Back = 0\n"
"Color = _gl4es_FrontMaterial.emission;\n"
"Color += _gl4es_Color*_gl4es_LightModel.ambient;\n"
"highp float att;\n"
"highp float spot;\n"
"highp vec3 VP;\n"
"highp float lVP;\n"
"highp float nVP;\n"
"highp vec3 aa,dd,ss;\n"
"highp vec3 hi;\n"
"// light 0 on, light_direction=0, light_cutoff180=0\n"
"att = 1.0;\n"
"VP = normalize(_gl4es_LightSource_0.position.xyz);\n"
"aa = _gl4es_Color.xyz * _gl4es_LightSource_0.ambient.xyz;\n"
"nVP = dot(normal, VP);\n"
"dd = (nVP>0.)?(nVP * _gl4es_Color.xyz * _gl4es_LightSource_0.diffuse.xyz):vec3(0.);\n"
"hi = normalize(VP + vec3(0., 0., 1.));\n"
"lVP = dot(normal, hi);\n"
"ss = (nVP>0. && lVP>0.)?(_gl4es_FrontLightProduct_0.specular.xyz):vec3(0.);\n"
"Color.rgb += att*(aa+dd+ss);\n"
"// end of light 0\n"
"Color.a = _gl4es_Color.a;\n"
"Color.rgb = clamp(Color.rgb, 0., 1.);\n"
"// texturing\n"
"}\n"
};

const char* fragment_shader[] = {
"#version 100\n"
"precision mediump float;\n"
"precision mediump int;\n"
"// FPE_Shader generated\n"
"// ** Fragment Shader **\n"
"// lighting=1, alpha=0, secondary=0, planes=000000, texture=00000000, texformat=00000000 point=0\n"
"varying vec4 Color;\n"
"void main() {\n"
"vec4 fColor = Color;\n"
"gl_FragColor = fColor;\n"
"}\n"
};

#ifdef __WIN32__
int WinMain(int argc, const char** argv)
#else
int main(int argc, const char** argv)
#endif
{
    if(SDL_Init(SDL_INIT_VIDEO)<0)
        return -1;

	SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1");
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK , SDL_GL_CONTEXT_PROFILE_ES);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);

    SDL_Window *window = SDL_CreateWindow("Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE| SDL_WINDOW_SHOWN);
    if(!window) {
        SDL_Quit();
        return -2;
    }


    SDL_GLContext glcontext = SDL_GL_CreateContext(window);

    // create the only program used here
    GLint status;
    vs = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vs, 1, vertex_shader, NULL);
    glCompileShader(vs);
    glGetShaderiv(vs, GL_COMPILE_STATUS, &status);
    if(status!=GL_TRUE) {
        char buff[1000];
        glGetShaderInfoLog(vs, 1000, NULL, buff);
        printf("Vertex shader compile failed: %s\n", buff);
        SDL_Quit();
        return -2;
    }
    fs = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fs, 1, fragment_shader, NULL);
    glCompileShader(fs);
    glGetShaderiv(fs, GL_COMPILE_STATUS, &status);
    if(status!=GL_TRUE) {
        char buff[1000];
        glGetShaderInfoLog(fs, 1000, NULL, buff);
        printf("Fragment shader compile failed: %s\n", buff);
    }
    prog = glCreateProgram();
    glAttachShader(prog, vs);
    glAttachShader(prog, fs);
    glLinkProgram(prog);
    glGetProgramiv(prog, GL_LINK_STATUS, &status);
    if(status!=GL_TRUE) {
        char buff[1000];
        glGetProgramInfoLog(prog, 1000, NULL, buff);
        printf("Program link failed: %s\n", buff);
    }
    // Use it now !
    glUseProgram(prog);

    att_normal = glGetAttribLocation(prog, "_gl4es_Normal");
    att_vertex = glGetAttribLocation(prog, "_gl4es_Vertex");
    att_color = glGetAttribLocation(prog, "_gl4es_Color");
    glDisableVertexAttribArray(att_normal);
    glEnableVertexAttribArray(att_vertex);
    glDisableVertexAttribArray(att_color);

    init();

    int quit = 0;
    while(!quit) {
        SDL_Event evt;
        while(SDL_PollEvent(&evt)) {
            switch(evt.type) {
                case SDL_QUIT:
                    quit = 1;
                    break;
                case SDL_KEYDOWN:
                    if(evt.key.keysym.sym == SDLK_ESCAPE)
                        quit = 1;
                    break;
            }
        }
        display();
        idle();

        glFlush();
        SDL_GL_SwapWindow(window);
    }

    SDL_GL_DeleteContext(glcontext);  
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}
