package railo.transformer.bytecode.statement;


import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

import railo.runtime.type.scope.Scope;
import railo.transformer.bytecode.Body;
import railo.transformer.bytecode.BytecodeContext;
import railo.transformer.bytecode.BytecodeException;
import railo.transformer.bytecode.Position;
import railo.transformer.bytecode.expression.ExprString;
import railo.transformer.bytecode.expression.Expression;
import railo.transformer.bytecode.expression.var.DataMember;
import railo.transformer.bytecode.expression.var.Variable;
import railo.transformer.bytecode.expression.var.VariableRef;
import railo.transformer.bytecode.expression.var.VariableString;
import railo.transformer.bytecode.literal.LitBoolean;
import railo.transformer.bytecode.literal.LitString;
import railo.transformer.bytecode.statement.tag.TagTry;
import railo.transformer.bytecode.util.ExpressionUtil;
import railo.transformer.bytecode.util.Types;
import railo.transformer.bytecode.visitor.OnFinally;
import railo.transformer.bytecode.visitor.TryCatchFinallyVisitor;

/**
 * produce  try-catch-finally
 */
public final class TryCatchFinally extends StatementBase implements Opcodes,HasBodies,FlowControlRetry {

	//private static LitString ANY=LitString.toExprString("any", -1);
	
	private static final Method TO_PAGE_EXCEPTION = new Method(
			"toPageException",
			Types.PAGE_EXCEPTION,
			new Type[]{Types.THROWABLE});
	
	
	
	//  public boolean typeEqual(String type);
	private static final Method TYPE_EQUAL = new Method(
			"typeEqual",
			Types.BOOLEAN_VALUE,
			new Type[]{Types.STRING});
	
	
	// Struct getCatchBlock(PageContext pc);
	private static final Method GET_CATCH_BLOCK = new Method(
			"getCatchBlock",
			Types.STRUCT,
			new Type[]{Types.PAGE_CONTEXT});


	// void isAbort(e)
	public static final Method IS_ABORT = new Method(
			"isAbort",
			Types.BOOLEAN_VALUE,
			new Type[]{Types.THROWABLE});

	

	private final static Method SET = new Method("set",Types.OBJECT,new Type[]{Types.PAGE_CONTEXT,Types.OBJECT});

	private static final Method REMOVE_EL = new Method("removeEL",Types.OBJECT,new Type[]{Types.PAGE_CONTEXT});
	
	
	
	
	private Body tryBody;
	private Body finallyBody;
	private List<Catch> catches=new ArrayList<Catch>();
	private Position finallyLine;


	private Label begin = new Label();

	private FlowControlFinal fcf;


	/**
	 * Constructor of the class
	 * @param body
	 * @param line
	 */
	public TryCatchFinally(Body body,Position start, Position end) {
		super(start,end);
		this.tryBody=body;
		body.setParent(this);
	}

	/**
	 * sets finally body
	 * @param body
	 */
	public void setFinally(Body body, Position finallyLine) {
		body.setParent(this);
		this.finallyBody=body;
		this.finallyLine=finallyLine;
	}

	/**
	 * data for a single catch block
	 */
	private class Catch {

		private ExprString type;
		private Body body;
		private VariableRef name;
		private Position line;

		public Catch(ExprString type, VariableRef name, Body body, Position line) {
			this.type=type;
			this.name=name;
			this.body=body;
			this.line=line;
		}
		
	}
	
	
	
	/**
	 *
	 * @see railo.transformer.bytecode.statement.StatementBase#_writeOut(org.objectweb.asm.commons.GeneratorAdapter)
	 */
	public void _writeOut(BytecodeContext bc) throws BytecodeException {
		GeneratorAdapter adapter = bc.getAdapter();
		
		adapter.visitLabel(begin);
		
		// Reference ref=null;
		final int lRef=adapter.newLocal(Types.REFERENCE);
		adapter.visitInsn(Opcodes.ACONST_NULL);
		adapter.storeLocal(lRef);
		
		// has no try body, if there is no try body, no catches are executed, only finally 
		if(!tryBody.hasStatements()) {
			if(finallyBody!=null)finallyBody.writeOut(bc);
			return;
		}
		
		
		TryCatchFinallyVisitor tcfv=new TryCatchFinallyVisitor(new OnFinally() {
			
			public void writeOut(BytecodeContext bc) throws BytecodeException {
				_writeOutFinally(bc,lRef);
			}
		},getFlowControlFinal());

		// try
		tcfv.visitTryBegin(bc);
			tryBody.writeOut(bc);
		int lThrow = tcfv.visitTryEndCatchBeging(bc);
			_writeOutCatch(bc, lRef, lThrow);
		tcfv.visitCatchEnd(bc);
		
	}
	
	
	
	
	
	
	
	
	private void _writeOutFinally(BytecodeContext bc, int lRef) throws BytecodeException {
		// ref.remove(pc);
		//Reference r=null;
		GeneratorAdapter adapter = bc.getAdapter();
		
		//if(fcf!=null && fcf.getAfterFinalGOTOLabel()!=null)ASMUtil.visitLabel(adapter,fcf.getFinalEntryLabel());
		ExpressionUtil.visitLine(bc, finallyLine);
		
		
		
		//if (reference != null)
		//    reference.removeEL(pagecontext);
		Label removeEnd=new Label();
		adapter.loadLocal(lRef);
		adapter.ifNull(removeEnd);
			adapter.loadLocal(lRef);
			adapter.loadArg(0);
			adapter.invokeInterface(Types.REFERENCE, REMOVE_EL);
			adapter.pop();
		adapter.visitLabel(removeEnd);
		
		if(finallyBody!=null)finallyBody.writeOut(bc); // finally
		/*if(fcf!=null){
			Label l = fcf.getAfterFinalGOTOLabel();
			if(l!=null)adapter.visitJumpInsn(Opcodes.GOTO, l);
		}*/
	}
	
	private void _writeOutCatch(BytecodeContext bc, int lRef,int lThrow) throws BytecodeException {
		GeneratorAdapter adapter = bc.getAdapter();
		int pe=adapter.newLocal(Types.PAGE_EXCEPTION);
		
		
		// instance of Abort
			Label abortEnd=new Label();
			adapter.loadLocal(lThrow);
			adapter.invokeStatic(Types.ABORT, TryCatchFinally.IS_ABORT);
			//adapter.instanceOf(Types.ABORT);
	        adapter.ifZCmp(Opcodes.IFEQ, abortEnd);
	        adapter.loadLocal(lThrow);
	        adapter.throwException();
	        adapter.visitLabel(abortEnd);


	        // PageExceptionImpl old=pc.getCatch();
	        int old=adapter.newLocal(Types.PAGE_EXCEPTION);
	        adapter.loadArg(0);
	        adapter.invokeVirtual(Types.PAGE_CONTEXT, TagTry.GET_CATCH);
			adapter.storeLocal(old);
	        
	        
			// cast to PageException  Caster.toPagException(t);
	        adapter.loadLocal(lThrow);
	        adapter.invokeStatic(Types.CASTER, TO_PAGE_EXCEPTION);
	        
	    // PageException pe=...
	        adapter.storeLocal(pe);

	    // catch loop
			Label endAllIf = new Label();
	        Iterator<Catch> it = catches.iterator();
	        Catch ctElse=null;
			while(it.hasNext()) {
				Catch ct=it.next();
				// store any for else
				if(ct.type!=null && ct.type instanceof LitString && ((LitString)ct.type).getString().equalsIgnoreCase("any")){
					ctElse=ct;
					continue;
				}
				
				ExpressionUtil.visitLine(bc, ct.line);
				
				// pe.typeEqual(type)
				if(ct.type==null){
					LitBoolean.TRUE.writeOut(bc, Expression.MODE_VALUE);
				}
				else {
					adapter.loadLocal(pe);
					ct.type.writeOut(bc, Expression.MODE_REF);
					adapter.invokeVirtual(Types.PAGE_EXCEPTION, TYPE_EQUAL);
				}
				
				
				

				Label endIf = new Label();
	            adapter.ifZCmp(Opcodes.IFEQ, endIf);
	            
	            catchBody(bc,adapter,ct,pe,lRef,true,true);
	            
	            adapter.visitJumpInsn(Opcodes.GOTO, endAllIf);
	            adapter.visitLabel(endIf);
	            

				
			}
			
			if(ctElse!=null){
				catchBody(bc,adapter,ctElse,pe,lRef,true,true);
			}
			else{
			// pc.setCatch(pe,true);
				adapter.loadArg(0);
		        adapter.loadLocal(pe);
		        adapter.push(false);
		        adapter.push(false);
		        adapter.invokeVirtual(Types.PAGE_CONTEXT, TagTry.SET_CATCH3);
	            
				
				adapter.loadLocal(pe);
				adapter.throwException();
			}
			adapter.visitLabel(endAllIf);
			
    		// PageExceptionImpl old=pc.setCatch(old);
            adapter.loadArg(0);
            adapter.loadLocal(old);
            adapter.invokeVirtual(Types.PAGE_CONTEXT, TagTry.SET_CATCH_PE);
			
	}

	private static void catchBody(BytecodeContext bc, GeneratorAdapter adapter, Catch ct, int pe, int lRef,boolean caugth,boolean store) throws BytecodeException {
		// pc.setCatch(pe,true);
		adapter.loadArg(0);
        adapter.loadLocal(pe);
        adapter.push(caugth);
        adapter.push(store);
        adapter.invokeVirtual(Types.PAGE_CONTEXT, TagTry.SET_CATCH3);
        
        
        // ref=
        ct.name.writeOut(bc, Expression.MODE_REF);
        adapter.storeLocal(lRef);
        
		adapter.loadLocal(lRef);
		adapter.loadArg(0);
		adapter.loadLocal(pe);// (...,pe.getCatchBlock(pc))
		adapter.loadArg(0);
        adapter.invokeVirtual(Types.PAGE_EXCEPTION, GET_CATCH_BLOCK);
		adapter.invokeInterface(Types.REFERENCE, SET);
		adapter.pop();
     	
        ct.body.writeOut(bc);
	}

	/**
	 * @param type
	 * @param name
	 * @param body
	 * @param line
	 */
	public void addCatch(ExprString type, VariableRef name, Body body, Position line) {
		body.setParent(this);
		catches.add(new Catch(type,name,body,line));
	}

	/**
	 * @param type
	 * @param name
	 * @param b
	 * @param line
	 * @throws BytecodeException
	 */
	public void addCatch(Expression type, Expression name, Body b, Position line) throws BytecodeException {
		
		// type
		if(type==null || type instanceof ExprString) ;
		else if(type instanceof Variable) {
			type=VariableString.toExprString(type);
		}
		else throw new BytecodeException("type from catch statement is invalid",type.getStart());
		
		// name
		if(name instanceof LitString){
			Variable v = new Variable(Scope.SCOPE_UNDEFINED,name.getStart(),name.getEnd());
			v.addMember(new DataMember(name));
			name=new VariableRef(v);
		}
		else if(name instanceof Variable) name=new VariableRef((Variable) name);
		else throw new BytecodeException("name from catch statement is invalid",name.getStart());
		
		addCatch((ExprString)type, (VariableRef)name, b, line);
	}	
	

	/**
	 * @see railo.transformer.bytecode.statement.HasBodies#getBodies()
	 */
	public Body[] getBodies() {
		
		int len=catches.size(),count=0;
		if(tryBody!=null)len++;
		if(finallyBody!=null)len++;
		Body[] bodies=new Body[len];
		Catch c;
		Iterator<Catch> it = catches.iterator();
		while(it.hasNext()) {
			c=it.next();
			bodies[count++]=c.body;
		}
		if(tryBody!=null)bodies[count++]=tryBody;
		if(finallyBody!=null)bodies[count++]=finallyBody;
		
		return bodies;
	}

	@Override
	public FlowControlFinal getFlowControlFinal() {
		if(fcf==null) fcf=new FlowControlFinalImpl();
		return fcf;
	}

	@Override
	public Label getRetryLabel() {
		return begin;
	}

	@Override
	public String getLabel() {
		return null;
	}
}
