/** Fork pool used by the philosophers */
class Forks {

    private bool[] fork = new bool[5];    // initially false, i.e. not used

    private Cond cond = new Cond ();
    private Mutex mutex = new Mutex ();

    // Try to pick up the forks with the designated numbers
    public void pick_up (int left, int right) {
        mutex.lock ();
        while (fork[left] || fork[right]) {
            cond.wait (mutex);
        }
        fork[left] = true;
        fork[right] = true;
        mutex.unlock ();
    }

    // Lay down the forks with the designated numbers
    public void lay_down (int left, int right) {
        mutex.lock ();
        fork[left] = false;
        fork[right] = false;
        cond.broadcast ();
        mutex.unlock ();
    }
}

/** A dining philosopher */
class Philosopher {

    private int number;         // this philosopher's number
    private int think_delay;    // how long does this philosopher think?
    private int eat_delay;      // how long does this philosopher eat?
    private int left;           // left fork number
    private int right;          // right fork number
    private Forks forks;        // forks used by all philosophers

    public Philosopher (int number, int think_delay, int eat_delay, Forks forks) {
        this.number = number;
        this.think_delay = think_delay;
        this.eat_delay = eat_delay;
        this.forks = forks;
        this.left = number == 0 ? 4 : number - 1;
        this.right = (number + 1) % 5;
    }

    public void* run () {
        while (true) {
            Thread.usleep (think_delay);
            forks.pick_up (left, right);
            stdout.printf ("Philosopher %d starts eating...\n", number);
            Thread.usleep (eat_delay);
            forks.lay_down (left, right);
            stdout.printf ("Philosopher %d stops eating...\n", number);
        }
    }
}

int main (string[] args) {

    if (!Thread.supported ()) {
        error ("Cannot run without thread support.");
    }

    var forks = new Forks ();

    Philosopher[] philos = {
        new Philosopher (0, 100000, 500000, forks),
        new Philosopher (1, 200000, 400000, forks),
        new Philosopher (2, 300000, 300000, forks),
        new Philosopher (3, 400000, 200000, forks),
        new Philosopher (4, 500000, 100000, forks)
    };

    try {
        foreach (var philosopher in philos) {
            Thread.create<void*> (philosopher.run, false);
        }
    } catch (ThreadError e) {
        error ("%s\n", e.message);
    }

    new MainLoop ().run ();

    return 0;
}
