package debuggees;

/**
 * jBixbe debuggee: deals with the "smoker problem".
 *
 * Three smokers sit around a table in a pub and are served by a waitress.
 * In order to smoke a smoker needs paper, tobacco and matches and every smoker
 * owns plenty of one of the three items. The waitress lays two items on the
 * table and every smoker grabs at his missing items.
 * Note, normally a deadlock would appear but we avoid this by the fact
 * that the waitress cyclically brings new items.
 *
 * @author ds-emedia
 */
public class ThePub {

    private Table table;

    private Waitress waitress;

    private Smoker smokerWithPaper;

    private Smoker smokerWithMatches;

    private Smoker smokerWithTobacco;

    public ThePub() {
        table = new Table();

        smokerWithPaper = new SmokerWithPaper(table);
        smokerWithPaper.start();

        smokerWithMatches = new SmokerWithMatches(table);
        smokerWithMatches.start();

        smokerWithTobacco = new SmokerWithTobacco(table);
        smokerWithTobacco.start();

        waitress = new Waitress(table);
        waitress.start();

    }

    public static void main(String[] args) {
        new ThePub();
    }
}

class Table {
    boolean paper;

    boolean matches;

    boolean tobacco;

    synchronized void putDownPaper() {
        paper = true;
        this.notifyAll();
    }

    synchronized void putDownMatches() {
        matches = true;
        this.notifyAll();
    }

    synchronized void putDownTobacco() {
        tobacco = true;
        this.notifyAll();
    }

    synchronized void takePaper() throws InterruptedException {
        while (!paper) {
            this.wait();
        }
        paper = false;
    }

    synchronized void takeMatches() throws InterruptedException {
        while (!matches) {
            this.wait();
        }
        matches = false;
    }

    synchronized void takeTobacco() throws InterruptedException {
        while (!tobacco) {
            this.wait();
        }
        tobacco = false;
    }
}

class Smoker extends Thread {
    void smoke() throws InterruptedException {
        sleep(500);
    }
}

class SmokerWithPaper extends Smoker {
    private Table table;

    SmokerWithPaper(Table table) {
        this.table = table;
        setName("Smoker with paper");
    }

    public void run() {
        while (true) {
            try {
                table.takeMatches();
                table.takeTobacco();
                smoke();
            } catch (InterruptedException e) {
                /* ignore */
            }
        }
    }
}

class SmokerWithMatches extends Smoker {
    private Table table;

    SmokerWithMatches(Table table) {
        this.table = table;
        setName("Smoker with matches");
    }

    public void run() {
        while (true) {
            try {
                table.takeTobacco();
                table.takePaper();
                smoke();
            } catch (InterruptedException e) {
                /* ignore */
            }
        }
    }
}

class SmokerWithTobacco extends Smoker {

    private Table table;

    SmokerWithTobacco(Table table) {
        this.table = table;
        setName("Smoker with tobacco");
    }

    public void run() {
        while (true) {
            try {
                table.takeMatches();
                table.takePaper();
                smoke();
            } catch (InterruptedException e) {
                /* ignore */
            }
        }
    }
}

class Waitress extends Thread {
    Table table;

    Waitress(Table table) {
        super("Waitress");
        this.table = table;
    }

    void bringPaperAndTobacco() {
        table.putDownPaper();
        table.putDownTobacco();
    }

    void bringPaperAndMatches() {
        table.putDownPaper();
        table.putDownMatches();
    }

    void bringMatchesAndTobacco() {
        table.putDownMatches();
        table.putDownTobacco();
    }

    public void run() {
        while (true) {
            int item = (int) (Math.random() * 10);
            if (item <= 3) {
                bringPaperAndTobacco();
            } else if (item <= 7) {
                bringPaperAndMatches();
            } else {
                bringMatchesAndTobacco();
            }

            try {
                sleep(2000);
            } catch (InterruptedException e) {
                /* ignore */
            }
        }
    }
}