#include "SceneOpenGL.h"

// Permet d'éviter la ré-écriture du namespace glm::
using namespace glm;

// Constructeur de Destucteur
SceneOpenGL::SceneOpenGL(std::string titreFenetre, int largeurFenetre, int hauteurFenetre)
: m_titreFenetre(titreFenetre), m_largeurFenetre(largeurFenetre),
m_hauteurFenetre(hauteurFenetre), m_fenetre(0), m_contexteOpenGL(0),
m_input(), m_texturLoader( "Textures/" ),
m_shader( "Shaders/texture.vert", "Shaders/texture.frag" ),
m_shLight( "Shaders/light.vert", "Shaders/light.frag" ),
m_shGris( "Shaders/texture.vert", "Shaders/gris.frag" )
{

}

SceneOpenGL::~SceneOpenGL()
{
    SDL_GL_DeleteContext(m_contexteOpenGL);
    SDL_DestroyWindow(m_fenetre);
    SDL_Quit();
}

// Méthodes
bool SceneOpenGL::initialiserFenetre()
{
    // Initialisation de la SDL
    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;
        SDL_Quit();

        return false;
    }

    #ifdef __APPLE__
        // Version d'OpenGL
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

        // Récupération du Bundle
        CFURLRef URLBundle = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
        char *cheminResources = new char[PATH_MAX];

        // Changement du 'Working Directory'
        if(CFURLGetFileSystemRepresentation(URLBundle, 1, (UInt8*)cheminResources, PATH_MAX))
            chdir(cheminResources);

        // Libération de la mémoire
        delete[] cheminResources;
        CFRelease(URLBundle);

    #else
        // Version d'OpenGL
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

    #endif

    // Création de la fenêtre
    uint32_t flag( SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL );
    m_fenetre = SDL_CreateWindow(m_titreFenetre.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, m_largeurFenetre, m_hauteurFenetre, flag);
    if(m_fenetre == 0)
    {
        std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;
        SDL_Quit();

        return false;
    }

    // Paramètres des évènements
    m_input.setWindow( m_fenetre );
    m_input.capturerPointeur( true );
    m_input.setMoveKeys( SDL_SCANCODE_W, SDL_SCANCODE_S, SDL_SCANCODE_D, SDL_SCANCODE_A );

    // Création du contexte OpenGL
    m_contexteOpenGL = SDL_GL_CreateContext(m_fenetre);
    if(m_contexteOpenGL == 0)
    {
        std::cout << SDL_GetError() << std::endl;
        SDL_DestroyWindow(m_fenetre);
        SDL_Quit();

        return false;
    }

    return true;
}


bool SceneOpenGL::initGL()
{
    #ifdef WIN32
        // On initialise GLEW
        GLenum initialisationGLEW( glewInit() );

        // Si l'initialisation a échoué :
        if(initialisationGLEW != GLEW_OK)
        {
            // On affiche l'erreur grâce à la fonction : glewGetErrorString(GLenum code)
            std::cout << "Erreur d'initialisation de GLEW : " << glewGetErrorString(initialisationGLEW) << std::endl;

            // On quitte la SDL
            SDL_GL_DeleteContext(m_contexteOpenGL);
            SDL_DestroyWindow(m_fenetre);
            SDL_Quit();

            return false;
        }
    #endif

    // Paramètres OpenGL avancés
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    // Tout s'est bien passé, on retourne true
    return true;
}


void SceneOpenGL::bouclePrincipale()
{
    // Variables temps
    unsigned int frameRate (1000 / 50);
    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Frame Buffer
    FrameBuffer frameBuffer(512, 512);
    frameBuffer.charger();
    m_texturLoader.addTextur("FBO_Tele", frameBuffer.getColorBuffer(0));

    // Shaders
    m_shader.charger();
    m_shLight.charger();
    m_shGris.charger();
    Shader* shTarget(0x0);

    // Matrices (première passe)
    mat4 projectionFBO;

    projectionFBO = perspective(70.0, (double)frameBuffer.getLargeur() / frameBuffer.getHauteur(), 1.0, 100.0);

    // Matrices (seconde passe)
    mat4 projection, projection30, projection70, plat;
    mat4 view, unicite(1.0);

    const float format( (float) m_largeurFenetre / m_hauteurFenetre );
    projection70 = perspective(70.0, (double)format, 0.1, 100.0);
    projection30 = perspective(30.0, (double)format, 0.1, 100.0);
    const float plat_extY( 30.0 );
    const float plat_extX( plat_extY * format );
    plat = ortho(-plat_extX, plat_extX, -plat_extY, plat_extY, 0.1f, 100.0f);

    // Vecteurs
    uint16_t nbLight(3);
    vec3 posLight[nbLight];
    posLight[0] = vec3( 10.0, 3.0, 0.0 );
    posLight[2] = vec3( 30.0, 3.5, 0.0 );
    float posLightV[nbLight*3];

    // Caméra mobile
    Camera camera(vec3(0, 1.78, 0), vec3(0, 1.78, -1), vec3(0, 1, 0), 0.5, 0.5);
    vec3 eyePos;
    m_input.afficherPointeur(false);
    m_input.capturerPointeur(true);

    // Objet Caisse
    OBJ metal;
    metal.charger("3DModels/kube.obj", m_texturLoader.take("caisse_basic"));
    mat4 model_metal(1.0);

    OBJ plan;
    plan.charger( "3DModels/plan.obj", m_texturLoader.take("pierres"), 50.0, 20.0);
    mat4 model_plan(1.0);
    model_plan = translate(model_plan, vec3(2.5,0.0,0.0));

    OBJ tele;
    tele.charger( "3DModels/plan.obj", m_texturLoader.take("FBO_Tele"), 2.5 );
    mat4 model_tele(1.0);
    model_tele = translate( model_tele, vec3(16.56,3.2,-0.5) );
    model_tele = rotate(model_tele, 90.0f, vec3(0, 0, 1));

    OBJ caisse;
    caisse.charger("3DModels/kube.obj", m_texturLoader.take("chantier"));
    mat4 model_caisseA(1.0),model_caisseB(1.0),model_caisseC(1.0);
    model_caisseA = translate( model_caisseA, vec3(-6.56,1.0,-0.5) );
    model_caisseB = translate( model_caisseB, vec3(6.56,1.0,4.5) );
    model_caisseC = translate( model_caisseC, vec3(3.5,1.0,3.5) );

    OBJ weapon;
    weapon.charger( "3DModels/MPX.obj", m_texturLoader.take("chantier"), 0.2 );
    mat4 model_weapon(1.0);

    Mosaic sword;
    sword.charger( "2DModels/sword_iron.bmp" );
    mat4 model_sword(1.0);

    float angle = 0.0;
    float theta, theta_old = 0.0;
    float phi, phi_old = 0.0;

    bool rangee(true), rangement(false), veutRanger(false);
    bool veutViser(false);
    bool veutTemporiser(false);
    bool veutLight(false), lightDone(false), lightState(false);
    bool veutOverLight(false), overLightDone(false); GLint overLightState(false);

    // Boucle principale
    while(!m_input.terminer())
    {
        // On définit le temps de début de boucle
        debutBoucle = SDL_GetTicks();

        // Gestion des évènements
        m_input.updateEvenements();

        camera.setVol( m_input.getTouche( SDL_SCANCODE_LSHIFT ) );

        if (m_input.getTouche(SDL_SCANCODE_ESCAPE))
            break;

        if (m_input.getTouche(SDL_SCANCODE_KP_3)) nbLight = 3;
        if (m_input.getTouche(SDL_SCANCODE_KP_2)) nbLight = 2;
        if (m_input.getTouche(SDL_SCANCODE_KP_1)) nbLight = 1;
        if (m_input.getTouche(SDL_SCANCODE_KP_0)) nbLight = 0;

        camera.deplacer(m_input);

        veutRanger = m_input.getTouche(SDL_SCANCODE_E) || m_input.getBoutonPad(1);
        if ( !rangement && veutRanger ) {
            rangement = true;
            rangee = !rangee;
            if ( rangee ) camera.setVitesse(0.5f);
            else camera.setVitesse(0.2f);
        }
        else if ( !veutRanger ) rangement = false;

        veutTemporiser = m_input.getTouche( SDL_SCANCODE_SPACE ) || m_input.getBoutonPad(2);

        veutViser = m_input.getBoutonSouris(3) || m_input.getBoutonPad(4);

        veutLight = m_input.getTouche( SDL_SCANCODE_LCTRL ) || m_input.getBoutonPad(3);
        if ( !lightDone && veutLight ) {
            lightDone = true;
            lightState = !lightState;
        }
        else if ( !veutLight ) lightDone = false;

        veutOverLight = m_input.getTouche( SDL_SCANCODE_Q ) || m_input.getBoutonPad(0);
        if ( !overLightDone && veutOverLight ) {
            overLightDone = true;
            overLightState = !overLightState;
        }
        else if ( !veutOverLight ) overLightDone = false;


        /* ***** Première Passe ***** */

        // Verrouillage du Frame Buffer
        glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer.getID());

            // Nettoyage de l'écran
            glClearColor(0.2, 0.2, 0.25, 1.0);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Redimensionnement de la zone d'affichage
            glViewport(0, 0, frameBuffer.getLargeur(), frameBuffer.getHauteur());

            // Placement de la caméra
            view = lookAt(vec3(0, 0, 3), vec3(0, 0, 0), vec3(0, 1, 0));

            // Gestion de la rotation de la caisse
            if ( veutTemporiser )
                angle = 0.05f;
            else
                angle = 2.0f;

            // Activation du shader
            glUseProgram( m_shGris.getProgramID() );

                // Affichage de la caisse
                model_metal = rotate(model_metal, angle, vec3(0, 1, 0));
                metal.afficher(projectionFBO*view*model_metal, model_metal, &m_shGris);

            // Désactivation du shader
            glUseProgram( 0 );

        // Déverrouillage du Frame Buffer
        glBindFramebuffer(GL_FRAMEBUFFER, 0);


        /* ***** Seconde Passe ***** */

        // Nettoyage de l'écran
        glClearColor(0.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Redimensionnement de la zone d'affichage
        glViewport(0, 0, m_largeurFenetre, m_hauteurFenetre);

        // Gestion de la caméra
        camera.lookAt(view);
        if ( veutViser ) projection = projection30;
        else if ( m_input.getTouche( SDL_SCANCODE_X ) ) projection = plat;
        else projection = projection70;

        // Gestion de la lumière
        posLight[1] = camera.getPos() + vec3(0.0, 1.2, 0.0);
        posLight[2] = rotateY( posLight[2], angle );

        uint16_t j;
        for ( uint16_t i(0); i<nbLight; i++ )
        {
            j = i * 3;
            posLightV[j] = posLight[i].x;
            posLightV[j+1] = posLight[i].y;
            posLightV[j+2] = posLight[i].z;
        }


        // Activation du shader
        if ( lightState )
        {
            eyePos = camera.getPos();
            shTarget = &m_shLight;
        }
        else
            shTarget = &m_shader;

        glUseProgram( shTarget->getProgramID() );

        if ( lightState )
        {
            glUniform3f(glGetUniformLocation(m_shLight.getProgramID(), "eyePos"), eyePos.x, eyePos.y, eyePos.z);
            glUniform3fv(glGetUniformLocation(m_shLight.getProgramID(), "posLight"), nbLight, posLightV);
            glUniform1i(glGetUniformLocation(m_shLight.getProgramID(), "overLight"), overLightState);
            glUniform1i(glGetUniformLocation(m_shLight.getProgramID(), "nbLight"), (int)nbLight);
        }

            // Afficher le plan
            plan.afficher(projection * view * model_plan, model_plan, shTarget);

            // Afficher la télé
            tele.afficher(projection * view * model_tele, model_tele, shTarget);

            // Affichage de la caisse
            model_caisseA = rotate(model_caisseA, angle, vec3(0, 1, 0));
            weapon.afficher(projection * view * model_caisseA, model_caisseA, shTarget);
            caisse.afficher(projection * view * model_caisseB, model_caisseB, shTarget);
            caisse.afficher(projection * view * model_caisseC, model_caisseC, shTarget);

            // Affichage de l'arme
            if ( rangee ) {
                theta = camera.getTheta() + 180;
                phi = 90;
            }
            else {
                theta = camera.getTheta();
                phi = camera.getPhi();
            }

            theta_old = theta_old*3 + theta;
            theta_old /= 4;
            phi_old = phi_old*3 - phi;
            phi_old /= 4;

            model_weapon = translate( unicite, camera.getPos() );
            model_weapon = rotate(model_weapon, theta_old, vec3(0, 1, 0));
            model_weapon = rotate(model_weapon, phi_old, vec3(1, 0, 0) );

            if ( veutViser ) model_weapon = translate( model_weapon, vec3(0,-0.5,1.0) );
            else model_weapon = translate( model_weapon, vec3(-0.5,-0.7,1.0) );

            glClear(GL_DEPTH_BUFFER_BIT); // L'arme s'affiche au premier plan quoi qu'il arrive

            weapon.afficher(projection * view * model_weapon, model_weapon, shTarget);

            // Affichage de l'épée
            sword.afficher(projection * view * model_sword, model_sword, shTarget);

        // Désactivation du shader
        shTarget = 0x0;
        glUseProgram( 0 );

        // Actualisation de la fenêtre
        SDL_GL_SwapWindow(m_fenetre);

        // Calcul du temps écoulé
        finBoucle = SDL_GetTicks();
        tempsEcoule = finBoucle - debutBoucle;

        // Si nécessaire, on met en pause le programme
        if(tempsEcoule < frameRate)
            SDL_Delay(frameRate - tempsEcoule);
    }
}
