package me.lpk.analysis;

import java.util.ArrayList;
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.FrameNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
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.VarInsnNode;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.Interpreter;
import org.objectweb.asm.tree.analysis.Value;

import me.lpk.log.Logger;
import me.lpk.util.OpUtils;

/**
 * @editor Matt
 */
@SuppressWarnings("all")
public class StackFrame extends Frame {
	public AbstractInsnNode ain;
	public LabelNode jin;
	public boolean doJump;

	public StackFrame(Frame src, AbstractInsnNode ain) {
		super(src);
		this.ain = ain;
	}

	public StackFrame(int nLocals, int nStack) {
		super(nLocals, nStack);
		this.ain = null;
	}
	
	public FrameNode toFrame(){
		List stack = new ArrayList();
		for (int s = 0; s < this.getStackSize(); s++){
			stack.add(this.getStack(s));
		}
		List locals = new ArrayList();
		for (int l = 0; l< this.locals; l++){
			stack.add(this.getLocal(l));
		}
		return new FrameNode(Opcodes.F_FULL, stack.size(),stack.toArray(), locals.size(), locals.toArray());
	}

	public void execute(final AbstractInsnNode insn, final StackHelper interpreter) throws AnalyzerException {
		InsnValue value1, value2, value3, value4;
		List values;
		int var;
		doJump = false;
		switch (insn.getOpcode()) {
		case Opcodes.NOP:
			break;
		case Opcodes.ACONST_NULL:
		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.LCONST_0:
		case Opcodes.LCONST_1:
		case Opcodes.FCONST_0:
		case Opcodes.FCONST_1:
		case Opcodes.FCONST_2:
		case Opcodes.DCONST_0:
		case Opcodes.DCONST_1:
		case Opcodes.BIPUSH:
		case Opcodes.SIPUSH:
		case Opcodes.LDC:
			push(interpreter.createConstant(insn));
			break;
		case Opcodes.ILOAD:
		case Opcodes.LLOAD:
		case Opcodes.FLOAD:
		case Opcodes.DLOAD:
		case Opcodes.ALOAD:
			push(interpreter.loadLocal(insn, getLocal(((VarInsnNode) insn).var)));
			break;
		case Opcodes.IALOAD:
		case Opcodes.LALOAD:
		case Opcodes.FALOAD:
		case Opcodes.DALOAD:
		case Opcodes.AALOAD:
		case Opcodes.BALOAD:
		case Opcodes.CALOAD:
		case Opcodes.SALOAD:
			value2 = pop();
			value1 = pop();
			push(interpreter.loadFromArray(insn, value1, value2));
			break;
		case Opcodes.ISTORE:
		case Opcodes.LSTORE:
		case Opcodes.FSTORE:
		case Opcodes.DSTORE:
		case Opcodes.ASTORE:
			value1 = pop();
			value1 = interpreter.loadLocal(insn, value1);
			var = ((VarInsnNode) insn).var;
			setLocal(var, value1);
			if (value1.getSize() == 2) {
				setLocal(var + 1, interpreter.newValue(null));
			}
			if (var > 0) {
				Value local = getLocal(var - 1);
				if (local != null && local.getSize() == 2) {
					setLocal(var - 1, interpreter.newValue(null));
				}
			}
			break;
		case Opcodes.IASTORE:
		case Opcodes.LASTORE:
		case Opcodes.FASTORE:
		case Opcodes.DASTORE:
		case Opcodes.AASTORE:
		case Opcodes.BASTORE:
		case Opcodes.CASTORE:
		case Opcodes.SASTORE:
			value3 = pop();
			value2 = pop();
			value1 = pop();
			String before = value1.toString();
			// arrayRef, index, value)
			value1 = interpreter.storeInArray(insn, value1, value2, value3);
			if (value1 != null) {
				Logger.logVeryHigh("\tUpdated array value: " + before + " --> " + value1);
			} else {
				Logger.errVeryHigh("\tFailed updating array value: " + before + " --> " + value1);
			}
			break;
		case Opcodes.POP:
			if (pop().getSize() == 2) {
				throw new AnalyzerException(insn, "Illegal use of POP");
			}
			break;
		case Opcodes.POP2:
			if (pop().getSize() == 1) {
				if (pop().getSize() != 1) {
					throw new AnalyzerException(insn, "Illegal use of POP2");
				}
			}
			break;
		case Opcodes.DUP:
			value1 = pop();
			if (value1.getSize() != 1) {
				throw new AnalyzerException(insn, "Illegal use of DUP");
			}
			push(value1);
			push(interpreter.loadLocal(insn, value1));
			break;
		case Opcodes.DUP_X1:
			value1 = pop();
			value2 = pop();
			if (value1.getSize() != 1 || value2.getSize() != 1) {
				throw new AnalyzerException(insn, "Illegal use of DUP_X1");
			}
			push(interpreter.loadLocal(insn, value1));
			push(value2);
			push(value1);
			break;
		case Opcodes.DUP_X2:
			value1 = pop();
			if (value1.getSize() == 1) {
				value2 = pop();
				if (value2.getSize() == 1) {
					value3 = pop();
					if (value3.getSize() == 1) {
						push(interpreter.loadLocal(insn, value1));
						push(value3);
						push(value2);
						push(value1);
						break;
					}
				} else {
					push(interpreter.loadLocal(insn, value1));
					push(value2);
					push(value1);
					break;
				}
			}
			throw new AnalyzerException(insn, "Illegal use of DUP_X2");
		case Opcodes.DUP2:
			value1 = pop();
			if (value1.getSize() == 1) {
				value2 = pop();
				if (value2.getSize() == 1) {
					push(value2);
					push(value1);
					push(interpreter.loadLocal(insn, value2));
					push(interpreter.loadLocal(insn, value1));
					break;
				}
			} else {
				push(value1);
				push(interpreter.loadLocal(insn, value1));
				break;
			}
			throw new AnalyzerException(insn, "Illegal use of DUP2");
		case Opcodes.DUP2_X1:
			value1 = pop();
			if (value1.getSize() == 1) {
				value2 = pop();
				if (value2.getSize() == 1) {
					value3 = pop();
					if (value3.getSize() == 1) {
						push(interpreter.loadLocal(insn, value2));
						push(interpreter.loadLocal(insn, value1));
						push(value3);
						push(value2);
						push(value1);
						break;
					}
				}
			} else {
				value2 = pop();
				if (value2.getSize() == 1) {
					push(interpreter.loadLocal(insn, value1));
					push(value2);
					push(value1);
					break;
				}
			}
			throw new AnalyzerException(insn, "Illegal use of DUP2_X1");
		case Opcodes.DUP2_X2:
			value1 = pop();
			if (value1.getSize() == 1) {
				value2 = pop();
				if (value2.getSize() == 1) {
					value3 = pop();
					if (value3.getSize() == 1) {
						value4 = pop();
						if (value4.getSize() == 1) {
							push(interpreter.loadLocal(insn, value2));
							push(interpreter.loadLocal(insn, value1));
							push(value4);
							push(value3);
							push(value2);
							push(value1);
							break;
						}
					} else {
						push(interpreter.loadLocal(insn, value2));
						push(interpreter.loadLocal(insn, value1));
						push(value3);
						push(value2);
						push(value1);
						break;
					}
				}
			} else {
				value2 = pop();
				if (value2.getSize() == 1) {
					value3 = pop();
					if (value3.getSize() == 1) {
						push(interpreter.loadLocal(insn, value1));
						push(value3);
						push(value2);
						push(value1);
						break;
					}
				} else {
					push(interpreter.loadLocal(insn, value1));
					push(value2);
					push(value1);
					break;
				}
			}
			throw new AnalyzerException(insn, "Illegal use of DUP2_X2");
		case Opcodes.SWAP:
			value2 = pop();
			value1 = pop();
			if (value1.getSize() != 1 || value2.getSize() != 1) {
				throw new AnalyzerException(insn, "Illegal use of SWAP");
			}
			push(interpreter.loadLocal(insn, value2));
			push(interpreter.loadLocal(insn, value1));
			break;
		case Opcodes.IADD:
		case Opcodes.LADD:
		case Opcodes.FADD:
		case Opcodes.DADD:
		case Opcodes.ISUB:
		case Opcodes.LSUB:
		case Opcodes.FSUB:
		case Opcodes.DSUB:
		case Opcodes.IMUL:
		case Opcodes.LMUL:
		case Opcodes.FMUL:
		case Opcodes.DMUL:
		case Opcodes.IDIV:
		case Opcodes.LDIV:
		case Opcodes.FDIV:
		case Opcodes.DDIV:
		case Opcodes.IREM:
		case Opcodes.LREM:
		case Opcodes.FREM:
		case Opcodes.DREM:
		case Opcodes.ISHL:
		case Opcodes.LSHL:
		case Opcodes.ISHR:
		case Opcodes.LSHR:
		case Opcodes.IUSHR:
		case Opcodes.LUSHR:
		case Opcodes.IAND:
		case Opcodes.LAND:
		case Opcodes.IOR:
		case Opcodes.LOR:
		case Opcodes.IXOR:
		case Opcodes.LXOR:
			value2 = pop();
			value1 = pop();
			push(interpreter.doMath(insn, value1, value2));
			break;
		case Opcodes.INEG:
		case Opcodes.LNEG:
		case Opcodes.FNEG:
		case Opcodes.DNEG:
			push(interpreter.invertValue(insn, pop()));
			break;
		case Opcodes.IINC:
			IincInsnNode iinc = (IincInsnNode) insn;
			var = iinc.var;
			setLocal(var, interpreter.incrementLocal(iinc, getLocal(var)));
			break;
		case Opcodes.I2L:
		case Opcodes.I2F:
		case Opcodes.I2D:
		case Opcodes.L2I:
		case Opcodes.L2F:
		case Opcodes.L2D:
		case Opcodes.F2I:
		case Opcodes.F2L:
		case Opcodes.F2D:
		case Opcodes.D2I:
		case Opcodes.D2L:
		case Opcodes.D2F:
		case Opcodes.I2B:
		case Opcodes.I2C:
		case Opcodes.I2S:
			push(interpreter.convertValue(insn, pop()));
			break;
		case Opcodes.LCMP:
		case Opcodes.FCMPL:
		case Opcodes.FCMPG:
		case Opcodes.DCMPL:
		case Opcodes.DCMPG:
			value2 = pop();
			value1 = pop();
			push(interpreter.compareConstants(insn, value1, value2));
			break;
		case Opcodes.IFEQ:
		case Opcodes.IFNE:
		case Opcodes.IFLT:
		case Opcodes.IFGE:
		case Opcodes.IFGT:
		case Opcodes.IFLE:
			InsnValue unaryIf = interpreter.compareConstant(insn, pop());
			if (unaryIf.getValue() != null) {
				if (getInt(unaryIf.getValue()) == 1) {
					doJump = true;
					jin = ((JumpInsnNode) insn).label;
				}
			}
			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:
		case Opcodes.IF_ACMPEQ:
		case Opcodes.IF_ACMPNE:
			value2 = pop();
			value1 = pop();
			InsnValue binaryIf = interpreter.compareConstants(insn, value1, value2);
			if (binaryIf.getValue() != null) {
				if (getInt(binaryIf.getValue()) == 1) {
					doJump = true;
					jin = ((JumpInsnNode) insn).label;
				}
			}
			break;
		case Opcodes.IFNULL:
		case Opcodes.IFNONNULL:
			InsnValue nullCheck = interpreter.checkNull(insn, pop());
			if (nullCheck.getValue() != null) {
				if (getInt(nullCheck.getValue()) == 1) {
					doJump = true;
					jin = ((JumpInsnNode) insn).label;
				}
			}
			break;
		case Opcodes.GOTO:
			jin = ((JumpInsnNode) insn).label;
			doJump = true;
			break;
		case Opcodes.JSR:
			push(interpreter.createConstant(insn));
			jin = ((JumpInsnNode) insn).label;
			doJump = true;
			break;
		case Opcodes.RET:
			break;
		case Opcodes.TABLESWITCH:
		case Opcodes.LOOKUPSWITCH:
			InsnValue switchValue = interpreter.getSwitchValue(insn, pop());
			int index = (int) switchValue.getValue();
			if (insn.getOpcode() == Opcodes.TABLESWITCH) {
				TableSwitchInsnNode tsin = (TableSwitchInsnNode) insn;
				jin = index == -1 ? tsin.dflt : tsin.labels.get(index);
			} else {
				LookupSwitchInsnNode lsin = (LookupSwitchInsnNode) insn;
				jin = index == -1 ? lsin.dflt : lsin.labels.get(index);
			}
			doJump = true;
			break;
		case Opcodes.IRETURN:
		case Opcodes.LRETURN:
		case Opcodes.FRETURN:
		case Opcodes.DRETURN:
		case Opcodes.ARETURN:
		case Opcodes.RETURN:
			if (getStackSize() > 0) {
				returnValue = pop();
			}
			break;
		case Opcodes.GETSTATIC:
			push(interpreter.getStatic((FieldInsnNode) insn));
			break;
		case Opcodes.PUTSTATIC:
			interpreter.putStatic((FieldInsnNode) insn, pop());
			break;
		case Opcodes.GETFIELD:
			push(interpreter.getField((FieldInsnNode) insn, pop()));
			break;
		case Opcodes.PUTFIELD:
			value2 = pop();
			value1 = pop();
			interpreter.putField((FieldInsnNode) insn, value1, value2);
			break;
		case Opcodes.INVOKEVIRTUAL:
		case Opcodes.INVOKESPECIAL:
		case Opcodes.INVOKESTATIC:
		case Opcodes.INVOKEINTERFACE: {
			// Brackets are here to prevent local variable name conflicts.
			values = new ArrayList<Value>();
			String desc = ((MethodInsnNode) insn).desc;
			for (int args = Type.getArgumentTypes(desc).length; args > 0; --args) {
				values.add(0, pop());
			}
			if (insn.getOpcode() != Opcodes.INVOKESTATIC) {
				values.add(0, pop());
			}
			if (Type.getReturnType(desc).equals(Type.VOID_TYPE)) {
				interpreter.onMethod(insn, values);
			} else {
				push(interpreter.onMethod(insn, values));
			}
			break;
		}
		case Opcodes.INVOKEDYNAMIC: {
			values = new ArrayList();
			String desc = ((InvokeDynamicInsnNode) insn).desc;
			for (int i = Type.getArgumentTypes(desc).length; i > 0; --i) {
				values.add(0, pop());
			}
			if (Type.getReturnType(desc) == Type.VOID_TYPE) {
				interpreter.onMethod(insn, values);
			} else {
				push(interpreter.onMethod(insn, values));
			}
			break;
		}
		case Opcodes.NEW:
			push(interpreter.createConstant(insn));
			break;
		case Opcodes.NEWARRAY:
		case Opcodes.ANEWARRAY:
		case Opcodes.ARRAYLENGTH:
			push(interpreter.array(insn, pop()));
			break;
		case Opcodes.ATHROW:
			interpreter.throwException(insn, pop());
			break;
		case Opcodes.CHECKCAST:
		case Opcodes.INSTANCEOF:
			push(interpreter.casting((TypeInsnNode) insn, pop()));
			break;
		case Opcodes.MONITORENTER:
		case Opcodes.MONITOREXIT:
			interpreter.monitor(insn, pop());
			break;
		case Opcodes.MULTIANEWARRAY:
			values = new ArrayList();
			for (int i = ((MultiANewArrayInsnNode) insn).dims; i > 0; --i) {
				values.add(0, pop());
			}
			push(interpreter.onMultiANewArray((MultiANewArrayInsnNode) insn, values));
			break;
		default:
			throw new RuntimeException("Illegal opcode " + insn.getOpcode());
		}
	}

	public StackFrame init(StackFrame src) {
		returnValue = src.returnValue;
		System.arraycopy(src.values, 0, values, 0, values.length);
		top = src.top;
		return this;
	}

	@Override
	public InsnValue getLocal(int i) {
		return (InsnValue) super.getLocal(i);
	}

	@Override
	public InsnValue pop() {
		return (InsnValue) super.pop();
	}

	private int getInt(Object value) {
		try {
			int i = Integer.parseInt(value.toString());
			return i;
		} catch (NumberFormatException nfe) {

		}
		return -1;
	}
}