#include <stdio.h>
#include <pthread.h>  // Biblioteca para suporte a threads em C.
#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
#include <time.h>  // Biblioteca para manipulação de tempo.
#include <Windows.h>

#define NUM_CARROS 120 // Define o número de carros na simulação.
#define NUM_CAMINHOES 10 // Define o número de caminhões na simulação.
#define MAX_TEMPO_CHEGADA 3 // Define o tempo máximo de chegada dos veículos.
#define TEMPO_TRAVESSIA_CARRO 5 // Define o tempo de travessia de um carro.
#define TEMPO_TRAVESSIA_CAMINHAO 10 // Define o tempo de travessia de um caminhão.
#define TEMPO_TRAVESSIA_CARRO_ANTIGA 4 // Define o tempo de travessia de um carro na ponte antiga.
#define TEMPO_ESPACO 1 // Define o intervalo entre os veículos.
#define TEMPO_ESPACO_ANTIGA 2 // Define o intervalo entre os veículos na ponte antiga.
#define MAX_CARROS_PONTE 5 // Define o número máximo de carros que podem estar na ponte ao mesmo tempo.
#define MAX_CARROS_PONTE_ANTIGA 2 // Define o número máximo de carros que podem estar na ponte antiga ao mesmo tempo.
#define MAX_CAMINHAO_PONTE 1 // Define o número máximo de caminhões que podem estar na ponte ao mesmo tempo.

// Inicialização de mutex e variáveis de condição para controle de concorrência.
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_carro[2] = {PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER};
pthread_cond_t cond_caminhao[2] = {PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER};
pthread_cond_t cond_carro_antiga[2] = {PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER};

// Declaração de variáveis globais para controlar o estado da ponte e coletar estatísticas.
int carros_na_ponte = 0;
int caminhoes_na_ponte = 0;
int carros_na_ponte_antiga = 0;
int direcao_na_ponte = -1; // -1: ninguém na ponte, 0: direção 0, 1: direção 1
int direcao_na_ponte_antiga = -1; // -1: ninguém na ponte, 0: direção 0, 1: direção 1

// Variáveis para rastrear a quantidade de veículos antes de trocar o sentido.
int contador_carros_mesmo_sentido = 0;
int contador_caminhoes_mesmo_sentido = 0;
int contador_carros_mesmo_sentido_antiga = 0;

// Estatísticas de carros.
int tempo_max_espera_carros = 0;
int tempo_min_espera_carros = INT_MAX;
double tempo_total_espera_carros = 0;
int carros_por_direcao[2] = {0, 0};

// Estatísticas de caminhões.
int tempo_max_espera_caminhoes = 0;
int tempo_min_espera_caminhoes = INT_MAX;
double tempo_total_espera_caminhoes = 0;
int caminhoes_por_direcao[2] = {0, 0};

// Estatísticas da ponte.
int tempo_total_execucao;
int tempo_total_ocupado = 0;
int ponte_ocupada = -1;

// Estatísticas da ponte antiga.
int tempo_total_ocupado_antiga = 0;
int ponte_antiga_ocupada = -1;

// Função que será executada por cada thread (carro).
void *carro(void *arg) {
    int id = *((int *)arg);
    int minha_direcao = id % 2; // A direção é determinada pelo id do carro.

    // O carro espera um tempo aleatório antes de tentar atravessar a ponte.
    sleep(rand() % MAX_TEMPO_CHEGADA + 1);

    pthread_mutex_lock(&mutex); // Adquire o lock antes de manipular os recursos compartilhados.

    int inicio_espera = time(NULL); // Registra o tempo de início da espera.

    // Tentar a ponte principal.
    while (direcao_na_ponte != -1 && (direcao_na_ponte != minha_direcao || carros_na_ponte == MAX_CARROS_PONTE || contador_carros_mesmo_sentido == MAX_CARROS_PONTE)) {
        // Se não for possível usar a ponte principal, tentar a ponte antiga.
        if (direcao_na_ponte_antiga == -1 || direcao_na_ponte_antiga == minha_direcao && carros_na_ponte_antiga < MAX_CARROS_PONTE_ANTIGA && contador_carros_mesmo_sentido_antiga < MAX_CARROS_PONTE_ANTIGA) {
            if (direcao_na_ponte_antiga == -1 || contador_carros_mesmo_sentido_antiga == MAX_CARROS_PONTE_ANTIGA) {
                direcao_na_ponte_antiga = minha_direcao;
                contador_carros_mesmo_sentido_antiga = 0;
                if (ponte_antiga_ocupada == -1) {
                    ponte_antiga_ocupada = time(NULL); // Registra o tempo em que a ponte antiga começou a ser ocupada.
                }
            }
            // Atualiza o estado da ponte antiga.
            carros_na_ponte_antiga++;
            contador_carros_mesmo_sentido_antiga++;
            carros_por_direcao[minha_direcao]++;

            printf("Carro %d está atravessando na direção %d na ponte antiga\n", id, minha_direcao); // Imprime que o carro começou a atravessar a ponte antiga.

            sleep(TEMPO_ESPACO_ANTIGA);  // Adicionado espaço entre carros na ponte antiga.

            pthread_mutex_unlock(&mutex); // Libera o lock após manipular os recursos compartilhados.

            // Simula o tempo que o carro leva para atravessar a ponte antiga.
            sleep(TEMPO_TRAVESSIA_CARRO_ANTIGA);

            pthread_mutex_lock(&mutex); // Adquire o lock novamente para atualizar o estado da ponte.

            // Imprime que o carro terminou de atravessar a ponte antiga.
            printf("Carro %d terminou de atravessar na direção %d na ponte antiga\n", id, minha_direcao);

            carros_na_ponte_antiga--; // Decrementa o número de carros na ponte antiga.

            // Caso a ponte antiga esteja vazia, notifica os carros.
            if (carros_na_ponte_antiga == 0) {
                direcao_na_ponte_antiga = -1;
                tempo_total_ocupado_antiga += time(NULL) - ponte_antiga_ocupada;
                ponte_antiga_ocupada = -1;
                pthread_cond_broadcast(&cond_carro_antiga[!minha_direcao]);
                pthread_cond_broadcast(&cond_carro[!minha_direcao]);
            } else {
                pthread_cond_signal(&cond_carro_antiga[minha_direcao]);
                pthread_cond_signal(&cond_carro[minha_direcao]);
            }

            // Libera o lock e a memória alocada para o carro que atravessou na ponte antiga.
            pthread_mutex_unlock(&mutex);
            free(arg);
            return NULL;
        } else {
            pthread_cond_wait(&cond_carro[minha_direcao], &mutex); // Caso não consiga passar na antiga, aguarda na principal.
        }
    }


    int fim_espera = time(NULL); // Registra o tempo de fim da espera.
    int tempo_espera = fim_espera - inicio_espera; // Calcula o tempo total de espera.

    // Atualiza as estatísticas de tempo de espera.
    tempo_min_espera_carros = tempo_espera < tempo_min_espera_carros ? tempo_espera : tempo_min_espera_carros;
    tempo_max_espera_carros = tempo_espera > tempo_max_espera_carros ? tempo_espera : tempo_max_espera_carros;
    tempo_total_espera_carros += tempo_espera;

    // Se 5 carros já passaram e a ponte estiver vazia, define a direção do tráfego.
    if (direcao_na_ponte == -1 || contador_carros_mesmo_sentido == MAX_CARROS_PONTE) {
        direcao_na_ponte = minha_direcao;
        contador_carros_mesmo_sentido = 0;
        if (ponte_ocupada == -1) {
            ponte_ocupada = time(NULL); // Registra o tempo em que a ponte começou a ser ocupada.
        }
    }

    // Atualiza o estado da ponte.
    carros_na_ponte++;
    contador_carros_mesmo_sentido++;
    carros_por_direcao[minha_direcao]++;

    printf("Carro %d está atravessando na direção %d na ponte principal\n", id, minha_direcao); // Imprime que o carro começou a atravessar a ponte.

    sleep(TEMPO_ESPACO);  // Espera um intervalo antes de permitir o próximo carro na ponte principal.

    pthread_mutex_unlock(&mutex); // Libera o lock após manipular os recursos compartilhados.

    // Simula o tempo que o carro leva para atravessar a ponte principal.
    sleep(TEMPO_TRAVESSIA_CARRO);

    pthread_mutex_lock(&mutex); // Adquire o lock novamente para atualizar o estado da ponte.

    // Imprime que o carro terminou de atravessar a ponte principal.
    printf("Carro %d terminou de atravessar na direção %d na ponte principal\n", id, minha_direcao);

    carros_na_ponte--; // Decrementa o número de carros na ponte principal.

    // Se a ponte estiver vazia, notifica veículos da direção oposta. Caso contrário, notifica veículos da mesma direção.
    if (carros_na_ponte == 0 && caminhoes_na_ponte == 0) {
        direcao_na_ponte = -1;
        tempo_total_ocupado += time(NULL) - ponte_ocupada;
        ponte_ocupada = -1;
        pthread_cond_broadcast(&cond_carro[!minha_direcao]);
        pthread_cond_broadcast(&cond_caminhao[!minha_direcao]);
    } else {
        pthread_cond_signal(&cond_carro[minha_direcao]);
        pthread_cond_signal(&cond_caminhao[minha_direcao]);
    }

    // Libera o lock e a memória alocada para o id do carro.
    pthread_mutex_unlock(&mutex);
    free(arg);

    return NULL;
}

// Função que será executada por cada thread (caminhão).
void *caminhao(void *arg) {
    int id = *((int *)arg);
    int minha_direcao = id % 2; // A direção é determinada pelo id do caminhão.

    // O caminhão espera um tempo aleatório antes de tentar atravessar a ponte.
    sleep(rand() % MAX_TEMPO_CHEGADA + 1);

    pthread_mutex_lock(&mutex); // Adquire o lock antes de manipular os recursos compartilhados.

    int inicio_espera = time(NULL); // Registra o tempo de início da espera.

    // Espera até que a ponte esteja vazia, ou a ponte esteja na mesma direção e haja espaço suficiente na ponte, ou até que 5 carros ou 1 caminhão atravessem.
    while (direcao_na_ponte != -1 && (direcao_na_ponte != minha_direcao || carros_na_ponte > 0 || caminhoes_na_ponte == MAX_CAMINHAO_PONTE || contador_caminhoes_mesmo_sentido == MAX_CAMINHAO_PONTE)) {
        pthread_cond_wait(&cond_caminhao[minha_direcao], &mutex);
    }

    int fim_espera = time(NULL); // Registra o tempo de fim da espera.
    int tempo_espera = fim_espera - inicio_espera; // Calcula o tempo total de espera.

    // Atualiza as estatísticas de tempo de espera.
    tempo_min_espera_caminhoes = tempo_espera < tempo_min_espera_caminhoes ? tempo_espera : tempo_min_espera_caminhoes;
    tempo_max_espera_caminhoes = tempo_espera > tempo_max_espera_caminhoes ? tempo_espera : tempo_max_espera_caminhoes;
    tempo_total_espera_caminhoes += tempo_espera;

    //Se 1 caminhão passou e a ponte estiver vazia, define a direção do tráfego.
    if (direcao_na_ponte == -1 || contador_caminhoes_mesmo_sentido == MAX_CAMINHAO_PONTE) {
        direcao_na_ponte = minha_direcao;
        contador_caminhoes_mesmo_sentido = 0;
        if (ponte_ocupada == -1) {
            ponte_ocupada = time(NULL); // Registra o tempo em que a ponte começou a ser ocupada.
        }
    }

    // Atualiza o estado da ponte.
    caminhoes_na_ponte++;
    contador_caminhoes_mesmo_sentido++;
    caminhoes_por_direcao[minha_direcao]++;

    printf("Caminhao %d está atravessando na direção %d\n", id, minha_direcao); // Imprime que o caminhão começou a atravessar a ponte.

    sleep(TEMPO_ESPACO); // Espera um intervalo antes de permitir o próximo caminhão.

    pthread_mutex_unlock(&mutex); // Libera o lock após manipular os recursos compartilhados.

    // Simula o tempo que o caminhão leva para atravessar a ponte.
    sleep(TEMPO_TRAVESSIA_CAMINHAO);

    pthread_mutex_lock(&mutex); // Adquire o lock novamente para atualizar o estado da ponte.

    // Imprime que o caminhão terminou de atravessar a ponte.
    printf("Caminhao %d terminou de atravessar na direção %d\n", id, minha_direcao);

    caminhoes_na_ponte--; // Decrementa o número de caminhões na ponte.

    // Se a ponte estiver vazia, notifica veículos da direção oposta. Caso contrário, notifica veículos da mesma direção.
    if (carros_na_ponte == 0 && caminhoes_na_ponte == 0) {
        direcao_na_ponte = -1;
        tempo_total_ocupado += time(NULL) - ponte_ocupada; // Atualiza o tempo total que a ponte principal foi ocupada.
        ponte_ocupada = -1;
        pthread_cond_broadcast(&cond_carro[!minha_direcao]);
        pthread_cond_broadcast(&cond_caminhao[!minha_direcao]);
    } else {
        pthread_cond_signal(&cond_caminhao[minha_direcao]);
        pthread_cond_signal(&cond_carro[minha_direcao]);
    }

    // Libera o lock e a memória alocada para o id do caminhão.
    pthread_mutex_unlock(&mutex);
    free(arg);

    return NULL;
}

// Função principal que inicializa e gerencia a simulação.
int main() {
    SetConsoleOutputCP(CP_UTF8); // Define o conjunto de caracteres de saída do console para UTF-8.
    pthread_t threads_carros[NUM_CARROS]; // Declara um array de threads para cada carro.
    pthread_t threads_caminhoes[NUM_CAMINHOES]; // Declara um array de threads para cada caminhão.

    int inicio_execucao = time(NULL); // Registra o tempo de início da execução.

    // Cria as threads de carros.
    for (int i = 0; i < NUM_CARROS; i++) {
        int *id = malloc(sizeof(int));
        *id = i;
        pthread_create(&threads_carros[i], NULL, carro, id);
    }

    // Cria as threads de caminhões.
    for (int i = 0; i < NUM_CAMINHOES; i++) {
        int *id = malloc(sizeof(int));
        *id = i;
        pthread_create(&threads_caminhoes[i], NULL, caminhao, id);
    }

    // Espera todas as threads dos veículos terminarem.
    for (int i = 0; i < NUM_CARROS; i++) {
        pthread_join(threads_carros[i], NULL);
    }
    for (int i = 0; i < NUM_CAMINHOES; i++) {
        pthread_join(threads_caminhoes[i], NULL);
    }

    int fim_execucao = time(NULL); // Registra o tempo de fim da execução.

    // Calcula o tempo total de execução.
    tempo_total_execucao = fim_execucao - inicio_execucao;

    // Imprime as estatísticas coletadas.
    printf("\n----------------------------------------------------------------");
    printf("\n|                      Estatísticas                            |\n");
    printf("|                                                              |");
    printf("\n| 🚗 Quantidade de Carros que cruzaram cada sentido: %d, %d    |\n", carros_por_direcao[0], carros_por_direcao[1]);
    printf("| 🚗 Tempo mínimo de espera para Carros: %d s                   |\n", tempo_min_espera_carros);
    printf("| 🚗 Tempo máximo de espera para Carros: %d s                 |\n", tempo_max_espera_carros);
    printf("| 🚗 Tempo médio de espera para Carros: %0.2f s                |\n", tempo_total_espera_carros / NUM_CARROS);
    printf("|                                                              |");
    printf("\n| 🚚 Quantidade de Caminhões que cruzaram cada sentido: %d, %d   |\n", caminhoes_por_direcao[0], caminhoes_por_direcao[1]);
    printf("| 🚚 Tempo mínimo de espera para Caminhões: %02d s               |\n", tempo_min_espera_caminhoes);
    printf("| 🚚 Tempo máximo de espera para Caminhões: %d s              |\n", tempo_max_espera_caminhoes);
    printf("| 🚚 Tempo médio de espera para Caminhões: %0.2f s            |\n", tempo_total_espera_caminhoes / NUM_CAMINHOES);
    printf("|                                                              |");
    printf("\n| ⌚ Eficiência da ponte: %0.2f%%                               |\n", ((double)tempo_total_ocupado / tempo_total_execucao) * 100);
    printf("| ⌛ Eficiência da ponte antiga: %0.2f%%                        |\n", ((double)tempo_total_ocupado_antiga / tempo_total_execucao) * 100);
    printf("|                                                              |\n");
    printf("----------------------------------------------------------------\n");

    return 0;
}