/*-----------------------------------------------------------------------
 * pvccoco-03
 *
 * Dans cette version : "2-opt + insertion-opt" appliqué au 14 balles avec gestion des obstacles
 * Voir les fichier README.francais et COPYING
 * 
 * Auteur : Alexandre AUPETIT (aaupetit@club-internet.fr)
 * Web : http://home.alex.tuxfamily.org/pvc.html
 *       http://home.alex.tuxfamily.org/pvc/robotique/coconut.html
 * 
 * Important : Ce logiciel est soumis à la licence de logiciel libre GPL
 *-----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <string.h>

#include "robot_math.cpp"

#define NB_BALLES 14
#define NB_OBSTACLES 2
#define NB_ITER 5
#define RAYON_ENCOMBREMENT 1.5
#define MAX_LENLINE 255
#define GAIN_MINI -0.0000000001

double pos_x[NB_BALLES+1]; // tableau des position en X des balles (en 0 : pos initiale du robot)
double pos_y[NB_BALLES+1]; // tableau des position en Y des balles (en 0 : pos initiale du robot)

double pos_x_o[NB_OBSTACLES]; // tableau des position en X des obstacles 
double pos_y_o[NB_OBSTACLES]; // tableau des position en Y des obstacles

double cache_distance[NB_BALLES+1][NB_BALLES+1];

int parcours[NB_BALLES+1]; // parcours (numéros des balles) (en 0 : pos initiale du robot)
double score_parcours;     // coût du parcours  == distance totale

int best_parcours[NB_BALLES+1]; // meme chose en sauvegarde (meilleur parcours calculé jusqu'à présent)
double score_best_parcours;

// retourne un chiffre au hasard entre 0 et max
int hasard(int max) {
        return (int)((rand()/(RAND_MAX + 1.0))*max);
}


double distance_point(int point1, int point2) {
        return cache_distance[point1][point2];
}

// distance entre deux points dont les coordonnées sont définies dans pos_x et pos_y
// prend en compte les obstacles
double distance_point_calcul(int point1, int point2) {
        int i;
        double dist, decalage;

        // distance euclidienne
        dist=sqrt((pos_x[point2]-pos_x[point1])*(pos_x[point2]-pos_x[point1])
                +(pos_y[point2]-pos_y[point1])*(pos_y[point2]-pos_y[point1]));

        // décalage pour chaque obstacle
        for (i=0; i<NB_OBSTACLES; i++) {
                if (detect_collision(pos_x[point1],pos_y[point1],pos_x[point2],pos_y[point2],
                                        pos_x_o[i], pos_y_o[i], RAYON_ENCOMBREMENT, &decalage)) {
                        dist+=2*decalage;
                        /*printf("collision : %d-%d (obstacle %d)\n", point1, point2, i);*/
                }
        }
        return dist;
}

// pré-calcul des distances dans un tableau
void remplit_cache_distance() {
        int i, j;

        for (i=0;i<NB_BALLES+1;i++) {
                cache_distance[i][i]=0;
                for (j=i+1;j<NB_BALLES+1;j++) {
                        cache_distance[i][j]=distance_point_calcul(i, j);
                        cache_distance[j][i]=cache_distance[i][j];
                }
        }
}

// distance totale du parcours
// attention on prend en compte le parcours entre le robot et la première balle 
// (parcours[0] à parcours[1]) mais on ne revient pas à la position initiale
// contrairement au PVC classique
double distance_parcours() {
        int i;
        double d=0;

        for (i=1;i<NB_BALLES+1;i++) {
                d+=distance_point(parcours[i-1], parcours[i]);

        }
        return d;
}

void copie_best_parcours() {
        int i;

        for (i=1;i<NB_BALLES+1;i++) {
                best_parcours[i]=parcours[i];

        }

        score_best_parcours=score_parcours;
}


// affichage du parcours (et de son score) à l'écran
// avec aussi un message d'info
void affiche_parcours(char *message_info) {
        int i;
        printf("%s", message_info);
        for (i=0;i<NB_BALLES+1;i++) {
                        printf("%d-", parcours[i]);
        }
        printf("(d=%lf)\n", score_parcours);
}

// affichage du meilleurs parcours (et de son score) à l'écran
// avec aussi un message d'info
void affiche_best_parcours(char *message_info) {
        int i;
        printf("%s", message_info);
        for (i=0;i<NB_BALLES+1;i++) {
                        printf("%d-", best_parcours[i]);
        }
        printf("(d=%lf)\n", score_best_parcours);
}

// initialisation de la position des balles par lecture du fichier pos.txt
// le fichier pos_balles.txt doit contenir les positions au format :
// x_robot;y_robot
// x_balle1;y_balle1
// x_balle2;y_balle2
// x_balle3;y_balle3
// ...
// x_balle8;y_balle8
void init_pos() {
        FILE *ficPos;
        char sLine[MAX_LENLINE + 1];
        char *sPos;
        int i;

        ficPos=fopen("pos_balles.txt", "rt");
        if (ficPos != NULL) {
                i=0;
                fgets(sLine, MAX_LENLINE, ficPos);
                do {
                        sPos = strtok(sLine, ";\n");
                        //printf("%s-\n",sPos);
                        pos_x[i] = atof(sPos);
                        sPos = strtok(NULL, ";\n");
                        //printf("%s-\n",sPos);
                        pos_y[i] = atof(sPos);
                        i++;
                        fgets(sLine, MAX_LENLINE, ficPos);
                } while ( !(feof(ficPos)) );

                /*if (i != NB_BALLES+2) {
                        printf("Erreur dans le nombre de balles du fichier pos_balles.txt !\n");
                        exit(1);
                }*/
        } else {
                printf("Erreur d'ouverture du fichier pos_balles.txt !");
                exit(1);
        }
        fclose(ficPos);

        // obstacles
        ficPos=fopen("pos_obstacles.txt", "rt");
        if (ficPos != NULL) {
                i=0;
                fgets(sLine, MAX_LENLINE, ficPos);
                do {
                        sPos = strtok(sLine, ";\n");
                        pos_x_o[i] = atof(sPos);
                        sPos = strtok(NULL, ";\n");
                        pos_y_o[i] = atof(sPos);
                        i++;
                        fgets(sLine, MAX_LENLINE, ficPos);
                } while ( !feof(ficPos) );

                /*if (i != NB_OBSTACLES+1) {
                        printf("Erreur dans le nombre d'obstacles du fichier pos_obstacles.txt !");
                        exit(1);
                }*/
        } else {
                printf("Erreur d'ouverture du fichier pos_obstacles.txt !");
                exit(1);
        }
        fclose(ficPos);


        /*for (i=0; i<NB_BALLES+1;i++) {
                printf("balle %d : %lf %lf\n", i, pos_x[i], pos_y[i]);
        }
        for (i=0; i<NB_OBSTACLES;i++) {
                printf("obstacle %d : %lf %lf\n", i, pos_x_o[i], pos_y_o[i]);
        }*/

}

// echange l'odre de parcours entre 2 balles
void echange_parcours(int ordre1, int ordre2) {
        int inter;

        inter=parcours[ordre2];
        parcours[ordre2]=parcours[ordre1];
        parcours[ordre1]=inter;
}

// renverse le parcours entre les balles i et j
// il s'agit de la transformation la plus simple d'un parcours
// voir graphiquement ce que ça représente
// c'est la transformation de base du 2-opt : le 2-change
void renverse_parcours(int i, int j) {
  int a, b;

  if (i < j) {
    a = i;
    b = j;
  } else {
    a = j;
    b = i;
  }

  while (a < b) {
    echange_parcours (a, b);
    a++;
    b--;
  }
}

// initialise un parcours au hasard
void calcule_parcours_hasard() {
        int i;
        for (i=1;i<NB_BALLES+1;i++) {
                parcours[i] = i;
        }

        int a,b;
        for (i=1;i<NB_BALLES+1;i++) {
                a=hasard(NB_BALLES)+1;
                b=hasard(NB_BALLES)+1;
                echange_parcours(a, b);
        }
}



// --------------- 2-OPT --------------------------

// différence de cout du parcours si on renversait le parcours entre les balles i et j
// on "casse" 2 liens entre 2 balles et on les remplace par 2 autres
double difference_cout_2opt(int i, int j) {
        if (j+1<NB_BALLES+1) {
                return (distance_point(parcours[i], parcours[j+1])
                 + distance_point(parcours[i-1], parcours[j])
                 - distance_point(parcours[i-1], parcours[i])
                 - distance_point(parcours[j], parcours[j+1]));
        } else {
                return (
                 distance_point(parcours[i-1], parcours[j])
                 - distance_point(parcours[i-1], parcours[i])
                 );
        }
}


// calcule un parcours 2-opt, c'est à dire qu'aucune permutation 
// de l'odre de parcours ne rend ce parcours plus court
void calcule_parcours_2opt() {
        int i, j, modifie;

         do {
                modifie=0;
                for (i=1;i<NB_BALLES+1;i++) {
                        for (j=i+1;j<NB_BALLES+1;j++) {
                                if (difference_cout_2opt(i, j) < 0.0) {
                                        renverse_parcours(i, j);
                                        modifie=1;
                                }
                        }
                }
        } while (modifie == 1);
}


// --------------- INSERTION-OPT --------------------------

// différence de longueur de trajet lorsqu'on isère i entre j et j+1
// attention aux conditions pour lesquelles ce gain est valide ( i+1<j ) ...
double difference_cout_insert(int i, int j) {
        if (j+1<NB_BALLES+1) {
                return (distance_point(parcours[i], parcours[j])
                 + distance_point(parcours[i], parcours[j+1])
                 + distance_point(parcours[i-1], parcours[i+1])
                 - distance_point(parcours[i-1], parcours[i])
                 - distance_point(parcours[i], parcours[i+1])
                 - distance_point(parcours[j], parcours[j+1]));
        } else {
                return (distance_point(parcours[i], parcours[j])
                 + distance_point(parcours[i-1], parcours[i+1])
                 - distance_point(parcours[i-1], parcours[i])
                 - distance_point(parcours[i], parcours[i+1])
                 );
        }
}

// différence de longueur de trajet lorsqu'on isère j entre i et i+1
double difference_cout_insert_inv(int i, int j) {
        if (j+1<NB_BALLES+1) {
                return (distance_point(parcours[i-1], parcours[j])
                 + distance_point(parcours[i], parcours[j])
                 + distance_point(parcours[j-1], parcours[j+1])
                 - distance_point(parcours[i-1], parcours[i])
                 - distance_point(parcours[j-1], parcours[j])
                 - distance_point(parcours[j], parcours[j+1]));
        } else {
                return (distance_point(parcours[i-1], parcours[j])
                 + distance_point(parcours[i], parcours[j])
                 - distance_point(parcours[i-1], parcours[i])
                 - distance_point(parcours[j-1], parcours[j])
                 );
        }
}

// insere la balle i entre j et j+1
// attention, il faut que i<j !!
void insert_parcours (int i, int j)
{
  int a, b, i_temp;

  a = i;
  b = j;
  i_temp = parcours[a];

  while (a < b) {
    parcours[a] = parcours[a + 1];
    a++;
  }
  parcours[b] = i_temp;
}

// insere la balle j entre i et i+1
// attention, il faut que i<j !!
void insert_inv_parcours (int i, int j)
{
  int a, b, i_temp;

  a = i;
  b = j;

  i_temp = parcours[b];

  while (b > a) {
    parcours[b] = parcours[b - 1];
    b--;
  }
  parcours[a] = i_temp;
}

// calcule un parcours insert-opt, c'est à dire qu'aucune insertion 
// d'une ville à un autre endroit du parcours ne rend ce parcours plus court
void calcule_parcours_insert() {
        int i, j;

        for (i=1;i<NB_BALLES+1;i++) {
                for (j=i+2;j<NB_BALLES+1;j++) {
                        if (difference_cout_insert(i, j) < 0.0) {
                                insert_parcours(i, j);
                        } else {
                                if (difference_cout_insert_inv(i, j) < 0.0) {
                                        insert_inv_parcours(i, j);
                                }
                        }
                }
        }
}


// prcédure qui combine de manière très efficace calcule_parcours_2opt et calcule_parcours_insert
// Le parcours résultant est 2-opt et insertion-opt
void calcule_parcours_2opt_et_insert() {
        int i, j, modifie;

         do {
                modifie=0;

                //2-opt
                for (i=1;i<NB_BALLES+1;i++) {
                        for (j=i+1;j<NB_BALLES+1;j++) {
                                if (difference_cout_2opt(i, j) < GAIN_MINI) {
                                        renverse_parcours(i, j);
                                        modifie=1;
                                        //printf("2-opt : i=%d j=%d\n", i, j);
                                }
                        }
                }

                // insert-opt
                // le 2-opt est prioritaire car il conduiut à de meilleures solutions, 
                // d'où la condition modifie==0
                for (i=1;(i<NB_BALLES+1)&&(modifie==0);i++) {
                        for (j=i+2;(j<NB_BALLES+1)&&(modifie==0);j++) {
                                if (difference_cout_insert(i, j) < GAIN_MINI) {
                                        insert_parcours(i, j);
                                        modifie=1;
                                } else {
                                        if (difference_cout_insert_inv(i, j) < GAIN_MINI) {
                                                insert_inv_parcours(i, j);
                                                modifie=1;
                                        }
                                }
                        }
                }

        } while (modifie == 1);
}


// ----------------------------------------------------------------------------
// prog principal
// ----------------------------------------------------------------------------
int main(int argc, char *argv[]) {
        int i, j, nb_essais;

        srand (time (NULL));

        nb_essais=1;
        if (argc == 2) {
                nb_essais=atoi(argv[1]);
        } else {
                if (argc != 1) {
                        printf("Erreur dans le nombre d'arguments !\n");
                        printf("Usage : pvccoco [Nb d'essais]\n");
                        exit(1);
                }
        }

        init_pos();


        for (i=0;i<nb_essais;i++) {
                // pour ne pas fausser le calcul du temps unitaire,
                // le remplissage du cache est refait à chaque itération
                remplit_cache_distance();

                for (j=0;j<NB_ITER;j++) {
                        calcule_parcours_hasard();
                        calcule_parcours_2opt_et_insert();
                        score_parcours=distance_parcours();

                        affiche_parcours("  itération : ");
                        if ((j==0) || (score_parcours < score_best_parcours)) {
                                copie_best_parcours();
                        }
                }

                affiche_best_parcours("Chemin trouvé : ");
        }

        return 0;
}