#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

#define N 20 // BROJ VOZILA
#define GRANICA_VREME_DOLASKA 10
#define GRANICA_VREME_PROLASKA 5

typedef enum tipVozila {
    Automobil = 1,
    Autobus = 2,
    Kamion = 3,
    HitnoVozilo = 4
} TipVozila;

typedef struct {
    long broj_automobila;
    long broj_kamiona;
    long broj_autobusa;
    long broj_hitnih_vozila;
    long broj_hitnih_vozila_koji_cekaju[2];
    int trenutni_smer;
} TunelVozila;


TunelVozila tunel_vozila;
pthread_mutex_t mutex_tunel;
pthread_cond_t cond_tunel;

void tunel_vozila_init() {
    tunel_vozila.broj_autobusa = 0;
    tunel_vozila.broj_automobila = 0;
    tunel_vozila.broj_kamiona = 0;
    tunel_vozila.broj_hitnih_vozila = 0;
    tunel_vozila.broj_hitnih_vozila_koji_cekaju[0] = 0;
    tunel_vozila.broj_hitnih_vozila_koji_cekaju[1] = 0;
    tunel_vozila.trenutni_smer = -1;
}

int automobil_moze_u_tunel(TunelVozila tv, int smer_vozila) {
    if (tv.trenutni_smer != -1 && tv.trenutni_smer != smer_vozila) {
        return 0;
    }
    
    if (tv.broj_hitnih_vozila_koji_cekaju[smer_vozila] > 0) {
        return 0;
    }

    if (tv.broj_kamiona > 0) {
        return 0;
    }

    if (tv.broj_automobila >= 3) {
        return 0;
    }

    return 1;
}

int autobus_moze_u_tunel(TunelVozila tv, int smer_vozila) {
    if (tv.trenutni_smer != -1 && tv.trenutni_smer != smer_vozila) {
        return 0;
    }
    
    if (tv.broj_hitnih_vozila_koji_cekaju[smer_vozila] > 0) {
        return 0;
    }

    if (tv.broj_kamiona > 0) {
        return 0;
    }

    if (tv.broj_autobusa >= 1) {
        return 0;
    }

    return 1;
}

int kamion_moze_u_tunel(TunelVozila tv, int smer_vozila) {
    if (tv.trenutni_smer != -1 && tv.trenutni_smer != smer_vozila) {
        return 0;
    }
    
    if (tv.broj_hitnih_vozila_koji_cekaju[smer_vozila] > 0) {
        return 0;
    }

    if (tv.broj_autobusa != 0 || tv.broj_automobila != 0 || tv.broj_hitnih_vozila != 0 || tv.broj_kamiona != 0) {
        return 0;
    }

    return 1;
}

int hitno_vozilo_moze_u_tunel(TunelVozila tv, int smer_vozila) {
    if (tv.trenutni_smer != -1 && tv.trenutni_smer != smer_vozila) {
        return 0;
    }

    if (tv.broj_kamiona > 0) {
        return 0;
    }

    return 1;
}

int nema_vozila_u_tunelu(TunelVozila tv) {
    if (tv.broj_autobusa == 0 && 
    tv.broj_automobila == 0 &&
    tv.broj_hitnih_vozila == 0 &&
    tv.broj_kamiona == 0) {
        return 1;
    }

    return 0;
}

void* tunel(void* arg) {
    long id = (long) arg;
    int vreme_dolaska = rand() % GRANICA_VREME_DOLASKA + 1;
    int vreme_prolaska = rand() % GRANICA_VREME_PROLASKA + 1;

    TipVozila tip  = rand() % 4 + 1;
    int smer = 0;

    if (id <= N / 2) {
        smer = 1;
    }

    sleep(vreme_dolaska);

    printf("Vozilo (id: %ld, tip: %d, smer: %d) dolazi do tunela u trenutku %d\n", id, tip, smer, vreme_dolaska);

    pthread_mutex_lock(&mutex_tunel);

    if (tip == HitnoVozilo) {
        tunel_vozila.broj_hitnih_vozila_koji_cekaju[smer]++;
        while (hitno_vozilo_moze_u_tunel(tunel_vozila, smer) == 0)
            pthread_cond_wait(&cond_tunel, &mutex_tunel);
    }
    else if (tip == Automobil) {
        while (automobil_moze_u_tunel(tunel_vozila, smer) == 0)
            pthread_cond_wait(&cond_tunel, &mutex_tunel);
    }
    else if (tip == Autobus) {
        while (autobus_moze_u_tunel(tunel_vozila, smer) == 0)
            pthread_cond_wait(&cond_tunel, &mutex_tunel);
    }
    else {
        while (kamion_moze_u_tunel(tunel_vozila, smer) == 0)
            pthread_cond_wait(&cond_tunel, &mutex_tunel);
    }
    
    if (nema_vozila_u_tunelu(tunel_vozila) == 1) {
        tunel_vozila.trenutni_smer = smer;
    }

    if (tip == HitnoVozilo) {
        tunel_vozila.broj_hitnih_vozila_koji_cekaju[smer]--;
        tunel_vozila.broj_hitnih_vozila++;
    }
    else if (tip == Automobil) {
        tunel_vozila.broj_automobila++;        
    }
    else if (tip == Autobus) {
        tunel_vozila.broj_autobusa++;
    }
    else {
        tunel_vozila.broj_kamiona++;
    }

    printf("Vozilo (id: %ld, tip: %d, smer: %d) ulazi u tunel.\n", id, tip, smer);

    pthread_mutex_unlock(&mutex_tunel);

    sleep(vreme_prolaska);

    pthread_mutex_lock(&mutex_tunel);

    if (tip == HitnoVozilo) {
        tunel_vozila.broj_hitnih_vozila--;
    }
    else if (tip == Automobil) {
        tunel_vozila.broj_automobila--;        
    }
    else if (tip == Autobus) {
        tunel_vozila.broj_autobusa--;
    }
    else {
        tunel_vozila.broj_kamiona--;
    }

    if (nema_vozila_u_tunelu(tunel_vozila) == 1) {
        tunel_vozila.trenutni_smer = -1;
    }

    printf("Vozilo (id: %ld, tip: %d, smer: %d) izlazi iz tunela nakon %d sekundi.\n", id, tip, smer, vreme_prolaska);

    pthread_cond_broadcast(&cond_tunel);
    pthread_mutex_unlock(&mutex_tunel);

    return NULL;
}

int main() {
    srand(time(NULL));
    
    pthread_t threads[N];

    tunel_vozila_init();
    pthread_mutex_init(&mutex_tunel, NULL);
    pthread_cond_init(&cond_tunel, NULL);

    for (long i = 0; i < N; i++) {
        pthread_create(&threads[i], NULL, tunel, (void*)(i + 1));
    }

    for (long i = 0; i < N; i++) {
        pthread_join(threads[i], NULL);
    }

    pthread_mutex_destroy(&mutex_tunel);
    pthread_cond_destroy(&cond_tunel);

    return 0;
}