# include <iostream>
# include <vector>

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

# include <SDL/SDL_gfxPrimitives.h>

struct Contact
{
	float temp ;
	Uint32 y ;
	Uint32 x ;
};

int main(int argc, char const *argv[])
{
	/// [0] Requête des informations
	std::cout << "LANCEMENT DIFFUSION CHALEUR" << std::endl << std::endl;
	// [0.1] Dimensions de la fenêtre
	bool changement(false);
	Uint32 w_cmd(400), h_cmd(300);
	float lambda( 0.2495 );
	unsigned int iteration( 1 );
	std::cout << "Changer les valeurs par défaut ? (0:non, 1:oui)" << std::endl << ">>>";
	std::cin >> changement;

	if ( changement )
	{
		std::cout << "Hauteur de la fenêtre ?" << std::endl << ">>>";
		std::cin >> h_cmd;
		std::cout << "Longueur de la fenêtre ?" << std::endl << ">>>";
		std::cin >> w_cmd;
		std::cout << "Conductivité thermique ?" << std::endl << ">>>";
		std::cin >> lambda;
		std::cout << "Nombre d'itérations par frame ?" << std::endl << ">>>";
		std::cin >> iteration;
	}

	const Uint32 w( w_cmd );
	const Uint32 h( h_cmd );

	/// [1] SDL
	// [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("Diffusion Chaleur", 0);

    /// [2] Préparation des composants SDL
    // [2.1] Préparation de la fenêtre
    SDL_Surface* screen = SDL_SetVideoMode(w, h, 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 });

    // [2.3] Préparation surfaces

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

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

    /// [3] Préparation de la chaleur
    // [3.1] Préparation des buffers
    float buffer[2][h][w]; // entre 0.0f et 1.0f
    int current_buffer(0), next_buffer(1);

    // [3.2] Liste des conditions aux limites
    std::vector<Contact> limiteur;
    bool trigger( false );
    float temp( 1.0f );

    // [3.3] Conditions initiales
    for ( int k(0); k < 2; k++ )
	    for ( Uint32 i(0); i < h; i ++ )
			for ( Uint32 j(0); j < w; j ++ )
			{
				buffer[k][i][j] = 0.0f;
			}

    /// [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 :
                    std::cout << std::endl << "Destruction des points de contact." << std::endl << std::endl;
                    limiteur.clear();
                    break;
                case SDLK_SPACE :
                	std::cout << std::endl << "Reinscription du cas initial." << std::endl << std::endl;
                    for ( Uint32 i(0); i < h; i ++ )
						for ( Uint32 j(0); j < w; j ++ )
						{
							buffer[current_buffer][i][j] = 0.0f;
						};
                    break;
                case SDLK_a :
                	temp = 1.0f ;
                    break;
                case SDLK_z :
                	temp = 0.5f ;
                    break;
                case SDLK_e :
                	temp = 0.0f ;
                    break;
                }
                break;
            case SDL_KEYUP:
                switch( event.key.keysym.sym )
                {
                case SDLK_LEFT :
                case SDLK_RIGHT :
                case SDLK_DOWN :
                    break;
                }
                break;
            case SDL_MOUSEMOTION:
                mouse.x = event.motion.x ;
                mouse.y = event.motion.y ;

                if ( trigger )
                	limiteur.push_back( { temp, mouse.y, mouse.x } );
                break;
            case SDL_MOUSEBUTTONDOWN:
                trigger = true ;
                break;
            case SDL_MOUSEBUTTONUP:
                trigger = false ;
                break;
            } // end switch event type
        } // end of message processing

        // [4.2] Calculs
        if ( !pause )
        {
        	for ( unsigned int c(0); c < iteration ; c++ )
        	{
	        	// Conditions initiales
	        	for ( unsigned int k(0); k < limiteur.size(); k++ )
	        		buffer[current_buffer][ limiteur[k].y ][ limiteur[k].x ] = limiteur[k].temp ;

	        	// Calcul
				for ( Uint32 i(1); i < h - 1; i ++ )
					for ( Uint32 j(1); j < w - 1; j ++ )
					{
						buffer[next_buffer][i][j] = lambda * buffer[current_buffer][i + 1][j]
													+ lambda * buffer[current_buffer][i][j + 1]
													+ lambda * buffer[current_buffer][i - 1][j]
													+ lambda * buffer[current_buffer][i][j - 1]
													+ (1.0f - 4.0f * lambda ) * buffer[current_buffer][i][j] ;

						if ( buffer[current_buffer][i][j] > 1.0f )
							buffer[current_buffer][i][j] = 1.0f ;

						else if ( buffer[current_buffer][i][j] < 0.0f )
							buffer[current_buffer][i][j] = 0.0f ;
					}


	        	// Changement de buffer
	        	current_buffer = next_buffer ;
	        	next_buffer = 1 - current_buffer ;
        	}
        }

        // [4.3] Affichage
        if ( !pause )
        {
        	// Calcul
			for ( Uint32 i(1); i < h - 1; i ++ )
				for ( Uint32 j(1); j < w - 1; j ++ )
					pixelRGBA( screen, j, i, (Uint8)( buffer[current_buffer][i][j] * 255 ), 0, 0, 255 );
        }

        SDL_Flip(screen);

        // [4.4] Temps
        total_frame ++;
        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
    SDL_FreeSurface( screen );

    ///[6] Affichage puissance de calcul
    std::cout << "Performances : " << std::endl;
    std::cout << "> Nombre de pixels : " << w*h << std::endl;
    std::cout << "> Nombre total d'images : " << total_frame << std::endl;
    std::cout << "> Temps total : " << SDL_GetTicks() << "ms." << std::endl;
    std::cout << "> Moyenne par frame : " << (float)SDL_GetTicks() / total_frame << std::endl;
    std::cout << "> Moyenne par frame et par pixel : " << (float)SDL_GetTicks() / total_frame / w / h << std::endl;

    ///[7] Tableau
    /*for ( Uint32 i(1); i < h - 1; i ++ )
    {
    	for ( Uint32 j(1); j < w - 1; j ++ )
		{
			std::cout << (int)(buffer[next_buffer][i][j] * 255) << ":";
		}
		std::cout << std::endl;
    }*/

	return 0;
}