/* sdl.c
 *
 * @2011 Kamil Kaminski
 *
 * Skeleton for SDL
 *
 */
 
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

/* function prototypes */
static void resize(int, int);
static void setup_opengl(void);
static void keys(SDL_keysym *, unsigned int *);
static void process_events(void);
static void render(void);

/* global */
int program_running = 1;

/* few arrays, they could make into the header at some point */
GLfloat fNoLight[]     = { 0.0f, 0.0f, 0.0f, 0.0f };
GLfloat fLowLight[]    = { 0.25f, 0.25f, 0.25f, 1.0f };
GLfloat fBrightLight[] = { 1.0f, 1.0f, 1.0f, 1.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_RESIZABLE);
}

static void setup_opengl(void)
{
    /* 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 };

    /* setup fog */
    glEnable(GL_FOG);
    glFogfv(GL_FOG_COLOR, fLowLight); /* set fog color to match background */
    glFogf(GL_FOG_START, 4.0f);
    glFogf(GL_FOG_END, 20.0f);
    glFogi(GL_FOG_MODE, GL_LINEAR);   /* fog equation */

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

    /* 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, fBrightLight);
    glLightfv(GL_LIGHT0, GL_SPECULAR, fBrightLight);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    
    /* enable lighting */    
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    
    /* enable color tracking */
    glEnable(GL_COLOR_MATERIAL);
    
    /* set Material properties to follow glColor values */
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glMaterialfv(GL_FRONT, GL_SPECULAR, fBrightLight);
    glMateriali(GL_FRONT, GL_SHININESS, 128);

    /* turn on anti aliasing for points, lines, and polygons */
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
    glEnable(GL_POINT_SMOOTH);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_LINE_SMOOTH);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

    /* gray background */
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
}

static void keys(SDL_keysym *keysym, unsigned int *keys_held)
{
    switch (keysym->sym)
    {
    case SDLK_ESCAPE: program_running = 0; break;
    default: break;
    }
}

/* process SDL events */
static void process_events(void)
{
    SDL_Event event;
    unsigned static int keys_held[323];
    SDLKey sym;
    
    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);
            break;
        }
        case SDL_VIDEORESIZE: { resize(event.resize.w, event.resize.h); break; }
        case SDL_QUIT: { program_running = 0; break; }
        default: break;
        }
    }
}

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();

    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();

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

    /* 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("SDL", NULL);

    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    if ((screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL | 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));
        
    /* display OpenGL version */
    GLint major;
    GLint minor;
    glGetIntegerv(GL_MAJOR_VERSION, &major);
    glGetIntegerv(GL_MINOR_VERSION, &minor);
    fprintf(stdout, "version: OpenGL %d.%d\n", major, minor);

    setup_opengl();

    while (program_running)
    {
        process_events();
        render();
    }

    puts("bye!");
    
    return 0;
}