package railo.transformer.bytecode.util;

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

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
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.aprint;
import railo.commons.digest.MD5;
import railo.commons.io.IOUtil;
import railo.commons.io.res.Resource;
import railo.commons.lang.StringUtil;
import railo.runtime.component.Property;
import railo.runtime.exp.PageException;
import railo.runtime.net.rpc.AxisCaster;
import railo.runtime.op.Caster;
import railo.runtime.op.Decision;
import railo.runtime.type.dt.TimeSpanImpl;
import railo.runtime.type.util.ArrayUtil;
import railo.runtime.type.util.ListUtil;
import railo.transformer.bytecode.Body;
import railo.transformer.bytecode.BytecodeContext;
import railo.transformer.bytecode.BytecodeException;
import railo.transformer.bytecode.Literal;
import railo.transformer.bytecode.Page;
import railo.transformer.bytecode.Position;
import railo.transformer.bytecode.ScriptBody;
import railo.transformer.bytecode.Statement;
import railo.transformer.bytecode.cast.Cast;
import railo.transformer.bytecode.cast.CastBoolean;
import railo.transformer.bytecode.cast.CastDouble;
import railo.transformer.bytecode.cast.CastString;
import railo.transformer.bytecode.expression.ExprDouble;
import railo.transformer.bytecode.expression.ExprString;
import railo.transformer.bytecode.expression.Expression;
import railo.transformer.bytecode.expression.var.Argument;
import railo.transformer.bytecode.expression.var.BIF;
import railo.transformer.bytecode.expression.var.DataMember;
import railo.transformer.bytecode.expression.var.Member;
import railo.transformer.bytecode.expression.var.NullExpression;
import railo.transformer.bytecode.expression.var.Variable;
import railo.transformer.bytecode.expression.var.VariableString;
import railo.transformer.bytecode.literal.Identifier;
import railo.transformer.bytecode.literal.LitBoolean;
import railo.transformer.bytecode.literal.LitDouble;
import railo.transformer.bytecode.literal.LitString;
import railo.transformer.bytecode.statement.FlowControl;
import railo.transformer.bytecode.statement.FlowControlBreak;
import railo.transformer.bytecode.statement.FlowControlContinue;
import railo.transformer.bytecode.statement.FlowControlFinal;
import railo.transformer.bytecode.statement.FlowControlRetry;
import railo.transformer.bytecode.statement.PrintOut;
import railo.transformer.bytecode.statement.TryCatchFinally;
import railo.transformer.bytecode.statement.tag.Attribute;
import railo.transformer.bytecode.statement.tag.Tag;
import railo.transformer.bytecode.statement.tag.TagComponent;
import railo.transformer.bytecode.statement.tag.TagTry;
import railo.transformer.cfml.evaluator.EvaluatorException;

public final class ASMUtil {

	//private static final int VERSION_2=1;
	//private static final int VERSION_3=2;

	public static final short TYPE_ALL=0;
	public static final short TYPE_BOOLEAN=1;
	public static final short TYPE_NUMERIC=2;
	public static final short TYPE_STRING=4;
	
	
	
	
	//private static int version=0;
	
	private final static Method CONSTRUCTOR_OBJECT = Method.getMethod("void <init> ()");
	private static final Method _SRC_NAME = new Method("_srcName",
        			Types.STRING,
        			new Type[]{}
            		);;
	//private static final String VERSION_MESSAGE = "you use an invalid version of the ASM Jar, please update your jar files";
	private static long id=0;
		
	/**
	 * Gibt zurueck ob das direkt uebergeordnete Tag mit dem uebergebenen Full-Name (Namespace und Name) existiert.
	 * @param el Startelement, von wo aus gesucht werden soll.
	 * @param fullName Name des gesuchten Tags.
	 * @return Existiert ein solches Tag oder nicht.
	 */
	public static boolean hasAncestorTag(Tag tag, String fullName) {
	    return getAncestorTag(tag, fullName)!=null;
	}
	

	/**
	 * Gibt das uebergeordnete CFXD Tag Element zurueck, falls dies nicht existiert wird null zurueckgegeben.
	 * @param el Element von dem das parent Element zurueckgegeben werden soll.
	 * @return uebergeordnete CFXD Tag Element
	 */
	public static Tag getParentTag(Tag tag)	{
		Statement p=tag.getParent();
		if(p==null)return null;
		p=p.getParent();
		if(p instanceof Tag) return (Tag) p;
		return null;
	}

	public static boolean isParentTag(Tag tag,String fullName)	{
		Tag p = getParentTag(tag);
		if(p==null) return false;
		return p.getFullname().equalsIgnoreCase(fullName);
		
	}
	public static boolean isParentTag(Tag tag,Class clazz)	{
		Tag p = getParentTag(tag);
		if(p==null) return false;
		return p.getClass()==clazz;
		
	}
	
	public static boolean hasAncestorRetryFCStatement(Statement stat,String label) {
		return getAncestorRetryFCStatement(stat,null,label)!=null;
	}
	
	public static boolean hasAncestorBreakFCStatement(Statement stat,String label) {
		return getAncestorBreakFCStatement(stat,null,label)!=null;
	}
	
	public static boolean hasAncestorContinueFCStatement(Statement stat,String label) {
		return getAncestorContinueFCStatement(stat,null,label)!=null;
	}
	
	
	
	public static FlowControlRetry getAncestorRetryFCStatement(Statement stat, List<FlowControlFinal> finallyLabels, String label) {
		return (FlowControlRetry) getAncestorFCStatement(stat, finallyLabels, FlowControl.RETRY,label);
	}
	public static FlowControlBreak getAncestorBreakFCStatement(Statement stat, List<FlowControlFinal> finallyLabels, String label) {
		return (FlowControlBreak) getAncestorFCStatement(stat, finallyLabels, FlowControl.BREAK,label);
	}
	
	public static FlowControlContinue getAncestorContinueFCStatement(Statement stat, List<FlowControlFinal> finallyLabels, String label) {
		return (FlowControlContinue) getAncestorFCStatement(stat, finallyLabels, FlowControl.CONTINUE,label);
	}

	private static FlowControl getAncestorFCStatement(Statement stat, List<FlowControlFinal> finallyLabels, int flowType, String label) {
		Statement parent = stat;
		FlowControlFinal fcf;
		while(true)	{
			parent=parent.getParent();
			if(parent==null)return null;
			if(
			   ((flowType==FlowControl.RETRY && parent instanceof FlowControlRetry) || 
			   (flowType==FlowControl.CONTINUE && parent instanceof FlowControlContinue) || 
			   (flowType==FlowControl.BREAK && parent instanceof FlowControlBreak))
			   &&
				labelMatch((FlowControl)parent,label))	{
				if(parent instanceof ScriptBody){
					List<FlowControlFinal> _finallyLabels=finallyLabels==null?null:new ArrayList<FlowControlFinal>();
					
					FlowControl scriptBodyParent = getAncestorFCStatement(parent,_finallyLabels,flowType,label);
					if(scriptBodyParent!=null) {
						if(finallyLabels!=null){
							Iterator<FlowControlFinal> it = _finallyLabels.iterator();
							while(it.hasNext()){
								finallyLabels.add(it.next());
							}
						}
						return scriptBodyParent;
					}
					return (FlowControl)parent;
				}
				return (FlowControl) parent;
			}
			
			// only if not last
			if(finallyLabels!=null){
				fcf = parent.getFlowControlFinal();
				if(fcf!=null){
					finallyLabels.add(fcf);
				}
			}
			
		}
	}
	
	private static boolean labelMatch(FlowControl fc, String label) {
		if(StringUtil.isEmpty(label,true)) return true;
		String fcl = fc.getLabel();
		if(StringUtil.isEmpty(fcl,true)) return false;
		
		return label.trim().equalsIgnoreCase(fcl.trim());
	}


	public static void leadFlow(BytecodeContext bc,Statement stat, int flowType, String label) throws BytecodeException {
		List<FlowControlFinal> finallyLabels=new ArrayList<FlowControlFinal>();
		
		FlowControl fc;
		String name;
		if(FlowControl.BREAK==flowType) {
			fc=ASMUtil.getAncestorBreakFCStatement(stat,finallyLabels,label);
			name="break";
		}
		else if(FlowControl.CONTINUE==flowType) {
			fc=ASMUtil.getAncestorContinueFCStatement(stat,finallyLabels,label);
			name="continue";
		}
		else  {
			fc=ASMUtil.getAncestorRetryFCStatement(stat,finallyLabels,label);
			name="retry";
		}
		
		if(fc==null)
			throw new BytecodeException(name+" must be inside a loop (for,while,do-while,<cfloop>,<cfwhile> ...)",stat.getStart());
		
		GeneratorAdapter adapter = bc.getAdapter();
		
		Label end;
		if(FlowControl.BREAK==flowType) end=((FlowControlBreak)fc).getBreakLabel();
		else if(FlowControl.CONTINUE==flowType) end=((FlowControlContinue)fc).getContinueLabel();
		else  end=((FlowControlRetry)fc).getRetryLabel();

		// first jump to all final labels
		FlowControlFinal[] arr = finallyLabels.toArray(new FlowControlFinal[finallyLabels.size()]);
		if(arr.length>0) {
			FlowControlFinal fcf;
			for(int i=0;i<arr.length;i++){
				fcf=arr[i];
				
				// first
				if(i==0) {
					adapter.visitJumpInsn(Opcodes.GOTO, fcf.getFinalEntryLabel());
				}
				
				// last
				if(arr.length==i+1) fcf.setAfterFinalGOTOLabel(end);
				else fcf.setAfterFinalGOTOLabel(arr[i+1].getFinalEntryLabel());
			}
			
		}
		else bc.getAdapter().visitJumpInsn(Opcodes.GOTO, end);
	}
	
	
	
	public static boolean hasAncestorTryStatement(Statement stat) {
		return getAncestorTryStatement(stat)!=null;
	}
	
	public static Statement getAncestorTryStatement(Statement stat) {
		Statement parent = stat;
		while(true)	{
			parent=parent.getParent();
			if(parent==null)return null;
			
			if(parent instanceof TagTry)	{
				return parent;
			}
			else if(parent instanceof TryCatchFinally)	{
				return parent;
			}
		}
	}
	


	
	/**
	 * Gibt ein uebergeordnetes Tag mit dem uebergebenen Full-Name (Namespace und Name) zurueck, 
	 * falls ein solches existiert, andernfalls wird null zurueckgegeben.
	 * @param el Startelement, von wo aus gesucht werden soll.
	 * @param fullName Name des gesuchten Tags.
	 * @return  Übergeornetes Element oder null.
	 */
	public static Tag getAncestorTag(Tag tag, String fullName) {
		Statement parent=tag;
		while(true)	{
			parent=parent.getParent();
			if(parent==null)return null;
			if(parent instanceof Tag)	{
				tag=(Tag) parent;
				if(tag.getFullname().equalsIgnoreCase(fullName))
					return tag;
			}
		}
	}
	
	

    /**
     * extract the content of a attribut
     * @param cfxdTag
     * @param attrName
     * @return attribute value
     * @throws EvaluatorException
     */
	public static Boolean getAttributeBoolean(Tag tag,String attrName) throws EvaluatorException {
		Boolean b= getAttributeLiteral(tag, attrName).getBoolean(null);
		if(b==null)throw new EvaluatorException("attribute ["+attrName+"] must be a constant boolean value");
		return b;
    }
    
    /**
     * extract the content of a attribut
     * @param cfxdTag
     * @param attrName
     * @return attribute value
     * @throws EvaluatorException
     */
	public static Boolean getAttributeBoolean(Tag tag,String attrName, Boolean defaultValue) {
		Literal lit=getAttributeLiteral(tag, attrName,null);
		if(lit==null) return defaultValue;
		return lit.getBoolean(defaultValue); 
    }


    /**
     * extract the content of a attribut
     * @param cfxdTag
     * @param attrName
     * @return attribute value
     * @throws EvaluatorException
     */
	public static String getAttributeString(Tag tag,String attrName) throws EvaluatorException {
		return getAttributeLiteral(tag, attrName).getString();
    }
    
    /**
     * extract the content of a attribut
     * @param cfxdTag
     * @param attrName
     * @return attribute value
     * @throws EvaluatorException
     */
	public static String getAttributeString(Tag tag,String attrName, String defaultValue) {
		Literal lit=getAttributeLiteral(tag, attrName,null);
		if(lit==null) return defaultValue;
		return lit.getString(); 
    }
	
	/**
     * extract the content of a attribut
     * @param cfxdTag
     * @param attrName
     * @return attribute value
     * @throws EvaluatorException
     */
	public static Literal getAttributeLiteral(Tag tag,String attrName) throws EvaluatorException {
		Attribute attr = tag.getAttribute(attrName);
		if(attr!=null && attr.getValue() instanceof Literal) return ((Literal)attr.getValue());
        throw new EvaluatorException("attribute ["+attrName+"] must be a constant value");
    }
	
	
    
    /**
     * extract the content of a attribut
     * @param cfxdTag
     * @param attrName
     * @return attribute value
     * @throws EvaluatorException
     */
	public static Literal getAttributeLiteral(Tag tag,String attrName, Literal defaultValue) {
		Attribute attr = tag.getAttribute(attrName);
		if(attr!=null && attr.getValue() instanceof Literal) return ((Literal)attr.getValue());
        return defaultValue; 
    }
	
	
	

	/**
	 * Prueft ob das das angegebene Tag in der gleichen Ebene nach dem angegebenen Tag vorkommt.
	 * @param tag Ausgangspunkt, nach diesem tag darf das angegebene nicht vorkommen.
	 * @param nameToFind Tag Name der nicht vorkommen darf
	 * @return kommt das Tag vor.
	 */
	public static boolean hasSisterTagAfter(Tag tag, String nameToFind) {
		Body body=(Body) tag.getParent();
		List<Statement> stats = body.getStatements();
		Iterator<Statement> it = stats.iterator();
		Statement other;
		
		boolean isAfter=false;
		while(it.hasNext()) {
			other=it.next();
			
			if(other instanceof Tag) {
				if(isAfter) {
					if(((Tag) other).getTagLibTag().getName().equals(nameToFind))
					return true;
				}
				else if(other == tag) isAfter=true;
			}
		}
		return false;
	}
	
	/**
	 * Prueft ob das angegebene Tag innerhalb seiner Ebene einmalig ist oder nicht.
	 * @param tag Ausgangspunkt, nach diesem tag darf das angegebene nicht vorkommen.
	 * @return kommt das Tag vor.
	 */
	public static boolean hasSisterTagWithSameName(Tag tag) {
		
		Body body=(Body) tag.getParent();
		List<Statement> stats = body.getStatements();
		Iterator<Statement> it = stats.iterator();
		Statement other;
		String name=tag.getTagLibTag().getName();
		
		while(it.hasNext()) {
			other=it.next();
			if(other != tag && other instanceof Tag && ((Tag) other).getTagLibTag().getName().equals(name))
					return true;
			
		}
		return false;
	}

	/**
	 * remove this tag from his parent body
	 * @param tag
	 */
	public static void remove(Tag tag) {
		Body body=(Body) tag.getParent();
		body.getStatements().remove(tag);
	}

	/**
	 * replace src with trg
	 * @param src
	 * @param trg
	 */
	public static void replace(Tag src, Tag trg, boolean moveBody) {
		trg.setParent(src.getParent());
		
		Body p=(Body) src.getParent();
		List<Statement> stats = p.getStatements();
		Iterator<Statement> it = stats.iterator();
		Statement stat;
		int count=0;
		
		while(it.hasNext()) {
			stat=it.next();
			if(stat==src) {
				if(moveBody && src.getBody()!=null)src.getBody().setParent(trg);
				stats.set(count, trg);
				break;
			}
			count++;
		}
	}
	
	public static Page getAncestorPage(Statement stat) throws BytecodeException {
		Statement parent=stat;
		while(true)	{
			parent=parent.getParent();
			if(parent==null) {
				throw new BytecodeException("missing parent Statement of Statement",stat.getStart());
				//return null;
			}
			if(parent instanceof Page)	return (Page) parent;
		}
	}
	
	public static Page getAncestorPage(Statement stat, Page defaultValue) {
		Statement parent=stat;
		while(true)	{
			parent=parent.getParent();
			if(parent==null) {
				return defaultValue;
			}
			if(parent instanceof Page)	return (Page) parent;
		}
	}
	
	public static void listAncestor(Statement stat) {
		Statement parent=stat;
		aprint.o(stat);
		while(true)	{
			parent=parent.getParent();
			if(parent instanceof Page)aprint.o("page-> "+ ((Page)parent).getSource());
			else aprint.o("parent-> "+ parent);
			if(parent==null) break;
		}
	}
	
	
	public static Tag getAncestorComponent(Statement stat) throws BytecodeException {
		//print.ln("getAncestorPage:"+stat);
		Statement parent=stat;
		while(true)	{
			parent=parent.getParent();
			//print.ln(" - "+parent);
			if(parent==null) {
				throw new BytecodeException("missing parent Statement of Statement",stat.getStart());
				//return null;
			}
			if(parent instanceof TagComponent)
			//if(parent instanceof Tag && "component".equals(((Tag)parent).getTagLibTag().getName()))	
				return (Tag) parent;
		}
	}
	
	public static Statement getRoot(Statement stat) {
		while(true)	{
			if(isRoot(stat))	{
				return stat;
			}
			stat=stat.getParent();
		}
	}



    public static boolean isRoot(Statement statement) { 
    	//return statement instanceof Page || (statement instanceof Tag && "component".equals(((Tag)statement).getTagLibTag().getName()));
    	return statement instanceof Page || statement instanceof TagComponent;
    }
	
	public static void invokeMethod(GeneratorAdapter adapter, Type type, Method method) {
		if(type.getClass().isInterface())
			adapter.invokeInterface(type, method);
		else
			adapter.invokeVirtual(type, method);
	}

    public static byte[] createPojo(String className, ASMProperty[] properties,Class parent,Class[] interfaces, String srcName) throws PageException {
    	className=className.replace('.', '/');
    	className=className.replace('\\', '/');
    	className=ListUtil.trim(className, "/");
    	String[] inter=null;
    	if(interfaces!=null){
    		inter=new String[interfaces.length];
    		for(int i=0;i<inter.length;i++){
    			inter[i]=interfaces[i].getName().replace('.', '/');
    		}
    	}
    // CREATE CLASS	
		//ClassWriter cw = new ClassWriter(true);
    	ClassWriter cw = ASMUtil.getClassWriter();
        cw.visit(Opcodes.V1_2, Opcodes.ACC_PUBLIC, className, null, parent.getName().replace('.', '/'), inter);
        String md5;
        try{
    		md5=createMD5(properties);
    	}
    	catch(Throwable t){
    		md5="";
    		t.printStackTrace();
    	}
        
        FieldVisitor fv = cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "_md5_", "Ljava/lang/String;", null, md5);
        fv.visitEnd();
        
        
    // Constructor
        GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC,CONSTRUCTOR_OBJECT,null,null,cw);
        adapter.loadThis();
        adapter.invokeConstructor(toType(parent,true), CONSTRUCTOR_OBJECT);
        adapter.returnValue();
        adapter.endMethod();
    
        // properties
        for(int i=0;i<properties.length;i++){
        	createProperty(cw,className,properties[i]);
        }
        
        // complexType src
        if(!StringUtil.isEmpty(srcName)) {
        	GeneratorAdapter _adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+ Opcodes.ACC_STATIC , _SRC_NAME, null, null, cw);
        	_adapter.push(srcName);
        	_adapter.returnValue();
        	_adapter.endMethod();
        }
        
        cw.visitEnd();
        return cw.toByteArray();
    }
    
    private static void createProperty(ClassWriter cw,String classType, ASMProperty property) throws PageException {
		String name = property.getName();
		Type type = property.getASMType();
		Class clazz = property.getClazz();
		
		cw.visitField(Opcodes.ACC_PRIVATE, name, type.toString(), null, null).visitEnd();
		
		int load=loadFor(type);
		//int sizeOf=sizeOf(type);
		
    	// get<PropertyName>():<type>
    		Type[] types=new Type[0];
    		Method method = new Method((clazz==boolean.class?"get":"get")+StringUtil.ucFirst(name),type,types);
            GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC , method, null, null, cw);
            
            Label start = new Label();
            adapter.visitLabel(start);
            
            adapter.visitVarInsn(Opcodes.ALOAD, 0);
			adapter.visitFieldInsn(Opcodes.GETFIELD, classType, name, type.toString());
			adapter.returnValue();
			
			Label end = new Label();
			adapter.visitLabel(end);
			adapter.visitLocalVariable("this", "L"+classType+";", null, start, end, 0);
			adapter.visitEnd();
			
			adapter.endMethod();
			
			
			
			
		
		// set<PropertyName>(object):void
			types=new Type[]{type};
			method = new Method("set"+StringUtil.ucFirst(name),Types.VOID,types);
            adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC , method, null, null, cw);
            
            start = new Label();
            adapter.visitLabel(start);
            adapter.visitVarInsn(Opcodes.ALOAD, 0);
            adapter.visitVarInsn(load, 1);
            adapter.visitFieldInsn(Opcodes.PUTFIELD, classType, name, type.toString());
			
			adapter.visitInsn(Opcodes.RETURN);
			end = new Label();
			adapter.visitLabel(end);
			adapter.visitLocalVariable("this", "L"+classType+";", null, start, end, 0);
			adapter.visitLocalVariable(name, type.toString(), null, start, end, 1);
			//adapter.visitMaxs(0, 0);//.visitMaxs(sizeOf+1, sizeOf+1);// hansx
			adapter.visitEnd();
        
			adapter.endMethod();
			
			
			
			
	}

    public static int loadFor(Type type) {
    	if(type.equals(Types.BOOLEAN_VALUE) || type.equals(Types.INT_VALUE) || type.equals(Types.CHAR) || type.equals(Types.SHORT_VALUE))
    		return Opcodes.ILOAD;
    	if(type.equals(Types.FLOAT_VALUE))
    		return Opcodes.FLOAD;
    	if(type.equals(Types.LONG_VALUE))
    		return Opcodes.LLOAD;
    	if(type.equals(Types.DOUBLE_VALUE))
    		return Opcodes.DLOAD;
    	return Opcodes.ALOAD;
	}

    public static int sizeOf(Type type) {
    	if(type.equals(Types.LONG_VALUE) || type.equals(Types.DOUBLE_VALUE))
    		return 2;
    	return 1;
	}


	/**
     * translate a string cfml type definition to a Type Object
     * @param cfType
     * @param axistype
     * @return
     * @throws PageException
     */
    public static Type toType(String cfType, boolean axistype) throws PageException {
		return toType(Caster.cfTypeToClass(cfType), axistype);
	}

    /**
     * translate a string cfml type definition to a Type Object
     * @param cfType
     * @param axistype
     * @return
     * @throws PageException
     */
    public static Type toType(Class type, boolean axistype) {
		if(axistype)type=AxisCaster.toAxisTypeClass(type);
		return Type.getType(type);	
	}
    

	public static String createMD5(ASMProperty[] props) {
		
		StringBuffer sb=new StringBuffer();
		for(int i=0;i<props.length;i++){
			sb.append("name:"+props[i].getName()+";");
			if(props[i] instanceof Property){
				sb.append("type:"+((Property)props[i]).getType()+";");
			}
			else {
				try {
					sb.append("type:"+props[i].getASMType()+";");
					
				} 
				catch (PageException e) {}
			}
		}
		try {
			return MD5.getDigestAsString(sb.toString());
		} catch (IOException e) {
			return "";
		}
	}



	public static void removeLiterlChildren(Tag tag, boolean recursive) {
		Body body=tag.getBody();
		if(body!=null) {
        	List<Statement> list = body.getStatements();
        	Statement[] stats = list.toArray(new Statement[list.size()]);
        	PrintOut po;
        	Tag t;
        	for(int i=0;i<stats.length;i++) {
            	if(stats[i] instanceof PrintOut) {
            		po=(PrintOut) stats[i];
            		if(po.getExpr() instanceof Literal) {
            			body.getStatements().remove(po);
            		}
            	}
            	else if(recursive && stats[i] instanceof Tag) {
            		t=(Tag) stats[i];
            		if(t.getTagLibTag().isAllowRemovingLiteral()) {
            			removeLiterlChildren(t, recursive);
            		}
            	}
            }
        }
	}


	public synchronized static String getId() {
		if(id<0)id=0;
		return StringUtil.addZeros(++id,6);
	}


	public static boolean isEmpty(Body body) {
		return body==null || body.isEmpty();
	}


	/**
	 * @param adapter
	 * @param expr
	 * @param mode
	 */
	public static void pop(GeneratorAdapter adapter, Expression expr,int mode) {
		if(mode==Expression.MODE_VALUE && (expr instanceof ExprDouble))adapter.pop2();
		else adapter.pop();
	}
	public static void pop(GeneratorAdapter adapter, Type type) {
		if(type.equals(Types.DOUBLE_VALUE))adapter.pop2();
		else if(type.equals(Types.VOID));
		else adapter.pop();
	}


	public static ClassWriter getClassWriter() {
		return new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES);
		/*if(true) return new ClassWriter(ClassWriter.COMPUTE_MAXS);
		
		
		if(version==VERSION_2)
			return new ClassWriter(ClassWriter.COMPUTE_MAXS+ClassWriter.COMPUTE_FRAMES);
		
		try{
			ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
			version=VERSION_2;
			return cw;
		}
		catch(NoSuchMethodError err){
			if(version==0){
				version=VERSION_3;
			}
			
			PrintWriter ew = ThreadLocalPageContext.getConfig().getErrWriter();
			SystemOut.printDate(ew, VERSION_MESSAGE);
			
			try {
				return  ClassWriter.class.getConstructor(new Class[]{boolean.class}).newInstance(new Object[]{Boolean.TRUE});
				
			} 
			catch (Exception e) {
				throw new RuntimeException(Caster.toPageException(e));
				
			}
		}*/
	}

	/*
	 * For 3.1
	 * 
	 * public static ClassWriter getClassWriter() {
		if(version==VERSION_3)
			return new ClassWriter(ClassWriter.COMPUTE_MAXS);
		
		try{
			ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
			version=VERSION_3;
			return cw;
		}
		catch(NoSuchMethodError err){
			if(version==0){
				version=VERSION_2;
				throw new RuntimeException(new ApplicationException(VERSION_MESSAGE+
						", after reload this version will work as well, but please update to newer version"));
			}
			
			PrintWriter ew = ThreadLocalPageContext.getConfig().getErrWriter();
			SystemOut.printDate(ew, VERSION_MESSAGE);
			//err.printStackTrace(ew);
			
			try {
				return (ClassWriter) ClassWriter.class.getConstructor(new Class[]{boolean.class}).newInstance(new Object[]{Boolean.TRUE});
				
			} 
			catch (Exception e) {
				throw new RuntimeException(Caster.toPageException(e));
				
			}
		}
	}*/


	public static String createOverfowMethod(String prefix, int id) { // pattern is used in function callstackget
		if(StringUtil.isEmpty(prefix)) prefix="call";
		return prefix+"_"+StringUtil.addZeros(id,6);
	}
	
	public static boolean isOverfowMethod(String name) {
		return name.length()>6 && Decision.isNumeric(name.substring(name.length()-6,name.length()));
		//return name.startsWith("_call") && name.length()>=11;
	}


	public static boolean isDotKey(ExprString expr) {
		return expr instanceof LitString && !((LitString)expr).fromBracket();
	}

	public static String toString(Expression exp,String defaultValue) {
		try {
			return toString(exp);
		} catch (BytecodeException e) {
			return defaultValue;
		}
	}
	public static String toString(Expression exp) throws BytecodeException {
		if(exp instanceof Variable) {
			return toString(VariableString.toExprString(exp));
		}
		else if(exp instanceof VariableString) {
			return ((VariableString)exp).castToString();
		}
		else if(exp instanceof Literal) {
			return ((Literal)exp).toString();
		}
		return null;
	}


	public static Boolean toBoolean(Attribute attr, Position start) throws BytecodeException {
		if(attr==null)
			throw new BytecodeException("attribute does not exist",start);
		
		if(attr.getValue() instanceof Literal){
			Boolean b=((Literal)attr.getValue()).getBoolean(null);
			if(b!=null) return b; 
		}
		throw new BytecodeException("attribute ["+attr.getName()+"] must be a constant boolean value",start);
		
		
	}
	public static Boolean toBoolean(Attribute attr, int line, Boolean defaultValue) {
		if(attr==null)
			return defaultValue;
		
		if(attr.getValue() instanceof Literal){
			Boolean b=((Literal)attr.getValue()).getBoolean(null);
			if(b!=null) return b; 
		}
		return defaultValue;	
	}


	public static boolean isCFC(Statement s) {
		Statement p;
		while((p=s.getParent())!=null){
			s=p;
		}
		
		return true;
	}

	public static boolean isLiteralAttribute(Tag tag, String attrName, short type,boolean required,boolean throwWhenNot) throws EvaluatorException {
		return isLiteralAttribute(tag,tag.getAttribute(attrName), type, required, throwWhenNot);
	}
	
	
	public static boolean isLiteralAttribute(Tag tag,Attribute attr, short type,boolean required,boolean throwWhenNot) throws EvaluatorException {
		String strType="/constant";
		if(attr!=null && !isNull(attr.getValue())) {
			
			switch(type){
			case TYPE_ALL:
				if(attr.getValue() instanceof Literal) return true;
			break;
			case TYPE_BOOLEAN:
				if(CastBoolean.toExprBoolean(attr.getValue()) instanceof LitBoolean) return true;
				strType=" boolean";
			break;
			case TYPE_NUMERIC:
				if(CastDouble.toExprDouble(attr.getValue()) instanceof LitDouble) return true;
				strType=" numeric";
			break;
			case TYPE_STRING:
				if(CastString.toExprString(attr.getValue()) instanceof LitString) return true;
				strType=" string";
			break;
			}
			if(!throwWhenNot) return false;
			throw new EvaluatorException("Attribute ["+attr.getName()+"] of the Tag ["+tag.getFullname()+"] must be a literal"+strType+" value. "+
					"attributes java class type "+attr.getValue().getClass().getName());
		}
		if(required){
			if(!throwWhenNot) return false;
			throw new EvaluatorException("Attribute ["+attr.getName()+"] of the Tag ["+tag.getFullname()+"] is required");
		}
		return false;
	}


	public static boolean isNull(Expression expr) {
		if(expr instanceof NullExpression) return true;
		if(expr instanceof Cast) {
			return isNull(((Cast)expr).getExpr());
		}
		return false;
	}


	public static boolean isRefType(Type type) {
		return 
		!(type==Types.BYTE_VALUE || type==Types.BOOLEAN_VALUE || type==Types.CHAR || type==Types.DOUBLE_VALUE || 
		type==Types.FLOAT_VALUE || type==Types.INT_VALUE || type==Types.LONG_VALUE || type==Types.SHORT_VALUE);
	}


	public static Type toRefType(Type type) {
		if(type==Types.BYTE_VALUE) return Types.BYTE;
		if(type==Types.BOOLEAN_VALUE) return Types.BOOLEAN;
		if(type==Types.CHAR) return Types.CHARACTER;
		if(type==Types.DOUBLE_VALUE) return Types.DOUBLE;
		if(type==Types.FLOAT_VALUE) return Types.FLOAT;
		if(type==Types.INT_VALUE) return Types.INTEGER;
		if(type==Types.LONG_VALUE) return Types.LONG;
		if(type==Types.SHORT_VALUE) return Types.SHORT;
		
		return type;
	}
	
	/**
	 * return value type only when there is one
	 * @param type
	 * @return
	 */
	public static Type toValueType(Type type) {
		if(type==Types.BYTE) return Types.BYTE_VALUE;
		if(type==Types.BOOLEAN) return Types.BOOLEAN_VALUE;
		if(type==Types.CHARACTER) return Types.CHAR;
		if(type==Types.DOUBLE) return Types.DOUBLE_VALUE;
		if(type==Types.FLOAT) return Types.FLOAT_VALUE;
		if(type==Types.INTEGER) return Types.INT_VALUE;
		if(type==Types.LONG) return Types.LONG_VALUE;
		if(type==Types.SHORT) return Types.SHORT_VALUE;
		
		return type;
	}


	public static Class getValueTypeClass(Type type, Class defaultValue) {

		if(type==Types.BYTE_VALUE) return byte.class;
		if(type==Types.BOOLEAN_VALUE) return boolean.class;
		if(type==Types.CHAR) return char.class;
		if(type==Types.DOUBLE_VALUE) return double.class;
		if(type==Types.FLOAT_VALUE) return float.class;
		if(type==Types.INT_VALUE) return int.class;
		if(type==Types.LONG_VALUE) return long.class;
		if(type==Types.SHORT_VALUE) return short.class;
		
		return defaultValue;
	}


	public static ASMProperty[] toASMProperties(Property[] properties) {
		ASMProperty[] asmp=new ASMProperty[properties.length];
		for(int i=0;i<asmp.length;i++){
			asmp[i]=(ASMProperty) properties[i];
		}
		return asmp;
	}
	

	public static boolean containsComponent(Body body) {
		if(body==null) return false;
		
		Iterator<Statement> it = body.getStatements().iterator();
		while(it.hasNext()){
			if(it.next() instanceof TagComponent)return true;
		}
		return false;
	}


	public static void dummy1(BytecodeContext bc) {
		bc.getAdapter().visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
		bc.getAdapter().visitInsn(Opcodes.POP2);
	}
	public static void dummy2(BytecodeContext bc) {
		bc.getAdapter().visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J");
		bc.getAdapter().visitInsn(Opcodes.POP2);
	}


	/**
	 * convert a clas array to a type array
	 * @param classes
	 * @return
	 */
	public static Type[] toTypes(Class<?>[] classes) {
		if(classes==null || classes.length==0) 
			return new Type[0];
		
		Type[] types=new Type[classes.length];
		for(int i=0;i<classes.length;i++)	{
			types[i]=Type.getType(classes[i]);
		}
		return types;
	}


	public static String display(ExprString name) {
		if(name instanceof Literal) {
			if(name instanceof Identifier) 
				return ((Identifier)name).getRaw();
			return ((Literal)name).getString();
			
		}
		return name.toString();
	}


	public static long timeSpanToLong(Expression val) throws EvaluatorException {
		if(val instanceof Literal) {
			Double d = ((Literal)val).getDouble(null);
			if(d==null) throw cacheWithinException();
			return TimeSpanImpl.fromDays(d.doubleValue()).getMillis();
		}
		// createTimespan
		else if(val instanceof Variable) {
			Variable var=(Variable)val;
			if(var.getMembers().size()==1) {
				Member first = var.getFirstMember();
				if(first instanceof BIF) {
					BIF bif=(BIF) first;
					if("createTimeSpan".equalsIgnoreCase(bif.getFlf().getName())) {
						Argument[] args = bif.getArguments();
						int len=ArrayUtil.size(args);
						if(len>=4 && len<=5) {
							double days=toDouble(args[0].getValue());
							double hours=toDouble(args[1].getValue());
							double minutes=toDouble(args[2].getValue());
							double seconds=toDouble(args[3].getValue());
							double millis=len==5?toDouble(args[4].getValue()):0;
							return new TimeSpanImpl((int)days,(int)hours,(int)minutes,(int)seconds,(int)millis).getMillis();
						}
					}
				}
			}
		}
		throw cacheWithinException();
	}



	private static EvaluatorException cacheWithinException() {
		return new EvaluatorException("value of cachedWithin must be a literal timespan, like 0.1 or createTimespan(1,2,3,4)");
	}


	private static double toDouble(Expression e) throws EvaluatorException {
		if(!(e instanceof Literal)) 
			throw new EvaluatorException("Paremeters of the function createTimeSpan have to be literal numeric values in this context");
		Double d = ((Literal)e).getDouble(null);
		if(d==null)
			throw new EvaluatorException("Paremeters of the function createTimeSpan have to be literal numeric values in this context");
		
		return d.doubleValue();
	}

	public static void visitLabel(GeneratorAdapter ga, Label label) {
		if(label!=null) ga.visitLabel(label);
	}
	

	
	public static String getClassName(Resource res) throws IOException{
		byte[] src=IOUtil.toBytes(res);
		ClassReader cr = new ClassReader(src);
		return cr.getClassName();
	}
	
	public static String getClassName(byte[] barr){
		return new ClassReader(barr).getClassName();
	}


	public static String getSourceName(Class clazz) throws IOException {
		return SourceNameClassVisitor.getSourceName(clazz);
	}

	public static boolean hasOnlyDataMembers(Variable var) {
		Iterator<Member> it = var.getMembers().iterator();
		Member m;
		while(it.hasNext()){
			m = it.next();
			if(!(m instanceof DataMember)) return false;
		}
		return true;
	}
	
}
