#include <pthread.h>
#include <stdio.h>
#include <x86/inline.h>

static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;

static int
random_l()
{
    int i;
    pthread_mutex_lock(&rand_mutex);
    i = rand();
    pthread_mutex_unlock(&rand_mutex);
    return i;
}

static pthread_mutex_t print_mutex;
#define PRINTMSG(x) do { pthread_mutex_lock(&print_mutex); x; pthread_mutex_unlock(&print_mutex); } while (0)

#define N_DINERS 5

static int room_occupancy;
static pthread_mutex_t chopsticks[N_DINERS];
static pthread_mutex_t room_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t room_cond = PTHREAD_COND_INITIALIZER;

typedef struct philo {
    int in_room;
    pthread_t tid;
} philo_t;

static philo_t philosophers[N_DINERS];

int
philosopher_thread(void *arg)
{
    int id = *(int *)arg;
    int l, r;
    int count;

    breakpoint();

    free(arg);

    l = id;
    r = l+1;

    if (r == N_DINERS) r = 0;

    if (l & 1) {
	int t = l;
	l = r;
	r = t;
    }

    PRINTMSG((fprintf(stderr, "Philosoper #%d has entered the room\n", id)));

    count = random_l() % 10 + 1;

    while (count--) {
	pthread_mutex_lock(&chopsticks[l]);
	pthread_mutex_lock(&chopsticks[r]);
	PRINTMSG((fprintf(stderr, "Philosoper #%d is eating spaghetti now\n", id)));
	sleep(random_l()%2);
	pthread_mutex_unlock(&chopsticks[r]);
	pthread_mutex_unlock(&chopsticks[l]);
	PRINTMSG((fprintf(stderr, "Philosoper #%d is pondering about life\n", id)));
	sleep(random_l()%2);

    }

    pthread_mutex_lock(&room_mutex);
    room_occupancy--;
    philosophers[id].in_room = 0;
    pthread_cond_signal(&room_cond);
    pthread_mutex_unlock(&room_mutex);

    PRINTMSG((fprintf(stderr, "Philosoper #%d has left the room\n", id)));
    return NULL;
}

void *
make_arg(int i)
{
    int *a = (int *)malloc(sizeof *a);
    *a = i;
    return (void *)a;
}

main(int argc, char **argv)
{
    int i;

    pthread_mutex_lock(&room_mutex);

    for (i = 0; i < N_DINERS; i++) {
	pthread_mutex_init(&chopsticks[i], NULL);
    }
    for (i = 0; i < N_DINERS; i++) {
	pthread_create(&philosophers[i].tid, NULL, philosopher_thread, make_arg(i));
	philosophers[i].in_room = 1;
    }
    room_occupancy = N_DINERS;

    for (;;) {
	if (room_occupancy == N_DINERS) 
	    PRINTMSG((fprintf(stderr, "main() about to block\n")));
	while (room_occupancy == N_DINERS) {
	    pthread_cond_wait(&room_cond, &room_mutex);
	}

	/* Someone has left the room */
	pthread_mutex_unlock(&room_mutex);

	PRINTMSG((fprintf(stderr, "main() thread sleep\n")));
	sleep(1);
	PRINTMSG((fprintf(stderr, "main() thread wake up\n")));

	pthread_mutex_lock(&room_mutex);
	for (i = 0; i < N_DINERS; i++)
	    if (philosophers[i].in_room == 0)
		break;
	if (i == N_DINERS) {
	    PRINTMSG((fprintf(stderr, "Contrary to what I was told, nobody left the room\n")));
	    PRINTMSG((fprintf(stderr, "I give up!\n")));
	    exit(1);
	}
	pthread_join(philosophers[i].tid, NULL);
	pthread_create(&philosophers[i].tid, NULL, philosopher_thread, make_arg(i));
	room_occupancy++;
    }
}
