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

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

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

int main(int argc, char** argv)
{
	/// [0] Box2D
	// Trucs
	B2_NOT_USED(argc);
	B2_NOT_USED(argv);

	// Define the gravity vector.
	b2Vec2 gravity(0.0f, 10.0f);

	// Construct a world object, which will hold and simulate the rigid bodies.
	b2World world(gravity);
	float multi( 100.0f ); // Pour passer de Box2D à pixels
	int incr( 0 );

	// Define the ground body.
	/*b2BodyDef solDef;
	solDef.position.Set(0.0f, 6.0f);
	b2Body* solBody = world.CreateBody(&solDef);
	b2PolygonShape solBox;
	solBox.SetAsBox(50.0f, 1.0f);
	solBody->CreateFixture(&solBox, 0.0f);*/

	// Define the dynamic body. We set its position and call the body factory.
	std::vector<b2Body*> tbody;
	float xstart( 3.0f );
	float zoom( 2.0f );
	b2BodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;

	// Define another box shape for our dynamic body.
	b2PolygonShape dynamicBox;
	b2FixtureDef fixtureDef;
	fixtureDef.shape = &dynamicBox;
	fixtureDef.density = 1.0f;
	fixtureDef.friction = 0.3f;

	// Prepare for simulation
	float timeStep = 1.0f / 60.0f;
	int32 velocityIterations = 8;
	int32 positionIterations = 3;

	// Variables
	b2Vec2 position;
	float angle;
	bool pause( false );
    bool enable_rotation( true );
	std::string msg("Victor");

	/// [1] Démarrage
    // [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
    // [2.1] Préparation de la fenêtre
    SDL_Surface* screen = SDL_SetVideoMode(600, 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 });

    std::vector< SDL_Surface* > trot ;
    std::vector< SDL_Surface* > timg ;

    // [2.3] Préparation surfaces
    SDL_Surface* blc = 0x0;
    SDL_Surface* bar = 0x0;
    SDL_Surface* ice = 0x0;
    SDL_Surface* heavy = 0x0;
    SDL_Surface* slim = 0x0;
    SDL_Surface* megaSlim = 0x0;
    SDL_Surface* zero = 0x0;
    SDL_Surface* anti = 0x0;

    blc = SDL_LoadBMP("Mur.bmp");
    bar = SDL_LoadBMP("Bar.bmp");
    ice = SDL_LoadBMP("Glace.bmp");
    heavy = SDL_LoadBMP("Heavy.bmp");
    slim = SDL_LoadBMP("Slim.bmp");
    megaSlim = SDL_LoadBMP("MegaSlim.bmp");
    zero = SDL_LoadBMP("Zero.bmp");
    anti = SDL_LoadBMP("Anti.bmp");

    if ( !blc || !bar || !ice || !heavy || !slim || !megaSlim || !zero || !anti )
        std::cout << "Pb avec la texture." << std::endl;

    // [2.4] Préparation du temps
    Uint32 tprev(0), twait( timeStep * 1000.0f );
    std::cout << twait << std::endl ;

	// Define the edge body
	b2BodyDef areaDef;
	areaDef.position.Set(0.0f, 0.0f);
	b2Body* areaBody = world.CreateBody(&areaDef);

	b2Vec2 vs[4];
	float areah( (float)screen->h / multi );
	float areaw( (float)screen->w / multi );
	vs[0].Set( 0.0f, 0.0f );
	vs[1].Set( 0.0f, areah );
	vs[2].Set( areaw, areah );
	vs[3].Set( areaw, 0.0f );
	b2ChainShape chain;
	chain.CreateLoop(vs, 4);
	areaBody->CreateFixture(&chain, 0.0f);

    /// [3] Boucle principale
    bool done = false;
    SDL_Event event;
    while (!done)
    {
        // [3.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 :
            		for ( unsigned int i(0); i < tbody.size(); i ++ )
            		{
            			world.DestroyBody( tbody[i] );
            			tbody[i] = 0x0 ;
            		}
            		tbody.clear();

            		for ( unsigned int i(0); i < trot.size(); i ++ )
            		{
            			SDL_FreeSurface( trot[i] ) ;
            		}
            		trot.clear();

            		timg.clear();
                	std::cout << std::endl << "Deleting all blocks." << std::endl << std::endl;

            		break;
                case SDLK_SPACE :
                    pause = !pause ;
                    break;
                case SDLK_LEFT :
                    enable_rotation = !enable_rotation ;
                    break;
            	case SDLK_UP :
                	std::cout << "SDLK_UP" << std::endl;
                	break;
                case SDLK_a :
					dynamicBox.SetAsBox( (float)blc->w * zoom / multi / 2 , (float)blc->h * zoom / multi / 2  );

					bodyDef.position.Set( (float)mouse.x / multi, (float)mouse.y / multi );
					bodyDef.gravityScale = 1.0f;

					fixtureDef.density = 1.0f;
					fixtureDef.friction = 0.3f;
					fixtureDef.restitution = 0.0f;

					tbody.push_back( 0x0 );
					tbody.back() = world.CreateBody(&bodyDef);
					tbody.back()->CreateFixture(&fixtureDef);

					timg.push_back( blc );
					trot.push_back( rotozoomSurface( blc, 0.0f, 2.0f, 0 ) ) ;

                    std::cout << "Standard bloc number " << tbody.size() << "." << std::endl;
                    break;
                case SDLK_q :
					dynamicBox.SetAsBox( (float)bar->w * zoom / multi / 2 , (float)bar->h * zoom / multi / 2  );

					bodyDef.position.Set( (float)mouse.x / multi, (float)mouse.y / multi );
					bodyDef.gravityScale = 1.0f;

					fixtureDef.density = 1.0f;
					fixtureDef.friction = 0.3f;
					fixtureDef.restitution = 0.0f;

					tbody.push_back( 0x0 );
					tbody.back() = world.CreateBody(&bodyDef);
					tbody.back()->CreateFixture(&fixtureDef);

					timg.push_back( bar );
					trot.push_back( rotozoomSurface( bar, 0.0f, 2.0f, 0 ) ) ;

                    std::cout << "Standard bar number " << tbody.size() << "." << std::endl;
                    break;
                case SDLK_z :
                case SDLK_w :
					dynamicBox.SetAsBox( (float)ice->w * zoom / multi / 2 , (float)ice->h * zoom / multi / 2  );

                	bodyDef.position.Set( (float)mouse.x / multi, (float)mouse.y / multi );
					bodyDef.gravityScale = 1.0f;

					fixtureDef.density = 0.5f;
					fixtureDef.friction = 0.005f;
					fixtureDef.restitution = 0.0f;

					tbody.push_back( 0x0 );
					tbody.back() = world.CreateBody(&bodyDef);
					tbody.back()->CreateFixture(&fixtureDef);

					timg.push_back( ice );
					trot.push_back( rotozoomSurface( ice, 0.0f, 2.0f, 0 ) ) ;

                    std::cout << "Bloc of ice " << tbody.size() <<" : able to slide !" << std::endl;
                    break;
                case SDLK_e :
					dynamicBox.SetAsBox( (float)heavy->w * zoom / multi / 2 , (float)heavy->h * zoom / multi / 2  );
					
					bodyDef.position.Set( (float)mouse.x / multi, (float)mouse.y / multi );
					bodyDef.gravityScale = 1.0f;

					fixtureDef.density = 100.0f;
					fixtureDef.friction = 0.3f;
					fixtureDef.restitution = 0.0f;
					
					tbody.push_back( 0x0 );
					tbody.back() = world.CreateBody(&bodyDef);
					tbody.back()->CreateFixture(&fixtureDef);

					timg.push_back( heavy );
					trot.push_back( rotozoomSurface( heavy, 0.0f, 2.0f, 0 ) ) ;

                    std::cout << "Heavy bloc " << tbody.size() << " : very massive bloc." << std::endl;
                    break;
                case SDLK_r :
					dynamicBox.SetAsBox( (float)slim->w * zoom / multi / 2 , (float)slim->h * zoom / multi / 2  );
					
					bodyDef.position.Set( (float)mouse.x / multi, (float)mouse.y / multi );
					bodyDef.gravityScale = 1.0f;

					fixtureDef.density = 1.0f;
					fixtureDef.friction = 0.3f;
					fixtureDef.restitution = 0.95f;
					
					tbody.push_back( 0x0 );
					tbody.back() = world.CreateBody(&bodyDef);
					tbody.back()->CreateFixture(&fixtureDef);

					timg.push_back( slim );
					trot.push_back( rotozoomSurface( slim, 0.0f, 2.0f, 0 ) ) ;

                    std::cout << "Slim bloc " << tbody.size() << " : able to bounce !" << std::endl;
                    break;
                case SDLK_t :
					dynamicBox.SetAsBox( (float)megaSlim->w * zoom / multi / 2 , (float)megaSlim->h * zoom / multi / 2  );
					
					bodyDef.position.Set( (float)mouse.x / multi, (float)mouse.y / multi );
					bodyDef.gravityScale = 1.0f;

					fixtureDef.density = 1.0f;
					fixtureDef.friction = 0.3f;
					fixtureDef.restitution = 1.4f;
					
					tbody.push_back( 0x0 );
					tbody.back() = world.CreateBody(&bodyDef);
					tbody.back()->CreateFixture(&fixtureDef);

					timg.push_back( megaSlim );
					trot.push_back( rotozoomSurface( megaSlim, 0.0f, 2.0f, 0 ) ) ;

                    std::cout << "MegaSlim bloc " << tbody.size() << " : able to bounce over physic limits !" << std::endl;
                    break;
                case SDLK_y :
					dynamicBox.SetAsBox( (float)zero->w * zoom / multi / 2 , (float)zero->h * zoom / multi / 2  );
					
					bodyDef.position.Set( (float)mouse.x / multi, (float)mouse.y / multi );
					bodyDef.gravityScale = 0.0f;

					fixtureDef.density = 1.0f;
					fixtureDef.friction = 0.3f;
					fixtureDef.restitution = 0.0f;
					
					tbody.push_back( 0x0 );
					tbody.back() = world.CreateBody(&bodyDef);
					tbody.back()->CreateFixture(&fixtureDef);

					timg.push_back( zero );
					trot.push_back( rotozoomSurface( zero, 0.0f, 2.0f, 0 ) ) ;

                    std::cout << "Zero gravity bloc " << tbody.size() << " : able to bypass gravity !" << std::endl;
                    break;
                case SDLK_u :
					dynamicBox.SetAsBox( (float)anti->w * zoom / multi / 2 , (float)anti->h * zoom / multi / 2  );

					bodyDef.position.Set( (float)mouse.x / multi, (float)mouse.y / multi );
					bodyDef.gravityScale = -1.0f;

					fixtureDef.density = 1.0f;
					fixtureDef.friction = 0.3f;
					fixtureDef.restitution = 0.0f;

					tbody.push_back( 0x0 );
					tbody.back() = world.CreateBody(&bodyDef);
					tbody.back()->CreateFixture(&fixtureDef);

					timg.push_back( anti );
					trot.push_back( rotozoomSurface( anti, 0.0f, 2.0f, 0 ) ) ;

                    std::cout << "Anti gravity bloc number " << tbody.size() << " : counter gravity !" << std::endl;
                    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;
            } // end switch event type
        } // end of message processing

        // [3.2] Calculs
        // It is generally best to keep the time step and iterations fixed.
        if ( !pause )
			world.Step(timeStep, velocityIterations, positionIterations);

        // [3.3] Dessin des composants
        SDL_FillRect(screen, 0, 0x000000);

        for ( unsigned int i(0); i < tbody.size(); i ++ )
        {
        	position = tbody[i]->GetPosition();
			angle = tbody[i]->GetAngle();

			if ( /*tbody[i]->IsAwake() &&*/ enable_rotation )
			{
				SDL_FreeSurface( trot[i] ) ;
				trot[i] = rotozoomSurface( timg[i], -angle * 180.0f / b2_pi, zoom + 0.15f, 0 );
			}


			pxpos.x = position.x * multi - trot[i]->w / 2 ;
			pxpos.y = position.y * multi - trot[i]->h / 2 ;
	        SDL_BlitSurface( trot[i], 0x0, screen, &pxpos);
        }

        // Messages
        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 = "Nombre de blocs : " ;
        msg += std::to_string( trot.size() ) ;
        msg += "." ;
        stringRGBA( screen, 16, 25, msg.c_str(), 0, 255, 0, 255 );

        msg = "Nombre de décès : " ;
        msg += std::to_string( incr ) ;
        msg += "." ;
        stringRGBA( screen, 16, 34, msg.c_str(), 0, 255, 0, 255 );

        msg = "Rotations : " ;
        msg += std::to_string( enable_rotation ) ;
        msg += "." ;
        stringRGBA( screen, 16, 43, msg.c_str(), 0, 255, 0, 255 );

        SDL_Flip(screen);

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

    } //fin bcl principale

    ///[4] Destruction des composants
    SDL_FreeSurface(screen);
    SDL_FreeSurface(blc);
    SDL_FreeSurface(bar);
    SDL_FreeSurface(ice);
    SDL_FreeSurface(heavy);
    SDL_FreeSurface(slim);
	SDL_FreeSurface(megaSlim);
	SDL_FreeSurface(zero);
	SDL_FreeSurface(anti);

	return 0;
}