#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <sys/time.h>
#include <math.h>
#include <vector>
#include <thread>
#include <sstream>

using namespace std;

double timevalsub(struct timeval *tv1, const struct timeval *tv2)
{
  double res = 0;
  res = tv2->tv_sec - tv1->tv_sec;
  res += (tv2->tv_usec - tv1->tv_usec) * 1.0 / 1000000;
  return res;
}

// Returns the duration of call
double fct(int num, int nbtours, double &temps_res)
{
  struct timeval tv1, tv2;
  int err;

  // pour les sectaire du C++, on peut se passer de printf
  // utilisation d'un ostringstream pour construire la chaine
  ostringstream c;
  // on vide la stream (c'est inutile ici car on vient de la creer mais
  // si c'est dans une boucle ...)
  c.str("");
  // on construit la chaine
  c << num << ": lancement de la fonction pour " << nbtours << " iterations"
    << endl;
  // on affiche le resultat
  cout << c.str();
  // dans la suite j'utilise fprintf qui ne pose aucun probleme.
  // #####################################
  // Partie calcul
  double temps_total = 0;
  for (int i = 0; i < nbtours; i++)
  {
    int j;

    err = gettimeofday(&tv1, NULL);
    if (err != 0)
    {
      perror("gettimeofday");
      exit(EXIT_FAILURE);
    }

    double sum = 0;
    for (j = 0; j < 5000000; j++)
    {
      sum += log(j + 1);
    }

    err = gettimeofday(&tv2, NULL);
    if (err != 0)
    {
      perror("gettimeofday");
      exit(EXIT_FAILURE);
    }
    double duree = timevalsub(&tv1, &tv2);
    temps_total += duree;

    // Version avec "cerr" si on veut voir les problemes
    // d'entrelacement des sorties :
    // cerr << num << " : je viens de faire un calcul dont le resultat est "
    //     << sum << ", il a fallu " << duree << " secondes" << endl ;
    // fprintf ne pose pas ce probleme :
    fprintf(stdout, "%d : je viens de faire un calcul dont le resultat est %g,"
                    "il a fallu %g secondes\n",
            num, sum, duree);
  }
  // fin de la partie calcul
  // ###########################

  // Recuperation du resultat
  // le resultat du thread est ignore.
  // je le stocke dans la reference qui a ete passee en argument,
  // le thread principale lira cette valeur apres le join
  temps_res = temps_total;
  fprintf(stdout, "%d : temps total %g\n", num, temps_total);
  return temps_total;
}

int main(int argc, char **argv)
{
  int i;

  // Parametres
  int nbthreads = 10;
  int nbtours = 5;

  // lecture des arguments
  if ((argc == 2) && (atoi(argv[1])))
  {
    // s'il y a un argument numerique on l'utilise pour
    // connaître le nombre de threads/lancements de la fonction
    nbthreads = atoi(argv[1]);
  }
  else if (argc != 1)
  {
    // il y a au moins un argument mais qui n'est pas le bon
    cerr << "usage " << argv[0] << " [<nb de lancements et de threads>]"
         << endl;
    exit(1);
  }
  cout << "Th principal : lancement de " << nbthreads << " fois la fonction"
       << endl;

  // un tableau pour stocker des threads
  vector<thread> tab;
  double results[nbthreads];
  double temps_total = 0;

  for (i = 0; i < nbthreads; i++)
  {
    // creation des threads en appelant la fonction.
    // attention, le passage d'une variable pour un argument de type reference
    // est un peu special.
    tab.push_back(thread(fct, i, nbtours, std::ref(results[i])));
    // Comme les threads sont stockes dans un tableau, ils ne sont pas detruits
    // ici.
  }

  // on attend la terminaison les threads
  for (i = 0; i < nbthreads; i++)
  {
    tab[i].join();
    temps_total += results[i];
    // recuperation du resultat du thread via le tableau results
    cout << "Th principale : Temps de calcul du thread " << i << " : " << results[i] << endl;
  }

  cout << "Th principal : Le temps total de calcul est " << temps_total << endl;

  return 0;
}
