package pp.s1184725.boppi.test;

import java.nio.file.Files;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import org.junit.Test;

import pp.iloc.*;
import pp.iloc.eval.Machine;
import pp.iloc.model.Program;

/**
 * Tests for the ILOC simple allocator (memlib.iloc).
 * 
 * @author Frank Wibbelink
 */
public class ILOCAllocatorTest {
	/**
	 * Default free memory (free slot size must report this value when nothing
	 * is allocated).
	 */
	private static final int FREEMEM = 0XFF;
	private static final int HEADER = 8;
	private static final int FIRSTPTR = 0;
	private static final int FIRSTSLOT = 4;
	private static final int SIZEOFFSET = 4;
	private static final int REFOFFSET = -8;

	private static final String obj1 = "r_obj1", obj2 = "r_obj2", obj3 = "r_obj3";

	private int returnAddress = 0;
	private Machine vm;

	private String malloc(int size, String reg) {
		returnAddress++;
		String s = "";
		s += "loadI #end" + returnAddress + " => r2\n";
		s += "push r2\n";
		s += "loadI " + size + " => r1\n";
		s += "push r1\n";
		s += "jumpI -> memalloc\n";
		s += "end" + returnAddress + ": pop => " + reg + "\n";
		if (size >= 4) {
			s += "loadI " + 0xDADACAFE + " => r2\n";
			s += "storeAI r2 => " + reg + ",0\n";
		}
		s += "\n";
		return s;
	}

	private String incRef(String reg) {
		returnAddress++;
		String s = "";
		s += "loadI #end" + returnAddress + " => r2\n";
		s += "push r2\n";
		s += "push " + reg + "\n";
		s += "jumpI -> memaddref\n";
		s += "end" + returnAddress + ": nop\n";
		s += "\n";
		return s;
	}

	private String free(String reg) {
		returnAddress++;
		String s = "";
		s += "loadI #end" + returnAddress + " => r2\n";
		s += "push r2\n";
		s += "push " + reg + "\n";
		s += "jumpI -> memfree\n";
		s += "end" + returnAddress + ": nop\n";
		s += "\n";
		return s;
	}

	private void sim(String code) {
		try {
			String memlib = new String(
					Files.readAllBytes(BoppiTests.TEST_PROGRAM_LOCATION.resolve("../../memlib.iloc")));
			Program program = Assembler.instance().assemble(memlib + "\n" + code);
			vm = new Machine();
			vm.setSize((FREEMEM + FIRSTSLOT + HEADER) * 2);
			Simulator sim = new Simulator(program, vm);
			sim.run();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Tests whether memlib correctly compiles and initialises.
	 */
	@Test
	public void compileAndRun() {
		sim("");
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(FIRSTPTR), is(FIRSTSLOT));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM));
	}

	/**
	 * Tests whether memlib correctly allocates and deallocates at the end of
	 * used memory Tests whether memlib correctly merges deallocated memory with
	 * the free slot at the end.
	 */
	@Test
	public void simpleMallocFree() {
//		Simulator.DEBUG = true;
		sim(malloc(11, obj1));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(FIRSTPTR), is(not(FIRSTSLOT)));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM - (HEADER + 11)));

		sim(malloc(11, obj1) + malloc(7, obj2));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(FIRSTPTR), is(not(FIRSTSLOT)));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM - (HEADER + 11) - (HEADER + 7)));

		sim(malloc(11, obj1) + free(obj1));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM));

		sim(malloc(11, obj1) + malloc(7, obj2) + free(obj2));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(0));

		sim(malloc(11, obj1) + malloc(7, obj2) + free(obj2) + free(obj1));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
	}

	/**
	 * Tests whether memlib correctly halts the machine if freeing wrong
	 * addresses or using null pointers.
	 */
	@Test
	public void incorrectTests() {
		sim(malloc(11, obj1) + free(obj1) + free(obj1));
		assertThat(vm.getInterrupt(), is(not(0)));

		sim(malloc(11, obj1) + incRef("m_0"));
		assertThat(vm.getInterrupt(), is(not(0)));

		sim(malloc(11, obj1) + free("m_0"));
		assertThat(vm.getInterrupt(), is(not(0)));
	}

	/**
	 * Tests whether memlib correctly allocates and deallocates memory in the
	 * middle of used memory. Tests whether memlib correctly merges deallocated
	 * memory with previous and next slots. Tests whether memlib correctly uses
	 * free slots that are either exactly the requested size or fit at least a
	 * new free slot.
	 */
	@Test
	public void discontinuousMemory() {
		sim(malloc(11, obj1) + malloc(7, obj2) + free(obj1));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(not(0)));

		sim(malloc(11, obj1) + malloc(7, obj2) + free(obj1) + free(obj2));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM));

		sim(malloc(11, obj1) + malloc(7, obj2) + free(obj1) + malloc(11, obj3));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM - (HEADER + 11) - (HEADER + 7)));

		sim(malloc(11, obj1) + malloc(7, obj2) + free(obj1) + malloc(10, obj3));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(not(0)));
		assertThat(vm.load(vm.load(vm.load(FIRSTPTR)) + SIZEOFFSET),
				is(FREEMEM - (HEADER + 11) - (HEADER + 7) - (HEADER + 10)));

		sim(malloc(11, obj1) + malloc(7, obj2) + free(obj1) + malloc(1, obj3) + malloc(2, obj1));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(FIRSTPTR), is(not(FIRSTSLOT)));
		assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
		assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM - (HEADER + 1) - (HEADER + 2) - (HEADER + 7)));
	}

	/**
	 * Tests whether memlib correctly tracks reference counts.
	 */
	@Test
	public void referenceCounting() {
		sim(malloc(11, obj1));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(vm.getReg(obj1) + REFOFFSET), is(1));

		sim(malloc(11, obj1) + incRef(obj1));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(vm.getReg(obj1) + REFOFFSET), is(2));

		sim(malloc(11, obj1) + incRef(obj1) + free(obj1));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(FIRSTPTR), is(not(FIRSTSLOT)));
		assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(not(FREEMEM)));

		sim(malloc(11, obj1) + incRef(obj1) + free(obj1) + free(obj1));
		assertThat(vm.getInterrupt(), is(0));
		assertThat(vm.load(FIRSTPTR), is(FIRSTSLOT));
		assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM));
	}

}
