import java.util.List;
import java.util.LinkedList;

public class TuottajaKuluttaja {
    public static void main(String[] args) {
	Varasto varasto = new Varasto(50);
	System.err.println(varasto);
	Tuottaja t1 = new Tuottaja("t1", varasto, 20, 50, 0);
	Tuottaja t2 = new Tuottaja("t2", varasto, 20, 50, 10);
	Kuluttaja k1 = new Kuluttaja("k1", varasto, 10, 200, 30);
	Kuluttaja k2 = new Kuluttaja("k2", varasto, 10, 200, 40);
	t1.start();
	t2.start();
	k1.start();
	k2.start();
// 	for (int i = 1; i < 8; i++) {
// 	    if (varasto.lisääTuote("tavara " + i)) {
// 		System.err.println("Onnistui, varasto = " + varasto);
// 	    } else {
// 		System.err.println("Epäonnistui, varasto = " + varasto);
// 	    }
// 	}
// 	for (int i = 1; i < 8; i++) {
// 	    Object o;
// 	    if ((o = varasto.otaTuote()) != null) {
// 		System.err.println("Onnistui, tavara = " + o);
// 	    } else {
// 		System.err.println("Epäonnistui, tavara = " + o);
// 	    }
// 	}
    }
}

class Toimija extends Thread {
    protected Varasto varasto;
    protected int kierrokset;
    protected int viive;
    protected int sisennys;

    public Toimija(ThreadGroup ryhmä, String nimi, Varasto varasto,
		   int kierrokset, int viive, int sisennys) {
	super(ryhmä, nimi);
	this.varasto = varasto;
	this.kierrokset = kierrokset;
	this.viive = viive;
	this.sisennys = sisennys;
    }

    public String toString() {
	String tulos = "";
	for (int i = 0; i < sisennys; i++) {
	    tulos += " ";
	}
	return tulos + getName();
    }
}

class Tuottaja extends Toimija {
    public static ThreadGroup ryhmä = new ThreadGroup("Tuottajat");

    public Tuottaja(String nimi, Varasto varasto,
		    int kierrokset, int viive, int sisennys) {
	super(ryhmä, nimi, varasto, kierrokset, viive, sisennys);
    }

    public void run() {
	for (int i = 0; i < kierrokset; i++) {
	    String tuotos = "tuote(" + getName() + ", " + i + ")";
	    if (varasto.lisääTuote(tuotos)) {
		System.err.print(this + ": tuota " + tuotos);
		System.err.println(" - onnistui");
	    } else {
		System.err.print(this + ": tuota " + tuotos);
		System.err.println(" - epäonnistui");
	    }
	    try {
		sleep((int)(Math.random()*viive));
	    } catch (InterruptedException e) {
	    }
	}
	if (ryhmä.activeCount() == 1) {
	    varasto.sulje();
	}
    }
}

class Kuluttaja extends Toimija {
    public static ThreadGroup ryhmä = new ThreadGroup("Kuluttajat");

    public Kuluttaja(String nimi, Varasto varasto,
		     int kierrokset, int viive, int sisennys) {
	super(ryhmä, nimi, varasto, kierrokset, viive, sisennys);
    }

    public void run() {
	for (int i = 0; i < kierrokset; i++) {
	    Object o;
	    if ((o = varasto.otaTuote()) != null) {
		System.err.print(this + ": kuluta ");
		System.err.println(" - onnistui: " + o);
	    } else {
		System.err.print(this + ": kuluta ");
		System.err.println(" - epäonnistui");
		break;
	    }
	    try {
		sleep((int)(Math.random()*viive));
	    } catch (InterruptedException e) {
	    }
	}
    }
}

class Varasto {
    private List sisältö;
    private int maksimi;
    private boolean suljettu;

    public Varasto(int maksimi) {
	this.maksimi = maksimi;
	sisältö = new LinkedList();
	suljettu = false;
    }

    synchronized public boolean suljettu () {
	return suljettu;
    }

    synchronized public void sulje () {
	suljettu = true;
	notifyAll();
    }

    synchronized public boolean täynnä() {
	return sisältö.size() == maksimi;
    }

    synchronized public boolean tyhjä() {
	return sisältö.isEmpty();
    }

    synchronized public boolean lisääTuote(Object t) {
	try {
	    while (!suljettu() && täynnä()) {
		System.err.println(Thread.currentThread() + " odottaa tyhjenemistä");
		wait();
	    }
	    if (suljettu()) {
		return false;
	    }
	} catch (InterruptedException e) {
	}
	sisältö.add(t);
	notify();
	return true;
    }

    synchronized public Object otaTuote() {
	try {
	    while (!suljettu() && tyhjä()) {
		System.err.println(Thread.currentThread() + " odottaa täyttymistä");
		wait();
	    }
	    if (suljettu()) {
		return null;
	    }
	} catch (InterruptedException e) {
	}
	notify();
	return sisältö.remove(0);
    }

    public String toString() {
	return "Varasto(" + maksimi + "): " + sisältö;
    }

}
