
#include <WFX/SDL/SDLRenderer.h>
#include <WFX/scene/TextureManager.h>

#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>

using namespace WFX::SDL;
using namespace WFX::math;
using namespace WFX::renderer;
using namespace WFX::scene;

SDLRenderer::SDLRenderer() : WFX::renderer::Renderer() {
}

void SDLRenderer::setCamera(Camera* c) {
    Renderer::setCamera(c);

    switch (CurrentCamera->getProjectionMode()) {
    case PM_ORTHOGRAPHIC:
        orthoMode();
        break;
    case PM_PERSPECTIVE:
        perspectiveMode();
        break;
    default:
        THROW(" SDLRenderer::beginRenderOp() -> unknown ProjectionMode : " << CurrentCamera->getProjectionMode());
    }

    //set modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    Vector3 pos = CurrentCamera->getPos();
    Vector3 view = CurrentCamera->getView();
    Vector3 up = CurrentCamera->getUp();

    gluLookAt(pos.X,pos.Y,pos.Z,
              view.X,view.Y,view.Z,
              up.X,up.Y,up.Z
             );

}

void SDLRenderer::setViewport(Viewport* v) {
    Renderer::setViewport(v);
    
    GLsizei x, y, w, h;
    
    y = (GLsizei)(v->getBottom() * CurrentTarget->getHeight());
    x = (GLsizei)(v->getLeft() * CurrentTarget->getWidth());
    w = (GLsizei)(v->getWidth() * CurrentTarget->getWidth());
    h = (GLsizei)(v->getHeight() * CurrentTarget->getHeight());

    glViewport(x, y, w, h);
    glScissor(x, y, w, h);

    //set actual values
    v->setActLeft(x);
    v->setActBottom(y);
    v->setActWidth(w);
    v->setActHeight(h);

}

void SDLRenderer::getProjectionMatrix(WFX::math::Matrix4& dest) {
    glGetFloatv(GL_PROJECTION_MATRIX, (Real*)&dest);
}

void SDLRenderer::getModelViewMatrix(WFX::math::Matrix4& dest) {
    glGetFloatv(GL_MODELVIEW_MATRIX, (Real*)&dest);
}

void SDLRenderer::beginRenderOp() {
    Renderer::beginRenderOp();
   
    setColor(Color::WHITE);
}

void SDLRenderer::endRenderOp() {
    Renderer::endRenderOp();
}

void SDLRenderer::drawTriMesh(WFX::scene::TriMesh& m) {
    //material
    Material* mat = m.getMaterial();
    if (mat)
        setMaterial(*mat);

    const Matrix3& rot = m.getWorldRotation();
    const Vector3& trans = m.getWorldTranslation();
    Real scale = m.getWorldScale();

    GLfloat model2world[16] = {
        scale*rot[0][0], scale*rot[1][0], scale*rot[2][0], 0.0f,
        scale*rot[0][1], scale*rot[1][1], scale*rot[2][1], 0.0f,
        scale*rot[0][2], scale*rot[1][2], scale*rot[2][2], 0.0f,
        trans.X,         trans.Y,         trans.Z,         1.0f,
    };

    //save modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    //apply model transformation
    glMultMatrixf(model2world);

    //switch according to meshmode
    glEnableClientState(GL_VERTEX_ARRAY);
    //pointer to the vertices
    GLfloat* vertices = (GLfloat*)m.getVertices();
    glVertexPointer(3,GL_FLOAT,0,vertices);
    //normals?
    GLfloat* normals = (GLfloat*)m.getNormals();
    if (normals) {
        glEnableClientState(GL_NORMAL_ARRAY);
        glNormalPointer(GL_FLOAT,0,normals);
    } else {
        glDisableClientState(GL_NORMAL_ARRAY);
    }
    //colors?
    GLfloat* colors = (GLfloat*)m.getColors();
    if (colors) {
        glEnableClientState(GL_COLOR_ARRAY);
        glColorPointer(4,GL_FLOAT,0,colors);
    } else {
        glDisableClientState(GL_COLOR_ARRAY);
        setColor(Color::WHITE);
    }
    //textures?
    GLfloat* texcoords = (GLfloat*)m.getTexCoords();
    if (texcoords) {
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer(2,GL_FLOAT,0,texcoords);
    } else {
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }

    //render triangles
    glDrawElements(GL_TRIANGLES,3*m.getFaceCount(),GL_UNSIGNED_INT,m.getConnectivity());

    //draw normals?
    if (m.DrawVertexNormals) {
        Color* cols = m.getColors();
        Vector3* verts = m.getVertices();
        Vector3* norms = m.getNormals();
        if (norms) {
            glBegin(GL_LINES);
            for (UInt i=0;i<m.getVertexCount();i++) {
                if (cols) {
                    glColor4f((*cols).R,(*cols).G,(*cols).B,(*cols).A);
                    cols++;
                }
                Vector3 v = verts[i];
                Vector3 n = norms[i] + v;
                glVertex3f(v[0],v[1],v[2]);
                glVertex3f(n[0],n[1],n[2]);
            }
            glEnd();
        }
    }

    //restore modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
}

void SDLRenderer::drawSphere(WFX::math::Sphere& s) {
    THROW("SDLRenderer::drawSphere not yet implemented");
}

void SDLRenderer::drawWireCube(WFX::math::Cube& c) {
    glColor3ub(255, 255, 0);
    Real hw = c.getWidth()/2;

    Vector3 tlf(c.getCenter().X - hw, c.getCenter().Y + hw, c.getCenter().Z + hw);
    Vector3 tlb(c.getCenter().X - hw, c.getCenter().Y + hw, c.getCenter().Z - hw);
    Vector3 trb(c.getCenter().X + hw, c.getCenter().Y + hw, c.getCenter().Z - hw);
    Vector3 trf(c.getCenter().X + hw, c.getCenter().Y + hw, c.getCenter().Z + hw);

    Vector3 blf(c.getCenter().X - hw, c.getCenter().Y - hw, c.getCenter().Z + hw);
    Vector3 blb(c.getCenter().X - hw, c.getCenter().Y - hw, c.getCenter().Z - hw);
    Vector3 brb(c.getCenter().X + hw, c.getCenter().Y - hw, c.getCenter().Z - hw);
    Vector3 brf(c.getCenter().X + hw, c.getCenter().Y - hw, c.getCenter().Z + hw);

    glBegin(GL_LINES);
        //top
        glVertex3f(tlf.X, tlf.Y, tlf.Z);
        glVertex3f(trf.X, trf.Y, trf.Z);

        glVertex3f(tlb.X, tlb.Y, tlb.Z);
        glVertex3f(trb.X, trb.Y, trb.Z);

        glVertex3f(tlf.X, tlf.Y, tlf.Z);
        glVertex3f(tlb.X, tlb.Y, tlb.Z);

        glVertex3f(trf.X, trf.Y, trf.Z);
        glVertex3f(trb.X, trb.Y, trb.Z);

        //bottom
        glVertex3f(blf.X, blf.Y, blf.Z);
        glVertex3f(brf.X, brf.Y, brf.Z);

        glVertex3f(blb.X, blb.Y, blb.Z);
        glVertex3f(brb.X, brb.Y, brb.Z);

        glVertex3f(blf.X, blf.Y, blf.Z);
        glVertex3f(blb.X, blb.Y, blb.Z);

        glVertex3f(brf.X, brf.Y, brf.Z);
        glVertex3f(brb.X, brb.Y, brb.Z);

        //sides
        glVertex3f(tlf.X, tlf.Y, tlf.Z);
        glVertex3f(blf.X, blf.Y, blf.Z);

        glVertex3f(tlb.X, tlb.Y, tlb.Z);
        glVertex3f(blb.X, blb.Y, blb.Z);

        glVertex3f(trb.X, trb.Y, trb.Z);
        glVertex3f(brb.X, brb.Y, brb.Z);

        glVertex3f(trf.X, trf.Y, trf.Z);
        glVertex3f(brf.X, brf.Y, brf.Z);
    glEnd();
}

void SDLRenderer::setMaterial(WFX::scene::Material& m) {
    //m.printDebug();
    glMaterialfv(GL_FRONT, GL_AMBIENT, (GLfloat*)&m.getAmbient());
    glMaterialfv(GL_FRONT, GL_EMISSION, (GLfloat*)&m.getEmissive());
    glMaterialfv(GL_FRONT, GL_DIFFUSE, (GLfloat*)&m.getDiffuse());
    //TODO: WTF?
    glMaterialfv(GL_FRONT, GL_SPECULAR, (GLfloat*)&m.getSpecular());
    glMaterialf(GL_FRONT, GL_SHININESS, (GLfloat)m.getShininess());
    TextureManager::getInstance().bindTexture(m.getTexture());
}

void SDLRenderer::setRenderContext(const RenderContext& r) {
    //TODO: make this part of the RenderContext
    glShadeModel(GL_SMOOTH); 
    //lighting
    if (r.Lighting) {
        glEnable(GL_LIGHTING);
        glEnable(GL_COLOR_MATERIAL);
        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

        //TODO: make this world properties
        glEnable(GL_LIGHT0);

        // GL_LIGHT0: the white light emitting light source
        // Create light components for GL_LIGHT0
        float ambientLight0[] = { 0.2f, 0.2f, 0.2f, 1.0f };
        float diffuseLight0[] = { 0.4f, 0.4f, 0.4f, 1.0f };
        float specularLight0[] = { 0.2f, 0.2f, 0.2f, 1.0f };
        float position0[] = { 0.0f, 1.0, -1.0f, 0.0f };
        // Assign created components to GL_LIGHT0
        glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight0);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight0);
        glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight0);
        glLightfv(GL_LIGHT0, GL_POSITION, position0);

    } else {
        glDisable(GL_LIGHTING);
    }
    //depth testing
    if (r.DepthTesting) {
        glEnable(GL_DEPTH_TEST);
    } else {
        glDisable(GL_DEPTH_TEST);
    }
    //texture mapping
    if (r.TextureMapping) {
        glEnable(GL_TEXTURE_2D);
    } else {
        glDisable(GL_TEXTURE_2D);
    }
    //blending
    if (r.Blending) {
        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
        glEnable(GL_BLEND);
    } else {
        glDisable(GL_BLEND);
    }

}

void SDLRenderer::orthoMode() {
    glMatrixMode(GL_PROJECTION); 
    glLoadIdentity();
   
    GLfloat width = (GLfloat)(CurrentTarget->getWidth() * CurrentViewport->getWidth());
    GLfloat height = (GLfloat)(CurrentTarget->getHeight() * CurrentViewport->getHeight());

    glOrtho(0.0f, -width, 0.0f, height, -OrthoMax, OrthoMax);

}

void SDLRenderer::perspectiveMode() {
    glMatrixMode(GL_PROJECTION); 
    glLoadIdentity();
   
    GLfloat width = (GLfloat)(CurrentTarget->getWidth() * CurrentViewport->getWidth());
    GLfloat height = (GLfloat)(CurrentTarget->getHeight() * CurrentViewport->getHeight());
    
    GLfloat ratio = width / height;

    gluPerspective(CurrentCamera->getFOVY(),             //FOV
                   ratio,                                //Ratio
                   CurrentCamera->getNear(),             //near clipping distance
                   CurrentCamera->getFar());             //far clipping distance
}




void SDLRenderer::setColor(const Color& c) {
    glColor4f(c.R, c.G, c.B, c.A);
}

