#include "os.h"
#include "ph.h"
#include "sphere.c"

#define BUFFER_WIDTH  128
#define BUFFER_HEIGHT 128
#define VIEW_WIDTH    (BUFFER_WIDTH + 1 + BUFFER_WIDTH)
#define VIEW_HEIGHT   (BUFFER_HEIGHT + 1 + (BUFFER_HEIGHT / 2) + 1 + (BUFFER_HEIGHT / 4) + 1 + (BUFFER_HEIGHT / 8))
#define FILTER_COUNT  4
#define STRINGIFY(A)  #A

#include "shaders/passv.glsl"
#include "shaders/blitf.glsl"
#include "shaders/combine4f.glsl"

PHsurface window;
PHsurface pass0[FILTER_COUNT];
GLuint sphereVerts;
GLuint sphereFaces;
GLuint blitProg;
GLuint combineProg;

GLfloat darkblue[4] = {7, 16, 141, 0};
GLfloat lightblue[4] = {122, 143, 248, 0};

static void init()
{
    int p, c;
    GLsizei width;
    GLsizei height;

    if (!GLEW_VERSION_2_0)
        fatalf("OpenGL 2.0 is required");

    if (!glewIsSupported("GL_EXT_framebuffer_object"))
        fatalf("GL_EXT_framebuffer_object is required");

    glGenBuffers(1, &sphereVerts);
    glBindBuffer(GL_ARRAY_BUFFER, sphereVerts);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertData), (void *) vertData, GL_STATIC_DRAW);

    glGenBuffers(1, &sphereFaces);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sphereFaces);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(faceData), (void *) faceData, GL_STATIC_DRAW);

    glMatrixMode(GL_MODELVIEW);

    // Compile shaders
    blitProg = phCompile(passv, blitf);
    combineProg = phCompile(passv, combine4f);

    // Normalize colors
    for (c = 0; c < 4; c++)
    {
        darkblue[c] /= 255.0f;
        lightblue[c] /= 255.0f;
    }

    // Create Window Surface
    window.fbo = 0;
    window.depth = 0;
    window.width = VIEW_WIDTH;
    window.height = VIEW_HEIGHT;
    window.clearColor[0] = darkblue[0];
    window.clearColor[1] = darkblue[1];
    window.clearColor[2] = darkblue[2];
    window.clearColor[3] = darkblue[3];
    window.viewport.x = 0;
    window.viewport.y = 0;
    window.viewport.width = window.width;
    window.viewport.height = window.height;
    glLoadIdentity();
    glGetFloatv(GL_MODELVIEW_MATRIX, window.modelview);
    glOrtho(0, window.width, window.height, 0, 0, 10);
    glGetFloatv(GL_MODELVIEW_MATRIX, window.projection);
    glLoadIdentity();

    // Create 3D Scene Surface
    width = BUFFER_WIDTH;
    height = BUFFER_HEIGHT;
    pass0[0].width = width;
    pass0[0].height = height;
    pass0[0].clearColor[0] = 0;
    pass0[0].clearColor[1] = 0;
    pass0[0].clearColor[2] = 0;
    pass0[0].clearColor[3] = 0;
    pass0[0].viewport.x = 0;
    pass0[0].viewport.y = 0;
    pass0[0].viewport.width = width;
    pass0[0].viewport.height = height;
    glGetFloatv(GL_MODELVIEW_MATRIX, pass0[0].modelview);
    glGetFloatv(GL_MODELVIEW_MATRIX, pass0[0].projection);
    phCreateSurface(pass0, GL_FALSE, GL_FALSE, GL_TRUE);
    width = width >> 1;
    height = height >> 1;

    // Create Source Surfaces
    for (p = 1; p < FILTER_COUNT; p++)
    {
        pass0[p].width = width;
        pass0[p].height = height;
        pass0[p].viewport.x = 0;
        pass0[p].viewport.y = 0;
        pass0[p].viewport.width = width;
        pass0[p].viewport.height = height;
        glGetFloatv(GL_MODELVIEW_MATRIX, pass0[p].modelview);
        glGetFloatv(GL_MODELVIEW_MATRIX, pass0[p].projection);
        phCreateSurface(pass0 + p, GL_FALSE, GL_FALSE, GL_TRUE);
        width = width >> 1;
        height = height >> 1;
    }
}

static void draw()
{
    int p;
    GLint loc;

    // Draw 3D scene.
    phBindSurface(pass0 + 0);
    phClearSurface();
    glScalef(0.25f, 0.25f, 0.25f);
    glTranslatef(-1, -1, 0);
    glBindBuffer(GL_ARRAY_BUFFER, sphereVerts);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sphereFaces);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, 0);
    glNormalPointer(GL_FLOAT, 0, 0);
    glDrawElements(GL_TRIANGLES, FACE_COUNT * 3, GL_UNSIGNED_INT, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);

    // Downsample the scene into the source surfaces.
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, pass0[0].texture);
    for (p = 1; p < FILTER_COUNT; p++)
    {
        phBindSurface(pass0 + p);
        glBegin(GL_QUADS);
        glTexCoord2i(0, 0); glVertex2i(-1, -1);
        glTexCoord2i(1, 0); glVertex2i(1, -1);
        glTexCoord2i(1, 1); glVertex2i(1, 1);
        glTexCoord2i(0, 1); glVertex2i(-1, 1);
        glEnd();
    }

    // Draw final window.
    glUseProgram(blitProg);
    loc = glGetUniformLocation(blitProg, "bkgd");
    glUniform4fv(loc, 1, lightblue);
    loc = glGetUniformLocation(blitProg, "source");
    glUniform1i(loc, 0);

    phBindSurface(&window);
    phClearSurface();
    glPushMatrix();
    for (p = 0; p < FILTER_COUNT; p++)
    {
        glBindTexture(GL_TEXTURE_2D, pass0[p].texture);
        glBegin(GL_QUADS);
        glTexCoord2i(0, 0); glVertex2i(0, 0);
        glTexCoord2i(1, 0); glVertex2i(pass0[p].width, 0);
        glTexCoord2i(1, 1); glVertex2i(pass0[p].width, pass0[p].height);
        glTexCoord2i(0, 1); glVertex2i(0, pass0[p].height);
        glEnd();
        glTranslatef(0, (GLfloat) pass0[p].height + 1, 0);
    }
    glPopMatrix();

    glTranslatef((GLfloat) pass0[0].width + 1, 0, 0);
    glUseProgram(combineProg);
    loc = glGetUniformLocation(combineProg, "bkgd");
    glUniform4fv(loc, 1, lightblue);

    for (p = 0; p < FILTER_COUNT; p++)
    {
        char name[] = "Pass#";

        glActiveTexture(GL_TEXTURE0 + p);
        glBindTexture(GL_TEXTURE_2D, pass0[p].texture);
        glEnable(GL_TEXTURE_2D);

        sprintf(name, "Pass%d", p);
        loc = glGetUniformLocation(combineProg, name);
        glUniform1i(loc, p);
    }

    glBegin(GL_QUADS);
    glTexCoord2i(0, 0); glVertex2i(0, 0);
    glTexCoord2i(1, 0); glVertex2i(pass0[0].width, 0);
    glTexCoord2i(1, 1); glVertex2i(pass0[0].width, pass0[0].height);
    glTexCoord2i(0, 1); glVertex2i(0, pass0[0].height);
    glEnd();

    glUseProgram(0);
    for (p = 0; p < FILTER_COUNT; p++)
    {
        glActiveTexture(GL_TEXTURE0 + p);
        glDisable(GL_TEXTURE_2D);
    }
    glActiveTexture(GL_TEXTURE0);
}

int main(int argc, char** argv)
{
    int done = 0;
    OS_Event event;

    osInit("Cheap Bloom" , VIEW_WIDTH, VIEW_HEIGHT, OS_OVERLAY, 0);
    osWaitVsync(0);
    init();

    while (!done)
    {
        while (osPollEvent(&event))
        {
            switch(event.type)
            {
                case OS_PAINT:
                    draw();
                    osSwapBuffers();
                    break;

                case OS_KEYUP:
                    switch (event.key.key)
                    {
                        case OSK_ESCAPE:
                        case 'X': case 'x':
                        case 'Q': case 'q':
                            done = 1;
                            break;
                    }
                    break;

                case OS_QUIT:
                    done = 1;
                    break;
            }
        }
        draw();
        osSwapBuffers();
    }

    osQuit();
    return 0;
}

