
#include "window.h"

#include "vec.h"
#include "mat.h"
#include "color.h"

#include "mesh.h"
#include "orbiter.h"

#include "wavefront.h"
#include "draw.h"

#include "texture.h"

Orbiter camera;


struct Body
{
    Mesh mesh;
    Vector displacement ;
    float spin ;
    float scale ;
    float revolution ;
    Body* parent ;
    
    Body( ) : 
      displacement(make_vector(0.f, 0.f, 0.f)),
      spin(0),
      scale(1),
      revolution(0), 
      parent(NULL)
    {}
    
    Body( const Mesh& m, const Color& c ) : 
      mesh(m), 
      displacement(make_vector(0.f, 0.f, 0.f)),
      spin(0),
      scale(1),
      revolution(0), 
      parent(NULL)
    {
        mesh.color= c ;
    }

    Transform get_parent_transform(float time)
    {
      Transform res = make_identity() ;
      if(parent)
      {
        res = res*parent->get_parent_transform(time) ;
      }
      res = res*make_rotationY(revolution*time) ;
      res = res*make_translation(displacement) ;
      return res ;
    }

    Transform get_transform(float time)
    {
      Transform res = make_identity() ;
      if(parent)
      {
        res = res*parent->get_parent_transform(time) ;
      }
      res = res*make_rotationY(revolution*time) ;
      res = res*make_translation(displacement) ;
      res = res*make_rotationY(spin*time) ;
      res = res*make_scale(scale, scale, scale) ;
      return res ;
    }

};


void draw( Body& body, float time, const Orbiter& orbiter )
{
    draw(body.mesh, body.get_transform(time), orbiter);
}


Mesh grid;

Body sun ;
Body planet ;
Body moon ;

// utilitaire. creation d'une grille / repere.
Mesh make_grid( )
{
    Mesh grid= create_mesh(GL_LINES);
    
    for(int x= 0; x < 10; x++)
    {
        float px= (float) x - 5.f + 0.5f;
        push_vertex(grid, make_point(px, 0, -4.5f)); 
        push_vertex(grid, make_point(px, 0, 4.5f)); 
    }

    for(int z= 0; z < 10; z++)
    {
        float pz= (float) z - 5.f + 0.5f;
        push_vertex(grid, make_point(-4.5f, 0, pz)); 
        push_vertex(grid, make_point(4.5f, 0, pz)); 
    }
    
    return grid;
}

// creation des objets openGL
int init( )
{
    // lire un maillage
    Mesh cube= read_mesh("data/cube.obj");
    if(cube.positions.size() == 0)
        return -1;
    
    sun = Body(cube, make_red());
    sun.spin = -0.02 ;
    
    planet = Body(cube, make_blue());
    planet.displacement = make_vector(4, 0, 0) ;
    planet.scale = 0.5 ;
    planet.spin = 0.1 ;
    planet.revolution = 0.05 ;
    planet.parent = &sun ;

    moon = Body(cube, make_green()) ;
    moon.displacement = make_vector(1, 0, 0) ;
    moon.scale = 0.2 ;
    moon.spin = 0.3 ;
    moon.revolution = -0.1 ;
    moon.parent = &planet ;
    
    camera= make_orbiter();
    grid= make_grid();
    
    // etat par defaut
    glClearColor(0.2, 0.2, 0.2, 1);           // couleur par defaut de la fenetre
    
    glClearDepthf(1);                   // profondeur par defaut
    glDepthFunc(GL_LEQUAL);               // ztest, conserver l'intersection la plus proche de la camera
    //~ glDepthFunc(GL_ALWAYS);               // ztest, conserver l'intersection la plus proche de la camera
    glEnable(GL_DEPTH_TEST);            // activer le ztest
    //~ glDisable(GL_DEPTH_TEST);            // activer le ztest
    
    glLineWidth(2.5f);                  // epaisseur des lignes de la grille (pixels)
    
    return 0;   // renvoyer 0 ras, pas d'erreur, sinon renvoyer -1
}

// affichage
int draw( )
{
    // on commence par effacer la fenetre avant de dessiner quelquechose
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // on efface aussi le zbuffer
    
    // recupere les mouvements de la souris, utilise directement SDL2
    int mx, my;
    unsigned int mb= SDL_GetRelativeMouseState(&mx, &my);
    
    // deplace la camera
    if(mb & SDL_BUTTON(1))              // le bouton gauche est enfonce
        // tourne autour de l'objet
        orbiter_rotation(camera, mx, my);
    
    else if(mb & SDL_BUTTON(3))         // le bouton droit est enfonce
        // approche / eloigne l'objet
        orbiter_move(camera, mx);
    
    else if(mb & SDL_BUTTON(2))         // le bouton du milieu est enfonce
        // deplace le point de rotation
        orbiter_translation(camera, (float) mx / (float) window_width(), (float) my / (float) window_height()); 
    
    // affiche la grille / repere
    draw(grid, camera);
    
    float sunx= 0;
    float sunz= 0;
    if(key_state('z'))
        sunz-= 0.1f;
    if(key_state('s'))
        sunz+= 0.1f;
    if(key_state('q'))
        sunx-= 0.1f;
    if(key_state('d'))
        sunx+= 0.1f;

    sun.displacement = sun.displacement + make_vector(sunx, 0, sunz) ;
    
    float planet_dist= 0;
    if(key_state('w'))
        planet_dist-= 0.1f;
    if(key_state('x'))
        planet_dist+= 0.1f;

    planet.displacement = planet.displacement + make_vector(planet_dist, 0, 0) ;
    
    float moon_dist= 0;
    if(key_state('c'))
        moon_dist-= 0.1f;
    if(key_state('v'))
        moon_dist+= 0.1f;

    moon.displacement = moon.displacement + make_vector(moon_dist, 0, 0) ;
    
    float time = (float) SDL_GetTicks() ;

    // affiche les objets
    draw(sun, time, camera);
    draw(planet, time, camera);
    draw(moon, time, camera);
    
    
    static bool video= false;
    if(key_state(SDLK_RETURN))
    {
        clear_key_state(SDLK_RETURN);
        video= !video;
        
        if(video) 
            printf("start video capture...\n");
        else 
            printf("stop video capture.\n");
    }
    if(video) 
        capture("tutok");
    
    
    return 1;   // on continue, renvoyer 0 pour sortir de l'application
}

// destruction des objets openGL
int quit( )
{
    return 0;   // ras, pas d'erreur
}


int main( int argc, char **argv )
{
    // etape 1 : creer la fenetre
    Window window= create_window(1024, 640);
    if(window == NULL) 
        return 1;       // erreur lors de la creation de la fenetre ou de l'init de sdl2
    
    // etape 2 : creer un contexte opengl pour pouvoir dessiner
    Context context= create_context(window);
    if(context == NULL) 
        return 1;       // erreur lors de la creation du contexte opengl
    
    // etape 3 : creation des objets 
    if(init() < 0)
    {
        printf("[error] init failed.\n");
        return 1;
    }
    
    // etape 4 : affichage de l'application, tant que la fenetre n'est pas fermee. ou que draw() ne renvoie pas 0
    run(window, draw);

    // etape 5 : nettoyage
    quit();
    release_context(context);
    release_window(window);

    return 0;
}
