#include "math3d.h"

static GLfloat xRot = 0.0f;
static GLfloat yRot = 0.0f;

static void ChangeSize(int w, int h)
{
    GLfloat fAspect = (GLfloat) w / (GLfloat) h;
    if (h == 0)
        h = 1;
    glViewport(0, 0, w, h);

    /* reset coordinate system */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    /* produce the perspective projection */
    gluPerspective(35.0f, fAspect, 1.0, 40.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

static void SetupRC()
{
    GLbyte *pBytes;
    GLint iWidth, iHeight, iComponents;
    GLenum eFormat;
    
    /* light values and coordinates */
    GLfloat whiteLight[] = { 0.05f, 0.05f, 0.05f, 1.0f };
    GLfloat sourceLight[] = { 0.75f, 0.75f, 0.75f, 1.0f };
    GLfloat    lightPos[] = { -10.f, 5.0f, 5.0f, 1.0f };

    glEnable(GL_DEPTH_TEST);    /* hidden surface removal */
    glFrontFace(GL_CCW);        /* counter clock-wise polygons face out */
    glEnable(GL_CULL_FACE);     /* do not calculate inside of a pyramid */

    /* enable lighting */
    glEnable(GL_LIGHTING);

    /* setup and enable light 0 */
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, whiteLight);
    glLightfv(GL_LIGHT0, GL_AMBIENT, sourceLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, sourceLight);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    glEnable(GL_LIGHT0);

    /* enable color tracking */
    glEnable(GL_COLOR_MATERIAL);
    
    /* set Material properties to follow glColor values */
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

    /* black blue background */
    glClearColor(0.0f, 0.0f, 0.5f, 1.0f);
    
    /* load texture */
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    pBytes = gltLoadTGA("stone.tga", &iWidth, &iHeight, &iComponents, &eFormat);
    if (!pBytes)
        fprintf(stderr, "gltLoadTGA: failed to load texture!\n");    

    /* load texture image */
    glTexImage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat,
                 GL_UNSIGNED_BYTE, pBytes);
    free(pBytes);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glEnable(GL_TEXTURE_2D);
}

static void SpecialKeys(int key, int x, int y)
{
    if (key == GLUT_KEY_UP)
        xRot-= 5.0f;

    if (key == GLUT_KEY_DOWN)
        xRot += 5.0f;

    if (key == GLUT_KEY_LEFT)
        yRot -= 5.0f;

    if (key == GLUT_KEY_RIGHT)
        yRot += 5.0f;
                
        xRot = (GLfloat) ((const int) xRot % 360);
        yRot = (GLfloat) ((const int) yRot % 360);

    glutPostRedisplay();
}

static void RenderScene(void)
{
    M3DVector3f vNormal;
    M3DVector3f vCorners[5] = {
        { 0.0f, .80f, 0.0f   },    /* top           0 */
        { -0.5f, 0.0f, -.5f  },    /* back left     1 */
        { 0.5f, 0.0f, -0.5f  },    /* back right    2 */
        { 0.5f, 0.0f, 0.5f   },    /* front right   3 */
        { -0.5f, 0.0f, 0.5f  }     /* front left    4 */
    };
                              
    /* clear the window with current clearing color */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /* save the matrix state and do the rotations */
    glPushMatrix();
        /* move object back and do in place rotation */
        glTranslatef(0.0f, -0.25f, -4.0f);
        glRotatef(xRot, 1.0f, 0.0f, 0.0f);
        glRotatef(yRot, 0.0f, 1.0f, 0.0f);

        /* draw the pyramid */
        glColor3f(1.0f, 1.0f, 1.0f);
        glBegin(GL_TRIANGLES);
        
            /* bottom section - two triangles */
            glNormal3f(0.0f, -1.0f, 0.0f);

            /* map texture to geometry */
            glTexCoord2f(1.0f, 1.0f);
            glVertex3fv(vCorners[2]);
            glTexCoord2f(0.0f, 1.0f);
            glVertex3fv(vCorners[4]);
            glTexCoord2f(0.0f, 0.0f);
            glVertex3fv(vCorners[1]);
            glTexCoord2f(1.0f, 1.0f);
            glVertex3fv(vCorners[2]);
            glTexCoord2f(1.0f, 0.0f);
            glVertex3fv(vCorners[3]);
            glTexCoord2f(0.0f, 0.0f);
            glVertex3fv(vCorners[4]);
            
            /* front face */
            m3dFindNormal(vNormal, vCorners[0], vCorners[4], vCorners[3]);
            glNormal3fv(vNormal);
            glTexCoord2f(0.5f, 1.0f);
            glVertex3fv(vCorners[0]);
            glTexCoord2f(0.0f, 0.0f);
            glVertex3fv(vCorners[4]);
            glTexCoord2f(1.0f, 0.0f);
            glVertex3fv(vCorners[3]);
            
            /* left face */
            m3dFindNormal(vNormal, vCorners[0], vCorners[1], vCorners[4]);
            glNormal3fv(vNormal);
            glTexCoord2f(0.5f, 1.0f);
            glVertex3fv(vCorners[0]);
            glTexCoord2f(0.0f, 0.0f);
            glVertex3fv(vCorners[1]);
            glTexCoord2f(1.0f, 0.0f);
            glVertex3fv(vCorners[4]);

            /* back face */
            m3dFindNormal(vNormal, vCorners[0], vCorners[2], vCorners[1]);
            glNormal3fv(vNormal);
            glTexCoord2f(0.5f, 1.0f);
            glVertex3fv(vCorners[0]);
            
            glTexCoord2f(0.0f, 0.0f);
            glVertex3fv(vCorners[2]);
            
            glTexCoord2f(1.0f, 0.0f);
            glVertex3fv(vCorners[1]);
            
            /* right face */
            m3dFindNormal(vNormal, vCorners[0], vCorners[3], vCorners[2]);
            glNormal3fv(vNormal);
            glTexCoord2f(0.5f, 1.0f);
            glVertex3fv(vCorners[0]);
            glTexCoord2f(0.0f, 0.0f);
            glVertex3fv(vCorners[3]);
            glTexCoord2f(1.0f, 0.0f);
            glVertex3fv(vCorners[2]);
        glEnd();
    
    /* restore the matrix state */
    glPopMatrix();

    /* buffer swap */
    glutSwapBuffers();
}

int main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(640, 480);
    glutCreateWindow("Textured Pyramid");
    glutReshapeFunc(ChangeSize);
    glutSpecialFunc(SpecialKeys);
    glutDisplayFunc(RenderScene);
    
    /* initalize glew */
    GLenum glewerr = glewInit();
    if (GLEW_OK != glewerr)
    {
        fprintf(stderr, "Error: %s\n", glewGetErrorString(glewerr));
        return 1;
    }
    else
    	fprintf(stdout, "status: using GLEW %s\n", glewGetString(GLEW_VERSION));
    	
    SetupRC();
    glutMainLoop();
    
    return 0;
}