/*
====================================================================
Name		: mfactorial.c
Date		: 2009-03-02
Author		: Haui, haui45@web.de 
Version		: 0.1
Copyright	: GPL
Description	: calculates argv[1]!
Compile with 'gcc -lgmp -lpthread mfactorial.c -o mfactorial'
====================================================================
*/
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <gmp.h>
#define THREADS 16

struct fac_data{
    int begin;
    int end;
    mpz_t num;
};

/* calculates begin*(begin+1)*(begin+2)*...*end
 * and stores the result in num
 * */
static void *mfac(void *my_data){
    struct fac_data *data;
    data = (struct fac_data *) my_data;
    unsigned int begin = (unsigned int)data->begin;
    int end = data->end;
    mpz_t fak;
    mpz_init(fak);
    mpz_set_ui(fak,begin);
    for (++begin; begin<=end;begin++){
        mpz_mul_ui (fak, fak, begin);
    }
    mpz_set(data->num, fak);
    mpz_clear(fak);
}

int main(int argc, char **argv){
    pthread_t p[THREADS];
    struct fac_data data[THREADS];
    int i = 0;
    int j = 0;
	if(argc != 2){
		fprintf(stderr, "Argument required!\n");
		return 1;
	}
    /*check argv[1]*/
    for (j=0; j<(int)strlen(argv[1]); j++){
        if((int)argv[1][j] < 0x30 || (int)argv[1][j]> 0x39){
            printf("%s: not a valid number!\n", argv[1]);
            return 1;
        }
    }
    int arg = atoi(argv[1]);
    if(! arg){
        printf("1\n");
        return 0;
    }
    int part = arg/THREADS;
    for(j=0; j<THREADS; j++){
        mpz_init(data[j].num);
    }
    for(j=0; j<THREADS; j+=4){
        for (i=0;i<4;i++){
            data[j+i].begin = (j+i)*part+1;
            data[j+i].end = (j+i+1)*part;
        }
    }
    data[THREADS-1].end = arg;

    for(j=0; j<THREADS; j++)
        pthread_create(&p[j], NULL, mfac, (void *)&data[j]);

    for(j=0; j<THREADS;j++)
        pthread_join(p[j], NULL);

    for(j=1;j<THREADS;j++)
        mpz_mul(data[0].num,data[0].num,data[j].num);

    mpz_out_str (stdout, 10, data[0].num); 
    printf("\n");

    return 0;
}
