/* pyramid.c
 *
 * @2010 Kamil Kaminski
 *
 * notes: since lighting is enabled, we need to specify normals for
 * each polygon face so the OpenGL can calculate e.g. how light reflects
 *
 * drawing a shadow for the pyramid would require drawing things twice, so on 2nd
 * pass we would draw with black color and multiply by squished matrix, we should
 * get to this later with better approach
 *
 */
 
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
/* for some reason order of the headers matters */
#include "math3d.h"
#include "gltools.h"
#include "glframe.h"
#include <sys/time.h>
#define FRAMES_PER_SECOND 60

/* global */
int program_running = 1;
GLFrame camera;

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

static void resize(int w, int h)
{
    printf("window: resizing to %dx%d\n", w, 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 */
    /* void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar) */
    gluPerspective(40.0f, fAspect, 1.0, 40.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    /* this needs to be ran again, glut does it for you I suppose */
    SDL_SetVideoMode(w, h, 32, SDL_OPENGL | SDL_GL_DOUBLEBUFFER | SDL_RESIZABLE);
}

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 */
    /* ambient RGBA intensity of the entire scene */
    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);

    /* gray background */
    glClearColor(0.5f, 0.5f, 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);
    
    /* set the camera to <0,0,0> */
    reset_glframe(&camera);
}

static void keys(SDL_keysym *keysym, unsigned int *keys_held, int flag)
{
    if (!flag)
    {
        switch (keysym->sym)
        {
        case SDLK_ESCAPE: program_running = 0; break;
        case SDLK_w: xRot -= 5.0f; break;
        case SDLK_s: xRot += 5.0f; break;
        case SDLK_a: yRot -= 5.0f; break;
        case SDLK_d: yRot += 5.0f; break;
        case SDLK_UP: move_forward(&camera, 0.1f); break;
        case SDLK_DOWN: move_forward(&camera, -0.1f); break;
        case SDLK_LEFT: rotate_local_y(&camera, 0.1f); break;
        case SDLK_RIGHT: rotate_local_y(&camera, -0.1f); break;        
        default: break;
        }
    }
    else
    {
        if (keys_held[SDLK_w])
            xRot -= 5.0f;
        if (keys_held[SDLK_s])
            xRot += 5.0f;
        if (keys_held[SDLK_a])
            yRot -= 5.0f;
        if (keys_held[SDLK_d])
            yRot += 5.0f;
            
        if (keys_held[SDLK_UP])
            move_forward(&camera, 0.01f);
        if (keys_held[SDLK_DOWN])
            move_forward(&camera, -0.01f);
        if (keys_held[SDLK_LEFT])
            rotate_local_y(&camera, 0.01f);
        if (keys_held[SDLK_RIGHT])
            rotate_local_y(&camera, -0.01f);            
    }
                
    xRot = (GLfloat) ((const int) xRot % 360);
    yRot = (GLfloat) ((const int) yRot % 360);
}

/* process SDL events */
void processEvents()
{
    SDL_Event event;
    unsigned static int keys_held[323];
    SDLKey sym;
    /* helper flag for keys() */
    int flag = 0;
    
    while (SDL_PollEvent(&event))
    {
        sym = event.key.keysym.sym;
    
        switch (event.type)
        {
        case SDL_KEYUP:
        {
            /* reset the key to 0 */
            keys_held[sym] = 0;
            break;
        }
        case SDL_KEYDOWN:
        {
            keys_held[sym] = 1;
            keys(&event.key.keysym, keys_held, flag);
            break;
        }
        case SDL_VIDEORESIZE: { resize(event.resize.w, event.resize.h); break; }
        case SDL_QUIT: { program_running = 0; break; }
        default: break;
        }
    }
    
    /* XXX below code has to be placed here */
    /* check for keys that are being constantly held */
    if (keys_held[SDLK_w] || keys_held[SDLK_s] || keys_held[SDLK_a] || keys_held[SDLK_d])
    {
        flag = !flag;
        keys(NULL, keys_held, flag);
    }
    else
        flag = !flag;    
}

static void draw_ground(void)
{
    GLfloat fExtent = 20.0f;
    GLfloat fStep = 0.5f;
    GLfloat y = -0.4f;
    GLfloat iLine;

    glLineWidth(1.0f);
    glBegin(GL_LINES);
    for (iLine = -fExtent; iLine <= fExtent; iLine += fStep)
    {
        glVertex3f(iLine, y, fExtent);
        glVertex3f(iLine, y, -fExtent);
        glVertex3f(fExtent, y, iLine);
        glVertex3f(-fExtent, y, iLine);
    }
    glEnd();
}

static void render(void)
{
    M3DVector3f vNormal;
    M3DVector3f vCorners[5] = {
        { 0.0f, 0.6f, 0.0f },       /* top           0 */
        { -0.5f, -0.2f, -.50f },    /* back left     1 */
        { 0.5f, -0.2f, -0.50f },    /* back right    2 */
        { 0.5f, -0.2f, 0.5f },      /* front right   3 */
        { -0.5f, -0.2f, 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();
    
    	apply_camera_transform(&camera);
    
        /* move object back and do in place rotation */
        glTranslatef(0.0f, 0.2f, -3.5f);
        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 */
            m3dFindNormalf(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 */
            m3dFindNormalf(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 */
            m3dFindNormalf(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 */
            m3dFindNormalf(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();
    
    glColor3ub(255, 0, 255);
    draw_ground();

    /* buffer swap */
    SDL_GL_SwapBuffers();
}

int main(int argc, char **argv)
{
    SDL_Surface *screen;

    if (SDL_Init(SDL_INIT_VIDEO) < 0 )
    {
        fprintf(stderr, "unable to init SDL: %s\n", SDL_GetError());
        exit(-1);
    }
    atexit(SDL_Quit);
    SDL_WM_SetCaption("Textured Pyramid", NULL);
    
    if ((screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL | SDL_GL_DOUBLEBUFFER
                         | SDL_RESIZABLE)) == NULL)
    {
        fprintf(stderr, "unable to set video mode: %s\n", SDL_GetError());
        exit(-1);
    }
    
    SDL_EnableUNICODE(1);
    /* SDL doesn't trigger off a ResizeEvent at startup, but as we need this 
     * for OpenGL, we do this ourselves */
    SDL_Event resizeEvent;
    resizeEvent.type = SDL_VIDEORESIZE;
    resizeEvent.resize.w = 640;
    resizeEvent.resize.h = 480;
    SDL_PushEvent(&resizeEvent);
    
    /* 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();
    
#ifdef STAT_FPS
    /* fps counter */
    Uint32 startclock = 0;
    Uint32 deltaclock = 0;
    Uint32 current_fps = 0;
#endif
    struct timeval m_LastCount;
    struct timeval lcurrent;
    while (program_running)
    {
        gettimeofday(&m_LastCount, 0);
#ifdef STAT_FPS
        startclock = SDL_GetTicks();
#endif
        processEvents();
        render();
        
        gettimeofday(&lcurrent, 0);
        float fSeconds = (float) (lcurrent.tv_sec - m_LastCount.tv_sec);
        float fFraction = (float) (lcurrent.tv_usec - m_LastCount.tv_usec) * 0.000001f;
        float delta = fSeconds + fFraction;
        
        if (delta < 1000 / FRAMES_PER_SECOND)
            SDL_Delay((1000 / FRAMES_PER_SECOND) - delta);
        
#ifdef STAT_FPS
        deltaclock = SDL_GetTicks() - startclock;
        if (deltaclock != 0 )
	        current_fps = 1000 / deltaclock;

        static char buffer[30] = { 0 };
        sprintf(buffer, "Textured Pyramid: %04d fps", current_fps);
        SDL_WM_SetCaption(buffer, NULL);
#endif
    }
    puts("bye!");
    
    return 0;
}