#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <SDL/SDL.h>
#include "mini3d.h"

#if defined(__unix__) || defined(unix)
#include <unistd.h>
#else   /* win32 */
#include <windows.h>
#define usleep(x) Sleep(x)
#endif  /* unix / win32 */

int init(void);
void update_gfx(void);
void clean_up(void);
void parse_cmdline(int argc, char **argv);

int xsz = 640;
int ysz = 480;
SDL_Surface *fbsurf;

#define MESH_NVERT  5535
#define MESH_NNORM  29124
#define MESH_NFACE  9708

extern float vertices[MESH_NVERT][3];
extern float normals[MESH_NNORM][3];
extern int triangles[MESH_NFACE][3];


int main(int argc, char **argv) {
    parse_cmdline(argc, argv);

    if(init() == -1) {
        return EXIT_FAILURE;
    }

    for(;;) {
        SDL_Event event;
        if(SDL_PollEvent(&event)) {
            if(event.type == SDL_KEYDOWN && event.key.keysym.sym == 27) {
                break;
            }
        } else {
            /* some supporting code to make this sleep when it doesn't need the CPU, ignore */
            const static unsigned int frame_interval = 33;  /* 30 fps (1000 / 30) */
            static int prev_time;
            int frame_time, cur_time;

            update_gfx();

            frame_time = (cur_time = SDL_GetTicks()) - prev_time;
            if(frame_time < frame_interval) {
                usleep((frame_interval - frame_time) * 1000);
            }

            prev_time = cur_time;
        }
    }

    clean_up();
    return 0;
}


int init(void) {
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
    if(!(fbsurf = SDL_SetVideoMode(xsz, ysz, 32, SDL_SWSURFACE))) {
        fputs("SDL init failed\n", stderr);
        return -1;
    }

    /* initialize mini3d */
    m3d_init();
    m3d_viewport(0, 0, xsz, ysz);

    /* set the color/depth that m3d_clear() uses to clear the buffers */
    m3d_clear_color(0, 0, 0, 0);
    m3d_clear_depth(1);

    /* enable depth-testing (zbuffering) and ligting */
    m3d_enable(M3D_DEPTH_TEST);
    m3d_enable(M3D_LIGHTING);
    m3d_enable(M3D_LIGHT0);

    /* setup a projection matrix */
    m3d_matrix_mode(M3D_PROJECTION);
    m3d_load_identity();
    m3d_perspective(45.0, 1.33333, 100.0, 1000.0);

    /* prepare the material for rendering the object */
    {
        float dif[] = {0.2, 0.4, 1.0, 1.0};
        float spec[] = {1, 1, 1, 1};
        m3d_materialv(M3D_DIFFUSE, dif);
        m3d_materialv(M3D_SPECULAR, spec);
        m3d_material(M3D_SHININESS, 60.0f);
    }

    return 0;
}

void update_gfx(void) {
    int i, j;
    uint32_t *pixels;
    float t = (float)SDL_GetTicks() / 10.0f;

    /* clear the buffers */
    m3d_clear(M3D_COLOR_BUFFER_BIT | M3D_DEPTH_BUFFER_BIT);

    /* set the current modelview transformation matrix */
    m3d_matrix_mode(M3D_MODELVIEW);
    m3d_load_identity();
    m3d_translate(0, 0, 870);
    m3d_rotate(t, 0, 1, 0);

    /* render the triangles of the object using m3d_vertex/m3d_normal calls
     * to specify the positions and normal vectors for each vertex
     */
    m3d_begin(M3D_TRIANGLES);
    for(i=0; i<MESH_NFACE; i++) {
        for(j=2; j>=0; j--) {
            int nidx = i * 3 + j;
            int vidx = triangles[i][j];

            m3d_normal(normals[nidx][0], normals[nidx][1], normals[nidx][2]);
            m3d_vertex(vertices[vidx][0], vertices[vidx][1], vertices[vidx][2]);
        }
    }
    m3d_end();

    pixels = m3d_get_pixel_data();  /* get a pointer to the framebuffer ... */

    /* ... and copy it to the SDL surface */
    SDL_LockSurface(fbsurf);
    memcpy(fbsurf->pixels, pixels, xsz * ysz * 4);
    SDL_UnlockSurface(fbsurf);

    SDL_Flip(fbsurf);   /* finally show the surface */
}

void clean_up(void) {
    m3d_destroy();
    SDL_Quit();
}

void parse_cmdline(int argc, char **argv) {
    int i;
    char *sep;

    for(i=1; i<argc; i++) {
        if(argv[i][0] == '-' && argv[i][2] == 0) {
            switch(argv[i][1]) {
            case 's':
                if(!isdigit(argv[++i][0]) || !(sep = strchr(argv[i], 'x')) || !isdigit(*(sep+1))) {
                    fprintf(stderr, "malformed -s argument: \"%s\"\n", argv[i]);
                    exit(EXIT_FAILURE);
                }
                xsz = atoi(argv[i]);
                ysz = atoi(sep + 1);
                break;

            case 'h':
            default:
                fprintf(stderr, "usage: example [-s WxH]\n");
                exit(argv[i][1] == 'h' ? 0 : EXIT_FAILURE);
            }
        } else {
            fprintf(stderr, "unrecognized argument: %s\n", argv[i]);
            exit(EXIT_FAILURE);
        }
    }
}