package me.lpk.analysis;

import java.util.List;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.Value;

public class StackHelper {

	public InsnValue createConstant(AbstractInsnNode insn) throws AnalyzerException {
		switch (insn.getOpcode()) {
		case Opcodes.ACONST_NULL:
			return InsnValue.NULL_REFERENCE_VALUE;
		case Opcodes.ICONST_M1:
		case Opcodes.ICONST_0:
		case Opcodes.ICONST_1:
		case Opcodes.ICONST_2:
		case Opcodes.ICONST_3:
		case Opcodes.ICONST_4:
		case Opcodes.ICONST_5:
		case Opcodes.BIPUSH:
		case Opcodes.SIPUSH:
			return InsnValue.intValue(insn);
		case Opcodes.LCONST_0:
		case Opcodes.LCONST_1:
			return InsnValue.longValue(insn.getOpcode());
		case Opcodes.FCONST_0:
		case Opcodes.FCONST_1:
		case Opcodes.FCONST_2:
			return InsnValue.floatValue(insn.getOpcode());
		case Opcodes.DCONST_0:
		case Opcodes.DCONST_1:
			return InsnValue.doubleValue(insn.getOpcode());
		case Opcodes.LDC:
			Object obj = ((LdcInsnNode) insn).cst;
			if (obj instanceof Type) {
				return new InsnValue((Type) obj);
			} else {
				Type t = Type.getType(obj.getClass());
				int sort = t.getSort();
				// Non-included types:
				// Type.ARRAY
				// Type.VOID
				// Type.METHOD
				switch (sort) {
				case Type.BOOLEAN:
					return InsnValue.intValue((int) obj);
				case Type.CHAR:
					return InsnValue.charValue((char) obj);
				case Type.BYTE:
					return InsnValue.byteValue((byte) obj);
				case Type.SHORT:
					return InsnValue.shortValue((short) obj);
				case Type.INT:
					return InsnValue.intValue((int) obj);
				case Type.FLOAT:
					return InsnValue.floatValue((float) obj);
				case Type.LONG:
					return InsnValue.longValue((long) obj);
				case Type.DOUBLE:
					return InsnValue.doubleValue((double) obj);
				case Type.OBJECT:
					return new InsnValue(t, obj);
				}
				return new InsnValue(t);
			}
		case Opcodes.NEW:
			return new InsnValue(Type.getType(((TypeInsnNode) insn).desc));
		case Opcodes.JSR:
			// TODO: IDK if this is right.
			return InsnValue.REFERENCE_VALUE;
		}

		return null;
	}

	public InsnValue loadLocal(AbstractInsnNode insn, InsnValue value) throws AnalyzerException {
		// Pretty sure with how this is called nothing needs to be done...
		return value;
	}

	public InsnValue loadFromArray(AbstractInsnNode insn, InsnValue value1, InsnValue indexValue) throws AnalyzerException {
		Object indexObj = indexValue.getValue(), arrayObj = value1.getValue();
		int index = indexObj == null ? -1 : ((Number) indexObj).intValue();
		boolean arrayNotSimulated = arrayObj == null;
		Type t = arrayNotSimulated ? null : Type.getType(arrayObj.getClass());
		switch (insn.getOpcode()) {
		case Opcodes.IALOAD:
			if (arrayNotSimulated) {
				return InsnValue.INT_VALUE;
			}
			// Sometimes Object[] contain values. Stringer obfuscator does this.
			// TODO: Have this sort of check for others?
			boolean oarr = t.equals(InsnValue.REFERENCE_ARR_VALUE.getType());
			if (oarr) {
				int[] ia = (int[]) arrayObj;
				return InsnValue.intValue(ia[index]);
			}else{
				Object[] obarr = (Object[]) arrayObj;
				Object o = obarr[index];
				if (o == null){
					return InsnValue.INT_VALUE;
				}
				return InsnValue.intValue(((Number) o).intValue());
			}
		case Opcodes.LALOAD:
			if (arrayNotSimulated) {
				return InsnValue.LONG_VALUE;
			}
			long[] la = (long[]) arrayObj;
			return InsnValue.longValue(la[index]);
		case Opcodes.FALOAD:
			if (arrayNotSimulated) {
				return InsnValue.FLOAT_VALUE;
			}
			float[] fa = (float[]) arrayObj;
			return InsnValue.floatValue(fa[index]);
		case Opcodes.DALOAD:
			if (arrayNotSimulated) {
				return InsnValue.DOUBLE_VALUE;
			}
			double[] da = (double[]) arrayObj;
			return InsnValue.doubleValue(da[index]);
		case Opcodes.AALOAD:
			// TODO: Check if it's an object array, but the contents aren't objects
			return InsnValue.REFERENCE_VALUE;
		case Opcodes.BALOAD:
			if (arrayNotSimulated) {
				return InsnValue.BYTE_VALUE;
			}
			boolean db = t.equals(InsnValue.DOUBLE_ARR_VALUE.getType()), in = t.equals(InsnValue.INT_ARR_VALUE.getType()), saa = t.equals(InsnValue.SHORT_ARR_VALUE.getType());
			if (db) {
				double[] dba = (double[]) arrayObj;
				return InsnValue.intValue(dba[index]);
			} else if (in) {
				int[] ina = (int[]) arrayObj;
				return InsnValue.intValue(ina[index]);
			} else if (saa){
				short[] saaa = (short[]) arrayObj;
				return InsnValue.intValue(saaa[index]);
			} else {
				System.err.println("UNKNOWN TYPE BALOAD: " + t);
				throw new RuntimeException();
			}

		case Opcodes.CALOAD:
			if (arrayNotSimulated) {
				return InsnValue.CHAR_VALUE;
			}
			char[] ca = (char[]) arrayObj;
			return InsnValue.charValue(ca[index]);
		case Opcodes.SALOAD:
			if (arrayNotSimulated) {
				return InsnValue.SHORT_VALUE;
			}
			short[] sa = (short[]) arrayObj;
			return InsnValue.intValue((short) sa[index]);
		}

		return null;
	}

	public InsnValue doMath(AbstractInsnNode insn, InsnValue value1, InsnValue value2) {
		Object o1 = value1.getValue(), o2 = value2.getValue();
		switch (insn.getOpcode()) {
		case Opcodes.IADD:
		case Opcodes.ISUB:
		case Opcodes.IMUL:
		case Opcodes.IDIV:
		case Opcodes.IREM:
		case Opcodes.ISHL:
		case Opcodes.ISHR:
		case Opcodes.IUSHR:
		case Opcodes.IAND:
		case Opcodes.IOR:
		case Opcodes.IXOR:
			if (o1 == null || o2 == null) {
				return InsnValue.INT_VALUE;
			}
			int i1 = ((Number) o1).intValue(), i2 = ((Number) o2).intValue();
			switch (insn.getOpcode()) {
			case Opcodes.IADD:
				return InsnValue.intValue(i1 + i2);
			case Opcodes.ISUB:
				return InsnValue.intValue(i1 - i2);
			case Opcodes.IMUL:
				return InsnValue.intValue(i1 * i2);
			case Opcodes.IDIV:
				return InsnValue.intValue(i1 / i2);
			case Opcodes.IREM:
				return InsnValue.intValue(i1 % i2);
			case Opcodes.ISHL:
				return InsnValue.intValue(i1 << i2);
			case Opcodes.ISHR:
				return InsnValue.intValue(i1 >> i2);
			case Opcodes.IUSHR:
				return InsnValue.intValue(i1 >>> i2);
			case Opcodes.IAND:
				return InsnValue.intValue(i1 & i2);
			case Opcodes.IOR:
				return InsnValue.intValue(i1 | i2);
			case Opcodes.IXOR:
				return InsnValue.intValue(i1 ^ i2);
			}
		case Opcodes.LADD:
		case Opcodes.LSUB:
		case Opcodes.LMUL:
		case Opcodes.LDIV:
		case Opcodes.LREM:
		case Opcodes.LSHL:
		case Opcodes.LSHR:
		case Opcodes.LUSHR:
		case Opcodes.LAND:
		case Opcodes.LOR:
		case Opcodes.LXOR:
			if (o1 == null || o2 == null) {
				return InsnValue.LONG_VALUE;
			}
			long l1 = ((Number) o1).longValue(), l2 = ((Number) o2).longValue();
			switch (insn.getOpcode()) {
			case Opcodes.LADD:
				return InsnValue.longValue(l1 + l2);
			case Opcodes.LSUB:
				return InsnValue.longValue(l1 - l2);
			case Opcodes.LMUL:
				return InsnValue.longValue(l1 * l2);
			case Opcodes.LDIV:
				return InsnValue.longValue(l1 / l2);
			case Opcodes.LREM:
				return InsnValue.longValue(l1 % l2);
			case Opcodes.LSHL:
				return InsnValue.longValue(l1 << l2);
			case Opcodes.LSHR:
				return InsnValue.longValue(l1 >> l2);
			case Opcodes.LUSHR:
				return InsnValue.longValue(l1 >>> l2);
			case Opcodes.LAND:
				return InsnValue.longValue(l1 & l2);
			case Opcodes.LOR:
				return InsnValue.longValue(l1 | l2);
			case Opcodes.LXOR:
				return InsnValue.longValue(l1 ^ l2);
			}
		case Opcodes.FADD:
		case Opcodes.FSUB:
		case Opcodes.FMUL:
		case Opcodes.FDIV:
		case Opcodes.FREM:
			if (o1 == null || o2 == null) {
				return InsnValue.FLOAT_VALUE;
			}
			float f1 = (float) o1, f2 = (float) o2;
			switch (insn.getOpcode()) {
			case Opcodes.FADD:
				return InsnValue.floatValue(f1 + f2);
			case Opcodes.FSUB:
				return InsnValue.floatValue(f1 - f2);
			case Opcodes.FMUL:
				return InsnValue.floatValue(f1 * f2);
			case Opcodes.FDIV:
				return InsnValue.floatValue(f1 / f2);
			case Opcodes.FREM:
				return InsnValue.floatValue(f1 % f2);
			}
		case Opcodes.DADD:
		case Opcodes.DSUB:
		case Opcodes.DMUL:
		case Opcodes.DDIV:
		case Opcodes.DREM:
			if (o1 == null || o2 == null) {
				return InsnValue.DOUBLE_VALUE;
			}
			double d1 = (double) o1, d2 = (double) o2;
			switch (insn.getOpcode()) {
			case Opcodes.DADD:
				return InsnValue.doubleValue(d1 + d2);
			case Opcodes.DSUB:
				return InsnValue.doubleValue(d1 - d2);
			case Opcodes.DMUL:
				return InsnValue.doubleValue(d1 * d2);
			case Opcodes.DDIV:
				return InsnValue.doubleValue(d1 / d2);
			case Opcodes.DREM:
				return InsnValue.doubleValue(d1 % d2);
			}
		}

		return null;
	}

	public InsnValue storeInArray(AbstractInsnNode insn, InsnValue arrayRef, InsnValue index, InsnValue value) throws AnalyzerException {
		boolean anythingNull = arrayRef.getValue() == null || index.getValue() == null || value.getValue() == null;
		int i = anythingNull ? -1 : ((Number) index.getValue()).intValue();
		switch (insn.getOpcode()) {
		case Opcodes.IASTORE:
		case Opcodes.CASTORE:
			if (anythingNull) {
				return InsnValue.INT_ARR_VALUE;
			}
			if (arrayRef.getType().equals(InsnValue.INT_ARR_VALUE.getType())) {
				// Unsure why arrayRef when IASTORE isn't always [I
				int[] ia = (int[]) arrayRef.getValue();
				ia[i] = (int) value.getValue();
				return new InsnValue(InsnValue.INT_ARR_VALUE.getType(), ia);
			} else {
				return InsnValue.INT_ARR_VALUE;
			}

		case Opcodes.LASTORE:
			if (anythingNull) {
				return InsnValue.LONG_ARR_VALUE;
			}
			long[] la = (long[]) arrayRef.getValue();
			la[i] = (long) value.getValue();
			return new InsnValue(InsnValue.LONG_ARR_VALUE.getType(), la);
		case Opcodes.FASTORE:
			if (anythingNull) {
				return InsnValue.FLOAT_ARR_VALUE;
			}
			float[] fa = (float[]) arrayRef.getValue();
			fa[i] = (float) value.getValue();
			return new InsnValue(InsnValue.FLOAT_ARR_VALUE.getType(), fa);
		case Opcodes.DASTORE:
			if (anythingNull) {
				return InsnValue.DOUBLE_ARR_VALUE;
			}
			double[] da = (double[]) arrayRef.getValue();
			da[i] = ((Number) value.getValue()).doubleValue();
			return new InsnValue(InsnValue.DOUBLE_ARR_VALUE.getType(), da);
		case Opcodes.BASTORE:
			if (anythingNull) {
				return InsnValue.DOUBLE_ARR_VALUE;
			}
			Type t = Type.getType(arrayRef.getValue().getClass());
			boolean db = t.equals(InsnValue.DOUBLE_ARR_VALUE.getType()), in = t.equals(InsnValue.INT_ARR_VALUE.getType()), saaa = t.equals(InsnValue.SHORT_ARR_VALUE.getType());
			if (db) {
				double[] da2 = (double[]) arrayRef.getValue();
				da2[i] = ((Number) value.getValue()).doubleValue();
				return new InsnValue(InsnValue.DOUBLE_ARR_VALUE.getType(), da2);
			} else if (in) {
				int[] ia = (int[]) arrayRef.getValue();
				ia[i] = (int) value.getValue();
				return new InsnValue(InsnValue.INT_ARR_VALUE.getType(), ia);
			} else if (saaa) {
				short[] saa = (short[]) arrayRef.getValue();
				saa[i] = ((Number)value.getValue()).shortValue();
				return new InsnValue(InsnValue.SHORT_ARR_VALUE.getType(), saa);
			} else {
				System.err.println("UNKNOWN BASTORE TYPE: " + t);
				throw new RuntimeException();
			}
		case Opcodes.SASTORE:
			if (anythingNull) {
				return InsnValue.SHORT_VALUE;
			}
			short[] sa = (short[]) arrayRef.getValue();
			sa[i] = (short) value.getValue();
			return new InsnValue(InsnValue.SHORT_ARR_VALUE.getType(), sa);
		case Opcodes.AASTORE:
			return InsnValue.REFERENCE_ARR_VALUE;

		// Can't exactly cast anything to Object[] willy nilly...
		//
		// Object[] aa = (Object[]) arrayRef.getValue();
		//
		// aa[i] = value.getValue();
		//
		// return new InsnValue(InsnValue.(Find the type).getType(), ca);
		}

		return null;
	}

	public InsnValue incrementLocal(IincInsnNode iinc, InsnValue value) {
		Object obj = value.getValue();
		if (obj == null) {
			return newValue(value.getType());
		}
		if (value.getType().equals(Type.BYTE_TYPE)) {
			return InsnValue.byteValue((byte) (((Number) obj).byteValue() + iinc.incr));
		} else if (value.getType().equals(Type.INT_TYPE)) {
			return InsnValue.byteValue((int) (((Number) obj).intValue() + iinc.incr));
		} else if (value.getType().equals(Type.CHAR_TYPE)) {
			return InsnValue.charValue((char) (((Number) obj).intValue() + iinc.incr));
		} else if (value.getType().equals(Type.LONG_TYPE)) {
			return InsnValue.longValue((long) (((Number) obj).longValue() + iinc.incr));
		} else if (value.getType().equals(Type.DOUBLE_TYPE)) {
			return InsnValue.doubleValue((double) (((Number) obj).doubleValue() + iinc.incr));
		} else if (value.getType().equals(Type.FLOAT_TYPE)) {
			return InsnValue.floatValue((float) (((Number) obj).floatValue() + iinc.incr));
		} else if (value.getType().equals(Type.SHORT_TYPE)) {
			return InsnValue.shortValue((short) (((Number) obj).shortValue() + iinc.incr));
		}

		return null;
	}

	public InsnValue convertValue(AbstractInsnNode insn, InsnValue value) {
		Object obj = value.getValue();
		switch (insn.getOpcode()) {
		case Opcodes.I2L:
		case Opcodes.D2L:
		case Opcodes.F2L:
			if (obj == null) {
				return InsnValue.LONG_VALUE;
			}
			return InsnValue.longValue(((Number) obj).longValue());
		case Opcodes.I2F:
		case Opcodes.L2F:
		case Opcodes.D2F:
			if (obj == null) {
				return InsnValue.FLOAT_VALUE;
			}
			return InsnValue.floatValue(((Number) obj).floatValue());
		case Opcodes.L2I:
		case Opcodes.F2I:
		case Opcodes.D2I:
			if (obj == null) {
				return InsnValue.INT_VALUE;
			}
			return InsnValue.intValue(((Number) obj).intValue());
		case Opcodes.I2D:
		case Opcodes.L2D:
		case Opcodes.F2D:
			if (obj == null) {
				return InsnValue.DOUBLE_VALUE;
			}
			return InsnValue.doubleValue(((Number) obj).doubleValue());
		case Opcodes.I2B:
			if (obj == null) {
				return InsnValue.BYTE_VALUE;
			}
			return InsnValue.byteValue(((Number) obj).byteValue());
		case Opcodes.I2C:
			if (obj == null) {
				return InsnValue.CHAR_VALUE;
			}
			return InsnValue.charValue(((Number) obj).intValue());
		case Opcodes.I2S:
			if (obj == null) {
				return InsnValue.SHORT_VALUE;
			}
			return InsnValue.shortValue(((Number) obj).shortValue());
		}

		return null;
	}

	public InsnValue invertValue(AbstractInsnNode insn, InsnValue value) {
		Object obj = value.getValue();
		switch (insn.getOpcode()) {
		case Opcodes.INEG:
			if (obj == null) {
				return InsnValue.INT_VALUE;
			}
			return InsnValue.intValue(-1 * (int) obj);
		case Opcodes.LNEG:
			if (obj == null) {
				return InsnValue.LONG_VALUE;
			}
			return InsnValue.longValue(-1L * (long) obj);
		case Opcodes.FNEG:
			if (obj == null) {
				return InsnValue.FLOAT_VALUE;
			}
			return InsnValue.floatValue(-1F * (float) obj);
		case Opcodes.DNEG:
			if (obj == null) {
				return InsnValue.DOUBLE_VALUE;
			}
			return InsnValue.doubleValue(-1D * (double) obj);
		}

		return null;
	}

	public InsnValue getStatic(FieldInsnNode fin) {
		return new InsnValue(Type.getType(fin.desc));
	}

	/**
	 * @param fin
	 * @param value
	 *            Object instance which the fields is being retrieved from.
	 * @return
	 */
	public InsnValue getField(FieldInsnNode fin, InsnValue value) {
		return new InsnValue(Type.getType(fin.desc));
	}

	public void putStatic(FieldInsnNode fin, InsnValue value) {
		// Not much has to be done here...
	}

	public void putField(FieldInsnNode insn, InsnValue value1, InsnValue value2) {
		// Not much has to be done here...
	}

	public InsnValue onMethod(AbstractInsnNode insn, List<InsnValue> values) {
		String desc = "V";
		if (insn.getOpcode() == Opcodes.INVOKEDYNAMIC){
			desc = ((InvokeDynamicInsnNode) insn).desc;
		}else{
			desc = ((MethodInsnNode) insn).desc;
		}
		// Until I'm ready to simulate method calls the opcode here really
		// doesn't matter.
		/*
		 * switch (insn.getOpcode()) { case Opcodes.INVOKEDYNAMIC: case
		 * Opcodes.INVOKESPECIAL: case Opcodes.INVOKEINTERFACE: case
		 * Opcodes.INVOKESTATIC: case Opcodes.INVOKEVIRTUAL: }
		 */
		if (desc.endsWith("V")) {
			return null;
		}
		return new InsnValue(Type.getReturnType(desc));
	}

	public InsnValue compareConstants(AbstractInsnNode insn, InsnValue value1, InsnValue value2) {
		Object o = value1.getValue(), oo = value2.getValue();
		if (o == null || oo == null) {
			// Can't compare since the values haven't been resolved.
			return InsnValue.INT_VALUE;
		}
		int v = 0;
		switch (insn.getOpcode()) {
		case Opcodes.LCMP:
			long l1 = (long) o, l2 = (long) oo;
			v = (l1 == l2) ? 0 : (l2 < l1) ? 1 : -1;
			break;
		case Opcodes.FCMPL:
		case Opcodes.FCMPG:
			float f1 = (float) o, f2 = (float) oo;
			if (f1 == Float.NaN || f2 == Float.NaN) {
				v = insn.getOpcode() == Opcodes.FCMPG ? -1 : 1;
			} else {
				v = (f1 == f2) ? 0 : (f2 < f1) ? 1 : -1;
			}
			break;
		case Opcodes.DCMPL:
		case Opcodes.DCMPG:
			double d1 = (float) o, d2 = (double) oo;
			if (d1 == Float.NaN || d2 == Float.NaN) {
				v = insn.getOpcode() == Opcodes.DCMPG ? -1 : 1;
			} else {
				v = (d1 == d2) ? 0 : (d2 < d1) ? 1 : -1;
			}
			break;
		case Opcodes.IF_ICMPEQ:
		case Opcodes.IF_ICMPNE:
		case Opcodes.IF_ICMPLT:
		case Opcodes.IF_ICMPGE:
		case Opcodes.IF_ICMPGT:
		case Opcodes.IF_ICMPLE:
			int i1 = (int) o, i2 = (int) oo;
			switch (insn.getOpcode()) {
			case Opcodes.IF_ICMPEQ:
				v = i2 == i1 ? 1 : 0;
				break;
			case Opcodes.IF_ICMPNE:
				v = i2 != i1 ? 1 : 0;
				break;
			case Opcodes.IF_ICMPLT:
				v = i2 < i1 ? 1 : 0;
				break;
			case Opcodes.IF_ICMPLE:
				v = i2 <= i1 ? 1 : 0;
				break;
			case Opcodes.IF_ICMPGE:
				v = i2 >= i1 ? 1 : 0;
				break;
			case Opcodes.IF_ICMPGT:
				v = i2 > i1 ? 1 : 0;
				break;
			}
			break;
		case Opcodes.IF_ACMPEQ:
		case Opcodes.IF_ACMPNE:
			v = ((insn.getOpcode() == Opcodes.IF_ACMPNE) ? !o.equals(oo) : o.equals(oo)) ? 1 : 0;
			break;
		}
		return InsnValue.intValue(v);
	}

	public InsnValue compareConstant(AbstractInsnNode insn, InsnValue value) {
		if (value.getValue() == null) {
			return InsnValue.INT_VALUE;
		}
		int i = ((Number)value.getValue()).intValue();
		switch (insn.getOpcode()) {
		case Opcodes.IFEQ:
			return InsnValue.intValue(i == 0);
		case Opcodes.IFNE:
			return InsnValue.intValue(i != 0);
		case Opcodes.IFLE:
			return InsnValue.intValue(i <= 0);
		case Opcodes.IFLT:
			return InsnValue.intValue(i < 0);
		case Opcodes.IFGE:
			return InsnValue.intValue(i >= 0);
		case Opcodes.IFGT:
			return InsnValue.intValue(i > 0);
		}

		return null;
	}

	public InsnValue checkNull(AbstractInsnNode insn, InsnValue value) {
		switch (insn.getOpcode()) {
		case Opcodes.IFNULL:
			return InsnValue.intValue(value.getValue() == null);
		case Opcodes.IFNONNULL:
			return InsnValue.intValue(value.getValue() != null);
		}

		return null;
	}

	public InsnValue casting(TypeInsnNode tin, InsnValue value) {
		switch (tin.getOpcode()) {
		case Opcodes.CHECKCAST:
			return value;
		case Opcodes.INSTANCEOF:
			if (value.getValue() == null) {
				return InsnValue.intValue(0);
			}
			Class<?> clazz = value.getValue().getClass();
			try {
				Class<?> compared = Class.forName(Type.getType(tin.desc).getClassName());
				return InsnValue.intValue(clazz.isAssignableFrom(compared));
			} catch (ClassNotFoundException e) {
			}
			return InsnValue.intValue(0);
		}

		return null;
	}

	public void monitor(AbstractInsnNode insn, InsnValue value) {
		// Nothing needs to be done
	}

	public void throwException(AbstractInsnNode insn, InsnValue value) {
		// Not much has to be done here...
	}

	public InsnValue getSwitchValue(AbstractInsnNode insn, InsnValue value) {
		if (value.getValue() == null) {
			return InsnValue.intValue(-1);
		}
		int i = (int) value.getValue();
		switch (insn.getOpcode()) {
		case Opcodes.TABLESWITCH:
			TableSwitchInsnNode tsin = (TableSwitchInsnNode) insn;
			if (i < tsin.min || i > tsin.max) {
				return InsnValue.intValue(-1);
			}
			return InsnValue.intValue(i);
		case Opcodes.LOOKUPSWITCH:
			LookupSwitchInsnNode lsin = (LookupSwitchInsnNode) insn;
			for (Object o : lsin.keys) {
				if (o.equals(i)) {
					return InsnValue.intValue(i);
				}
			}
			return InsnValue.intValue(-1);
		}

		return null;
	}

	public InsnValue array(AbstractInsnNode insn, InsnValue value) {
		Object obj = value.getValue();
		int n = -1;
		switch (insn.getOpcode()) {
		case Opcodes.NEWARRAY:
			IntInsnNode iin = (IntInsnNode) insn;
			n = obj == null ? -1 : (int) obj;
			switch (iin.operand) {
			case Type.BOOLEAN:
			case Type.INT:
				if (n == -1) {
					return InsnValue.INT_ARR_VALUE;
				}
				return new InsnValue(InsnValue.INT_ARR_VALUE.getType(), new int[n]);
			case Type.CHAR:
				if (n == -1) {
					return InsnValue.CHAR_ARR_VALUE;
				}
				return new InsnValue(InsnValue.CHAR_ARR_VALUE.getType(), new int[n]);
			case Type.BYTE:
				if (n == -1) {
					return InsnValue.BYTE_ARR_VALUE;
				}
				return new InsnValue(InsnValue.BYTE_ARR_VALUE.getType(), new byte[n]);
			case Type.SHORT:
				if (n == -1) {
					return InsnValue.SHORT_ARR_VALUE;
				}
				return new InsnValue(InsnValue.SHORT_ARR_VALUE.getType(), new short[n]);
			case Type.FLOAT:
				if (n == -1) {
					return InsnValue.FLOAT_ARR_VALUE;
				}
				return new InsnValue(InsnValue.FLOAT_ARR_VALUE.getType(), new float[n]);
			case Type.LONG:
				if (n == -1) {
					return InsnValue.LONG_ARR_VALUE;
				}
				return new InsnValue(InsnValue.LONG_ARR_VALUE.getType(), new long[n]);
			case Type.DOUBLE:
				if (n == -1) {
					return InsnValue.DOUBLE_ARR_VALUE;
				}
				return new InsnValue(InsnValue.DOUBLE_ARR_VALUE.getType(), new double[n]);
			case Type.OBJECT:
				if (n == -1) {
					return InsnValue.REFERENCE_ARR_VALUE;
				}
				return new InsnValue(InsnValue.REFERENCE_ARR_VALUE.getType(), new Object[n]);
			}
			break;
		case Opcodes.ANEWARRAY:
			if (n == -1) {
				n = obj == null ? -1 : (int) obj;
			}
			TypeInsnNode tin = (TypeInsnNode) insn;
			Type t = Type.getType(tin.desc);
			if (t.getDescriptor().length() > 1) {
				return new InsnValue(Type.getType("[" + tin.desc));
			}
			switch (t.getSort()) {
			case Type.BOOLEAN:
			case Type.INT:
				if (n == -1) {
					return InsnValue.INT_ARR_VALUE;
				}
				return new InsnValue(InsnValue.INT_ARR_VALUE.getType(), new int[n]);
			case Type.CHAR:
				if (n == -1) {
					return InsnValue.CHAR_ARR_VALUE;
				}
				return new InsnValue(InsnValue.CHAR_ARR_VALUE.getType(), new int[n]);
			case Type.BYTE:
				if (n == -1) {
					return InsnValue.BYTE_ARR_VALUE;
				}
				return new InsnValue(InsnValue.BYTE_ARR_VALUE.getType(), new byte[n]);
			case Type.SHORT:
				if (n == -1) {
					return InsnValue.SHORT_ARR_VALUE;
				}
				return new InsnValue(InsnValue.SHORT_ARR_VALUE.getType(), new short[n]);
			case Type.FLOAT:
				if (n == -1) {
					return InsnValue.FLOAT_ARR_VALUE;
				}
				return new InsnValue(InsnValue.FLOAT_ARR_VALUE.getType(), new float[n]);
			case Type.LONG:
				if (n == -1) {
					return InsnValue.LONG_ARR_VALUE;
				}
				return new InsnValue(InsnValue.LONG_ARR_VALUE.getType(), new long[n]);
			case Type.DOUBLE:
				if (n == -1) {
					return InsnValue.DOUBLE_ARR_VALUE;
				}
				return new InsnValue(InsnValue.DOUBLE_ARR_VALUE.getType(), new double[n]);
			}
		case Opcodes.ARRAYLENGTH:
			int len = getLength(obj);
			if (len == -1) {
				return InsnValue.INT_VALUE;
			}
			return InsnValue.intValue(len);
		}

		return null;
	}

	public InsnValue onMultiANewArray(MultiANewArrayInsnNode insn, List<InsnValue> values) {
		Type t = Type.getType((insn).desc);
		// I have no idea how I would go about making new N-D arrays without
		// really REALLY ugly code.
		//
		// TODO: Suck it up and do the ugly.
		return new InsnValue(t);
	}

	public InsnValue newValue(Type type) {
		return new InsnValue(type);
	}

	private int getLength(Object obj) {
		// Ewww....
		if (obj instanceof Object[]) {
			return ((Object[]) obj).length;
		}
		if (obj instanceof boolean[]) {
			return ((boolean[]) obj).length;
		}
		if (obj instanceof byte[]) {
			return ((byte[]) obj).length;
		}
		if (obj instanceof short[]) {
			return ((short[]) obj).length;
		}
		if (obj instanceof char[]) {
			return ((char[]) obj).length;
		}
		if (obj instanceof int[]) {
			return ((int[]) obj).length;
		}
		if (obj instanceof long[]) {
			return ((long[]) obj).length;
		}
		if (obj instanceof float[]) {
			return ((float[]) obj).length;
		}
		if (obj instanceof double[]) {
			return ((double[]) obj).length;
		}
		return -1;
	}

	/**
	 * Determines if two frames are compatible.
	 * 
	 * @param oldFrame
	 * @param afterRET
	 * @param interpreter2
	 * @return
	 */
	public boolean isMergeCompatible(StackFrame oldFrame, StackFrame afterRET) {
		if (oldFrame == null || oldFrame.getStackSize() != afterRET.getStackSize()) {
			return false;
		}
		boolean changes = false;
		for (int i = 0; i < oldFrame.getLocals() + oldFrame.getStackSize(); ++i) {
			Value v = afterRET.getStack(i);
			if (v != null && !v.equals(oldFrame.getStack(i))) {
				changes = true;
			}
		}
		return changes;
	}
}