package railo.transformer.bytecode.op;

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

import railo.runtime.exp.TemplateException;
import railo.transformer.bytecode.BytecodeContext;
import railo.transformer.bytecode.BytecodeException;
import railo.transformer.bytecode.Literal;
import railo.transformer.bytecode.cast.CastBoolean;
import railo.transformer.bytecode.expression.ExprBoolean;
import railo.transformer.bytecode.expression.Expression;
import railo.transformer.bytecode.expression.ExpressionBase;
import railo.transformer.bytecode.literal.LitBoolean;
import railo.transformer.bytecode.util.Methods;
import railo.transformer.bytecode.util.Methods_Operator;
import railo.transformer.bytecode.util.Types;

public final class OpBool extends ExpressionBase implements ExprBoolean {

    public static final int AND=0;
    public static final int OR=1;
    public static final int XOR=2;
	public static final int EQV = 3;
	public static final int IMP = 4;
    
    private ExprBoolean left;
    private ExprBoolean right;
    private int operation;

    /**
     *
     * @see railo.transformer.bytecode.expression.ExpressionBase#_writeOut(org.objectweb.asm.commons.GeneratorAdapter, int)
     */
    public Type _writeOut(BytecodeContext bc, int mode) throws BytecodeException {
    	GeneratorAdapter adapter = bc.getAdapter();
    	
    	if(mode==MODE_REF) {
            _writeOut(bc,MODE_VALUE);
            adapter.invokeStatic(Types.CASTER,Methods.METHOD_TO_BOOLEAN_FROM_BOOLEAN);
            return Types.BOOLEAN;
        }
    	
    	
    	Label doFalse = new Label();
    	Label end = new Label();

    	if(operation==AND) {
    		left.writeOut(bc, MODE_VALUE);
        	adapter.ifZCmp(Opcodes.IFEQ, doFalse);
        	
        	right.writeOut(bc, MODE_VALUE);
        	adapter.ifZCmp(Opcodes.IFEQ, doFalse);
        	adapter.push(true);
        	
        	adapter.visitJumpInsn(Opcodes.GOTO, end);
        	adapter.visitLabel(doFalse);

        	adapter.push(false);
        	adapter.visitLabel(end);
    	}
    	if(operation==OR) {
    		left.writeOut(bc, MODE_VALUE);
        	adapter.ifZCmp(Opcodes.IFNE, doFalse);

        	right.writeOut(bc, MODE_VALUE);
        	adapter.ifZCmp(Opcodes.IFNE, doFalse);

        	adapter.push(false);
        	adapter.visitJumpInsn(Opcodes.GOTO, end);
        	adapter.visitLabel(doFalse);

        	adapter.push(true);
        	adapter.visitLabel(end);
    	}
    	else if(operation==XOR) {
    		left.writeOut(bc, MODE_VALUE);
    		right.writeOut(bc, MODE_VALUE);
    		adapter.visitInsn(Opcodes.IXOR);
    	}
    	else if(operation==EQV) {

            left.writeOut(bc,MODE_VALUE);
            right.writeOut(bc,MODE_VALUE);
            adapter.invokeStatic(Types.OPERATOR,Methods_Operator.OPERATOR_EQV_BV_BV);
    	}
    	else if(operation==IMP) {

            left.writeOut(bc,MODE_VALUE);
            right.writeOut(bc,MODE_VALUE);
            adapter.invokeStatic(Types.OPERATOR,Methods_Operator.OPERATOR_IMP_BV_BV);
    	}
    	return Types.BOOLEAN_VALUE;
    	
    }
    

    
    
    
    
    
    private OpBool(Expression left, Expression right, int operation) {
        super(left.getStart(),right.getEnd());
        this.left=CastBoolean.toExprBoolean(left);
        this.right=CastBoolean.toExprBoolean(right);  
        this.operation=operation;
    }
    
    /**
     * Create a String expression from a Expression
     * @param left 
     * @param right 
     * 
     * @return String expression
     * @throws TemplateException 
     */
    public static ExprBoolean toExprBoolean(Expression left, Expression right,int operation) {
        if(left instanceof Literal && right instanceof Literal) {
        	Boolean l=((Literal) left).getBoolean(null);
        	Boolean r=((Literal) right).getBoolean(null);
        	
        	
        	if(l!=null && r!=null) {
        		switch(operation) {
        		case AND:	return new LitBoolean(l.booleanValue()&&r.booleanValue(),left.getStart(),right.getEnd());
        		case OR:	return new LitBoolean(l.booleanValue()||r.booleanValue(),left.getStart(),right.getEnd());
        		case XOR:	return new LitBoolean(l.booleanValue()^r.booleanValue(),left.getStart(),right.getEnd());
        		}
        	}
        }
        return new OpBool(left,right,operation);
    }
    
    public String toString(){
    	return left+" "+toStringOperation()+" "+right;
    }

	private String toStringOperation() {
		if(AND==operation)	return "and";
		if(OR==operation) 	return "or";
		if(XOR==operation) 	return "xor";
		if(EQV==operation) 	return "eqv";
		if(IMP==operation) 	return "imp";
		return operation+"";
	}
}

