import java.io.IOException;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class ErzeugerVerbraucher {
	private final Puffer buffer;
	private final Semaphor free;
	private final Semaphor used;
	private final int slots = 4;
	private final int bytesPerSlot = 3;

	public static void main(String[] args) {
		if (args.length == 0) {
			new ErzeugerVerbraucher(System.err);
		} else if (args.length == 1) {
			try {
				OutputStream out = new FileOutputStream(args[0]);
				new ErzeugerVerbraucher(out);
			} catch (IOException e) {
				System.err.println("failed to open file");
				e.printStackTrace();
				System.exit(1);
			}
		} else {
			System.out.println("usage: java ErzeugerVerbraucher [outputname]");
			System.out.println("(ohne Parameter schreibt der Verbraucher seine Ausgaben auf stderr)");
		}
	}

	public ErzeugerVerbraucher(OutputStream out) {
		buffer = new Puffer(slots, bytesPerSlot);    // create our buffer
		free = new Semaphor(slots * bytesPerSlot);   // everythis is free
		used = new Semaphor(0);						 // nothing is used		

		new Thread(new Erzeuger()).start();
		new Thread(new Verbraucher(out)).start();
	}

	class Erzeuger implements Runnable {
		public void run() {
			byte input[] = new byte[slots * bytesPerSlot];
			try {
				while (1 == 1) {  // forever
					System.out.print(">");
					int n = System.in.read(input);
					// caution: the P operation may block forever if n > buffer_size
					// to avoid this our input.length < buffer_size
					// if the user inserts more than input.lenth characters,
					// the read operation will return with the first input.lenth characters
					// the following call to read will return the next characters
					free.P(n);
					buffer.put(input, n);
					used.V(n);
				}
			} catch (IOException e) {
				System.err.println("error while waiting for input");
				e.printStackTrace();
				System.exit(1);
			}
		}

	}

	class Verbraucher implements Runnable {
		private final OutputStream out;

		public Verbraucher(OutputStream out){
			this.out = out;
		}

		public void run() {
			while (1 == 1) {  // forever
				used.P(bytesPerSlot);
				byte[] data = buffer.get();
				free.V(bytesPerSlot);
				write(data);
			}
		}

		// now we can process the data (we just print it)
		private void write(byte[] data) {
			try {
				for (int i = 0; i < data.length; i++) {
					out.write((char) data[i]);
				}
				out.flush();
			} catch (IOException e) {
				System.err.println("write failed");
				e.printStackTrace();
			}
			dosomething();
		}

		// just waste some time
		private void dosomething() {
			try {
				synchronized (this) {
					this.wait(5000);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

	}

}
