/**************************************************************************
COLLISIONS: 10 éléments rectangulaires bougent à l'écran
            ils rebondissent sur les bords de l'écran
            et entrent en collision les uns avec les autres

            Ce code reprend largement le code 1_0 et 1_1 du cours 3
            on a modifié essentiellement la fonction actualiserTabActeurs
            pour détecter et réagir aux collisions.

            La détection proprement dite des collisions est faite
            par une fonction auxilliaire : collisionActeurs

            On peut interagir :
            Cliquer à la souris sur un rectangle pour le bloquer
            ou lui donner une vitesse (drag and throw)
            Espace pour figer tout le monde
            Entrée pour relancer tout le monde avec nouveaux mouvements

**************************************************************************/

#include <allegro.h>
#include <time.h>


// Ici nombre d'acteurs fixe

#define NACTEUR 10


/****************************/
/*     STRUCTURE ACTEUR     */
/*  devra aller dans un .h  */
/****************************/

// données personnelles de chaque acteur qui se déplace
typedef struct acteur
{
    int x, y;    // coordonnée (du coin sup. gauche)
    int dx, dy;  // vecteur deplacement
    int tx,ty;   // tailles : horizontal/vertical
    int couleur; // couleur de l'élément graphique
} t_acteur;


/*****************************/
/*     PROTOTYPES            */
/*  devront aller dans un .h */
/*****************************/

// Fonctions principales (appelées depuis le main)

// Allouer et initialiser un acteur
t_acteur * creerActeur();

// Remplir un tableau avec des acteurs créés
void remplirTabActeurs(t_acteur * tab[NACTEUR]);


// Actualiser un acteur (bouger ...)
void actualiserActeur(t_acteur *acteur);

// Gérer l'évolution de l'ensemble des acteurs
void actualiserTabActeurs(t_acteur * tab[NACTEUR]);

// Gérer la possibilité de bouger un acteur à la souris
void interfaceTabActeurs(t_acteur * tab[NACTEUR]);


// Dessiner un acteur sur une bitmap bmp
void dessinerActeur(BITMAP *bmp, t_acteur *acteur);

// Dessiner l'ensemble des acteurs sur une bitmap bmp
void dessinerTabActeurs(BITMAP *bmp,t_acteur * tab[NACTEUR]);


// Fonctions annexes

// (ré)initialiser vecteur déplacement aléatoire avec composantes non nulles
void vecDepAleaActeur(t_acteur *acteur);

// Déterminer si les rectangles de 2 acteurs s'intersectent
// Retourne 0 si pas de collision
// Si collision alors retourne une valeur selon la position de a2 par rapport à a1
// 1 : plutôt à droite   2 : plutôt à gauche   3 : plutôt en bas   4 : plutôt en haut
int collisionActeurs(t_acteur *a1, t_acteur *a2);



/******************************************/
/* PROGRAMME PRINCIPAL                    */
/* initialisation puis boucle de jeu      */
/******************************************/

int main()
{
    // Le tableau regroupant tous les acteurs
    // c'est un tableau de pointeurs sur structures t_acteurs
    t_acteur * mesActeurs[NACTEUR];

    // BITMAP servant de buffer d'affichage (double buffer)
    BITMAP *page;


    // On va utiliser du hasard
    srand(time(NULL));

    // Lancer allegro et le mode graphique
    allegro_init();
    install_keyboard();
    install_mouse();

    set_color_depth(desktop_color_depth());
    if (set_gfx_mode(GFX_AUTODETECT_WINDOWED,800,600,0,0)!=0)
    {
        allegro_message("prb gfx mode");
        allegro_exit();
        exit(EXIT_FAILURE);
    }

    // Montrer la souris à l'écran
    show_mouse(screen);

    // CREATION DU BUFFER D'AFFICHAGE à la taille de l'écran
    page=create_bitmap(SCREEN_W,SCREEN_H);
    clear_bitmap(page);

    // Initialisation aléatoire des paramètres des acteurs :
    // remplir le tableau avec des acteurs alloués et initialisés
    remplirTabActeurs(mesActeurs);


    // Boucle de jeu
    while (!key[KEY_ESC])
    {
        // EFFACER POSITIONs ACTUELLEs SUR LE BUFFER
        clear_to_color(page,makecol(96,96,96));

        // GESTION INTERFACE
        interfaceTabActeurs(mesActeurs);

        // DETERMINER NOUVELLEs POSITIONs
        actualiserTabActeurs(mesActeurs);

        // AFFICHAGE NOUVELLEs POSITIONs SUR LE BUFFER
        dessinerTabActeurs(page,mesActeurs);

        // AFFICHAGE DU BUFFER MIS A JOUR A L'ECRAN
        blit(page,screen,0,0,0,0,SCREEN_W,SCREEN_H);

        // ON FAIT UNE PETITE PAUSE
        rest(10);
    }

    return 0;
}
END_OF_MAIN();


/************************************************/
/*     DEFINITIONS DES SOUS-PROGRAMMES          */
/*  devront aller dans un autre .c : acteurs.c  */
/************************************************/


// Allouer et initialiser (aléatoirement) un acteur
t_acteur * creerActeur()
{
    // pointeur sur l'acteur qui sera créé (et retourné)
    t_acteur *acteur;

    // Création (allocation)
    acteur = (t_acteur *)malloc(1*sizeof(t_acteur));

    // Initialisation

    acteur->tx = rand()%140+40;
    acteur->ty = rand()%140+40;

    // Position aléatoire (on tient compte de la taille...)
    acteur->x = rand()%(SCREEN_W - acteur->tx);
    acteur->y = rand()%(SCREEN_H - acteur->ty);

    // Initialiser vecteur déplacement aléatoire
    vecDepAleaActeur(acteur);

    // Couleur dominante verte
    acteur->couleur = makecol(rand()%40+40,rand()%64+192,rand()%40+40);

    // on retourne cet acteur fraichement créé
    // ( en fait on retourne le POINTEUR sur lui )
    return acteur;
}

// Remplir un tableau avec des (pointeurs sur) acteurs créés
void remplirTabActeurs(t_acteur * tab[NACTEUR])
{
    int i;

    // On "accroche" NACTEUR nouveaux acteurs
    // à chaque case du tableau
    for (i=0;i<NACTEUR;i++)
        tab[i]=creerActeur();
}

// Actualiser un acteur (bouger ...)
void actualiserActeur(t_acteur *acteur)
{
    // contrôle des bords : ici on décide de rebondir sur les bords
    if  (  ( acteur->x < 0 && acteur->dx < 0 ) ||
            ( acteur->x + acteur->tx > SCREEN_W && acteur->dx > 0) )
        acteur->dx = -acteur->dx;

    if  (  ( acteur->y < 0 && acteur->dy < 0 ) ||
            ( acteur->y + acteur->ty > SCREEN_H && acteur->dy > 0) )
        acteur->dy = -acteur->dy;

    // calculer nouvelle position
    // nouvelle position = position actuelle + deplacement
    acteur->x = acteur->x + acteur->dx;
    acteur->y = acteur->y + acteur->dy;

}


// Gérer l'évolution de l'ensemble des acteurs
void actualiserTabActeurs(t_acteur * tab[NACTEUR])
{
    int i,j,cote;

    // Contrôle des collisions : si on entre en collision avec un autre
    // et que la collision tend à nous rapprocher alors on rebondit
    for (i=0;i<NACTEUR;i++)
        for (j=i+1;j<NACTEUR;j++)
            if ( (cote=collisionActeurs(tab[i], tab[j]) ) )
            {
                if ((cote==1 && tab[i]->dx<0) || (cote==2 && tab[i]->dx>0))
                    tab[i]->dx=-tab[i]->dx;
                if ((cote==3 && tab[i]->dy<0) || (cote==4 && tab[i]->dy>0))
                    tab[i]->dy=-tab[i]->dy;
                if ((cote==1 && tab[j]->dx>0) || (cote==2 && tab[j]->dx<0))
                    tab[j]->dx=-tab[j]->dx;
                if ((cote==3 && tab[j]->dy>0) || (cote==4 && tab[j]->dy<0))
                    tab[j]->dy=-tab[j]->dy;
            }

    // Gérer les déplacements habituels...
    for (i=0;i<NACTEUR;i++)
        actualiserActeur(tab[i]);

}

// Gérer la possibilité de bouger un acteur à la souris
void interfaceTabActeurs(t_acteur * tab[NACTEUR])
{
    int i,mmx,mmy;
    t_acteur *acteur;

    // ESPACE pour figer tous les acteurs
    if (key[KEY_SPACE]){
        for (i=0;i<NACTEUR;i++)
        {
            tab[i]->dx=0;
            tab[i]->dy=0;
        }
    }

    // ENTREE pour réinitialiser déplacement aléatoire tous les acteurs
    if (key[KEY_ENTER]){
        for (i=0;i<NACTEUR;i++)
            vecDepAleaActeur(tab[i]);
    }


    // Mouvement mouse depuis le dernier appel à get_mouse_mickeys ?
    get_mouse_mickeys(&mmx,&mmy);

    // Si on clique et qu'un des rectangles est sous la souris,
    // on met son déplacement identique à celui de la souris
    if (mouse_b&1){

        for (i=0;i<NACTEUR;i++)
        {
            acteur=tab[i];
            if ( mouse_x >= acteur->x && mouse_x <= acteur->x + acteur->tx &&
                 mouse_y >= acteur->y && mouse_y <= acteur->y + acteur->ty )
                {
                    acteur->dx=mmx;
                    acteur->dy=mmy;
                }
        }

    }
}


// Dessiner un acteur sur une bitmap bmp
void dessinerActeur(BITMAP *bmp, t_acteur *acteur)
{
    rectfill(bmp,acteur->x,acteur->y,acteur->x+acteur->tx,acteur->y+acteur->ty,acteur->couleur);
}

// Dessiner sur une bitmap l'ensemble des acteurs
void dessinerTabActeurs(BITMAP *bmp,t_acteur * tab[NACTEUR])
{
    int i;

    for (i=0;i<NACTEUR;i++)
        dessinerActeur(bmp,tab[i]);

}


// (ré)initialiser vecteur déplacement aléatoire avec composantes non nulles
void vecDepAleaActeur(t_acteur *acteur)
{
    do
    {
        acteur->dx = rand()%11-5;
        acteur->dy = rand()%11-5;
    }
    while (acteur->dx==0 || acteur->dy==0);
}

// Déterminer si les rectangles de 2 acteurs s'intersectent
// Retourne 0 si pas de collision
// Si collision alors retourne une valeur selon la position de a2 par rapport à a1
// 1 : plutôt à droite   2 : plutôt à gauche   3 : plutôt en bas   4 : plutôt en haut
int collisionActeurs(t_acteur *a1, t_acteur *a2)
{
    int retour;
    int m[4],imin,i;

    // Calcul des marges d'intersection (a2 par rapport à a1)
    m[0]=a2->x + a2->tx - a1->x; // 0: à droite
    m[1]=a1->x + a1->tx - a2->x; // 1: à gauche
    m[2]=a2->y + a2->ty - a1->y; // 2: en bas
    m[3]=a1->y + a1->ty - a2->y; // 3: en haut

    // Chercher l'indice de la plus petite marge
    imin=0;
    for (i=1;i<4;i++)
        if (m[i]<m[imin])
            imin=i;

    // A priori pas de collision
    retour=0;

    // Si la plus petite marge n'est pas strictement négative
    // alors c'est qu'on a une collision et cette collision est de ce coté
    if (m[imin]>=0)
        retour=imin+1;  // on retourne l'indice du coté + 1 (car 0 signifie "pas de collision")

    return retour;
}