#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include "libconjunto.h"
#include "libfila.h"
#include "liblef.h"

#define MEM_ERROR_EXIT										\
	do {													\
		printf("Erro: não foi possível alocar memória.");	\
		exit(1);											\
	} while(0)

#define CHEGADA 0
#define SAIDA 1
#define MISSAO 2
#define FIM 3

typedef struct {
	int id;
	int paciencia;
	int idade;
	int exp;
	conjunto_t *habilidades_do_heroi;
} heroi_t;

typedef struct {
	int id;
	int lotacao_max;
	conjunto_t *publico;
	fila_t *fila;
	int posx;
	int posy;
} local_t;

typedef struct {
	int tempo_atual;
	int tamanho_mundo; /* o mundo é um quadrado */
	int fim_do_mundo;
	int n_missoes;
	conjunto_t *cj_habilidades;
	int n_herois;
	int n_locais;
	heroi_t *herois;
	local_t *locais;
} mundo_t;

/* retorna um inteiro aleatório entre a e b (inclusive) */
int aleat(int a, int b) {
	return rand() % b + 1 + a;
}

/* retorna a distancia euclidiana entre dois locais em double */
double distancia(local_t loc1, local_t loc2) {
	int deltax = loc2.posx - loc1.posx;
	int deltay = loc2.posy - loc1.posy;
	int quadrado_da_distancia = deltax * deltax + deltay * deltay;
	return sqrt((double)quadrado_da_distancia);
}

int max(int a, int b) {
	return a > b ? a : b; 
}

int vazia_lef(lef_t *l) {
	return l->Primeiro == NULL;
}

heroi_t inicializa_heroi(int id, conjunto_t *conjunto_de_habilidades) {
	heroi_t h;
	
	h.id = id;
	h.paciencia = aleat(0, 100);
	h.idade = aleat(18, 100);
	h.exp = 0;

	if ( !(h.habilidades_do_heroi = cria_subcjt_cjt(conjunto_de_habilidades, aleat(2, 5))) )
		MEM_ERROR_EXIT;

	return h;
}

local_t inicializa_local(int id, int tamanho_mundo) {
	local_t l;
	
	l.id = id;
	l.lotacao_max = aleat(5, 30);

	if ( !(l.publico = cria_cjt(l.lotacao_max)) )
		MEM_ERROR_EXIT;

	if ( !(l.fila = cria_fila()) )
		MEM_ERROR_EXIT;

	l.posx = aleat(0, tamanho_mundo-1);
	l.posy = aleat(0, tamanho_mundo-1);

	return l;
}

/* evento_t *cria_evento(int tempo, int tipo, int dado1, int dado2) { */
/* 	evento_t *e; */
/* 	if ( !(e = malloc(sizeof(evento_t))) ) */
/* 		MEM_ERROR_EXIT; */

/* 	e->tempo = tempo; */
/* 	e->tipo = tipo; */
/* 	e->dado1 = dado1; */
/* 	e->dado2 = dado2; */

/* 	return e; */
/* } */

mundo_t *cria_mundo(lef_t *lista_de_eventos) {
	mundo_t *m;
	if ( !(m = malloc(sizeof(mundo_t))) )
		MEM_ERROR_EXIT;
	
	m->tempo_atual = 0;
	m->tamanho_mundo = 20000;
	m->fim_do_mundo = 34944;
	m->n_missoes = m->fim_do_mundo / 100;

	const int n_habilidades = 10;
	if ( !(m->cj_habilidades = cria_cjt(n_habilidades)) )
		MEM_ERROR_EXIT;
	int i;
	for (i = 0; i < n_habilidades; i++)
		insere_cjt(m->cj_habilidades, i);

	m->n_herois = n_habilidades * 5;
	m->n_locais = m->n_herois / 6;

	/* cria vetor de heróis e preenche */
	if ( !(m->herois = malloc(m->n_herois * sizeof(heroi_t))) )
		MEM_ERROR_EXIT;
	for (i = 0; i < m->n_herois; i++)
		m->herois[i] = inicializa_heroi(i, m->cj_habilidades);

	/* cria vetor de locais e preenche */
	if ( !(m->locais = malloc(m->n_locais * sizeof(local_t))) )
		MEM_ERROR_EXIT;
	for (i = 0; i < m->n_locais; i++)
		m->locais[i] = inicializa_local(i, m->tamanho_mundo);

	/* para cada herói, cria evento de chegada e insere na lef */
	/* evento_t *evento_chegada_heroi; */
	for (i = 0; i < m->n_herois; i++) {
		evento_t evento_chegada_heroi = { aleat(0, 96*7), CHEGADA, m->herois[i].id, aleat(0, m->n_locais-1) };
		adiciona_ordem_lef(lista_de_eventos, &evento_chegada_heroi);
	}

	/* para cada missao, cria evento e insere na lef */
	for (i = 0; i < m->n_missoes; i++) {
		evento_t evento_missao = { aleat(0, m->fim_do_mundo), MISSAO, i, 0 };
		adiciona_ordem_lef(lista_de_eventos, &evento_missao);
	}

	/* cria evento de fim e insere na lef */
	evento_t fim = { m->fim_do_mundo, FIM, 0, 0 };
	adiciona_ordem_lef(lista_de_eventos, &fim);

	return m;
}

int local_lotado(int id_local, mundo_t *m) {
	return cardinalidade_cjt(m->locais[id_local].publico) >= m->locais[id_local].lotacao_max;
}

int heroi_tem_paciencia(int id_heroi, int id_local, mundo_t *m) {
	return m->herois[id_heroi].paciencia/4 > tamanho_fila(m->locais[id_local].fila);
}

int velocidade_heroi(heroi_t heroi) {
	return 100 - max(0, heroi.idade - 40);
}

int main() {
	srand(time(0));

	lef_t *lista_de_eventos;
	if ( !(lista_de_eventos = cria_lef()) )
		MEM_ERROR_EXIT;

	mundo_t *mundo;
	mundo = cria_mundo(lista_de_eventos);
	printf("inicializou o mundo!\n");
	printf("tamanho do mundo: %d\n", mundo->tamanho_mundo);
	printf("conjunto de habilidades do mundo:\n");
	imprime_cjt(mundo->cj_habilidades);
	
	/* int i = 0; */
	/* nodo_lef_t *exemplo = lista_de_eventos->Primeiro; */
	/* printf("eventos alocados na lef:\n"); */
	/* while (exemplo) { */
	/* 	printf("tempo do evento %d: %d\n", i, exemplo->evento->tempo); */
	/* 	printf(" tipo do evento %d: ", i); */
	/* 	switch (exemplo->evento->tipo) { */
	/* 		case CHEGADA: */
	/* 			printf("CHEGADA\n"); */
	/* 			break; */
	/* 		case SAIDA: */
	/* 			printf("SAIDA\n"); */
	/* 			break; */
	/* 		case MISSAO: */
	/* 			printf("MISSAO\n"); */
	/* 			break; */
	/* 		case FIM: */
	/* 			printf("FIM\n"); */
	/* 			break; */
	/* 		default: */
	/* 			printf("deu alguma coisa muito louca aqui\n"); */
	/* 			break; */
	/* 	} */
	/* 	i++; */
	/* 	exemplo = exemplo->prox; */
	/* } */

	/* ciclo da simulação */
	evento_t *evento_atual;
	evento_atual = obtem_primeiro_lef(lista_de_eventos);
	mundo->tempo_atual = evento_atual->tempo;
	/* trata o evento e atualiza estado do sistema */

	/* TODO: criar função wrapper para tratar os eventos e poder criar variaveis temporarias */

	switch (evento_atual->tipo) {
		case CHEGADA:
			evento_t saida;
			int id_heroi = evento_atual->dado1;
			int id_local = evento_atual->dado2;
			if (local_lotado(id_local, mundo)) {
				if (heroi_tem_paciencia(id_heroi, id_local, mundo)) {
					insere_fila(mundo->locais[id_local].fila, id_heroi);
				} else {
					saida = { mundo->tempo_atual, SAIDA, id_heroi, id_local };
					adiciona_ordem_lef(lista_de_eventos, &saida);
				}
			} else {
				insere_cjt(mundo->locais[id_local].publico, id_heroi);
				int t_permanencia_local = max(1, mundo->herois[id_heroi].paciencia/10 + aleat(-2,6));
				saida = { mundo->tempo_atual + t_permanencia_local, SAIDA, id_heroi, id_local };
				adiciona_ordem_lef(lista_de_eventos, &saida);
			}
			break;

		case SAIDA:
			evento_t chegada_heroi, chegada_heroi_fila;
			int id_heroi = evento_atual->dado1;
			int id_local = evento_atual->dado2;
			if (pertence_cjt(mundo->locais[id_local].publico, id_heroi)) {
				retira_cjt (mundo->locais[id_local].publico, id_heroi);
				if (!vazia_fila(mundo->locais[id_local].fila)) {
					int id_heroi_fila;
					retira_fila(mundo->locais[id_local].fila, &id_heroi_fila);
					chegada_heroi_fila = { mundo->tempo_atual, CHEGADA, id_heroi_fila, id_local };
					adiciona_inicio_lef(lista_de_eventos, &chegada_heroi_fila);
				}
			}
			int id_local_destino = aleat(0, mundo->n_locais-1);
			int t_deslocamento = distancia(mundo->locais[id_local], mundo->locais[id_local_destino]) / velocidade_heroi(mundo->herois[id_heroi]);
			chegada_heroi = { mundo->tempo_atual + t_deslocamento/15, CHEGADA, id_heroi, id_local_destino };
			adiciona_ordem_lef(lista_de_eventos, &chegada_heroi);
			break;

		case MISSAO:
			break;
		case FIM:

			lista_de_eventos = destroi_lef(lista_de_eventos);
			break;
	}
	/* agenda os novos eventos na lista_de_eventos; */


	return 0;
}