#include<stdio.h>
#include<stdlib.h>
#include<math.h>

// *** Tiempo de proceso del dispositivo: NORMAL(m,d)
#define NORMAL_MEAN 3.2
#define NORMAL_DESV 0.6
// *** Tiempo de llegadas: EXPONENCIAL(m)
#define EXPONENTIAL_MEAN 4.5

// *** Número máximo de elementos en la cola
#define QUEUE_SIZE 101
// *** Número de unidades que se procesan
#define UNITS 1000
// *** Tiempo máximo de proceso
#define MAX_TIME 7.0
// *** Número de eventos diferentes
#define EVENT_TYPES 2
// *** Indica la inexistencia de evento
#define NO_EVENT 1000000

//*** Reloj de simulación
float simulationTime;	
//*** Tiempo de utilización del dispositivo
float busyTime;		
//*** Tiempo de respuesta acumulado
float responseTime;		
//*** Momento en el que sucedió el último evento
float lastEventTime; 	
//*** Momento de llegadas de unidades a la cola
float queue[QUEUE_SIZE];	
//*** Momento de sucesión de los próximos eventos. [0]: entrada, [1]: salida
float events[EVENT_TYPES];		

// *** Tipo del siguiente evento
int nextEvent;
// *** Número de unidades en la cola
int unitsInQueue;
// *** Número máximo de unidades en la cola
int maxQueueOccupation;
// *** Número total de unidades procesadas
int unitsTotal;
// *** Número de unidades procesadas por encima del tiempo máximo
int unitsInMaxTime;
// *** Indica si el dispositivo está o no ocupado
bool busyDevice;

float 
exponential(float fmean)
{
      float r = 1.*random()/RAND_MAX;;
	
      return -fmean*log(r);
}

float 
normal(float mean, float sigma){
	double rone = 1.*random()/RAND_MAX; 
	double rtwo = 1.*random()/RAND_MAX; 
	double zone = sqrt(-2*log(rone))*sin(2*M_PI*rtwo);
   
      return (float) zone*sigma+mean;
}

void 
initialize()
{
	// Reloj de simulación a cero
	simulationTime = 0;
	
	// Las condiciones iniciales indican que el sistema está vacío y por
	// tanto el dispositivo libre.
	busyDevice = false;
	
	// No se ha producido ningún evento previo
	lastEventTime = 0;
	
	// Inicialización de las variables que almacenan los datos estadísticos
	busyTime = 0;
	responseTime = 0;
	unitsInQueue = 0;
	maxQueueOccupation = 0;
	unitsTotal = 0;
	unitsInMaxTime = 0;
	
	// Se planifica el primer evento de llegada y se inicializa a un tiempo
	// elevado el de salida con el fin de no ser elegido
	events[0] = simulationTime + exponential(EXPONENTIAL_MEAN);
	events[1] = NO_EVENT;
}

void 
time_advance()
{
       float fmin;
       int i;

       nextEvent = 0;
       fmin = events[nextEvent];
	
       for(i = 1; i < EVENT_TYPES; i++){
           if (events[i] < fmin) {
              fmin = events[i];
              nextEvent = i;
           }
       }

       simulationTime = events[nextEvent];
}

void llegada( ) {
        // Determina si el servidor está ocupado		
        if (!busyDevice)
	{
		// Actualiza el estado y almacena el tiempo de 
		// de llegada para el siguiente cliente
                busyDevice = true;
                queue[0] = simulationTime;

                // Genera un tiempo de servicio y prepara el 
		// evento de salida
                events[1] = simulationTime + normal(NORMAL_MEAN, NORMAL_DESV);
		

                // Actualiza estadistica LastEventTime para ver la 
		// ocupacion del servidor
		lastEventTime = simulationTime;
	}
	else
	{
		unitsInQueue++;	
		
		// Actualiza el estado y almacena el tiempo de
		// llegada para el siguiente cliente
		if (unitsInQueue > QUEUE_SIZE ){
			printf("DESBORDAMIENTO DE LA COLA \n");
			exit(0);
		}
		else
		{
			if (unitsInQueue > maxQueueOccupation)
				maxQueueOccupation = unitsInQueue;
		}

		queue[unitsInQueue] = simulationTime;
		
		// Actualiza estadísticas
		busyTime= busyTime + simulationTime - lastEventTime;
		lastEventTime = simulationTime;
	}

        // Genera un tiempo entre llegadas y planifica el siguiente evento
	events[0] = simulationTime + exponential(EXPONENTIAL_MEAN);
}

void salida( ) { 
        // Actualiza estadísticas acumulativas b,s,nd y f
	busyTime = busyTime + simulationTime - lastEventTime;
	float rt = simulationTime - queue[0];
	
	responseTime = responseTime + rt;
	unitsTotal++;

	if (rt > MAX_TIME) 
		unitsInMaxTime++;

        // Comprueba el estado de la cola de espera
	if (unitsInQueue == 0){
                // No hay clientes en la cola-> servidor inactivo y 
		// tiempo de salida muy alto para no ser elegido
		busyDevice = false;
		events[1] = NO_EVENT;
	}else{
		// Cada cliente en cola avanza una  posicion
		for(int i=0; i<unitsInQueue; i++) 
			queue[i] = queue[i+1];

		// Actualiza el estado del sistema
		unitsInQueue=unitsInQueue-1;

		// Genera un nuevo tiempo de servicio para el cliente
		// al que se le comienza a dar servicio. Planifica el 
		// siguiente evetno de salida
		events[1] = simulationTime + normal(NORMAL_MEAN, NORMAL_DESV);
                
		// Actualiza estadísticas para obtener el tiempo de
                // servicio
		lastEventTime = simulationTime;
          }
}


void rptgen( ) {
       float rho, avgr, pc;
 
       // Estadisticas de resumen
       rho = busyTime/simulationTime;
       avgr = responseTime/unitsTotal;
       pc = (float)(100*unitsInMaxTime)/unitsTotal;

       // Escribe la salida
       printf("SIMULACION DE UNA COLA CON UN SERVIDOR \n");
       printf("Tiempo medio entre llegadas: %4.2f minutos\n", EXPONENTIAL_MEAN);
       printf("Tiempo medio de servicio: %4.2f minutos\n", NORMAL_MEAN);
       printf("Desviacion estandar de los tiempos de servicio: %4.2f minutos\n", NORMAL_DESV);
       printf("Numero de clientes atendidos %4d \n", UNITS);
       printf("Utilizacion del servidor %4.2f \n", rho);
       printf("Maximo tamaño de la cola %4d clientes \n", maxQueueOccupation);
       printf("Tiempo de respuesta medio %4.2f \n", avgr);
       printf("Proporcion clientes que han pasado más de %4.2f mins. en el sistema: %4.2f \%\n", MAX_TIME, pc);
       printf("Duracion de la simulacion: %7.2f mins. \n",simulationTime);
}

main( ){
	// Semilla del generador de numeros
	unsigned int seed = 13; 

	// Inicializa el generador de numeros aleatorios
	srand(seed); 

	// Inicialización de estructuras de la simulacion
	initialize( );

	// Repite mientras no se acabe la simulacion
	while (unitsTotal < UNITS){
		// Localiza el evento inminente
               time_advance();
	       
		// Ejecuta la funcion de evento correspondiente
	       
		switch(nextEvent){
                    case 0: 
			llegada( );
		    
			break;
                    case 1: 
			salida( );
                    
			break;
		}
        }

        //  Generacion de informes
        rptgen( ) ;
} 
