//  ---------------------------------------------------------------------------
//
//  @file       TwSimpleSFML.cpp
//  @brief      A simple example that uses AntTweakBar with OpenGL and SFML.
//              This example draws moving cubic particles with some
//              interactive control on particles generation.
//
//              AntTweakBar: http://anttweakbar.sourceforge.net/doc
//              OpenGL:      http://www.opengl.org
//              SFML:        http://www.sfml-dev.org
//  
//  @author     Philippe Decaudin
//
//  ---------------------------------------------------------------------------


#include <AntTweakBar.h>

#if defined(_WIN32)
//  MiniSFML16.h is provided to avoid the need of having SFML installed to 
//  recompile this example. Do not use it in your own programs, better
//  install and use the actual SFML library SDK.
#   define USE_MINI_SFML
#endif

#ifdef USE_MINI_SFML
#   include "../src/MiniSFML16.h"
#else
#   include <SFML/Graphics.hpp>
#endif

#if defined(_WIN32)
#   include <windows.h> // required by gl.h
#endif
#include <GL/gl.h>
#include <GL/glu.h>

#include <list>
#include <cstdlib>
#include <cmath>


// Pseudo-random value between -1 and 1
float Random() 
{
    return 2.0f * ((float)rand() / RAND_MAX) - 1.0f;
}

// Particle randomly initialized
struct Particle
{
    float Size;
    float Position[3];      // [px, py, pz]
    float Speed[3];         // [vx, vy, vz]
    float RotationAxis[3];  // [rx, ry, rz]
    float RotationAngle;    // in degree
    float RotationSpeed;
    float Color[3];         // [r, g, b]
    float Age;
    Particle(float size, float speedDir[3], float speedNorm, float color[3]) // Constructor
    {
        Size = size * (1.0f + 0.2f * Random());
        Position[0] = Position[1] = Position[2] = 0;
        Speed[0] = speedNorm * (speedDir[0] + 0.1f * Random());
        Speed[1] = speedNorm * (speedDir[1] + 0.1f * Random());
        Speed[2] = speedNorm * (speedDir[2] + 0.1f * Random());
        RotationAxis[0] = Random();
        RotationAxis[1] = Random();
        RotationAxis[2] = Random();
        RotationAngle = 360.0f * Random();
        RotationSpeed = 360.0f * Random();
        Color[0] = color[0] + 0.2f * Random();
        Color[1] = color[1] + 0.2f * Random();
        Color[2] = color[2] + 0.2f * Random();
        Age = 0;
    }
    void Update(float dt) // Apply one animation step
    {
        Position[0] += dt * Speed[0];
        Position[1] += dt * Speed[1];
        Position[2] += dt * Speed[2];
        Speed[1] -= dt * 9.81f; // gravity
        RotationAngle += dt * RotationSpeed;
        Age += dt;
    }
};


// Main 
int main()
{
    // Create main window
    sf::RenderWindow app(sf::VideoMode(800, 600), "AntTweakBar simple example using SFML");
    app.PreserveOpenGLStates(true);

    // Particules
    std::list<Particle> particles;
    std::list<Particle>::iterator p;
    float birthCount = 0;
    float birthRate = 20;               // number of particles generated per second
    float maxAge = 3.0f;                // particles life time
    float speedDir[3] = {0, 1, 0};      // initial particles speed direction
    float speedNorm = 7.0f;             // initial particles speed amplitude
    float size = 0.1f;                  // particles size
    float color[3] = {0.8f, 0.6f, 0};   // particles color
    float bgColor[3] = {0, 0.6f, 0.6f}; // background color


    // Initialize AntTweakBar
    TwInit(TW_OPENGL, NULL);

    // Tell the window size to AntTweakBar
    TwWindowSize(app.GetWidth(), app.GetHeight());

    // Create a tweak bar
    TwBar *bar = TwNewBar("Particles");
    TwDefine(" GLOBAL help='This example shows how to integrate AntTweakBar with SFML and OpenGL.' "); // Message added to the help bar.
    
    // Change bar position
    int barPos[2] = {16, 240};
    TwSetParam(bar, NULL, "position", TW_PARAM_INT32, 2, &barPos);
    
    // Add 'birthRate' to 'bar': this is a modifiable variable of type TW_TYPE_FLOAT in range [0.1, 100]. Its shortcuts are [+] and [-].
    TwAddVarRW(bar, "Birth rate", TW_TYPE_FLOAT, &birthRate, " min=0.1 max=100 step=0.1 keyIncr='+' keyDecr='-' ");

    // Add 'speedNorm' to 'bar': this is a modifiable variable of type TW_TYPE_FLOAT in range [0.1, 10]. Its shortcuts are [s] and [S].
    TwAddVarRW(bar, "Speed", TW_TYPE_FLOAT, &speedNorm, " min=0.1 max=10 step=0.1 keyIncr='s' keyDecr='S' ");

    // Add 'speedDir' to 'bar': this is a modifiable variable of type TW_TYPE_DIR3F. Just displaying the arrow widget
    TwAddVarRW(bar, "Direction", TW_TYPE_DIR3F, &speedDir, " opened=true showval=false ");

    // Add 'color' to 'bar': this is a modifiable variable of type TW_TYPE_COLOR3F. Switched to HLS
    TwAddVarRW(bar, "Color", TW_TYPE_COLOR3F, &color, " colorMode=hls opened=true ");

    // Add 'bgColor' to 'bar': this is a modifiable variable of type TW_TYPE_COLOR3F. Switched to HLS
    TwAddVarRW(bar, "Background color", TW_TYPE_COLOR3F, &bgColor, " colorMode=hls opened=true ");

    // Initialize OpenGL states
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(90.f, (float)app.GetWidth()/app.GetHeight(), 0.1f, 100.f);
    glMatrixMode(GL_MODELVIEW);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_NORMALIZE);
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);

    // Init time
    sf::Clock clock;
    float time = clock.GetElapsedTime();

    // Main loop
    while (app.IsOpened())
    {
        // Process events
        sf::Event event;
        while (app.GetEvent(event))
        {
            // Send event to AntTweakBar
            int handled = TwEventSFML(&event, 1, 6); // Assume SFML version 1.6 here

            // If event has not been handled by AntTweakBar, process it
            if( !handled )
            {
                // Close or Escape
                if (event.Type == sf::Event::Closed
                    || (event.Type == sf::Event::KeyPressed && event.Key.Code == sf::Key::Escape))
                    app.Close();

                // Resize
                if (event.Type == sf::Event::Resized)
                {
                    glViewport(0, 0, event.Size.Width, event.Size.Height);
                    glMatrixMode(GL_PROJECTION);
                    glLoadIdentity();
                    gluPerspective(90.f, (float)event.Size.Width/event.Size.Height, 1.f, 500.f);
                    glMatrixMode(GL_MODELVIEW);

                    // TwWindowSize has been called by TwEventSFML, 
                    // so it is not necessary to call it again here.
                }
            }
        }

        if (!app.IsOpened())
            continue;

        // Update time
        float dt = clock.GetElapsedTime() - time;
        if (dt < 0) dt = 0;
        time += dt;

        // Update particles
        p = particles.begin(); 
        while (p != particles.end())
        {
            p->Update(dt);
            if (p->Age >= maxAge) 
                p = particles.erase(p); // Die!
            else
                ++p;
        }

        // Generate new particles
        birthCount += dt * birthRate;
        while (birthCount >= 1.0f) 
        {
            particles.push_back(Particle(size, speedDir, speedNorm, color));
            birthCount--;
        }

        // Clear depth buffer
        glClearColor(bgColor[0], bgColor[1], bgColor[2], 1);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

        // Draw particles
        for (p = particles.begin(); p != particles.end(); ++p)
        {
            glColor4fv(p->Color);
            glLoadIdentity();
            glTranslatef(0.0f, -1.0f, -3.0f); // Camera position
            glTranslatef(p->Position[0], p->Position[1], p->Position[2]);
            glScalef(p->Size, p->Size, p->Size);
            glRotatef(p->RotationAngle, p->RotationAxis[0], p->RotationAxis[1], p->RotationAxis[2]);

            // Draw a cube
            glBegin(GL_QUADS);
                glNormal3f(0,0,-1); glVertex3f(0,0,0); glVertex3f(0,1,0); glVertex3f(1,1,0); glVertex3f(1,0,0); // front face
                glNormal3f(0,0,+1); glVertex3f(0,0,1); glVertex3f(1,0,1); glVertex3f(1,1,1); glVertex3f(0,1,1); // back face
                glNormal3f(-1,0,0); glVertex3f(0,0,0); glVertex3f(0,0,1); glVertex3f(0,1,1); glVertex3f(0,1,0); // left face
                glNormal3f(+1,0,0); glVertex3f(1,0,0); glVertex3f(1,1,0); glVertex3f(1,1,1); glVertex3f(1,0,1); // right face
                glNormal3f(0,-1,0); glVertex3f(0,0,0); glVertex3f(1,0,0); glVertex3f(1,0,1); glVertex3f(0,0,1); // bottom face  
                glNormal3f(0,+1,0); glVertex3f(0,1,0); glVertex3f(0,1,1); glVertex3f(1,1,1); glVertex3f(1,1,0); // top face
            glEnd();
        }

        TwDraw();

        // Finally, display the rendered frame on screen
        app.Display();
    }

    // Un-initialize AntTweakBar 
    TwTerminate();

    return EXIT_SUCCESS;
}
