package edu.mit.streamjit.apps;

import com.google.common.base.Stopwatch;
import edu.mit.streamjit.api.CompiledStream;
import edu.mit.streamjit.api.StreamCompiler;
import edu.mit.streamjit.apps.Benchmark.Input;
import edu.mit.streamjit.impl.blob.Buffer;
import edu.mit.streamjit.impl.common.CheckVisitor;
import edu.mit.streamjit.impl.compiler.CompilerStreamCompiler;
import java.util.ServiceLoader;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 *
 * @author Jeffrey Bosboom <jeffreybosboom@gmail.com>
 * @since 8/12/2013
 */
public final class Benchmarker {
	private static final long TIMEOUT = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
	public static void main(String[] args) throws InterruptedException {
		StreamCompiler sc = new CompilerStreamCompiler();
		ServiceLoader<Benchmark> loader = ServiceLoader.load(Benchmark.class);

		for (Benchmark benchmark : loader) {
			benchmark.instantiate().visit(new CheckVisitor());
			for (Input input : benchmark.inputs())
				run(benchmark, input, sc);
		}
	}

	private static void run(Benchmark benchmark, Input input, StreamCompiler compiler) {
		String statusText = null;
		long compileMillis = -1, runMillis = -1;
		try {
			Stopwatch stopwatch = new Stopwatch();
			stopwatch.start();
			CompiledStream<Object, Object> stream = compiler.compile(benchmark.instantiate());
			stopwatch.stop();
			compileMillis = stopwatch.elapsed(TimeUnit.MILLISECONDS);
			stopwatch.reset();

			Thread ot = new OutputThread(input.output(), stream);
			ot.start();
			Thread it = new InputThread(input.input(), stream);
			stopwatch.start();
			it.start();

			it.join(TIMEOUT);
			if (it.isAlive())
				throw new TimeoutException();
			ot.join(TIMEOUT);
			if (ot.isAlive())
				throw new TimeoutException();
			stopwatch.stop();
			runMillis = stopwatch.elapsed(TimeUnit.MILLISECONDS);
		} catch (TimeoutException ex) {
			statusText = "timed out";
		} catch (InterruptedException ex) {
			statusText = "interrupted";
		} catch (Throwable t) {
			statusText = "failed: "+t;
		}
		if (statusText == null)
			statusText = String.format("%d ms compile, %d ms run", compileMillis, runMillis);

		System.out.format("%s / %s / %s: %s%n", compiler, benchmark, input, statusText);
	}

	private static class InputThread extends Thread {
		private final Buffer input;
		private final CompiledStream<Object, Object> stream;
		private InputThread(Buffer input, CompiledStream<Object, Object> stream) {
			super("benchmark-input");
			this.input = input;
			this.stream = stream;
		}
		@Override
		@SuppressWarnings("empty-statement")
		public void run() {
			Object o;
			while ((o = input.read()) != null) {
				while (!stream.offer(o))
					/* intentional empty statement */;
			}
			stream.drain();
		}
	}

	private static class OutputThread extends Thread {
		private final Buffer output;
		private final CompiledStream<Object, Object> stream;
		private OutputThread(Buffer output, CompiledStream<Object, Object> stream) {
			super("benchmark-output");
			this.output = output;
			this.stream = stream;
		}
		@Override
		public void run() {
			Object o;
			while ((o = stream.poll()) != null || !stream.isDrained()) {
				if (output != null && o != null)
					if (!o.equals(output.read()))
						throw new AssertionError("bad output");
			}
		}
	}
}

@@ -357,24 +357,82 @@
 amCompiler;%0A
+import edu.mit.streamjit.impl.interp.DebugStreamCompiler;%0A
 import java.
@@ -833,14 +833,58 @@
 iler
- sc = 
+%5B%5D compilers = %7B%0A%09%09%09new DebugStreamCompiler(),%0A%09%09%09
 new 
@@ -907,16 +907,20 @@
 mpiler()
+%0A%09%09%7D
 ;%0A%09%09Serv
@@ -987,16 +987,57 @@
 lass);%0A%0A
+%09%09for (StreamCompiler sc : compilers) %7B%0A%09
 %09%09for (B
@@ -1067,16 +1067,17 @@
 ader) %7B%0A
+%09
 %09%09%09bench
@@ -1122,16 +1122,17 @@
 tor());%0A
+%09
 %09%09%09for (
@@ -1169,16 +1169,17 @@
 ())%0A%09%09%09%09
+%09
 run(benc
@@ -1197,16 +1197,21 @@
 t, sc);%0A
+%09%09%09%7D%0A
 %09%09%7D%0A%09%7D%0A%0A

handle multiple compilers