#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <pthread.h>
/**
 * Reduzco codigo de los hilos que producen datos
 * de ventas de cada sucursal
 * compilar:
 * $ gcc -Wall -o cv1 cv1.c -lm -lpthread
 */
// totales de ventas de 3 sucursales en 31 dias
double ventas[3][31];
// total de ventas por sucursal
double tsucventas[3];
// total de ventas por dia
double tdiaventas[31];
double producirNumero(int desde,int hasta,int decimales);
double sumo(double *,int n);
// hilo que carga los datos en la matriz ventas para cada sucursal 1..3
void *sucursal(void *);
// hilo que suma cada dia
void *totaldia(void *);
// dia completo? Verifica si el dia en cuestion esta producido
int diaCompleto(int);

// inicializo mutex y varibles de condicion para sumarizar por dia
pthread_mutex_t m1[31];
pthread_cond_t  v1[31];

int main(int argc,char **argv) {
	// inicializo la matriz y vectores
	memset(&ventas[0][0],0,sizeof(double)*3*31); // ventas[][]=0
	memset(&tsucventas[0],0,sizeof(double)*3);   // tsicventas[]=0
	memset(&tdiaventas[0],0,sizeof(double)*31);  // tdiaventas[]=0
	// referencias a los hilos que voy a crear
	pthread_t thr[3];
	pthread_t thrdia[31];
	// parametros (codigo sucursal) a pasar a cada llamada de sucursal()
	int parametros[3] = { 1, 2, 3 };
	int paramdia[31];
	int i;
	// inicializo mutex y variables de condicion
	for(i=0;i<31;i++) {
		pthread_mutex_init(&m1[i],NULL);
		pthread_cond_init(&v1[i],NULL);
	}
	// creo hilos sumadores diarios
	for(i=0;i<31;i++) {
		paramdia[i]=i+1;
		pthread_create(&thrdia[i],NULL,totaldia,&paramdia[i]);
	}
	// creo hilos productores por sucursal
	for(i=0;i<3;i++) {
		printf("main(): creacion de hilo sucursal%d\n",i+1);
		pthread_create(&thr[i],NULL,sucursal,&parametros[i]);
	}
	// espero hasta que terminen todos los hilos productores
	for(i=0;i<3;i++) {
		printf("main(): espero por la finalizacion de hilo sucursal%d\n",i+1);
		pthread_join(thr[i],NULL);
	}
	// espero hasta que terminen todos los hilos sumadores
	for(i=0;i<31;i++) {
		printf("main(): espero por la finalizacion de hilo sumador dia %2d\n",i+1);
		pthread_join(thrdia[i],NULL);
	}
	// A esta altura, si terminaron los productores y los sumadores diarios
	// entonces puedo sumar las ventas de cada sucursal
	for(i=0;i<3;i++) {
		tsucventas[i]=sumo(&ventas[i][0],31);
		printf("main(): total ventas sucursal %1d: $%10.2lf\n",i+1,tsucventas[i]);
	}
	// sumatoria general de todas las sucursales
	printf("main(): total general de ventas (en base a sucursal): $%10.2lf\n",sumo(&tsucventas[0],3));
	printf("main(): total general de ventas (en base a dias    ): $%10.2lf\n",sumo(&tdiaventas[0],31));
	printf("main(): fin!\n");
	return 0;
}

/**
 * Hilo que produce y carga las ventas de
 * la sucursal que se indique como parametro
 */
void *sucursal(void *pSuc) {
	int i;
	// guardo en suc el entero apuntado por pSuc menos uno
	int suc = *((int *) pSuc) - 1;
	for(i=0;i<31;i++) {
		double vta = producirNumero(1,1000,2);
		printf("sucursal(): Sucursal %1d dia %2d = $ %10.2lf\n",suc+1,i+1,vta);
		ventas[suc][i]=vta;
		if ( diaCompleto(i) ) {
			pthread_mutex_lock(&m1[i]);
			pthread_cond_signal(&v1[i]);
			pthread_mutex_unlock(&m1[i]);
		}
		sleep(1);
	}
	pthread_exit(NULL);
}

/**
 * Hilo que suma las ventas de un dia
 * (siempre y cuando se haya producido el dia)
 */
void *totaldia(void *pDia) {
	int i;
	// guardo en dia el entero apuntado por pDia menos uno
	int dia = *((int *) pDia) - 1;
	printf("totaldia() dia %2d\n",dia+1);
	pthread_mutex_lock(&m1[dia]);
	// verifico condicion (deben estar producidas todas las ventas del dia!
	if ( !diaCompleto(dia) ) {
		printf("totaldia() dia %2d bloqueado, faltan ventas!\n",dia+1);
		pthread_cond_wait(&v1[dia], &m1[dia]);
	}
	// control de seguridad
	for(i=0;i<3 && ventas[i][dia];i++);
	if ( i < 3 ) printf("totaldia() dia %2d ERROR!! SUMA INCOMPLETA!!\n",dia+1);
	
	// ahora puedo sumar
	for(i=0;i<3;i++) tdiaventas[dia]+=ventas[i][dia];
	// libero el lock
	pthread_mutex_unlock(&m1[dia]);
	printf("totaldia() dia %2d total $ %10.2lf\n",dia+1,tdiaventas[dia]);
	pthread_exit(NULL);	
}

/**
 * Verifica que se hayan producido todos los totales de 
 * ventas de todas las sucursales para ese dia
 */
int diaCompleto(int dia) {
	int i,completo=1;
	for(i=0;completo && i<3;i++) if ( !(ventas[i][dia] >= 1.0) ) completo=0; 
	return completo;
}

/**
 * Funcion que suma numeros double que se encuentran en 
 * memoria contigua, como puede ser el caso de un arreglo
 */
double sumo(double *p,int n) {
	double total=0.0;
	int i;
	for(i=0;i<n;i++,p++) total+=*p;
	return total;
}
/**
 * Produce numeros aleatorios en un contexto de hilos
 * (usa version re-entrante de funcion rand(), 
 * srand(unsigned int) ) para evitar problemas de ejecucion
 * simultanea de mas de un hilo. rand() no esta preparada
 * para eso.
 * desde = numero desde
 * hasta = numero hasta
 * decimales = cantidad de decimales
 * Ejemplo de uso: si quiero producir un numero double con
 * 2 decimales entre 1 y 1000:
 * double numero = producirNumero(1,1000,2);
 */
double producirNumero(int desde,int hasta,int decimales) {
	unsigned int ahora = (unsigned int) time(NULL);
	static int offset = 0;
	ahora+=offset++;
	int num=desde+rand_r(&ahora)%((hasta+1)-desde);
	int dec=0;
	if ( decimales > 0 ) {
		dec = (int) (producirNumero(1,pow(10,decimales),0)-1.0);
	}
	double ret = (double) num;
	if ( decimales > 0 ) ret+= ((double) dec / pow(10,decimales));
	return ret;
}
