package edu.mit.streamjit.impl.compiler2;

import com.google.common.math.IntMath;
import edu.mit.streamjit.impl.blob.Buffer;
import static edu.mit.streamjit.util.LookupUtils.findVirtual;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Array;

/**
 * A ConcreteStorage backed by a circular array.
 * @author Jeffrey Bosboom <jeffreybosboom@gmail.com>
 * @since 10/10/2013
 */
public class CircularArrayConcreteStorage implements ConcreteStorage, BulkReadableConcreteStorage, BulkWritableConcreteStorage {
	private static final Lookup LOOKUP = MethodHandles.lookup();
	private static final MethodHandle INDEX = findVirtual(LOOKUP, CircularArrayConcreteStorage.class, "index", int.class, int.class, int.class);
	private static final MethodHandle ADJUST = findVirtual(LOOKUP, CircularArrayConcreteStorage.class, "adjust", void.class);;
	private final Object array;
	private final int capacity, throughput;
	private int head;
	private final MethodHandle readHandle, writeHandle, adjustHandle;
	public CircularArrayConcreteStorage(Storage s) {
		this.capacity = s.steadyStateCapacity();
		this.array = Array.newInstance(s.type(), capacity);
		this.throughput = s.throughput();
		this.head = 0;

		MethodHandle index = MethodHandles.insertArguments(INDEX.bindTo(this), 1, capacity);
		MethodHandle arrayGetter = MethodHandles.arrayElementGetter(array.getClass()).bindTo(array);
		this.readHandle = MethodHandles.filterArguments(arrayGetter, 0, index);
		MethodHandle arraySetter = MethodHandles.arrayElementSetter(array.getClass()).bindTo(array);
		this.writeHandle = MethodHandles.filterArguments(arraySetter, 0, index);
		this.adjustHandle = ADJUST.bindTo(this);
	}

	@Override
	public Class<?> type() {
		return array.getClass().getComponentType();
	}

	@Override
	public Object read(int index) {
		try {
			return readHandle.invoke(index);
		} catch (Throwable ex) {
			throw new AssertionError(ex);
		}
	}

	@Override
	public void write(int index, Object data) {
		try {
			writeHandle.invoke(index, data);
		} catch (Throwable ex) {
			throw new AssertionError(ex);
		}
	}

	@Override
	public void adjust() {
		//head + throughput >= 0 && capacity > 0, so % is mod.
		head = (head + throughput) % capacity;
	}

	@Override
	public void sync() {
	}

	@Override
	public MethodHandle readHandle() {
		return readHandle;
	}

	@Override
	public MethodHandle writeHandle() {
		return writeHandle;
	}

	@Override
	public MethodHandle adjustHandle() {
		return adjustHandle;
	}

	@Override
	public void bulkRead(Buffer dest, int index, int count) {
		assert type().equals(Object.class);
		index = index(index, capacity);
		int countBeforeEnd = Math.min(count, capacity - index);
		for (int written = 0; written < countBeforeEnd;)
			written += dest.write((Object[])array, index + written, countBeforeEnd - written);
		int remaining = count - countBeforeEnd;
		for (int written = 0; written < remaining;)
			written += dest.write((Object[])array, written, remaining - written);
	}

	@Override
	public void bulkWrite(Buffer source, int index, int count) {
		assert type().equals(Object.class);
		index = index(index, capacity);
		int countBeforeEnd = Math.min(count, capacity - index);
		int itemsRead = source.read((Object[])array, index, countBeforeEnd);
		assert itemsRead == countBeforeEnd;
		int remaining = count - itemsRead;
		int secondItemsRead = source.read((Object[])array, 0, remaining);
		assert secondItemsRead == remaining;
		assert itemsRead + secondItemsRead == count;
	}

	private int index(int physicalIndex, int capacity) {
		//assumes (physicalIndex + head) >= 0
		//I'd assert but that would add bytes to the method, hampering inlining.
		return (physicalIndex + head) % capacity;
	}

	public static StorageFactory factory() {
		return new StorageFactory() {
			@Override
			public ConcreteStorage make(Storage storage) {
				return new CircularArrayConcreteStorage(storage);
			}
		};
	}
}

@@ -118,16 +118,181 @@
 Buffer;%0A
+import edu.mit.streamjit.util.Combinators;%0Aimport static edu.mit.streamjit.util.LookupUtils.findGetter;%0Aimport static edu.mit.streamjit.util.LookupUtils.findStatic;%0A
 import s
@@ -867,31 +867,30 @@
 INDEX = find
-Virtual
+Static
 (LOOKUP, Cir
@@ -944,32 +944,43 @@
 lass, int.class,
+ int.class,
  int.class);%0A%09pr
@@ -1100,16 +1100,140 @@
 lass);;%0A
+%09private static final MethodHandle HEAD_GETTER = findGetter(LOOKUP, CircularArrayConcreteStorage.class, %22head%22, int.class);%0A
 %09private
@@ -1600,77 +1600,220 @@
 dle 
-index = MethodHandles.insertArguments(INDEX.bindTo(this), 1, capacity
+arrayLength = Combinators.arraylength(array.getClass()).bindTo(array);%0A%09%09MethodHandle index = MethodHandles.foldArguments(INDEX, arrayLength);%0A%09%09index = MethodHandles.foldArguments(index, HEAD_GETTER.bindTo(this)
 );%0A%09
@@ -3124,39 +3124,32 @@
 dex = index(
-index, 
 capacity
 );%0A%09%09int cou
@@ -3128,32 +3128,45 @@
 = index(capacity
+, head, index
 );%0A%09%09int countBe
@@ -3643,23 +3643,16 @@
 dex(
-index, 
 capacity
 );%0A%09
@@ -3643,24 +3643,37 @@
 dex(capacity
+, head, index
 );%0A%09%09int cou
@@ -4030,24 +4030,31 @@
 %7D%0A%0A%09private 
+static 
 int index(in
@@ -4059,35 +4059,45 @@
 int 
-physicalIndex, int capacity
+capacity, int head, int physicalIndex
 ) %7B%0A

use arraylength instead of constant capacity