/* Factoriel: version recursive parallele dichotomique avec valeur de coupure/

   Compilation et execution sur solaris:
     gcc ceFichier.c -o fact -lpthread -lposix4
     fact n coupure

*/
 
#include <pthread.h>
#include <stdio.h>
#include <assert.h>

/* Attribut (global) pour creation des divers threads. */
#define SHARED 1
pthread_attr_t attr;

/* Structure pour les arguments lors des appels a fact. */
typedef struct {
  int inf, sup;
  int coupure;
} Args;

/* En-tete de la fonction fact. */
void *fact( void *arg );

int main(int argc, char *argv[]) 
{
  pthread_t fid;  
  int n,      /* Valeur lue a l'appel du programme. */
    res,      /* Variable ou mettre le resultat. */
    coupure;  /* Valeur de coupure pour la recursion. */
  Args args;  /* Les arguments. */

  /* Initialisation (standard) de la structure d'attribut pour le thread. */
  pthread_attr_init(&attr);
  pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

  /* Lecture des arguments a utiliser pour l'appel. */
  assert( argc >= 3 );
  n = atoi(argv[1]);
  coupure = atoi(argv[2]);
  assert( n >= 0 && coupure >= 1 );

  /* Creation du thread principal. */
  args.inf = 1;
  args.sup = n;
  args.coupure = coupure;
  pthread_create(&fid, &attr, fact, &args);

  /* Attente pour le thread enfant se termine. */
  pthread_join(fid, (void *) &res);
  printf( "fact( %d ) = %d\n", n, res );
}

/**/

void *fact(void *arg) 
{
  Args *args;
  Args args1, args2; /* Arguments pour les appels recursifs. */
  int res1, res2;    /* Les deux resultats intermediaires. */

  args = (Args *) arg; /* Pour eviter les casting subsequents. */
  if (args->sup - args->inf + 1  <= args->coupure) {
    /* Cas de base. */
    int i, r = 1;
    for ( i = args->inf; i <= args->sup; i++ )
      r *= i;
    pthread_exit( (void*) r );

  } else {
    /* Cas recursif. */
    pthread_t fid1, fid2;
    Args args1, args2;
    
    /* On definit les arguments pour les appels recursifs. */
    args1 = args2 = *args;
    args1.sup = (args->sup + args->inf) / 2;
    args2.inf = args1.sup + 1;

    /* On cree les deux threads et on attend les resultats. */
    pthread_create(&fid1, &attr, fact, (void *) &args1);
    pthread_create(&fid2, &attr, fact, (void *) &args2);
    pthread_join(fid1, (void *) &res1);
    pthread_join(fid2, (void *) &res2);
    
    /* On combine les resultats et on retourne. */
    pthread_exit((void*) (res1*res2));
  }
}
