package railo.transformer.bytecode.cast;

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

import railo.commons.lang.ClassException;
import railo.commons.lang.ClassUtil;
import railo.commons.lang.StringUtil;
import railo.transformer.bytecode.BytecodeContext;
import railo.transformer.bytecode.BytecodeException;
import railo.transformer.bytecode.expression.Expression;
import railo.transformer.bytecode.expression.ExpressionBase;
import railo.transformer.bytecode.expression.var.Variable;
import railo.transformer.bytecode.expression.var.VariableString;
import railo.transformer.bytecode.util.Methods_Caster;
import railo.transformer.bytecode.util.Types;

/**
 * cast a Expression to a Double
 */
public final class CastOther extends ExpressionBase implements Cast { 
    // TODO support short type
    private Expression expr;
    private String type;
    private String lcType;
    
    private CastOther(Expression expr, String type, String lcType) {
        super(expr.getStart(),expr.getEnd());
        this.expr=expr;
        this.type=type;
        this.lcType=lcType;
        
    }

    public static Expression toExpression(Expression expr, String type) {
    	if(type==null) return expr;
    	
    	String lcType=StringUtil.toLowerCase(type);
    	switch(lcType.charAt(0)) {
    	case 'a':
            if("any".equals(lcType)) 							{
            	return expr;
            }
    	break;
    	case 'b':
            if("boolean".equals(type) || "bool".equals(lcType)) return CastBoolean.toExprBoolean(expr);
        break;
    	case 'd':
    		if("double".equals(type))							return CastDouble.toExprDouble(expr);
    	break;
    	case 'i':
        	if("int".equals(lcType))							return CastInt.toExprInt(expr);
    	case 'n':
        	if("number".equals(lcType) || "numeric".equals(lcType))return CastDouble.toExprDouble(expr);
        break;
    	case 'o':
        	if("object".equals(lcType))							{
        		return expr;
        	}
        break;
        case 's':
        	if("string".equals(lcType))							return CastString.toExprString(expr);
        	//if("string_array".equals(lcType))					return StringArray.toExpr(expr);     
        break;
        case 'u':
        	if("uuid".equals(lcType)) 							return  CastString.toExprString(expr);
        break;
        case 'v':
        	if("variablename".equals(lcType))					return VariableString.toExprString(expr);
        	if("variable_name".equals(lcType))					return VariableString.toExprString(expr);
        	if("variablestring".equals(lcType))					return VariableString.toExprString(expr);
        	if("variable_string".equals(lcType))				return VariableString.toExprString(expr);
        	if("void".equals(lcType))							return expr;
        break;
        
        
        
        
    	}
        return new CastOther(expr,type,lcType);
    }

    // Array toArray(Object)
    final public static Method TO_ARRAY = new Method("toArray",
    		Types.ARRAY,
    		new Type[]{Types.OBJECT});
    
    // String toBase64 (Object);
    final public static Method TO_BASE64 = new Method("toBase64",
			Types.STRING,
			new Type[]{Types.OBJECT}); 
    
    // byte[] toBinary (Object)
    final public static Method TO_BINARY = new Method("toBinary",
			Types.BYTE_VALUE_ARRAY,
			new Type[]{Types.OBJECT}); 
    
    // byte[] toCollection (Object)
    final public static Method TO_COLLECTION = new Method("toCollection",
			Types.BYTE_VALUE_ARRAY,
			new Type[]{Types.OBJECT});
    
    // railo.runtime.Component toComponent (Object)
    final public static Method TO_COMPONENT = new Method("toComponent",
			Types.COMPONENT,
			new Type[]{Types.OBJECT});
	
    
    // String toDecimal (Object)
    final public static Method TO_DECIMAL = new Method("toDecimal",
			Types.STRING,
			new Type[]{Types.OBJECT});
    
    // railo.runtime.config.Config getConfig ()
    final public static Method GET_CONFIG = new Method("getConfig",
			Types.CONFIG_WEB,
			new Type[]{}); 
    
    //java.util.TimeZone getTimeZone ()
    final public static Method GET_TIMEZONE = new Method("getTimeZone",
    			Types.TIMEZONE,
    			new Type[]{});

    // Excel toExcel (Object)
    final public static Method TO_EXCEL = new Method("toExcel",
			Types.EXCEL,
			new Type[]{Types.OBJECT}); 
    
    /**
     * @see railo.transformer.bytecode.expression.Expression#_writeOut(org.objectweb.asm.commons.GeneratorAdapter, int)
     */
    public Type _writeOut(BytecodeContext bc, int mode) throws BytecodeException {
//Caster.toDecimal(null);
    	GeneratorAdapter adapter = bc.getAdapter();
        char first=lcType.charAt(0);
        Type rtn;
        
        switch(first) {
        case 'a':
            if("array".equals(lcType)) {
                rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.ARRAY)) 
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_ARRAY);
                return Types.ARRAY;
            }
        break;
        case 'b':
            if("base64".equals(lcType)) {
                expr.writeOut(bc,MODE_REF);
                adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_BASE64);
                return Types.STRING;
            }
            if("binary".equals(lcType)) {
                rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.BYTE_VALUE_ARRAY)) 
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_BINARY);
                return Types.BYTE_VALUE_ARRAY;
            }
            if("byte".equals(type)) {
            	rtn=expr.writeOut(bc,MODE_VALUE);
            	if(!rtn.equals(Types.BYTE_VALUE)) 
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_BYTE_VALUE[Types.getType(rtn)]);
            	return Types.BYTE_VALUE;
            }
            if("byte".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.BYTE)) 
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_BYTE[Types.getType(rtn)]);
                return Types.BYTE;
            }
            if("boolean".equals(lcType)) {
            	return CastBoolean.toExprBoolean(expr).writeOut(bc, MODE_REF);
            }
        break;
        case 'c':
            if("char".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_VALUE);
            	if(!rtn.equals(Types.CHAR)) 
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_CHAR_VALUE[Types.getType(rtn)]);
                return Types.CHAR;
            }
            if("character".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.CHARACTER)) 
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_CHARACTER[Types.getType(rtn)]);
                return Types.CHARACTER;
            }
            if("collection".equals(lcType)) {
                rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.COLLECTION)) 
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_COLLECTION);
                return Types.COLLECTION;
            }
            if("component".equals(lcType)) {
                rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.COMPONENT)) 
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_COMPONENT);
                return Types.COMPONENT;
            }
        break;
        case 'd':
            if("double".equals(lcType))	{
            	return CastDouble.toExprDouble(expr).writeOut(bc, MODE_REF);
            }
            if("date".equals(lcType) || "datetime".equals(lcType)) {
                // First Arg
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(rtn.equals(Types.DATE_TIME)) return Types.DATE_TIME;
                
            	int type=Types.getType(rtn);
                
                // Second Arg
                adapter.loadArg(0);
                adapter.invokeVirtual(Types.PAGE_CONTEXT,GET_CONFIG);
                adapter.invokeInterface(Types.CONFIG_WEB,GET_TIMEZONE);
                adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_DATE[type]);
                return Types.DATE_TIME;
            }
            if("decimal".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_DECIMAL[Types.getType(rtn)]);
                return Types.STRING;
            }
        break;
        case 'e':
        	if("excel".equals(type)) {
            	expr.writeOut(bc,MODE_REF);
                adapter.invokeStatic(Types.EXCEL_UTIL,TO_EXCEL);
                return Types.EXCEL;
            }
        break;
        case 'f':
            if("file".equals(lcType)) {
                rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.FILE))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_FILE);
                return Types.FILE;
            }
            if("float".equals(type)) {
            	rtn=expr.writeOut(bc,MODE_VALUE);
            	if(!rtn.equals(Types.FLOAT_VALUE))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_FLOAT_VALUE[Types.getType(rtn)]);
                return Types.FLOAT_VALUE;
            }
            if("float".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.FLOAT))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_FLOAT[Types.getType(rtn)]);
                return Types.FLOAT;
            }
        break;
        case 'i':
            if("int".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_VALUE);
            	if(!rtn.equals(Types.INT_VALUE))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_INT_VALUE[Types.getType(rtn)]);
                return Types.INT_VALUE;
            }
            if("integer".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.INTEGER))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_INTEGER[Types.getType(rtn)]);
                return Types.INTEGER;
            }
        break;
        case 'j':

            if("java.lang.boolean".equals(lcType))	{
            	return CastBoolean.toExprBoolean(expr).writeOut(bc, MODE_REF);
            }
            if("java.lang.double".equals(lcType))	{
            	return CastDouble.toExprDouble(expr).writeOut(bc, MODE_REF);
            }
            if("java.lang.string".equals(lcType))	{
            	return CastString.toExprString(expr).writeOut(bc, MODE_REF);
            }
            if("java.lang.stringbuffer".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.STRING_BUFFER))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_STRING_BUFFER);
                return Types.STRING_BUFFER;
            }
            if("java.lang.byte".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.BYTE))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_BYTE[Types.getType(rtn)]);
                return Types.BYTE;
            }
            if("java.lang.character".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.CHARACTER))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_CHARACTER[Types.getType(rtn)]);
                return Types.CHARACTER;
            }
            if("java.lang.short".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.SHORT))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_SHORT[Types.getType(rtn)]);
                return Types.SHORT;
            }
            if("java.lang.integer".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.INTEGER))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_INTEGER[Types.getType(rtn)]);
                return Types.INTEGER;
            }
            if("java.lang.long".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.LONG))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_LONG[Types.getType(rtn)]);
                return Types.LONG;
            }
            if("java.lang.float".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.FLOAT))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_FLOAT[Types.getType(rtn)]);
                return Types.FLOAT;
            }
            if("java.io.file".equals(lcType)) {
                rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.FILE))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_FILE);
                return Types.FILE;
            }
            if("java.lang.object".equals(lcType)) {
            	return expr.writeOut(bc,MODE_REF);
            }
            else if("java.util.date".equals(lcType)) {
                // First Arg
                rtn=expr.writeOut(bc,MODE_REF);
                if(rtn.equals(Types.DATE)) return Types.DATE;
                if(rtn.equals(Types.DATE_TIME)) return Types.DATE_TIME;

                // Second Arg
                adapter.loadArg(0);
                adapter.invokeVirtual(Types.PAGE_CONTEXT,GET_CONFIG);
                adapter.invokeVirtual(Types.CONFIG_WEB,GET_TIMEZONE);
                adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_DATE[Types.getType(rtn)]);
                return Types.DATE_TIME;
            }
        break;
        case 'l':
            if("long".equals(type)) {
            	rtn=expr.writeOut(bc,MODE_VALUE);
                if(!rtn.equals(Types.LONG_VALUE))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_LONG_VALUE[Types.getType(rtn)]);
                return Types.LONG_VALUE;
            }
            else if("long".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.LONG))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_LONG[Types.getType(rtn)]);
                return Types.LONG;
            }
        break;
        case 'n':
            if("node".equals(lcType)) {
                rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.NODE))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_NODE);
                return Types.NODE;
            }
            else if("null".equals(lcType)) {
                expr.writeOut(bc,MODE_REF);
                adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_NULL);
                // TODO gibt es einen null typ?
                return Types.OBJECT;
            }
        break;
        case 'o':
            if("object".equals(lcType) || "other".equals(lcType)) {
                expr.writeOut(bc,MODE_REF);
                return Types.OBJECT;
            }
        break;
        case 's':
            if("struct".equals(lcType)) {
                rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.STRUCT))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_STRUCT);
                return Types.STRUCT;
            }
            if("short".equals(type)) {
            	rtn=expr.writeOut(bc,MODE_VALUE);
                if(!rtn.equals(Types.SHORT_VALUE))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_SHORT_VALUE[Types.getType(rtn)]);
                return Types.SHORT_VALUE;
            }
            if("short".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.SHORT))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_SHORT[Types.getType(rtn)]);
                return Types.SHORT;
            }
            if("stringbuffer".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
            	if(!rtn.equals(Types.STRING_BUFFER))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_STRING_BUFFER);
                return Types.STRING_BUFFER;
            }
        break;

        case 'x':
            if("xml".equals(lcType)) {
                rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.NODE))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_NODE);
                return Types.NODE;
            }
        break;
        default:
            if("query".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.QUERY))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_QUERY);
                return Types.QUERY;
            }
            if("querycolumn".equals(lcType)) {
            	rtn=(expr instanceof Variable)?((Variable)expr).writeOutCollection(bc, mode):expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.QUERY_COLUMN)) {

                    adapter.loadArg(0);
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_QUERY_COLUMN);
                }
                return Types.QUERY_COLUMN;
            }
            else if("timespan".equals(lcType)) {
            	rtn=expr.writeOut(bc,MODE_REF);
                if(!rtn.equals(Types.TIMESPAN))
                	adapter.invokeStatic(Types.CASTER,Methods_Caster.TO_TIMESPAN);
                return Types.TIMESPAN;
            }
        }
        Type t=getType(type);
        
        expr.writeOut(bc,MODE_REF);
        adapter.checkCast(t);
        return t;
    }

    /*
    private int writeExprx(BytecodeContext bc) throws TemplateException {
    	return Types.getType(expr.writeOut(bc,MODE_VALUE));
	}*/

	

	public static Type getType(String type) throws BytecodeException {
		if(StringUtil.isEmpty(type)) return Types.OBJECT;
    	
    	
    	String lcType=StringUtil.toLowerCase(type);
    	switch(lcType.charAt(0)) {
    	case 'a':
    		if("any".equals(lcType)) 							return Types.OBJECT;
    		if("array".equals(lcType)) 							return Types.ARRAY;
    	break;
    	case 'b':
    		if("bool".equals(lcType) || "boolean".equals(type))	return Types.BOOLEAN_VALUE;
            if("boolean".equals(lcType)) 						return Types.BOOLEAN;
            if("base64".equals(lcType)) 						return Types.STRING;
            
            if("binary".equals(lcType))							return Types.BYTE_VALUE_ARRAY;
            if("byte".equals(type))								return Types.BYTE_VALUE;
            if("byte".equals(lcType))							return Types.BYTE;
            
    	break;
    	
    	case 'c':
            if("char".equals(lcType)) 							return Types.CHAR;
            if("character".equals(lcType))						return Types.CHARACTER;
            if("collection".equals(lcType))						return Types.COLLECTION;
            if("component".equals(lcType))						return Types.COMPONENT;
    	break;
    	
        case 'd':
            if("double".equals(type))							return Types.DOUBLE_VALUE;
            if("double".equals(lcType))							return Types.DOUBLE;
            
            if("date".equals(lcType) || "datetime".equals(lcType))return Types.DATE_TIME;
            if("decimal".equals(lcType))						return Types.STRING;
            
        break;

        case 'e':
        	if("excel".equals(lcType)) 							return Types.EXCEL;
        break;
        case 'f':
        	if("file".equals(lcType)) 							return Types.FILE;
            if("float".equals(type)) 							return Types.FLOAT_VALUE;
            if("float".equals(lcType))							return Types.FLOAT;
            if("function".equals(lcType))							return Types.UDF;
        break;

        case 'i':
            if("int".equals(lcType)) 							return Types.INT_VALUE;
            if("integer".equals(lcType))						return Types.INTEGER;
        break;

        case 'l':
            if("long".equals(type))								return Types.LONG_VALUE;
            if("long".equals(lcType))							return Types.LONG;
        break;
        case 'n':
            if("node".equals(lcType))							return Types.NODE;
            if("null".equals(lcType))							return Types.OBJECT;
            if("number".equals(lcType))							return Types.DOUBLE_VALUE;
            if("numeric".equals(lcType))						return Types.DOUBLE_VALUE;
        break;
        case 's':
            if("string".equals(lcType))							return Types.STRING;
            if("struct".equals(lcType))							return Types.STRUCT;
            if("short".equals(type))							return Types.SHORT_VALUE;
            if("short".equals(lcType))							return Types.SHORT;
        break;
        case 'o':
        	if("other".equals(lcType)) 							return Types.OBJECT;
        	if("object".equals(lcType)) 						return Types.OBJECT;
        break;
        case 'u':
        	if("uuid".equals(lcType)) 							return Types.STRING;
        	if("udf".equals(lcType)) 							return Types.UDF;
        break;
        case 'q':
        	if("query".equals(lcType)) 							return Types.QUERY;
        	if("querycolumn".equals(lcType)) 							return Types.QUERY_COLUMN;
        break;
        case 't':
        	if("timespan".equals(lcType))						return Types.TIMESPAN;
        break;
        case 'v':
        	if("variablename".equals(lcType))					return Types.STRING;
        	if("variable_name".equals(lcType))					return Types.STRING;
        	if("variablestring".equals(lcType))					return Types.STRING;
        	if("variable_string".equals(lcType))				return Types.STRING;
        	if("void".equals(lcType))							return Types.VOID;
        break;
        case 'x':
        	if("xml".equals(lcType))							return Types.NODE;
        break;
    	}
        try {
			return Type.getType(ClassUtil.loadClass(type));
		} 
        catch (ClassException e) {
			throw new BytecodeException(e.getMessage(),null);
		}
		
	}

	@Override
	public Expression getExpr() {
		return expr;
	}

	/**
	 * @return the type
	 */
	public String getType() {
		return type;
	}

}




