// Basiques
# include <iostream>
# include <vector>
# include <string>

// Random
# include <ctime>
# include <cstdlib>

// SDL
# include <SDL/SDL.h>
# undef main
# include <SDL/SDL_rotozoom.h>
# include <SDL/SDL_gfxPrimitives.h>

// bBox2D
# include <box2d/box2d.h>

// Local
# include "Cars/PickUp.h"
# include "Cars/PinkLiner.h"
# include "Terrain.h"

// SnapFile
#include "SnapFile.h"

int main(int argc, char** argv)
{
	/// [1] Démarrage
    // [1.0] Démarrage aléatoire
    srand( time(0) );

    // [1.1] Démarrages SDL
    if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        std::cout << "Impossible d'initialiser la SDL: " << SDL_GetError() << std::endl;
        return 1;
    }

    // [1.2] Préparation de fermeture
    atexit(SDL_Quit);

    // [1.3] Para-fenêtre
    SDL_WM_SetCaption("Box2DTests", 0);

    /// [2] Préparation des composants SDL
    // [2.1] Préparation de la fenêtre
    SDL_Surface* screen = SDL_SetVideoMode(1200, 600, 32, SDL_HWSURFACE|SDL_DOUBLEBUF);
    if ( !screen )
    {
        std::cout << "Bug à l'initialisation: " << SDL_GetError() << std::endl;
        return 1;
    }

    // [2.2] Préparation variables
    SDL_Rect mouse({ 400, 200, 0, 0 });
    SDL_Rect pxpos({ 400, 200, 0, 0 });

    // [2.3] Préparation surfaces
    SDL_Surface* tempo( 0x0 );

    // [2.4] Préparation du temps
    Uint32 frameRate( 60 );
    Uint32 tprev(0), twait( 1000 / frameRate );
    bool pause( false );

    // [2.5] Préparation des messages
    std::string msg("Victor");

    /// [3] Box2D
    // Trucs
    B2_NOT_USED(argc);
    B2_NOT_USED(argv);

    // Construct a world object
    b2Vec2 gravity( 0.0f, 10.0f );
    b2World world(gravity);

    // Simulation settings
    float timeStep = 1.0f / frameRate;
    int32 velocityIterations = 8;
    int32 positionIterations = 3;
    float angle, multi(MULTI);
    b2Vec2 position, ref, decalage;

	// Define the edge body
	float areah( (float)screen->h / multi );
	float areaw( (float)screen->w / multi );

    // Terrain
    //std::cout << "Quel niveau ?" << std::endl;
    std::string lvl("NiveauInstant");
    std::string indexName("Niveaux/Level.idx");
    //std::cin >> lvl;
    Terrain myTerrain;
    myTerrain.load( "Niveaux/" + lvl + ".lvl" );
    myTerrain.build( world );
    myTerrain.textureLoad( screen );

    // Voiture
    PinkLiner myCar;
    myCar.init( world, 3.0f, -1.0f );

    // Évènements
    bool spin_right( false );
    bool spin_left( false );

    /// [4] Boucle principale
    bool done = false;
    SDL_Event event;
    while (!done)
    {
        // [4.1] Gestion évènements
        while (SDL_PollEvent(&event))
        {
            switch (event.type)
            {
            case SDL_QUIT:
                done = true;
                break;
            case SDL_KEYDOWN:
                switch( event.key.keysym.sym )
                {
                case SDLK_ESCAPE :
                    done = true;
                    break;
                case SDLK_BACKSPACE :
                    // Destroy joints first of all
                    myCar.init(world, 3.0f, -1.0f );
                    std::cout << std::endl << "Respawn !" << std::endl << std::endl;
                    break;
                case SDLK_a :
                case SDLK_q :
                    spin_left = true;
                    break;
                case SDLK_d :
                    spin_right = true;
                    break;
                case SDLK_p :
                    pause = !pause ;
                    break;
                case SDLK_RIGHT :
                    myCar.drive( GO );
                    break;
                case SDLK_LEFT :
                    myCar.drive( REVERSE );
                    break;
                case SDLK_DOWN :
                    myCar.drive( BREAK );
                    break;
                case SDLK_l:
                    SF_choose( indexName, lvl, screen );
                    if ( lvl != "" )
                    {
                        myCar.init( world, 3.0f, -1.0f );
                        myTerrain.load( "Niveaux/" + lvl );
                        myTerrain.build( world );
                    }
                case SDLK_SPACE :
                    if ( myCar.GetIsOnGround() )
                        myCar.jump();
                    break;
                default :
                    break;
                }
                break;
            case SDL_KEYUP:
                switch( event.key.keysym.sym )
                {
                case SDLK_LEFT :
                case SDLK_RIGHT :
                case SDLK_DOWN :
                    myCar.drive( FREE );
                    break;
                case SDLK_a :
                case SDLK_q :
                    spin_left = false;
                    break;
                case SDLK_d :
                    spin_right = false;
                    break;
                default :
                    break ;
                }
                break;
            case SDL_MOUSEMOTION:
                mouse.x = event.motion.x ;
                mouse.y = event.motion.y ;
                break;
            case SDL_MOUSEBUTTONDOWN:
                std::cout << "SDL_MOUSEBUTTONDOWN" << std::endl;
                break;
            case SDL_MOUSEBUTTONUP:
                std::cout << "SDL_MOUSEBUTTONUP" << std::endl;
                break;
            default:
                break;

            } // end switch event type
        } // end of message processing

        // [4.2] Calculs
        // It is generally best to keep the time step and iterations fixed.
        /*motor->SetMotorSpeed( speed );
        motor->SetMaxMotorTorque( torque );*/

        if ( !pause )
        {
            // Spin
            if ( spin_left )
                myCar.spin( -0.005f );

            if ( spin_right )
                myCar.spin( 0.005f );

            // Physique
			world.Step(timeStep, velocityIterations, positionIterations);

            myCar.update();
        }

        // Référentiel
        decalage = 0.95f * decalage + 0.005f * myCar.GetVelocity();

        ref = myCar.GetPosition();
        ref.Set( ref.x - areaw / 5.0f , ref.y - areah / 2.0f );
        ref += decalage;

        // Zoom
        multi = MULTI * (1.0f - 0.2f * decalage.Length());
        myTerrain.setMulti( multi );

        // [4.3] Dessin des composants
        SDL_FillRect(screen, 0, 0x00000000 );

        myTerrain.drawArtist( screen, ref );//drawUni

        for ( b2Body* b = world.GetBodyList(); b; b = b->GetNext() )
        {
            position = b->GetPosition() - ref ;
            angle = b->GetAngle();

            tempo = (SDL_Surface*)b->GetUserData().pointer;
            if ( tempo == 0x0 )
                continue ;

            tempo = rotozoomSurface( tempo, -angle * 180.0f / b2_pi, multi / MULTI, 0 );


            pxpos.x = position.x * multi - tempo->w / 2 ;
            pxpos.y = position.y * multi - tempo->h / 2 ;
            SDL_BlitSurface( tempo, 0x0, screen, &pxpos);

            SDL_FreeSurface( tempo );
            tempo = 0x0 ;
        }

        // Messages
        msg = "Niveau : " ;
        msg += lvl ;
        stringRGBA( screen, 16, 7, msg.c_str(), 0, 255, 0, 255 );

        msg = "Ratio CPU : " ;
        msg += std::to_string( ((float)SDL_GetTicks() - tprev) * 100 / twait ) ;
        msg += " pourcents." ;
        stringRGBA( screen, 16, 16, msg.c_str(), 0, 255, 0, 255 );

        msg = "Position : " ;
        msg += std::to_string( ref.x ) ;
        msg += " m." ;
        stringRGBA( screen, 16, 25, msg.c_str(), 0, 255, 0, 255 );

        msg = "Torque : " ;
        msg += std::to_string( myCar.GetTorque() ) ;
        msg += " N.m." ;
        stringRGBA( screen, 16, 34, msg.c_str(), 0, 255, 0, 255 );

        msg = "Speed : " ;
        msg += std::to_string( myCar.GetSpeed() ) ;
        msg += " rad/s." ;
        stringRGBA( screen, 16, 43, msg.c_str(), 0, 255, 0, 255 );

        msg = "Is on air : " ;
        msg += std::to_string( myCar.GetIsOnGround() ) ;
        msg += "." ;
        stringRGBA( screen, 16, 52, msg.c_str(), 0, 255, 0, 255 );

        SDL_Flip(screen);

        // [4.4] Temps
        while( SDL_GetTicks() - tprev < twait )
        {
        	if ( SDL_GetTicks() - tprev < twait/2 )
        		SDL_Delay( twait/3 );
        }
        tprev = SDL_GetTicks();

    } //fin bcl principale

    ///[5] Destruction des composants

	return 0;
}
