#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>

#define SIZE 16

void check_errno(int error) {
    if (error<0) {
        perror("Error");
        exit(EXIT_FAILURE);
    }
}

void check(int error) {
    if (error!=0) {
        fprintf(stderr, "Error: %s\n", strerror(error));
        exit(EXIT_FAILURE);
    }
}

char buffer[SIZE];          // buffer voor het opslaan van SIZE char's
int indexGet;               // index waar het volgende element uit de buffer gelezen wordt
int indexPut;               // index waar het volgende element in de buffer geschreven wordt
sem_t semMutualExclusive;   // binaire semaphore: zorgt voor wederzijdse uitsluiting
sem_t semEmpty;             // counting semaphore: telt aantal lege plaatsen
sem_t semFilled;            // counting semaphore: telt aantal gevulde plaatsen
sem_t semPrintf;            // binaire semaphore: zorgt voor mutual excusive gebruik van printf (1 thread tegelijk).

void put(const char c) {
    check_errno( sem_wait(&semEmpty) );             // verlaag het aantal lege plaatsen. WACHT als er geen lege plaatsen zijn!
    check_errno( sem_wait(&semMutualExclusive) );   // ga critisch gebied binnen
    buffer[indexPut++]=c;
    indexPut&=SIZE-1;                               // slim maar werkt alleen als SIZE een macht van 2 is
    check_errno( sem_post(&semMutualExclusive) );   // verlaat critische gebied
    check_errno( sem_post(&semFilled) );            //  verhoog het aantal gevulde plaatsen.
}

char get(void) {
    char c;
    check_errno( sem_wait(&semFilled) );            // verlaag het aantal gevulde plaatsen. WACHT als er geen gevulde plaatsen zijn!
    check_errno( sem_wait(&semMutualExclusive) );   // ga critische gebied binnen
    c=buffer[indexGet++];
    indexGet&=SIZE-1;                               // slim maar werkt alleen als SIZE een macht van 2 is
    check_errno( sem_post(&semMutualExclusive) );   // verlaat critische gebied
    check_errno( sem_post(&semEmpty) );             // verhoog het aantal lege plaatsen
    return c;
}

void* producer(void* arg) { // functie voor producer thread
    char c=*(char*)arg; 
    int i;
    check_errno( sem_wait(&semPrintf) );
    check_errno( printf("Thread: %d met arg: %c gestart\n", pthread_self(), c) );
    check_errno( sem_post(&semPrintf) );
    for (i=0; i<1000; ++i) {
        put(c);
    }
    check_errno( sem_wait(&semPrintf) );
    check_errno( printf("Thread: %d gestopt\n", pthread_self()) );
    check_errno( sem_post(&semPrintf) );
}

void* consumer(void* arg) { // functie voor consumer thread
    int i;
    char c;
    check_errno( sem_wait(&semPrintf) );
    check_errno( printf("Thread: %d gestart\n", pthread_self()) );
    check_errno( sem_post(&semPrintf) );
    for (i=0; i<2000; ++i) {
        c=get();
        check_errno( sem_wait(&semPrintf) );
        check_errno( putchar(c) );
        check_errno( sem_post(&semPrintf) );
    }
    check_errno( sem_wait(&semPrintf) );
    check_errno( printf("Thread: %d gestopt\n", pthread_self()) );
    check_errno( sem_post(&semPrintf) );
}

int main(int argc, char *argv[]) {
    struct sched_param sp, spc, spp1, spp2;
    int p, prioc, priop1, priop2;
    pthread_attr_t ptac, ptap1, ptap2;
    pthread_t ptp1, ptp2, ptc;
    char frikadel='F', kroket='K';

    // De main thread moet de hoogste prioriteit hebben omdat deze thread de andere threads start en 
    // we willen de onderlinge wisselwerking van deze threads bestuderen!
    check( pthread_getschedparam(pthread_self(), &p, &sp) );
    sp.sched_priority=60;
    check( pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp) );

    if (argc!=4) {
        check_errno( fprintf(stderr, "Usage: opdr1.out <Priority Consumer> <Priority Frikadel Producer> <Priority Kroket Producer>\n") );
        exit (-1);
    }

    prioc=atoi(argv[1]);
    priop1=atoi(argv[2]);
    priop2=atoi(argv[3]);

    check_errno( printf("Output voor Consumer priority = %d ", prioc) );
    check_errno( printf("Frikadel Producer priority = %d ", priop1) );
    check_errno( printf("Kroket Producer priority = %d\n", priop2) );

    check_errno( sem_init(&semPrintf, 0, 1) );
    check_errno( sem_init(&semMutualExclusive, 0, 1) ); // 1 thread tegelijk toelaten in critische gebied
    check_errno( sem_init(&semEmpty, 0, SIZE) );        // er zijn SIZE lege plaatsen
    check_errno( sem_init(&semFilled, 0, 0) );          // er zijn 0 gevulde plaatsen

    check( pthread_attr_init(&ptac) );
    check( pthread_attr_init(&ptap1) );
    check( pthread_attr_init(&ptap2) );

    check( pthread_attr_setinheritsched(&ptac, PTHREAD_EXPLICIT_SCHED) );
    check( pthread_attr_setinheritsched(&ptap1, PTHREAD_EXPLICIT_SCHED) );
    check( pthread_attr_setinheritsched(&ptap2, PTHREAD_EXPLICIT_SCHED) );

    check( pthread_attr_setschedpolicy(&ptac, SCHED_FIFO) );
    check( pthread_attr_setschedpolicy(&ptap1, SCHED_FIFO) );
    check( pthread_attr_setschedpolicy(&ptap2, SCHED_FIFO) );

    check( pthread_attr_getschedparam(&ptac, &spc) );
    check( pthread_attr_getschedparam(&ptap1, &spp1) );
    check( pthread_attr_getschedparam(&ptap2, &spp2) );

    spc.sched_priority=prioc;
    spp1.sched_priority=priop1;
    spp2.sched_priority=priop2;

    check( pthread_attr_setschedparam(&ptac, &spc) );
    check( pthread_attr_setschedparam(&ptap1, &spp1) );
    check( pthread_attr_setschedparam(&ptap2, &spp2) );

    check( pthread_create(&ptc, &ptac, consumer, 0) );
    check( pthread_create(&ptp1, &ptap1, producer, &frikadel) );
    check( pthread_create(&ptp2, &ptap2, producer, &kroket) );

    check( pthread_join(ptc, 0) );
    check( pthread_join(ptp1, 0) );
    check( pthread_join(ptp2, 0) );

    check_errno( sem_destroy(&semPrintf) );
    check_errno( sem_destroy(&semMutualExclusive) );
    check_errno( sem_destroy(&semEmpty) );
    check_errno( sem_destroy(&semFilled) );

    check( pthread_attr_destroy(&ptac) );
    check( pthread_attr_destroy(&ptap1) );
    check( pthread_attr_destroy(&ptap2) );

    return EXIT_SUCCESS;
}
