/* pyramid.c
 *
 * @2011 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
 *
 * so far we have been using immediate mode rendering, we should begin using
 * display lists / batch processing to reduce overhead, aka compiling commands
 *
 * it might be time to split the code, and make a shader version...
 *
 */

#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include <sys/time.h>
#include "glframe.h"
#include "gltools.h"
#include "math3d.h"
#include "platform.h"
#include "window.h"

/* function prototypes */
static void keys(SDL_keysym *, const unsigned int *, const int);
static void process_events(void);
static void platform_init(void);
static void platform_destroy(void);
static void render(void);

/* global */
int program_running = 1;
const unsigned int xres = 640;
const unsigned int yres = 480;
const unsigned int bpp  = 32;
const char *window_caption   = "Textured Pyramid";
const char *window_icon_path = "tux.png";

/* platform struct */
static struct
{
    GLFrame camera;

    /* display lists identifiers */
    GLuint ground_list;
    GLuint triangle_list;

    GLfloat xrot;
    GLfloat yrot;
} p;

static void keys(SDL_keysym *keysym, const unsigned int *keys_held, const int flag)
{
    if (!flag)
    {
        switch (keysym->sym)
        {
        case SDLK_ESCAPE: program_running = 0; break;
        case SDLK_w: p.xrot -= 5.0f; break;
        case SDLK_s: p.xrot += 5.0f; break;
        case SDLK_a: p.yrot -= 5.0f; break;
        case SDLK_d: p.yrot += 5.0f; break;
        case SDLK_UP: glframe_move_forward(&p.camera, 0.5f); break;
        case SDLK_DOWN: glframe_move_forward(&p.camera, -0.5f); break;
        case SDLK_LEFT: glframe_rotate_local_y(&p.camera, 0.1f); break;
        case SDLK_RIGHT: glframe_rotate_local_y(&p.camera, -0.1f); break;
        case SDLK_n: glframe_rotate_local_x(&p.camera, 0.1f); break;
        case SDLK_m: glframe_rotate_local_x(&p.camera, -0.1f); break;
        default: break;
        }
    }
    else
    {
        if (keys_held[SDLK_w])
            p.xrot -= 5.0f;
        if (keys_held[SDLK_s])
            p.xrot += 5.0f;
        if (keys_held[SDLK_a])
            p.yrot -= 5.0f;
        if (keys_held[SDLK_d])
            p.yrot += 5.0f;
            
        if (keys_held[SDLK_UP])
            glframe_move_forward(&p.camera, 0.05f);
        if (keys_held[SDLK_DOWN])
            glframe_move_forward(&p.camera, -0.05f);
        if (keys_held[SDLK_LEFT])
            glframe_rotate_local_y(&p.camera, 0.02f);
        if (keys_held[SDLK_RIGHT])
            glframe_rotate_local_y(&p.camera, -0.02f);

        if (keys_held[SDLK_n])
            glframe_rotate_local_x(&p.camera, 0.02f);
        if (keys_held[SDLK_m])
            glframe_rotate_local_x(&p.camera, -0.02f);
    }
                
    p.xrot = (GLfloat) ((const int) p.xrot % 360);
    p.yrot = (GLfloat) ((const int) p.yrot % 360);
}

static void process_events(void)
{
    /* process SDL events */
    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: { window_resize(event.resize.w, event.resize.h); break; }
        case SDL_QUIT: { program_running = 0; break; }
        default: break;
        }
    }

    /* 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] ||
        keys_held[SDLK_UP] || keys_held[SDLK_DOWN] || keys_held[SDLK_LEFT] || keys_held[SDLK_RIGHT] ||
        keys_held[SDLK_n] || keys_held[SDLK_m])
    {
        flag = !flag;
        keys(NULL, keys_held, flag);
    }
    else
        flag = !flag;
}

static void platform_init(void)
{
    /* variables used for texture loading */
    GLbyte *pBytes;
    GLint iWidth, iHeight, iComponents;
    GLenum eFormat;

    /* set the camera to <0,0,0> */
    glframe_reset(&p.camera);
    p.xrot = 0;
    p.yrot = 0;

    /* 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);

    /* display list, precompile commands */
    p.ground_list = glGenLists(2);
    p.triangle_list = p.ground_list + 1;

    glNewList(p.ground_list, GL_COMPILE);
       gltDrawGround();
    glEndList();

    glNewList(p.triangle_list, GL_COMPILE);
       gltDrawTriangle();
    glEndList();
}

static void platform_destroy(void)
{
    glDeleteLists(p.ground_list, 2);
}

static void render(void)
{
    /* 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, and draw the ground */
        glframe_apply_camera_transform(&p.camera);
        glColor3ub(255, 0, 255);
        glCallList(p.ground_list);

        glPushMatrix();
        /* move object back and do in place rotation */
        glTranslatef(0.0f, 0.2f, -3.5f);
        glRotatef(p.xrot, 1.0f, 0.0f, 0.0f);
        glRotatef(p.yrot, 0.0f, 1.0f, 0.0f);

        /* draw the pyramid */
        glColor3f(1.0f, 1.0f, 1.0f);
        glCallList(p.triangle_list);
        glPopMatrix();

        /* draw a snowman */
        glTranslatef(0.0f, 0.0f, -7.0f);
        gltDrawSnowman();

    /* restore the matrix state */
    glPopMatrix();

    /* buffer swap */
    SDL_GL_SwapBuffers();
}

int main(int argc, char **argv)
{
    setup_sdl();
    setup_glew();
    setup_opengl();
    platform_init();

    unsigned int startclock;

    while (program_running)
    {
        startclock = SDL_GetTicks();

        process_events();
        render();

        fps_control(startclock);
    }

    platform_destroy();
    puts("bye!");

    return 0;
}