/*******************************************************************************
************************ Les bibliothèques à inclure ***************************
*******************************************************************************/
#include "main.hpp"

using namespace USpeedGameEngine;


// Les textures
Material matModel;
Material matBackground;
Material matWall;
Material::Texture modelTexture;
Material::Texture bgTexture;
Material::Texture wallTexture;
Video::Camera cam;
// Les modèles
int selectedModel;
int modelCount;
Object3D *currentModel3D;
Object3D model3D[2];
Object3D modelBox[5];
Surface2D modelBG;
float xTexBG = 0.0f;
float yTexBG = 0.0f;

InputControl::Mouse mouse;
InputControl::Keyboard kb;

// Les modes
const char allModes[] = "MCL";
unsigned int processMode;

/**
 * Point d'entrée de l'application.
 * @author U.Speed
 * @param int : argCount : nombre d'arguments passés au programme
 * @param char** : argValues : liste des arguments passés au programme
 */
int main(int argNumber, char *argList[])
{
    // Anti-aliasing maximum
    Video::getInstance()->activateFSAA(true, 8);
    Core *core = Core::getInstance();
    core->setApplicationTitle("Tests...");
    return core->run(argNumber, argList, myInit, myMain, myFinish, OPENGL);
}

/**
 * Initialisation de l'application.
 * @author U.Speed
 */
void myInit()
{
    processMode = 0;
    // Activation de l'éclairage
    Video::Light::activate(true);

    // Paramétrage de la lumière 1
    Video::Light *light = Video::Light::getLight(1);
    light->setAmbient(USGE_Float3(1.0f, 1.0f, 1.0f));
    light->setDiffuse(USGE_Float3(1.0f, 1.0f, 1.0f));
    light->setPosition(USGE_Float3(-5.0f, 0.0f, 10.0f));
    light->setState(true);

    // Temps d'attente entre deux pressions de touche
    kb.setKeyDelayAll(500, 10);

    // Les textures
    Material::Texture::setDefaultMode(LINEAR);
    if (modelTexture.fromImageFile((char*)"model.jpg", JPG) == false)
    {
        modelTexture.fromImageFile((char*)"../demoData/model.jpg", JPG);
    }
    if (bgTexture.fromImageFile((char*)"background.jpg", JPG) == false)
    {
        bgTexture.fromImageFile((char*)"../demoData/background.jpg", JPG);
    }
    if (wallTexture.fromImageFile((char*)"wall.jpg", JPG) == false)
    {
        wallTexture.fromImageFile((char*)"../demoData/wall.jpg", JPG);
    }
    // Les matériaux
    matModel.setTexture(&modelTexture);
    matBackground.setTexture(&bgTexture);
    matWall.setTexture(&wallTexture);
    matModel.setSpecular(USGE_Float3(1.0f, 0.0f, 0.0f));
    matBackground.setSpecular(USGE_Float3(1.0f, 0.0f, 0.0f));
    matWall.setSpecular(USGE_Float3(1.0f, 0.0f, 0.0f));
    matModel.setShininess(0.0f);
    matBackground.setShininess(0.0f);
    matWall.setShininess(0.0f);

    ////////////////////////////////////////////////////////////
    // Chargement du premier modèle
    {
        bool mdlOk = model3D[0].load3D("model-1.us3dm");
        if (mdlOk == false)
        {
            mdlOk = model3D[0].load3D("../demoData/model-1.us3dm");
        }
        printf("1st - 3D loaded : %s !\n", mdlOk?"Ok":"Error");
    }
    model3D[0].scaleToUnit();
    model3D[0].setMaterial(&matModel);
    model3D[0].setRotation(USGE_Float3(212.0f, 135.0f, 0.0f));
    model3D[0].setTranslation(USGE_Float3(0.0f, 0.0f, -3.0f));

    ////////////////////////////////////////////////////////////
    // Chargement du second modèle
    {
        bool mdlOk = model3D[1].load3D("model-2.us3dm");
        if (mdlOk == false)
        {
            mdlOk = model3D[1].load3D("../demoData/model-2.us3dm");
        }
        printf("2nd - 3D loaded : %s !\n", mdlOk?"Ok":"Error");
    }
    model3D[1].scaleToUnit();
    model3D[1].setMaterial(&matModel);
    model3D[1].setRotation(USGE_Float3(230.0f, 135.0f, 0.0f));
    model3D[1].setTranslation(USGE_Float3(0.0f, 0.0f, -3.0f));

    ////////////////////////////////////////////////////////////
    // Choix du premier modèle
    selectedModel = 1;
    modelCount = 2;
    currentModel3D = &model3D[0];

    ////////////////////////////////////////////////////////////
    // Chargement des 5 murs de la salle d'accueil
    for (int idx = 0; idx < 5; idx++)
    {
        modelBox[idx].loadBox(USGE_Float3(10.0f, 0.1f, 10.0f));
        modelBox[idx].setMaterial(&matWall);
    }
    // Sol
    modelBox[0].setTranslation(USGE_Float3(0.0f, -10.0f, -10.0f));
    // Plafond
    modelBox[1].setTranslation(USGE_Float3(0.0f, 10.0f, -10.0f));
    // Mur du fond
    modelBox[2].setTranslation(USGE_Float3(0.0f, 0.0f, -20.0f));
    modelBox[2].setRotation(USGE_Float3(-90.0f, -90.0f, 0.0f));
    // Mur de gauche
    modelBox[3].setTranslation(USGE_Float3(-10.0f, 0.0f, -10.0f));
    modelBox[3].setRotation(USGE_Float3(0.0f, 0.0f, 90.0f));
    // Mur de droite
    modelBox[4].setTranslation(USGE_Float3(10.0f, 0.0f, -10.0f));
    modelBox[4].setRotation(USGE_Float3(0.0f, 0.0f, -90.0f));

    ////////////////////////////////////////////////////////////
    // Chargement d'une surface prenant la taille de l'écran
    modelBG.loadSurface();
    modelBG.setMaterial(&matBackground);
    modelBG.setTranslation(USGE_Float3(0.0f, 0.0f, -20.0f));

    Video::Camera::setActiveCamera(&cam);
}

/**
 * Traitement principal.
 * @author U.Speed
 * @param elapsedTime unsigned long int : temps écoulé depuis le dernier appel
 */
void myMain(unsigned long int elapsedTime)
{
    // L'environnement
    modelBG.render();
    for (int idx = 0; idx < 5; idx++)
    {
        //modelBox[idx].render();
    }
    currentModel3D->renderShadow(USGE_Float3(1.0f, 1.0f, 1.0f));
    // Le modèle
    currentModel3D->render();

    // Traitement différent selon le mode
    switch (allModes[processMode])
    {
        // Traitement des mouvements de la caméra
        case 'C':
        {
            processCamera();
        }
        break;
        // Traitement du déplacement du modèle
        case 'L':
        {
            // Test LUA
            if (kb.getKeyState('L') == true)
            {
                Lua luaTest;
                printf("Prepare : %s\n", luaTest.prepare()?"true":"false");
                printf("Add Fct : %s\n", luaTest.addFunction("luaPrint", luaPrint)?"true":"false");
                printf("Load    : %s\n", luaTest.loadFromBuffer("luaPrint(); luaPrint(\"#1\", \"#2\", \"#3\");")?"true":"false");
                printf("Run     : %s\n", luaTest.run()?"true":"false");
                luaTest.finish();
            }
        }
        break;
        // Traitement du déplacement du modèle
        case 'M':
        {
            processModel();
        }
        break;
        // Mode non reconnu
        default:
        {
        }
        break;
    }

    // Test de modification de la texture
    if (kb.getKeyState('I') == true)
    {
        printf("(x=%f, y=%f) -> ", xTexBG, yTexBG);
        if ((xTexBG == 0.0f) && (yTexBG == 0.0f))       { xTexBG = 0.5f; }
        else if ((xTexBG == 0.5f) && (yTexBG == 0.0f))  { yTexBG = 0.5f; }
        else if ((xTexBG == 0.5f) && (yTexBG == 0.5f))  { xTexBG = 0.0f; }
        else if ((xTexBG == 0.0f) && (yTexBG == 0.5f))  { yTexBG = 0.0f; }
        printf("(x=%f, y=%f)\n", xTexBG, yTexBG);
        modelBG.setTexturePart(xTexBG, yTexBG, xTexBG + 0.5f, yTexBG + 0.5f);
    }
    // Restauration de l'initialisation
    if (kb.getKeyState(' ') == true)
    {
        processMode++;
        if (processMode >= strlen(allModes))
        {
            processMode = 0;
        }
        printf("SWITCH MODE : %c\n", allModes[processMode]);
    }

    for (int idxKey = 0; idxKey < 256; idxKey++)
    {
        if (kb.getKeyState((char)idxKey) == true)
        {
            printf("Key pressed : %d\n", idxKey);
        }
    }
}

/**
 * Finalisation de l'application.
 * @author U.Speed
 */
void myFinish()
{
}

/**
 * Fonction de test "Lua".
 * @author U.Speed
 * @param state void* : l'état Lua a fournir à la classe "Lua".
 * @return int : le nombre de variable en retour
 */
int luaPrint(void *state)
{
    unsigned int count = Lua::getParametersCount(state);
    printf("Test (%d) !\n", count);
    for (unsigned int idx = 0; idx < count; idx++)
    {
        std::string localString;
        Lua::getString(state, (idx+1), localString);
        printf(" -> %d : String(%s)\n", (idx+1), localString.c_str());
    }
    return 0;
}

/**
 * Traitement de la caméra.
 * @author U.Speed
 */
void processCamera()
{
    // Récupération des informations
    USGE_Float3 pos = cam.getPosition();
    USGE_Float3 rot = cam.getDirection();

    //////////////////
    // Orientations //
    //////////////////
    // Orientation de la caméra
    // Informations de contrôle
    static bool previousMouse = false;
    static long int xMouse = 0;
    static long int yMouse = 0;
    //////////////////////////////////////////////////////////////////////////////
    // Gestion de la souris pour effectuer des rotations/translations du modèle //
    if (mouse.getClickState(mouseLeftClick) == true)
    {
        if (previousMouse == false)
        {
            previousMouse = true;
            mouse.getPosition(&xMouse, &yMouse);
        }
        else
        {
            rot.x -= yMouse;
            rot.y -= xMouse;
            mouse.getPosition(&xMouse, &yMouse);
            rot.x += yMouse;
            rot.y += xMouse;
            // Borne X
            if (rot.x > 85.0f)       { rot.x = 85.0f; }
            else if (rot.x < -85.0f) { rot.x = -85.0f; }
            // Borne Y
            //if (rot.y > 85.0f)       { rot.y = 85.0f; }
            //else if (rot.y < -85.0f) { rot.y = -85.0f; }
        }
    }
    else
    {
        previousMouse = false;
    }

    //////////////////
    // Déplacements //
    //////////////////
    // Déplacements selon X
    {
        bool left = kb.getKeyState('Q');
        bool right = kb.getKeyState('D');
        if ((left == true) || (right == true))
        {
            float offset = 0.1f;
            if (right == true)
            {
                offset = -0.1f;
            }
            pos.x += ((float)cos((rot.y) * 3.1415f / 180.0f) * offset);
            pos.z += ((float)sin((rot.y) * 3.1415f / 180.0f) * offset);
        }
    }
    // Déplacements selon Y
    if (kb.getKeyState('A') == true)        { pos.y += 0.1f; }
    else if (kb.getKeyState('E') == true)   { pos.y -= 0.1f; }
    // Déplacements selon Z
    {
        bool forward = kb.getKeyState('Z');
        bool backward = kb.getKeyState('S');
        if ((forward == true) || (backward == true))
        {
            float offset = 0.1f;
            if (backward == true)
            {
                offset = -0.1f;
            }
            pos.x += ((float)cos((rot.y + 90) * 3.1415f / 180.0f) * offset);
            pos.z += ((float)sin((rot.y + 90) * 3.1415f / 180.0f) * offset);
        }
    }

    // Affectation des informations
    cam.setPosition(pos);
    cam.setDirection(rot);
}

/**
 * Traitement du modèle.
 * @author U.Speed
 */
void processModel()
{
    // Informations de contrôle du modèle 3D
    static bool previousMouse = false;
    static bool rotationMode = true;
    static long int xMouse = 0;
    static long int yMouse = 0;
    //////////////////////////////////////////////////////////////////////////////
    // Gestion de la souris pour effectuer des rotations/translations du modèle //
    if (mouse.getClickState(mouseLeftClick) == true)
    {
        if (previousMouse == false)
        {
            previousMouse = true;
            mouse.getPosition(&xMouse, &yMouse);
        }
        else
        {
            if (rotationMode == true)
            {
                USGE_Float3 rot = currentModel3D->getRotation();
                rot.x -= yMouse;
                rot.y -= xMouse;
                mouse.getPosition(&xMouse, &yMouse);
                rot.x += yMouse;
                rot.y += xMouse;
                currentModel3D->setRotation(rot);
            }
            else
            {
                USGE_Float3 trans = currentModel3D->getTranslation();
                trans.x -= ((float)xMouse * 0.01f);
                trans.y += ((float)yMouse * 0.01f);
                mouse.getPosition(&xMouse, &yMouse);
                trans.x += ((float)xMouse * 0.01f);
                trans.y -= ((float)yMouse * 0.01f);
                currentModel3D->setTranslation(trans);
            }
        }
    }
    ///////////////////////////////////////////////////
    // Gestion de la souris pour effectuer des zooms //
    else if (mouse.getClickState(mouseRightClick) == true)
    {
        if (previousMouse == false)
        {
            previousMouse = true;
            mouse.getPosition(&xMouse, &yMouse);
        }
        else
        {
            USGE_Float3 trans = currentModel3D->getTranslation();

            float delta = 0.0f;
            delta -= yMouse;
            mouse.getPosition(&xMouse, &yMouse);
            delta += yMouse;
            delta /= 100.0f;
            trans.z += delta;
            // Borné entre -2 et -10
            if (trans.z > -2.0f)
            {
                trans.z = -2.0f;
            }
            else if (trans.z < -10.0f)
            {
                trans.z = -10.0f;
            }
            currentModel3D->setTranslation(trans);
        }
    }
    else
    {
        previousMouse = false;
    }

    // Affichage de la position du modèle
    if (kb.getKeyState('C') == true)
    {
        selectedModel++;
        if (selectedModel > modelCount)
        {
            selectedModel = 1;
        }
        currentModel3D = &model3D[selectedModel - 1];
        printf("Modele 3D : %d\n", selectedModel);
    }

    // Affichage de la position du modèle
    if (kb.getKeyState('P') == true)
    {
        USGE_Float3 pos = currentModel3D->getTranslation();
        printf("Position : %f, %f, %f\n", pos.x, pos.y, pos.z);
    }

    // Affichage de la rotation du modèle
    if (kb.getKeyState('R') == true)
    {
        USGE_Float3 rot = currentModel3D->getRotation();
        printf("Rotation : %f, %f, %f\n", rot.x, rot.y, rot.z);
    }

    // Mode de contrôle à la souris, bouton gauche : rotation/translation
    if (kb.getKeyState('M') == true)
    {
        rotationMode = (rotationMode == false);
        printf("Mode : %s\n", rotationMode?"Rotation":"Position");
    }

    // Etat de l'éclairage
    if (kb.getKeyState('L') == true)
    {
        bool state = (Video::Light::isActive() == false);
        Video::Light::activate(state);
        printf("Eclairage : %s\n", state?"Actif":"Inactif");
    }

    // Affichage doux "Smooth"
    if (kb.getKeyState('S') == true)
    {
        bool state = (currentModel3D->getSmooth() == false);
        currentModel3D->setSmooth(state);
        printf("Rendu doux : %s\n", state?"Actif":"Inactif");
    }

    // Affichage des faces du cube (modèle)
    USGE_KeyFrame kf;
    kf.duration = 1000;
    kf.position = currentModel3D->getTranslation();

    static float shininess = 0.0f;
    // Shininess
    if (kb.getKeyState('G') == true)
    {
        if (shininess > 0.0f)
        {
            shininess -= 0.001f;
            printf("Shininess - (shininess = %f)\n", shininess);
            matModel.setShininess(shininess);
            matBackground.setShininess(shininess);
            matWall.setShininess(shininess);
        }
    }
    if (kb.getKeyState('H') == true)
    {
        if (shininess < 1.0f)
        {
            shininess += 0.001f;
            printf("Shininess + (shininess = %f)\n", shininess);
            matModel.setShininess(shininess);
            matBackground.setShininess(shininess);
            matWall.setShininess(shininess);
        }
    }
    if (kb.getKeyState('J') == true)
    {
        if (shininess > 0.0f)
        {
            shininess -= 0.1f;
            printf("Shininess - (%f)\n", shininess);
            matModel.setShininess(shininess);
            matBackground.setShininess(shininess);
            matWall.setShininess(shininess);
        }
    }
    if (kb.getKeyState('K') == true)
    {
        if (shininess < 1.0f)
        {
            shininess += 0.1f;
            printf("Shininess + (%f)\n", shininess);
            matModel.setShininess(shininess);
            matBackground.setShininess(shininess);
            matWall.setShininess(shininess);
        }
    }

    static float opacity = 1.0f;
    // Opacité
    if (kb.getKeyState('W') == true)
    {
        if (opacity > 0.0f)
        {
            opacity -= 0.01f;
            printf("Opacity - (%f)\n", opacity);
            matModel.setOpacity(opacity);
        }
    }
    if (kb.getKeyState('X') == true)
    {
        if (opacity < 1.0f)
        {
            opacity += 0.01f;
            printf("Opacity + (%f)\n", opacity);
            matModel.setOpacity(opacity);
        }
    }

    // Restauration de l'initialisation
    if (kb.getKeyState('\t') == true)
    {
        printf("** Restauration **\n");
        currentModel3D->setRotation(USGE_Float3(230.0f, 135.0f, 0.0f));
        currentModel3D->setTranslation(USGE_Float3(0.0f, 0.0f, -3.0f));
    }
}
