/*
 * Assignment 4 of Operating Systems: PThread simulation.
 *
 * Sander van Veen (6167969) and Taddeus Kroes (6054129).
 * <sandervv@gmail.com>      and <taddeuskroes@hotmail.com>.
 *
 * Submission date: 21 november 2010.
 */

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

#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>

#include "main.h"

int forks_len;
pthread_mutex_t *forks;
pthread_cond_t wait_threshold;
pthread_barrier_t wait_barrier;

int diner_finished = 0;

/*
 * Release fork taken by a philosopher.
 */
    static inline void release_fork(diner_stats *stats, int f) {
        if(diner_finished)
            return;

        // Unlock left or right fork.
        stats->locked ^= 1 << (f % 2);
        pthread_mutex_unlock(forks+f);
    }

/*
 * Try to claim a fork, but do not wait if the fork cannot be claimed.
 * This call is therefore non-blocking.
 */
static inline int try_take_fork(diner_stats *stats, int f) {
    // Try to lock left or right fork.
    if(diner_finished || !pthread_mutex_trylock(forks+f))
        return 1;

    stats->locked |= 1 << (f % 2);
    stats->forks++;

    return 0;
}

/*
 * Claim a fork, and wait (blocking) if the fork is already taken.
 */
    static inline void take_fork(diner_stats *stats, int f) {
        if(diner_finished)
            return;

        // Lock left or right fork.
        pthread_mutex_lock(forks+f);
        stats->locked |= 1 << (f % 2);
        stats->forks++;
    }

#define PHILO_EATING \
    if(diner_finished) \
return 1; \
\
stats->meals++;

static const char *philo_type_names[] = {"left", "right", "optimistic", "shy",
    "random"};
static const philo_t philo_types[] = {&philo_left, &philo_right, &philo_shy, \
    &philo_optimistic, &philo_random};

static const int philo_type_count = sizeof(philo_types) / sizeof(philo_t);

/*
 * Philosopher favoring the left fork above the right fork. He will wait for
 * both forks, and wouldn't release the first while he is waiting on the second.
 */
int philo_left(diner_stats *stats, int id) {
    // Let the philosopher take two forks (first left, then right).
    take_fork(stats, id);
    take_fork(stats, (id+1) % forks_len);

    PHILO_EATING;

    // Release both forks.
    release_fork(stats, (id+1) % forks_len);
    release_fork(stats, id);

    return 0;
}

/*
 * Philosopher favoring the right fork above the left fork. He will wait for
 * both forks, and wouldn't release the first while he is waiting on the second.
 */
int philo_right(diner_stats *stats, int id) {
    // Let the philosopher take two forks (first right, then left).
    take_fork(stats, (id+1) % forks_len);
    take_fork(stats, id);

    PHILO_EATING;

    // Release both forks.
    release_fork(stats, id);
    release_fork(stats, (id+1) % forks_len);

    return 0;
}

/*
 * Philosopher with optimistic behaviour: try to take the left or right fork,
 * and if one fork is taken succesfully, wait till the second fork is released.
 */
int philo_optimistic(diner_stats *stats, int id) {
    // Let the philosopher take the left fork of the two forks.
    if(!try_take_fork(stats, id)) {
        if(diner_finished)
            return 1;

        // Left fork is already taken, so try to take the right fork.
        if(!try_take_fork(stats, (id+1) % forks_len)) {
            if(diner_finished)
                return 1;
            // No forks available, be faster next time!
            sched_yield();
        }
        else {
            // Grabbed the right fork successfully.
            if(diner_finished)
                return 1;

            take_fork(stats, id);

            PHILO_EATING;

            // Release both forks.
            release_fork(stats, id);
            release_fork(stats, (id+1) % forks_len);
        }
    }
    else {
        // Grabbed the left fork successfully.
        take_fork(stats, (id+1) % forks_len);

        PHILO_EATING;

        // Release both forks.
        release_fork(stats, id);
        release_fork(stats, (id+1) % forks_len);
    }

    return 0;
}

/*
 * Philosopher with shy behaviour: try again next time, when taking one of the
 * forks fails.
 */
int philo_shy(diner_stats *stats, int id) {
    // Try to take the right fork.
    if(!try_take_fork(stats, (id+1) % forks_len)) {
        if(diner_finished)
            return 1;

        // Failed to take fork, so try again next time.
        sched_yield();
    }
    // Let the philosopher try to take the left fork.
    else if(!try_take_fork(stats, id)) {
        if(diner_finished)
            return 1;

        // Release the right fork and try again next time.
        release_fork(stats, (id+1) % forks_len);
        sched_yield();
    }
    else {
        // Grabbed the left and right fork successfully.
        PHILO_EATING;

        // Release both forks.
        release_fork(stats, id);
        release_fork(stats, (id+1) % forks_len);
    }

    return 0;
}

/*
 * Philosopher with random behaviour: change each iteration to one of the other
 * philosopher behaviours.
 */
int philo_random(diner_stats *stats, int id) {
    // Pick a behaviour randomly (except for the "random" behaviour, since that
    // would be recursive and thus useless to simulate).
    return (*philo_types[rand() % (philo_type_count - 1)])(stats, id);
}

void *philo_start(void *raw_stats) {
    diner_stats *stats = (diner_stats *) raw_stats;
    int id = stats->id,
        type = rand() % philo_type_count;

    printf("P #%u: hello everybody, i'm %s!\n", id, philo_type_names[type]);

    // Philosophers wait for each other, before they can start eating diner.
    pthread_barrier_wait(&wait_barrier);

    printf("P #%u: let's eating!\n", id);

    while(!diner_finished && !(*philo_types[type])(stats, id));

    printf("P #%u: i'm leaving, after paying the bill.\n", id);

    pthread_exit(NULL);
}

void host_start(int philos) {
    if( philos < 2 ) {
        fprintf(stderr, "Invalid philosopher count.\n");
        exit(EXIT_FAILURE);
    }

    pthread_attr_t attr;
    pthread_t *threads = malloc(philos * sizeof(pthread_t));
    forks = malloc(philos * sizeof(pthread_mutex_t));
    diner_stats *stats = malloc(philos * sizeof(diner_stats));
    forks_len = philos;

    void *status;
    int rc;

    // POSIX standard specifies that threads are joinable by default, but
    // unfortunately, not all pthread implementations set threads as joinable by
    // default. Therefore, set the thread's detach state.
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    // Let all philosophers wait before they can eat diner.
    pthread_barrier_init(&wait_barrier, NULL, philos);

    // Create the cutlery (mutexes) and initialise the statistics.
    for(int i = 0; i < philos; i++) {
        pthread_mutex_init(&forks[i], NULL);
        stats[i].meals = 0;
        stats[i].forks = 0;
        stats[i].id = i;
        stats[i].locked = 0;
    }

    // Invite the philosophers.
    for(int i = 0; i < philos; i++) {
        printf("Host: inviting philo #%d\n", i);

        rc = pthread_create(threads+i, &attr, philo_start, (void*)&stats[i]);

        if(rc) {
            fprintf(stderr, "pthread_create() returned: %d\n", rc);
            exit(-1);
        }
    }

    puts("Host: invitations done.");

    struct timeval wct;
    gettimeofday(&wct, NULL);
    double start_time = wct.tv_sec + wct.tv_usec / 1e6;

    // Duration of the diner party is 10 seconds (see main.h). This loop will
    // prevent the philosphers from exceeding this time limit.
    do {
        // Check each 10 ms for deadlock.
        usleep(10000);

        // If an deadlock occured (all philosophers are waiting on their left or
        // right fork), finish the diner party immidiately. It is unclear in the
        // assignment, if the philosophers should leave the diner party, or if
        // they only had to release their claimed forks. I choose the first
        // assumption: leave the party. If they start arguing which fork is
        // theirs, there's no party anymore.
        int p;
        for(p = 0; p < philos && (stats[p].locked & 1
                    || stats[p].locked & 2); p++);

        if( p == philos ) {
            puts("Host: deadlock occured.");
            diner_finished = 1;

            break;
        }

        gettimeofday(&wct, NULL);
    } while(wct.tv_sec + wct.tv_usec / 1e6 - start_time < TIME_LIMIT);

    diner_finished = 1;

    puts("Host: diner is finished.");

    for(int i = 0; i < philos; i++) {
        // Reclaim all cutlery (mutexes) in reverse order.
        for(int p = philos; p >= i; p--)
            pthread_mutex_unlock(&forks[p]);

        // Wait on the other philosophers.
        if( (rc = pthread_join(threads[i], &status)) ) {
            fprintf(stderr, "return code from pthread_join() is %d\n", rc);
            exit(EXIT_FAILURE);
        }

        printf("Philo #%d ate %d meal(s) and grabbed %d fork(s).\n",
                i, stats[i].meals, stats[i].forks);

        pthread_mutex_destroy(&forks[i]);
    }

    puts("Host: philosophers are done.");

    pthread_barrier_destroy(&wait_barrier);
    pthread_attr_destroy(&attr);

    pthread_exit(NULL);
}

/*
 * Start the philosopher diner problem simulation. By default two philosophers
 * are simulated. The first commandline argument changes the total philosophers
 * being simulated. If the argument is less than two, the philosopher count is
 * set to the default count of two.
 */
int main(int argc, const char **argv) {
    // Get system clock's milliseconds and set it as seed to ensure a
    // more "pseudo-random" simulation.
    struct timeval wtc;
    gettimeofday(&wtc, NULL);
    srand(wtc.tv_usec);

    host_start( argc > 1 ? atoi(argv[1]) : 2 );
}

