// Compile com gcc -o primo-hugo-m5 primo-hugo-m5.c -lm -lpthread

#include<stdio.h>
#include<stdlib.h>
#include<math.h> // Mudança aqui
#include<pthread.h> // MUDANÇA AQUI
#include <unistd.h> // MUDANÇA AQUI, para incluir definição de _SC_NPROCESSORS_ONLN

// Verifica se o número num é primo
int primo (int num)
{
	int i, ultimo_div; // Mudança aqui

        ultimo_div= (int) sqrt (num); // MELHORIA 2: há um teorema que diz que, se um número possui divisores maiores que um, então
        // pelo menos um desses divisores está entre 2 e a raiz quadrada desse número. Com base nesse conhecimento, precisamos buscar
        // por um divisor apenas no intervalor [2,sqrt(num)], a fim de descobrir se o número é primo ou não.

        if (num%2 == 0) return 0; // Mudança: retorna logo que não é primo se num for divisível por 2.

	for(i=3 ; i <= ultimo_div ; i+=2)  // Melhoria: como num não é divisível por 2, então também não é divisível por
        // qualquer outro número par. Assim, podemos pular o teste de divisão com todos os números pares maiores que 2,
        // o que reduz pela metade a quantidade de teste de divisores no laço for.
	{
		if(num%i == 0)
			return 0; // Número não é primo
	}
	return 1; // Número é primo
}



// Define estrutura do parâmetro passado aos Threads
struct thread_data{
   int  first,  // primeiro valor da sequência a verificar se é primo
        last,   // último valor da sequência a verificar se é primo
        step;   // incremento do valor da sequencia
   int  count;  // Guarda a quantidade de primos encontrado em [first,last] com passo step
};


// Thread para computar a quantidade de números primos em intervalo passado por parâmetro, incluindo um passo de incremento
void *quant_primos_thread(void *threadarg) {
   struct thread_data *my_data;
 
   my_data = (struct thread_data *) threadarg;  
   int i, first=my_data->first, last=my_data->last, step=my_data->step, count=0;
   for(i=first ; i <= last ; i+=step)  // Melhoria: pula os números pares, 
        // reduzindo  pela metade a quantidade de chamadas à função primo. 
        // Contudo, essas chamadas não demoravam muito, pois os valores pares
        // eram rapidamente identificados como não primos, quando testava-se 2 como divisor. 
	{
	    if(primo(i)) count++;
	}
   my_data->count=count; // define valor de retorno
   pthread_exit(NULL);
}


// Retorna a quantidade de números primos até n
int quant_primos (int n, int NUM_THREADS)
{
   int ct=(n>=2)? 1:0;  // Mudança aqui para começar a contagem com um, para considerar o valor 2
        
   pthread_t threads[NUM_THREADS];  // Cria um vetor de threads
   struct thread_data thread_data_array[NUM_THREADS]; // cria um vetor de parâmetros

    // Cria todas as threads. Para o caso de haver três threads, a thread 1 fica encarregada 
    // dos testes dos números 3,9,15,..., a thread 2 testa os números 5,11,17,... e a 
    // thread 3 fica encarregada do teste dos números 7,13,19,...
    long t; 
    int rc;
    for(t=0; t<NUM_THREADS; t++){ 
      thread_data_array[t].first = (int)(3+(t*2));  // começa a contar a partir de 3, pulando de dois em dois para cada thread
      thread_data_array[t].last = n; // valor final a ser testado como primo
      thread_data_array[t].step = (2*NUM_THREADS); // passo de incremento.  o
      thread_data_array[t].count = 0; // Variável de retorno
      rc = pthread_create(&threads[t], NULL, quant_primos_thread, (void *) &thread_data_array[t]);
      if (rc){
         printf("ERROR; return code from pthread_create() is %d\n", rc);
         exit(-1);
      }
   }
   
   // Aguarda as threads terminarem sua execução e recupera a quantidade de primos de computados 
   for(t=0; t<NUM_THREADS; t++) {
      rc = pthread_join(threads[t], NULL); // Aguarda finalização da Thread t
      if (rc) {
         printf("ERROR; return code from pthread_join() is %d\n", rc);
         exit(-1);
       }
      ct+=thread_data_array[t].count;  // Adiciona o resultado gerado pela thread à contagem geral
   }
    
   return ct;    
}


// Ler um numero inteiro N e imprime quantos primos existem entre 2 e N
int main (int argc, char * argv[])
{
	int n, nthreads;
      
        // Recebe o valor via argumento ou via scanf
	if(!argv[1])
	{
		printf("Digite um valor:");
		scanf("%d",&n);
	}
	else
		n = atoi(argv[1]);

        // MUDANÇA aqui, para obter a quantidade de CPUs/Cores
        // Recebe qt de threads via argumento ou através da função sysconfig
	if(!argv[2])
	{
		nthreads = sysconf( _SC_NPROCESSORS_ONLN );  // Obtém a quantidade de CPUs/COREs da Máquina
	}
	else
		nthreads = atoi(argv[2]);

	printf("Existem %d primos entre 2 e %d (computado com %d thread(s))\n", quant_primos(n, nthreads), n, nthreads); // MUDANÇA aqui
}

