/*
 *    Fernflower - The Analytical Java Decompiler
 *    http://www.reversed-java.com
 *
 *    (C) 2008 - 2010, Stiver
 *
 *    This software is NEITHER public domain NOR free software 
 *    as per GNU License. See license.txt for more details.
 *
 *    This software is distributed WITHOUT ANY WARRANTY; without 
 *    even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 *    A PARTICULAR PURPOSE. 
 */

package de.fernflower.modules.decompiler.exps;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

import de.fernflower.code.CodeConstants;
import de.fernflower.modules.decompiler.ExprProcessor;
import de.fernflower.modules.decompiler.vars.CheckTypesResult;
import de.fernflower.struct.gen.VarType;
import de.fernflower.util.InterpreterUtil;
import de.fernflower.util.ListStack;



public class FunctionExprent extends Exprent {

	public static final int FUNCTION_ADD = 0;
	public static final int FUNCTION_SUB = 1;
	public static final int FUNCTION_MUL = 2;
	public static final int FUNCTION_DIV = 3;
	
	public static final int FUNCTION_AND = 4;
	public static final int FUNCTION_OR = 5;
	public static final int FUNCTION_XOR = 6;
	
	public static final int FUNCTION_REM = 7;
	
	public static final int FUNCTION_SHL = 8;
	public static final int FUNCTION_SHR = 9;
	public static final int FUNCTION_USHR = 10;

	public static final int FUNCTION_BITNOT = 11;
	public static final int FUNCTION_BOOLNOT = 12;
	public static final int FUNCTION_NEG = 13;
	
	public final static int	FUNCTION_I2L = 14;
	public final static int	FUNCTION_I2F = 15;
	public final static int	FUNCTION_I2D = 16;
	public final static int	FUNCTION_L2I = 17;
	public final static int	FUNCTION_L2F = 18;
	public final static int	FUNCTION_L2D = 19;
	public final static int	FUNCTION_F2I = 20;
	public final static int	FUNCTION_F2L = 21;
	public final static int	FUNCTION_F2D = 22;
	public final static int	FUNCTION_D2I = 23;
	public final static int	FUNCTION_D2L = 24;
	public final static int	FUNCTION_D2F = 25;
	public final static int	FUNCTION_I2B = 26;
	public final static int	FUNCTION_I2C = 27;
	public final static int	FUNCTION_I2S = 28;
	
	public final static int	FUNCTION_CAST = 29;
	public final static int	FUNCTION_INSTANCEOF = 30;
	
	public final static int	FUNCTION_ARRAYLENGTH = 31;

	public final static int	FUNCTION_IMM = 32;
	public final static int	FUNCTION_MMI = 33;

	public final static int	FUNCTION_IPP = 34;
	public final static int	FUNCTION_PPI = 35;
	
	public final static int	FUNCTION_IIF = 36;
	
	public final static int	FUNCTION_LCMP = 37;
	public final static int	FUNCTION_FCMPL = 38;
	public final static int	FUNCTION_FCMPG = 39;
	public final static int	FUNCTION_DCMPL = 40;
	public final static int	FUNCTION_DCMPG = 41;
	
	public static final int FUNCTION_EQ = 42;
	public static final int FUNCTION_NE = 43;
	public static final int FUNCTION_LT = 44;
	public static final int FUNCTION_GE = 45;
	public static final int FUNCTION_GT = 46;
	public static final int FUNCTION_LE = 47;

	public static final int FUNCTION_CADD = 48;
	public static final int FUNCTION_COR = 49;
	
	public static final int FUNCTION_STRCONCAT = 50;

	private static final VarType[] types = new VarType[] {
		VarType.VARTYPE_LONG,
		VarType.VARTYPE_FLOAT,
		VarType.VARTYPE_DOUBLE,
		VarType.VARTYPE_INT,
		VarType.VARTYPE_FLOAT,
		VarType.VARTYPE_DOUBLE,
		VarType.VARTYPE_INT,
		VarType.VARTYPE_LONG,
		VarType.VARTYPE_DOUBLE,
		VarType.VARTYPE_INT,
		VarType.VARTYPE_LONG,
		VarType.VARTYPE_FLOAT,
		VarType.VARTYPE_BYTE,
		VarType.VARTYPE_CHAR,
		VarType.VARTYPE_SHORT
	};
	
	private static final String[] operators = new String[] {
		" + ",
		" - ",
		" * ",
		" / ",
		" & ",
		" | ",
		" ^ ",
		" % ",
		" << ",
		" >> ",
		" >>> ",
		" == ",
		" != ",
		" < ",
		" >= ",
		" > ",
		" <= ",
		" && ",
		" || ",
		" + "
	};
	
	private static final int[] precedence = new int[] {
		3,	//		FUNCTION_ADD
		3,	//		FUNCTION_SUB
		2,	//		FUNCTION_MUL
		2,	//		FUNCTION_DIV
		7,	//		FUNCTION_AND
		9,	//		FUNCTION_OR
		8,	//		FUNCTION_XOR
		2,	//		FUNCTION_REM
		4,	//		FUNCTION_SHL
		4,	//		FUNCTION_SHR
		4,	//		FUNCTION_USHR
		1,	//		FUNCTION_BITNOT 
		1,	//		FUNCTION_BOOLNOT 
		1,	//		FUNCTION_NEG
		1,	//		FUNCTION_I2L
		1,	//		FUNCTION_I2F
		1,	//		FUNCTION_I2D
		1,	//		FUNCTION_L2I
		1,	//		FUNCTION_L2F
		1,	//		FUNCTION_L2D
		1,	//		FUNCTION_F2I
		1,	//		FUNCTION_F2L
		1,	//		FUNCTION_F2D
		1,	//		FUNCTION_D2I
		1,	//		FUNCTION_D2L
		1,	//		FUNCTION_D2F
		1,	//		FUNCTION_I2B
		1,	//		FUNCTION_I2C
		1,	//		FUNCTION_I2S
		1,	//		FUNCTION_CAST
		6,	//		FUNCTION_INSTANCEOF
		0,	//		FUNCTION_ARRAYLENGTH
		1,	//		FUNCTION_IMM
		1,	//		FUNCTION_MMI
		1,	//		FUNCTION_IPP
		1,	//		FUNCTION_PPI
		12,	//		FUNCTION_IFF 
		-1,	//		FUNCTION_LCMP
		-1,	//		FUNCTION_FCMPL
		-1,	//		FUNCTION_FCMPG
		-1,	//		FUNCTION_DCMPL
		-1,	//		FUNCTION_DCMPG
		6,	//		FUNCTION_EQ = 41;
		6,	//		FUNCTION_NE = 42;
		5,	//		FUNCTION_LT = 43;
		5,	//		FUNCTION_GE = 44;
		5,	//		FUNCTION_GT = 45;
		5,	//		FUNCTION_LE = 46;
		10,	//		FUNCTION_CADD = 47;
		11,	//		FUNCTION_COR = 48;
		3	//		FUNCTION_STRCONCAT = 49;
	};

	private static final HashSet<Integer> associativity = new HashSet<Integer>(Arrays.asList(new Integer[]{FUNCTION_ADD, FUNCTION_MUL, FUNCTION_AND,
			FUNCTION_OR, FUNCTION_XOR, FUNCTION_CADD, FUNCTION_COR, FUNCTION_STRCONCAT}));
	
	private int functype;
	
	private VarType implicitType;
	
	private List<Exprent> lstOperands = new ArrayList<Exprent>();  
	
	{
		this.type = EXPRENT_FUNCTION;
	}
	
	public FunctionExprent(int functype, ListStack<Exprent> stack) {
		this.functype = functype;
		if(functype>=FUNCTION_BITNOT && functype<=FUNCTION_PPI && functype!=FUNCTION_CAST  
										&& functype!=FUNCTION_INSTANCEOF) {
			lstOperands.add(stack.pop());
		} else if(functype == FUNCTION_IIF) {
			throw new RuntimeException("no direct instantiation possible");
		} else {
			Exprent expr = stack.pop();
			lstOperands.add(stack.pop());
			lstOperands.add(expr);
		}
	}
	
	public FunctionExprent(int functype, List<Exprent> operands) {
		this.functype = functype;
		this.lstOperands = operands;
	}
	
	public VarType getExprType() {
		VarType exprType = null;
		
		if(functype <= FUNCTION_NEG || functype == FUNCTION_IPP || functype == FUNCTION_PPI
				 || functype == FUNCTION_IMM || functype == FUNCTION_MMI) {

			VarType type1 = lstOperands.get(0).getExprType();
			VarType type2 = null;
			if(lstOperands.size() > 1) {
				type2 = lstOperands.get(1).getExprType();
			}
			
			switch(functype) {
			case FUNCTION_IMM:
			case FUNCTION_MMI:
			case FUNCTION_IPP:
			case FUNCTION_PPI:
				exprType = implicitType;
				break;
			case FUNCTION_BOOLNOT:
				exprType = VarType.VARTYPE_BOOLEAN;
				break;
			case FUNCTION_SHL:
			case FUNCTION_SHR:
			case FUNCTION_USHR:
			case FUNCTION_BITNOT:
			case FUNCTION_NEG:
				exprType = getMaxVarType(new VarType[]{type1});
				break;
			case FUNCTION_ADD:
			case FUNCTION_SUB:
			case FUNCTION_MUL:
			case FUNCTION_DIV:
			case FUNCTION_REM:
				exprType = getMaxVarType(new VarType[]{type1, type2});
				break;
			case FUNCTION_AND:
			case FUNCTION_OR:
			case FUNCTION_XOR:
				if(type1.type == CodeConstants.TYPE_BOOLEAN & type2.type == CodeConstants.TYPE_BOOLEAN) {
					exprType = VarType.VARTYPE_BOOLEAN;
				} else {
					exprType = getMaxVarType(new VarType[]{type1, type2});
				}
			}
		} else if(functype == FUNCTION_CAST){
			exprType = lstOperands.get(1).getExprType();
		} else if(functype == FUNCTION_IIF){
			Exprent param1 = lstOperands.get(1);
			Exprent param2 = lstOperands.get(2);
			VarType supertype = VarType.getCommonSupertype(param1.getExprType(), param2.getExprType());
			
			if(param1.type == Exprent.EXPRENT_CONST && param2.type == Exprent.EXPRENT_CONST &&
				supertype.type != CodeConstants.TYPE_BOOLEAN && VarType.VARTYPE_INT.isSuperset(supertype)) {
					exprType = VarType.VARTYPE_INT;
			} else {
				exprType = supertype;
			}
		} else if(functype == FUNCTION_STRCONCAT){
			exprType = VarType.VARTYPE_STRING;
		} else if(functype >= FUNCTION_EQ){
			exprType = VarType.VARTYPE_BOOLEAN;
		} else if(functype == FUNCTION_INSTANCEOF) {
			exprType = VarType.VARTYPE_BOOLEAN;
		} else if(functype >= FUNCTION_ARRAYLENGTH) {
			exprType = VarType.VARTYPE_INT;
		} else {
			exprType = types[functype - FUNCTION_I2L];
		}
		
		return exprType;
	}
	
	public int getExprentUse() {

		if(functype >= FUNCTION_IMM && functype <= FUNCTION_PPI) {
			return 0;
		} else {
			int ret = Exprent.MULTIPLE_USES | Exprent.SIDE_EFFECTS_FREE;
			for(Exprent expr: lstOperands) {
				ret &= expr.getExprentUse();
			}
			return ret;
		}
	}
	
	public CheckTypesResult checkExprTypeBounds() {
		CheckTypesResult result = new CheckTypesResult();

		Exprent param1 = lstOperands.get(0);
		VarType type1 = param1.getExprType();
		Exprent param2 = null;
		VarType type2 = null;
		
		if(lstOperands.size() > 1) {
			param2 = lstOperands.get(1);
			type2 = param2.getExprType();
		}

		switch(functype) {
		case FUNCTION_IIF:
			VarType supertype = getExprType();
			if(supertype == null) {
				supertype = getExprType();
			}
			result.addMinTypeExprent(param1, VarType.VARTYPE_BOOLEAN);
			result.addMinTypeExprent(param2, VarType.getMinTypeInFamily(supertype.type_family));
			result.addMinTypeExprent(lstOperands.get(2), VarType.getMinTypeInFamily(supertype.type_family));
			break;
		case FUNCTION_I2L:
		case FUNCTION_I2F:
		case FUNCTION_I2D:
		case FUNCTION_I2B:
		case FUNCTION_I2C:
		case FUNCTION_I2S:
			result.addMinTypeExprent(param1, VarType.VARTYPE_BYTECHAR);
			result.addMaxTypeExprent(param1, VarType.VARTYPE_INT);
			break;
		case FUNCTION_IMM:
		case FUNCTION_IPP:
		case FUNCTION_MMI:
		case FUNCTION_PPI:
			result.addMinTypeExprent(param1, implicitType);
			result.addMaxTypeExprent(param1, implicitType);
			break;
		case FUNCTION_ADD:
		case FUNCTION_SUB:
		case FUNCTION_MUL:
		case FUNCTION_DIV:
		case FUNCTION_REM:
		case FUNCTION_SHL:
		case FUNCTION_SHR:
		case FUNCTION_USHR:
		case FUNCTION_LT:
		case FUNCTION_GE:
		case FUNCTION_GT:
		case FUNCTION_LE:
			result.addMinTypeExprent(param2, VarType.VARTYPE_BYTECHAR);
		case FUNCTION_BITNOT:
		// case FUNCTION_BOOLNOT:
		case FUNCTION_NEG:
			result.addMinTypeExprent(param1, VarType.VARTYPE_BYTECHAR);
			break;
		case FUNCTION_AND:
		case FUNCTION_OR:
		case FUNCTION_XOR:
			if(type1.type == CodeConstants.TYPE_BOOLEAN && 
						((type1.convinfo & VarType.FALSEBOOLEAN) != 0 || type2.type != CodeConstants.TYPE_BOOLEAN)) {
				result.addMinTypeExprent(param1, VarType.VARTYPE_BYTECHAR);
			} 
			if(type2.type == CodeConstants.TYPE_BOOLEAN && 
					((type2.convinfo & VarType.FALSEBOOLEAN) != 0 || type1.type != CodeConstants.TYPE_BOOLEAN)) {
				result.addMinTypeExprent(param2, VarType.VARTYPE_BYTECHAR);
			}
			break;
		case FUNCTION_EQ:
		case FUNCTION_NE:
			
			if(type1.type == CodeConstants.TYPE_BOOLEAN) {
				
				if(type2.isStrictSuperset(type1)) {
					result.addMinTypeExprent(param1, VarType.VARTYPE_BYTECHAR);
				} else {
					if(param1.type == Exprent.EXPRENT_CONST && !((ConstExprent)param1).hasBooleanValue()) {
						if(param2.type != Exprent.EXPRENT_CONST || !((ConstExprent)param2).hasBooleanValue()) { // variable or not boolean constant
							result.addMinTypeExprent(param1, VarType.VARTYPE_BYTECHAR);
						}
					}
				}
			}
			
			if(type2.type == CodeConstants.TYPE_BOOLEAN) {
				
				if(type1.isStrictSuperset(type2)) {
					result.addMinTypeExprent(param2, VarType.VARTYPE_BYTECHAR);
				} else {
					if(param2.type == Exprent.EXPRENT_CONST && !((ConstExprent)param2).hasBooleanValue()) {
						if(param1.type != Exprent.EXPRENT_CONST || !((ConstExprent)param1).hasBooleanValue()) { // variable or not boolean constant
							result.addMinTypeExprent(param2, VarType.VARTYPE_BYTECHAR);
						}
					}
				}
			}
		}
		
		return result;
	}
	
	public List<Exprent> getAllExprents() {
		List<Exprent> lst = new ArrayList<Exprent>();
		lst.addAll(lstOperands);
		return lst;
	}
	
	public Exprent copy() {
		List<Exprent> lst = new ArrayList<Exprent>();
		for(Exprent expr: lstOperands) {
			lst.add(expr.copy());
		}
		FunctionExprent func = new FunctionExprent(functype, lst);
		func.setImplicitType(implicitType);
		
		return func;
	}
	
	public boolean equals(Object o) {
    if(o == this) return true;
    if(o == null || !(o instanceof FunctionExprent)) return false;

    FunctionExprent fe = (FunctionExprent)o;
    return functype==fe.getFunctype() &&
        InterpreterUtil.equalLists(lstOperands, fe.getLstOperands()); // TODO: order of operands insignificant
  }
	
	public void replaceExprent(Exprent oldexpr, Exprent newexpr) {
		for(int i=0;i<lstOperands.size();i++) {
			if(oldexpr == lstOperands.get(i)) {
				lstOperands.set(i, newexpr);
			} 
		}
	}

	public String toJava(int indent) {
		
		if(functype <= FUNCTION_USHR) {
			return wrapOperandString(lstOperands.get(0), false, indent)+operators[functype]+
						wrapOperandString(lstOperands.get(1), true, indent);
		} 

		if(functype >= FUNCTION_EQ) {
			return wrapOperandString(lstOperands.get(0), false, indent)+operators[functype-FUNCTION_EQ+11]+
						wrapOperandString(lstOperands.get(1), true, indent);
		} 
		
		switch(functype) {
		case FUNCTION_BITNOT:
			return "~"+wrapOperandString(lstOperands.get(0), true, indent);
		case FUNCTION_BOOLNOT:
			return "!"+wrapOperandString(lstOperands.get(0), true, indent);
		case FUNCTION_NEG:
			return "-"+wrapOperandString(lstOperands.get(0), true, indent);
		case FUNCTION_CAST:
			return "("+lstOperands.get(1).toJava(indent)+")"+wrapOperandString(lstOperands.get(0), true, indent);
		case FUNCTION_ARRAYLENGTH:
			Exprent arr = lstOperands.get(0);
			
			String res = wrapOperandString(arr, false, indent);
			if(arr.getExprType().arraydim == 0) {
				VarType objarr = VarType.VARTYPE_OBJECT.copy();
				objarr.arraydim = 1; // type family does not change
				
				res = "(("+ExprProcessor.getCastTypeName(objarr)+")"+res+")";
			}
			return res+".length";
		case FUNCTION_IIF:
			return wrapOperandString(lstOperands.get(0), true, indent)+"?"+wrapOperandString(lstOperands.get(1), true, indent)+":"+
					wrapOperandString(lstOperands.get(2), true, indent);
		case FUNCTION_IPP:
			return wrapOperandString(lstOperands.get(0), true, indent)+"++";
		case FUNCTION_PPI:
			return "++"+wrapOperandString(lstOperands.get(0), true, indent);
		case FUNCTION_IMM:
			return wrapOperandString(lstOperands.get(0), true, indent)+"--";
		case FUNCTION_MMI:
			return "--"+wrapOperandString(lstOperands.get(0), true, indent);
		case FUNCTION_INSTANCEOF:
			return wrapOperandString(lstOperands.get(0), true, indent)+" instanceof "+wrapOperandString(lstOperands.get(1), true, indent);
		}
		
		if(functype <= FUNCTION_I2S) {
			return "("+ExprProcessor.getTypeName(types[functype - FUNCTION_I2L])+")"+wrapOperandString(lstOperands.get(0), true, indent);
		}
		
//		return "<unknown function>";
		throw new RuntimeException("invalid function"); 
	}
	
	public int getPrecedence() {
		return getPrecedence(functype);
	}

	public static int getPrecedence(int func) {
		return precedence[func];
	}

	public VarType getSimpleCastType() {
		return types[functype - FUNCTION_I2L];
	}
	
	private String wrapOperandString(Exprent expr, boolean eq, int indent) {
		
		int myprec = getPrecedence();
		int exprprec = expr.getPrecedence();
		
		boolean parentheses = exprprec > myprec;
		if(!parentheses && eq) {
			parentheses = (exprprec == myprec);
			if(parentheses) {
				if(expr.type == Exprent.EXPRENT_FUNCTION && 
						((FunctionExprent)expr).getFunctype() == functype) {
					parentheses = !associativity.contains(functype);
				}
			}
		}
		
		String res = expr.toJava(indent);

		if(parentheses) {
			res = "("+res+")";
		}
		
		return res;
	}
	
	private VarType getMaxVarType(VarType[] arr) {
		
		int[] types = new int[] {CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_LONG};
		VarType[] vartypes = new VarType[] {VarType.VARTYPE_DOUBLE, VarType.VARTYPE_FLOAT, VarType.VARTYPE_LONG};
		
		for(int i=0;i<types.length;i++) {
			for(int j=0;j<arr.length;j++) {
				if(arr[j].type == types[i]) {
					return vartypes[i];
				}
			}
		}
		
		return VarType.VARTYPE_INT;
	}
	
	// *****************************************************************************
	// getter and setter methods
	// *****************************************************************************
	
	public int getFunctype() {
		return functype;
	}

	public void setFunctype(int functype) {
		this.functype = functype;
	}

	public List<Exprent> getLstOperands() {
		return lstOperands;
	}

	public void setLstOperands(List<Exprent> lstOperands) {
		this.lstOperands = lstOperands;
	}

	public VarType getImplicitType() {
		return implicitType;
	}

	public void setImplicitType(VarType implicitType) {
		this.implicitType = implicitType;
	}
	
}

