/*
SDL OpenGL test. Daniel Edgecumbe 2012.
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define GLEW_STATIC
#include <GL/glew.h>
#include <SDL/SDL.h>
#define SCREEN_W 960
#define SCREEN_H 544
#define FRAME_MS 17 // 17 is roughly 60FPS
#define FILENAME "image.bmp"

float vertexdata[] = { -1.0f,  1.0f,  1.0f, // cube vertices
                       -1.0f, -1.0f,  1.0f,
                        1.0f, -1.0f,  1.0f,
                        1.0f,  1.0f,  1.0f,
                       -1.0f,  1.0f, -1.0f,
                       -1.0f, -1.0f, -1.0f,
                        1.0f, -1.0f, -1.0f,
                        1.0f,  1.0f, -1.0f
};

int indexdata[] = {     0, 1, 2, 2, 3, 0, // cube indices
                        3, 2, 6, 6, 7, 3,
                        7, 6, 5, 5, 4, 7,
                        4, 0, 3, 3, 7, 4,
                        0, 1, 5, 5, 4, 0,
                        1, 5, 6, 6, 2, 1
};

SDL_Surface *screen;
SDL_Event event;
SDL_Surface *image;
int render_mode;
int execution;
int frame;
int time_cumulative;
int rmouse;
int movement_x, movement_y, movement_z;
int mouse_x, mouse_y;
float camera_theta, camera_phi, camera_x, camera_y, camera_z;
float cubes;
float voxels;

Uint32 getpixel(SDL_Surface *surface, int x, int y)
{
    int bpp = surface->format->BytesPerPixel;
    /* Here p is the address to the pixel we want to retrieve */
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
    case 1:
        return *p;

    case 2:
        return *(Uint16 *)p;

    case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return p[0] << 16 | p[1] << 8 | p[2];
        else
            return p[0] | p[1] << 8 | p[2] << 16;

    case 4:
        return *(Uint32 *)p;

    default:
        return 0;       /* shouldn't happen, but avoids warnings */
    }
}

void InitSDL() {
    if(SDL_Init(SDL_INIT_VIDEO) < 0) { SDL_Quit(); }
    atexit(SDL_Quit);
    screen = SDL_SetVideoMode(SCREEN_W, SCREEN_H, 32, SDL_OPENGL);
}

void InitGL() {
    glViewport(0, 0, SCREEN_W, SCREEN_H);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // black background
    glClearDepth(1.0); // clear depth buffer enabled
    glewInit();
}

void InitEngine() {
    render_mode = 1;
    camera_x = camera_theta = camera_phi = 0;
    camera_y = 5;
    camera_z = -100;
    cubes = 2000;
    frame = 0;
    execution = 1;
}

void LoadBMP() {
    SDL_Surface *temp;

    temp = SDL_LoadBMP(FILENAME);
    if (temp == NULL) {
        printf("Unable to load bitmap: %s\n", SDL_GetError());
        //return 1;
    }

    image = SDL_DisplayFormat(temp);
    SDL_FreeSurface(temp);
}

void ModePerspective() { // perspective projection, 3D
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f,(GLfloat)SCREEN_W/(GLfloat)SCREEN_H,0.1f,10000.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void DrawVACube(){
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_INDEX_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glIndexPointer(GL_UNSIGNED_INT, 0, indexdata);
    glVertexPointer(3, GL_FLOAT, 0, vertexdata);
    glDrawElements(GL_TRIANGLES, sizeof(indexdata) / sizeof(int), GL_UNSIGNED_INT, indexdata);
    voxels += 1;
}

void DrawSquare(GLfloat r, GLfloat g, GLfloat b, GLfloat w, GLfloat h) {
    glColor3f(r,g,b);
    glBegin(GL_QUADS);
        glNormal3f( 0.0f, 0.0f, 1.0f);
        glVertex3f(-(w/2), (h/2), 0);
        glVertex3f(-(w/2),-(h/2), 0);
        glVertex3f( (w/2),-(h/2), 0);
        glVertex3f( (w/2), (h/2), 0);
    glEnd();
}

void PollEvents() {
    while (SDL_PollEvent(&event)) { // poll for events
        switch (event.type) {
            case SDL_QUIT:
                execution = 0;
            break;
            case SDL_MOUSEMOTION:
                if (rmouse) {
                    if (frame > 10) { camera_phi += (float)event.motion.xrel/10; }
                    if (frame > 10) { camera_theta += (float)event.motion.yrel/10; }
                    if (camera_theta > 90) { camera_theta = 90; }
                    if (camera_theta < -90) { camera_theta = -90; }
                }
            break;
            case SDL_MOUSEBUTTONDOWN:
                switch(event.button.button){
                    case SDL_BUTTON_RIGHT:
                        if (!rmouse) {
                            mouse_x = event.button.x;
                            mouse_y = event.button.y;
                            rmouse = 1;
                            SDL_ShowCursor(SDL_DISABLE);
                            SDL_WM_GrabInput(SDL_GRAB_ON);
                        }
                    break;
                    case SDL_BUTTON_WHEELUP: if (cubes > 500) { cubes -= 500; } break;
                    case SDL_BUTTON_WHEELDOWN: cubes += 500; break;
                }
            break;
            case SDL_MOUSEBUTTONUP:
                if (event.button.button == SDL_BUTTON_RIGHT) { // disable mouselook
                    rmouse = 0;
                    SDL_ShowCursor(SDL_ENABLE);
                    SDL_WM_GrabInput(SDL_GRAB_OFF);
                    SDL_WarpMouse(mouse_x,mouse_y);
                }
            break;
            case SDL_KEYDOWN:
                switch(event.key.keysym.sym){
                    case SDLK_w: movement_z = 1; break;
                    case SDLK_s: movement_z = -1; break;
                    case SDLK_a: movement_x = 1; break;
                    case SDLK_d: movement_x = -1; break;
                    case SDLK_SPACE: movement_y = -1; break;
                    case SDLK_x: movement_y = 1; break;
                    case SDLK_f: InitEngine(); break;
                    case SDLK_KP1: render_mode = 1; break;
                    case SDLK_KP2: render_mode = 2; break;
                    case SDLK_KP8: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); break;
                    case SDLK_KP9: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); break;
                    default: break;
                }
            break;
            case SDL_KEYUP:
                switch(event.key.keysym.sym){
                    case SDLK_w: if (movement_z == 1) { movement_z = 0; } break;
                    case SDLK_s: if (movement_z == -1) { movement_z = 0; } break;
                    case SDLK_a: if (movement_x == 1) { movement_x = 0; } break;
                    case SDLK_d: if (movement_x == -1) { movement_x = 0; } break;
                    case SDLK_SPACE: if (movement_y == -1) { movement_y = 0; } break;
                    case SDLK_x: if (movement_y == 1) { movement_y = 0; } break;
                    default: break;
                }
            break;
        }
        break;
    }
}

void IncrementStuff() {
    GLfloat model[16];
    glGetFloatv(GL_MODELVIEW_MATRIX, model);
    float step = 0.3;

    if (movement_x) {
        camera_x += step*movement_x*model[0];
        camera_y += step*movement_x*model[4];
        camera_z += step*movement_x*model[8];
    }
    if (movement_y) {
        camera_x += step*movement_y*model[1];
        camera_y += step*movement_y*model[5];
        camera_z += step*movement_y*model[9];
    }
    if (movement_z) {
        camera_x += step*movement_z*model[2];
        camera_y += step*movement_z*model[6];
        camera_z += step*movement_z*model[10];
    }
}
void TranslateWorld() {
    ModePerspective();
    glLoadIdentity();
    glRotatef(camera_theta, 1, 0, 0);
    glRotatef(camera_phi, 0, 1, 0);
    glTranslatef(camera_x, camera_y, camera_z);
}

void DrawCoil() {
	GLfloat rgb_r, rgb_g, rgb_b;
    float i, theta;
    float r = 20;
    for (i=0; i<cubes; i++) { // draw sine wave
        theta = ((float)i - (float)frame)/30;
        rgb_r = 1-(i/cubes); rgb_g = 0; rgb_b = (i/cubes);
        glPushMatrix();
            glTranslatef((i-cubes/2)*0.01,r*sin(theta),r*cos(theta));
            glScalef(0.25,0.25,0.25);
            glColor3f(rgb_r,rgb_g,rgb_b);
            DrawVACube();
        glPopMatrix();
    }
}

void DrawFloor() {
    glPushMatrix();
        glTranslatef(0,-30,0);
        glRotatef(90, 1, 0, 0);
        DrawSquare(0.6,0.6,0.6,70,30);
    glPopMatrix();
}

void DrawImage() {
    Uint8 sdl_r, sdl_g, sdl_b;
	GLfloat rgb_r, rgb_g, rgb_b;
    float ix, iy, theta;
    float r = 5;
    float scale = 0.35;
    for (iy=0; iy<image -> h; iy++) { // draw sine wave
        theta = ((float)iy - (float)frame)/30;
        for (ix=0; ix<image -> w; ix++) { // draw sine wave
            SDL_GetRGB(getpixel(image,ix,iy), image->format, &sdl_r, &sdl_g, &sdl_b);
            rgb_r = (GLfloat)sdl_r/255;
            rgb_g = (GLfloat)sdl_g/255;
            rgb_b = (GLfloat)sdl_b/255;
            glPushMatrix();
                glTranslatef((ix-(image -> w)/2)+r*sin(theta),-(iy-(image -> h)/2),0);
                glScalef(scale,scale,scale);
                glColor3f(rgb_r,rgb_g,rgb_b);
                DrawVACube();
            glPopMatrix();
        }
    }
}

void DrawGLScene() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear buffers
    TranslateWorld();
    switch (render_mode) {
        case 1: DrawImage(); DrawFloor(); break;
        case 2: DrawCoil(); DrawFloor(); break;
    }

    SDL_GL_SwapBuffers(); // swap buffers, double buffered
}

//void BMPFile() {
//SDL_Surface * temp = SDL_CreateRGBSurface(SDL_SWSURFACE, SCREEN_W, SCREEN_H, 24, 0x000000FF, 0x0000FF00, 0x00FF0000, 0);
//
//char pixels[3 * SCREEN_W * SCREEN_H];
//
//glReadPixels(0, 0, SCREEN_W, SCREEN_H, GL_RGB, GL_UNSIGNED_BYTE, pixels);
//int i;
//for (i = 0 ; i < SCREEN_H ; i++) {
//    memcpy( ((char *) temp->pixels) + temp->pitch * i, pixels + 3 * SCREEN_W * (SCREEN_H-i - 1), SCREEN_W*3 );
//}
//
//char filename[200];
//sprintf(filename, "gif/%05d.bmp", frame);
//SDL_SaveBMP(temp, filename);
//}

int main(int argc,char *argv[]) {
	InitSDL();
	InitGL();
	InitEngine();
	LoadBMP();

	int ticks_60 = SDL_GetTicks();
	int ticks, delay;
	char filename[200];

	while(execution) {
        ticks = SDL_GetTicks();

        DrawGLScene();
        PollEvents();
        IncrementStuff();
//        BMPFile();

        delay = FRAME_MS - (SDL_GetTicks() - ticks);
        time_cumulative += (SDL_GetTicks() - ticks);
        if (delay > 0) { SDL_Delay(delay); } // code for smooth framerate
        frame++;
        if (!(frame % 15)) {
            float ms = (float)time_cumulative/15;
            char window_title[500];
            sprintf(window_title, "%.1fms %.0fKvps %.0fvpf %.0fv^2pf [%7.1f, %7.1f, %7.1f]"
                    " [%6.1f, %6.1f]",
                    ms, voxels/(15*ms), (16.66*voxels)/(15*ms), sqrt((16.66*voxels)/(15*ms)),
                    camera_x, camera_y, camera_z, camera_theta, camera_phi);
            SDL_WM_SetCaption(window_title, NULL);
            time_cumulative = 0;
            voxels = 0;
            ticks_60 = SDL_GetTicks();
        }
    }

  SDL_Quit();
  return(0);
}
