/**
 * 
 */
package org.luaj.vm2.luajc;

import java.util.Vector;

import org.luaj.vm2.Lua;
import org.luaj.vm2.Prototype;

public class BasicBlock {
	int pc0,pc1;        // range of program counter values for the block
	BasicBlock[] prev;  // previous basic blocks (0-n of these)
	BasicBlock[] next;  // next basic blocks (0, 1, or 2 of these)
	boolean islive;     // true if this block is used
	
	public BasicBlock(Prototype p, int pc0) {
		this.pc0 = this.pc1 = pc0;
	}
	
	public String toString() {
		StringBuffer sb = new StringBuffer();		
		sb.append( (pc0+1)+"-"+(pc1+1)
				+(prev!=null? "  prv: "+str(prev,1): "")
				+(next!=null? "  nxt: "+str(next,0): "")
				+"\n" );
		return sb.toString();
	}
	
	private String str(BasicBlock[] b, int p) {
		if ( b == null )
			return "";
		StringBuffer sb = new StringBuffer();
		sb.append("(");
		for ( int i=0, n=b.length; i<n; i++ ) {
			if ( i > 0 )
				sb.append( "," );
			sb.append( String.valueOf( p==1? b[i].pc1+1: b[i].pc0+1 ) );
		}
		sb.append(")");
		return sb.toString();
	}

	public static BasicBlock[] findBasicBlocks(Prototype p) {
		
		// mark beginnings, endings
		final int n = p.code.length;
		final boolean[] isbeg = new boolean[n];
		final boolean[] isend = new boolean[n];
		isbeg[0] = true;
		BranchVisitor bv = new MarkAndMergeVisitor(isbeg, isend);
		visitBranches(p, bv); // 1st time to mark branches
		visitBranches(p, bv); // 2nd time to catch merges
			
		// create basic blocks
		final BasicBlock[] blocks = new BasicBlock[n];
		for ( int i=0; i<n; i++ ) {
			isbeg[i] = true;
			BasicBlock b = new BasicBlock(p,i);
			blocks[i] = b;
			while ( !isend[i] && i+1<n && !isbeg[i+1] )
				blocks[b.pc1=++i] = b;
		}
	
		// count previous, next
		final int[] nnext = new int[n];
		final int[] nprev = new int[n];
		visitBranches(p, new CountPrevNextVistor(isbeg, nnext, nprev));
		
		// allocate and cross-reference
		visitBranches( p, new AllocAndXRefVisitor(isbeg, nnext, nprev, blocks));
		return blocks;
	}
	
	private static final class AllocAndXRefVisitor extends BranchVisitor {
		private final int[] nnext;
		private final int[] nprev;
		private final BasicBlock[] blocks;

		private AllocAndXRefVisitor(boolean[] isbeg, int[] nnext, int[] nprev,
				BasicBlock[] blocks) {
			super(isbeg);
			this.nnext = nnext;
			this.nprev = nprev;
			this.blocks = blocks;
		}

		public void visitBranch(int pc0, int pc1) {
			if ( blocks[pc0].next == null ) blocks[pc0].next = new BasicBlock[nnext[pc0]];
			if ( blocks[pc1].prev == null ) blocks[pc1].prev = new BasicBlock[nprev[pc1]];
			blocks[pc0].next[--nnext[pc0]] = blocks[pc1];
			blocks[pc1].prev[--nprev[pc1]] = blocks[pc0];
		}
	}

	private static final class CountPrevNextVistor extends BranchVisitor {
		private final int[] nnext;
		private final int[] nprev;

		private CountPrevNextVistor(boolean[] isbeg, int[] nnext, int[] nprev) {
			super(isbeg);
			this.nnext = nnext;
			this.nprev = nprev;
		}

		public void visitBranch(int pc0, int pc1) {
			nnext[pc0]++;
			nprev[pc1]++;
		}
	}

	private static final class MarkAndMergeVisitor extends BranchVisitor {
		private final boolean[] isend;

		private MarkAndMergeVisitor(boolean[] isbeg, boolean[] isend) {
			super(isbeg);
			this.isend = isend;
		}

		public void visitBranch(int pc0, int pc1) {
			isend[pc0] = true;
			isbeg[pc1] = true;
		}

		public void visitReturn(int pc) {
			isend[pc] = true;
		}
	}

	abstract public static class BranchVisitor {
		final boolean[] isbeg;
		public BranchVisitor(boolean[] isbeg) {
			this.isbeg = isbeg;
		}
		public void visitBranch( int frompc, int topc ) {}
		public void visitReturn( int atpc ) {}
	}
	
	public static void visitBranches( Prototype p, BranchVisitor visitor ) {
		int sbx,j,c;
		int[] code = p.code;
		int n = code.length;
		for ( int i=0; i<n; i++ ) {
			int ins = code[i];
			switch ( Lua.GET_OPCODE( ins ) ) {
			case Lua.OP_LOADBOOL:
				if ( 0 == Lua.GETARG_C(ins) )
					break;
				if ( Lua.GET_OPCODE(code[i+1]) == Lua.OP_JMP  )
					throw new IllegalArgumentException("OP_LOADBOOL followed by jump at "+i);
				visitor.visitBranch( i, i+2 );
				continue;
			case Lua.OP_EQ:
			case Lua.OP_LT:
			case Lua.OP_LE:
			case Lua.OP_TEST: 
			case Lua.OP_TESTSET:
				if ( Lua.GET_OPCODE(code[i+1]) != Lua.OP_JMP  )
					throw new IllegalArgumentException("test not followed by jump at "+i); 
				sbx = Lua.GETARG_sBx(code[i+1]);
				++i;
				j = i + sbx + 1;
				visitor.visitBranch( i, j );
				visitor.visitBranch( i, i+1 ); 				
				continue;
			case Lua.OP_TFORLOOP:
			case Lua.OP_FORLOOP:
				sbx = Lua.GETARG_sBx(ins);
				j = i + sbx + 1;
				visitor.visitBranch( i, j );
				visitor.visitBranch( i, i+1 ); 				
				continue;
			case Lua.OP_JMP:
			case Lua.OP_FORPREP:
				sbx = Lua.GETARG_sBx(ins);
				j = i + sbx + 1;
				visitor.visitBranch( i, j );
				continue;
			case Lua.OP_TAILCALL:
			case Lua.OP_RETURN:
				visitor.visitReturn( i );
				continue;
			}
			if ( i+1<n && visitor.isbeg[i+1] )
				visitor.visitBranch( i, i+1 );
		}
	}
	
	public static BasicBlock[] findLiveBlocks(BasicBlock[] blocks) {
		// add reachable blocks
		Vector next = new Vector ();
		next.addElement( blocks[0] );
		while ( ! next.isEmpty() ) {
			BasicBlock b = (BasicBlock) next.elementAt(0);
			next.removeElementAt(0);
			if ( ! b.islive ) {
				b.islive = true;
				for ( int i=0, n=b.next!=null? b.next.length: 0; i<n; i++ )
					if ( ! b.next[i].islive )
						next.addElement( b.next[i] );
			}
		}
		
		// create list in natural order
		Vector list = new Vector();
		for ( int i=0; i<blocks.length; i=blocks[i].pc1+1 )
			if ( blocks[i].islive )
				list.addElement(blocks[i]);
		
		// convert to array
		BasicBlock[] array = new BasicBlock[list.size()];
		list.copyInto(array);
		return array;
	}
}