package name.panitz.fun4u.machine;

import java.util.Stack;

public class Kellermaschine {
	int gcs=0;
	int lastUsed = 500;
	Stack<Integer> keller = new Stack<Integer>();
	Stack<Integer> cKeller = new Stack<Integer>();

	Stack<int[]> gcStack;
	
	HeapNode[] heap = new HeapNode[1000];
	int nextFreeHeapNode = 0;

	int pc = 0;
	Instruction[] prog;
	private HeapNode[] newHeap;
	private int nextFreeNewHeapNode = 0;

	int store(int n) {
		return store(new IntO(n));
	}

	int store(HeapNode n) {
		if (nextFreeHeapNode>=heap.length){
			gc(n);
		}
		heap[nextFreeHeapNode] = n;
		nextFreeHeapNode++;
		return nextFreeHeapNode - 1;
	}

	private void gc(HeapNode n) {
		System.out.println("Start GC: "+gcs);
		
		newHeap = new HeapNode[Math.max(heap.length, lastUsed*4)];
		nextFreeNewHeapNode=0;
		for (int i=0;i<keller.size();i++){
			keller.set(i,copyHeapNode(keller.get(i)));
		}
		gcStack =new Stack<int[]>();
		if (n instanceof StructO) {
			StructO struct = (StructO) n;
			gcStack.push(struct.args);
		}
		while (!gcStack.isEmpty()){
			copyHeapNode(gcStack.pop());
		}
		
		heap = newHeap;
		newHeap=null;
		nextFreeHeapNode=nextFreeNewHeapNode;
		System.out.println("END  GC: "+gcs+ " new heap size: "+heap.length);
		gcs++;
		lastUsed=nextFreeHeapNode;
	}

	void copyHeapNode(int[] oldAdrs){
		for (int i=0;i<oldAdrs.length;i++){
			if (heap[oldAdrs[i]] instanceof HasMovedTo) {
				HasMovedTo new_name = (HasMovedTo) heap[oldAdrs[i]];
				oldAdrs[i] = new_name.newAddress;
			}else if (heap[oldAdrs[i]] instanceof IntO) {
				IntO new_name = (IntO) heap[oldAdrs[i]];
				int result = nextFreeNewHeapNode;
				heap[oldAdrs[i]] = new HasMovedTo(result);
				newHeap[result] = new_name;
				nextFreeNewHeapNode++;
				oldAdrs[i] = result;
			}else if (heap[oldAdrs[i]] instanceof StructO) {
				StructO struct = (StructO) heap[oldAdrs[i]];
				heap[oldAdrs[i]] = new HasMovedTo(nextFreeNewHeapNode);
				newHeap[nextFreeNewHeapNode]=new StructO(struct.name, struct.args);
				oldAdrs[i] = nextFreeNewHeapNode;

				nextFreeNewHeapNode++;
				gcStack.push(struct.args);
//				copyHeapNode(struct.args);
			}else 
				throw new RuntimeException("unknown heap node "+heap[oldAdrs[i]]);
		}
	}
	
	int copyHeapNode(int oldAdr) {
		if (heap[oldAdr] instanceof HasMovedTo) {
			HasMovedTo new_name = (HasMovedTo) heap[oldAdr];
			return new_name.newAddress;
		}
		if (heap[oldAdr] instanceof IntO) {
			IntO new_name = (IntO) heap[oldAdr];
			int result = nextFreeNewHeapNode;
			heap[oldAdr] = new HasMovedTo(result);
			newHeap[result] = new_name;
			nextFreeNewHeapNode++;
			return result;
		}
		if (heap[oldAdr] instanceof StructO) {			
			StructO struct = (StructO) heap[oldAdr];
			int result = nextFreeNewHeapNode;
			heap[oldAdr] = new HasMovedTo(nextFreeNewHeapNode);
			nextFreeNewHeapNode++;
			newHeap[result] = new StructO(struct.name, struct.args);
			copyHeapNode(struct.args);			
			return result;
		}
		throw new RuntimeException();
	}
	
	public Kellermaschine(Instruction[] prog) {
		super();
		this.prog = prog;
	}

	void step() {
		Instruction instr = prog[pc];
		instr.execute(this);
	}

	public void run() {
		while (!end()) {
			step();
		}
		printTop(keller.pop());
		System.out.println();
	}

	private void printTop(int adr) {
		HeapNode heapNode = heap[adr];
		if (heapNode instanceof IntO) {
			IntO intN = (IntO) heapNode;
			System.out.print(intN.n);
		}else if (heapNode instanceof StructO) {
			StructO structO = (StructO) heapNode;
			System.out.print(structO.name);
			System.out.print("(");
			boolean first = true;
			for (int i:structO.args){
				if (first)first=false;else System.out.print(",");
				printTop(i);
			}
			System.out.print(")");	
		}	
	}

	boolean end() {
		return prog[pc] instanceof End;
	}

	@Override
	public String toString() {
//		String result="\n"+pc+" "+prog[pc]+"\n"+keller+"\nHeap:\n";
	//	for (int i=0;i<heap.length&&heap[i]!=null;i++){
	//		result=result+i+": "+heap[i]+"\n";
	//	}		
		return "";//result+"]";
	}
}
