package jadx.core.dex.instructions;

import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.DecodeException;

import com.android.dex.Code;
import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes;
import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;

public class InsnDecoder {

	private final MethodNode method;
	private final DexNode dex;
	private DecodedInstruction[] insnArr;

	public InsnDecoder(MethodNode mthNode) {
		this.method = mthNode;
		this.dex = method.dex();
	}

	public void decodeInsns(Code mthCode) throws DecodeException {
		short[] encodedInstructions = mthCode.getInstructions();
		int size = encodedInstructions.length;
		DecodedInstruction[] decoded = new DecodedInstruction[size];
		ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);

		try {
			while (in.hasMore()) {
				decoded[in.cursor()] = DecodedInstruction.decode(in);
			}
		} catch (Exception e) {
			throw new DecodeException(method, "", e);
		}
		insnArr = decoded;
	}

	public InsnNode[] process() throws DecodeException {
		InsnNode[] instructions = new InsnNode[insnArr.length];
		for (int i = 0; i < insnArr.length; i++) {
			DecodedInstruction rawInsn = insnArr[i];
			if (rawInsn != null) {
				InsnNode insn = decode(rawInsn, i);
				if (insn != null) {
					insn.setOffset(i);
				}
				instructions[i] = insn;
			} else {
				instructions[i] = null;
			}
		}
		insnArr = null;
		return instructions;
	}

	private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException {
		switch (insn.getOpcode()) {
			case Opcodes.NOP:
			case Opcodes.PACKED_SWITCH_PAYLOAD:
			case Opcodes.SPARSE_SWITCH_PAYLOAD:
			case Opcodes.FILL_ARRAY_DATA_PAYLOAD:
				return null;

			// move-result will be process in invoke and filled-new-array instructions
			case Opcodes.MOVE_RESULT:
			case Opcodes.MOVE_RESULT_WIDE:
			case Opcodes.MOVE_RESULT_OBJECT:
				return new InsnNode(InsnType.NOP, 0);

			case Opcodes.CONST:
			case Opcodes.CONST_4:
			case Opcodes.CONST_16:
			case Opcodes.CONST_HIGH16:
				return insn(InsnType.CONST, InsnArg.reg(insn, 0, ArgType.NARROW),
						InsnArg.lit(insn, ArgType.NARROW));

			case Opcodes.CONST_WIDE:
			case Opcodes.CONST_WIDE_16:
			case Opcodes.CONST_WIDE_32:
			case Opcodes.CONST_WIDE_HIGH16:
				return insn(InsnType.CONST, InsnArg.reg(insn, 0, ArgType.WIDE),
						InsnArg.lit(insn, ArgType.WIDE));

			case Opcodes.CONST_STRING:
			case Opcodes.CONST_STRING_JUMBO: {
				InsnNode node = new ConstStringNode(dex.getString(insn.getIndex()));
				node.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
				return node;
			}

			case Opcodes.CONST_CLASS: {
				InsnNode node = new ConstClassNode(dex.getType(insn.getIndex()));
				node.setResult(InsnArg.reg(insn, 0, ArgType.CLASS));
				return node;
			}

			case Opcodes.MOVE:
			case Opcodes.MOVE_16:
			case Opcodes.MOVE_FROM16:
				return insn(InsnType.MOVE,
						InsnArg.reg(insn, 0, ArgType.NARROW),
						InsnArg.reg(insn, 1, ArgType.NARROW));

			case Opcodes.MOVE_WIDE:
			case Opcodes.MOVE_WIDE_16:
			case Opcodes.MOVE_WIDE_FROM16:
				return insn(InsnType.MOVE,
						InsnArg.reg(insn, 0, ArgType.WIDE),
						InsnArg.reg(insn, 1, ArgType.WIDE));

			case Opcodes.MOVE_OBJECT:
			case Opcodes.MOVE_OBJECT_16:
			case Opcodes.MOVE_OBJECT_FROM16:
				return insn(InsnType.MOVE,
						InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT),
						InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));

			case Opcodes.ADD_INT:
			case Opcodes.ADD_INT_2ADDR:
				return arith(insn, ArithOp.ADD, ArgType.INT);

			case Opcodes.ADD_DOUBLE:
			case Opcodes.ADD_DOUBLE_2ADDR:
				return arith(insn, ArithOp.ADD, ArgType.DOUBLE);

			case Opcodes.ADD_FLOAT:
			case Opcodes.ADD_FLOAT_2ADDR:
				return arith(insn, ArithOp.ADD, ArgType.FLOAT);

			case Opcodes.ADD_LONG:
			case Opcodes.ADD_LONG_2ADDR:
				return arith(insn, ArithOp.ADD, ArgType.LONG);

			case Opcodes.ADD_INT_LIT8:
			case Opcodes.ADD_INT_LIT16:
				return arithLit(insn, ArithOp.ADD, ArgType.INT);

			case Opcodes.SUB_INT:
			case Opcodes.SUB_INT_2ADDR:
				return arith(insn, ArithOp.SUB, ArgType.INT);

			case Opcodes.RSUB_INT_LIT8:
			case Opcodes.RSUB_INT: // LIT16
				return new ArithNode(ArithOp.SUB,
						InsnArg.reg(insn, 0, ArgType.INT),
						InsnArg.lit(insn, ArgType.INT),
						InsnArg.reg(insn, 1, ArgType.INT));

			case Opcodes.SUB_LONG:
			case Opcodes.SUB_LONG_2ADDR:
				return arith(insn, ArithOp.SUB, ArgType.LONG);

			case Opcodes.SUB_FLOAT:
			case Opcodes.SUB_FLOAT_2ADDR:
				return arith(insn, ArithOp.SUB, ArgType.FLOAT);

			case Opcodes.SUB_DOUBLE:
			case Opcodes.SUB_DOUBLE_2ADDR:
				return arith(insn, ArithOp.SUB, ArgType.DOUBLE);

			case Opcodes.MUL_INT:
			case Opcodes.MUL_INT_2ADDR:
				return arith(insn, ArithOp.MUL, ArgType.INT);

			case Opcodes.MUL_DOUBLE:
			case Opcodes.MUL_DOUBLE_2ADDR:
				return arith(insn, ArithOp.MUL, ArgType.DOUBLE);

			case Opcodes.MUL_FLOAT:
			case Opcodes.MUL_FLOAT_2ADDR:
				return arith(insn, ArithOp.MUL, ArgType.FLOAT);

			case Opcodes.MUL_LONG:
			case Opcodes.MUL_LONG_2ADDR:
				return arith(insn, ArithOp.MUL, ArgType.LONG);

			case Opcodes.MUL_INT_LIT8:
			case Opcodes.MUL_INT_LIT16:
				return arithLit(insn, ArithOp.MUL, ArgType.INT);

			case Opcodes.DIV_INT:
			case Opcodes.DIV_INT_2ADDR:
				return arith(insn, ArithOp.DIV, ArgType.INT);

			case Opcodes.REM_INT:
			case Opcodes.REM_INT_2ADDR:
				return arith(insn, ArithOp.REM, ArgType.INT);

			case Opcodes.REM_LONG:
			case Opcodes.REM_LONG_2ADDR:
				return arith(insn, ArithOp.REM, ArgType.LONG);

			case Opcodes.REM_FLOAT:
			case Opcodes.REM_FLOAT_2ADDR:
				return arith(insn, ArithOp.REM, ArgType.FLOAT);

			case Opcodes.REM_DOUBLE:
			case Opcodes.REM_DOUBLE_2ADDR:
				return arith(insn, ArithOp.REM, ArgType.DOUBLE);

			case Opcodes.DIV_DOUBLE:
			case Opcodes.DIV_DOUBLE_2ADDR:
				return arith(insn, ArithOp.DIV, ArgType.DOUBLE);

			case Opcodes.DIV_FLOAT:
			case Opcodes.DIV_FLOAT_2ADDR:
				return arith(insn, ArithOp.DIV, ArgType.FLOAT);

			case Opcodes.DIV_LONG:
			case Opcodes.DIV_LONG_2ADDR:
				return arith(insn, ArithOp.DIV, ArgType.LONG);

			case Opcodes.DIV_INT_LIT8:
			case Opcodes.DIV_INT_LIT16:
				return arithLit(insn, ArithOp.DIV, ArgType.INT);

			case Opcodes.REM_INT_LIT8:
			case Opcodes.REM_INT_LIT16:
				return arithLit(insn, ArithOp.REM, ArgType.INT);

			case Opcodes.AND_INT:
			case Opcodes.AND_INT_2ADDR:
				return arith(insn, ArithOp.AND, ArgType.INT);

			case Opcodes.AND_INT_LIT8:
			case Opcodes.AND_INT_LIT16:
				return arithLit(insn, ArithOp.AND, ArgType.INT);

			case Opcodes.XOR_INT_LIT8:
			case Opcodes.XOR_INT_LIT16:
				return arithLit(insn, ArithOp.XOR, ArgType.INT);

			case Opcodes.AND_LONG:
			case Opcodes.AND_LONG_2ADDR:
				return arith(insn, ArithOp.AND, ArgType.LONG);

			case Opcodes.OR_INT:
			case Opcodes.OR_INT_2ADDR:
				return arith(insn, ArithOp.OR, ArgType.INT);

			case Opcodes.OR_INT_LIT8:
			case Opcodes.OR_INT_LIT16:
				return arithLit(insn, ArithOp.OR, ArgType.INT);

			case Opcodes.XOR_INT:
			case Opcodes.XOR_INT_2ADDR:
				return arith(insn, ArithOp.XOR, ArgType.INT);

			case Opcodes.OR_LONG:
			case Opcodes.OR_LONG_2ADDR:
				return arith(insn, ArithOp.OR, ArgType.LONG);

			case Opcodes.XOR_LONG:
			case Opcodes.XOR_LONG_2ADDR:
				return arith(insn, ArithOp.XOR, ArgType.LONG);

			case Opcodes.USHR_INT:
			case Opcodes.USHR_INT_2ADDR:
				return arith(insn, ArithOp.USHR, ArgType.INT);

			case Opcodes.USHR_LONG:
			case Opcodes.USHR_LONG_2ADDR:
				return arith(insn, ArithOp.USHR, ArgType.LONG);

			case Opcodes.SHL_INT:
			case Opcodes.SHL_INT_2ADDR:
				return arith(insn, ArithOp.SHL, ArgType.INT);

			case Opcodes.SHL_LONG:
			case Opcodes.SHL_LONG_2ADDR:
				return arith(insn, ArithOp.SHL, ArgType.LONG);

			case Opcodes.SHR_INT:
			case Opcodes.SHR_INT_2ADDR:
				return arith(insn, ArithOp.SHR, ArgType.INT);

			case Opcodes.SHR_LONG:
			case Opcodes.SHR_LONG_2ADDR:
				return arith(insn, ArithOp.SHR, ArgType.LONG);

			case Opcodes.SHL_INT_LIT8:
				return arithLit(insn, ArithOp.SHL, ArgType.INT);
			case Opcodes.SHR_INT_LIT8:
				return arithLit(insn, ArithOp.SHR, ArgType.INT);
			case Opcodes.USHR_INT_LIT8:
				return arithLit(insn, ArithOp.USHR, ArgType.INT);

			case Opcodes.NEG_INT:
				return neg(insn, ArgType.INT);
			case Opcodes.NEG_LONG:
				return neg(insn, ArgType.LONG);
			case Opcodes.NEG_FLOAT:
				return neg(insn, ArgType.FLOAT);
			case Opcodes.NEG_DOUBLE:
				return neg(insn, ArgType.DOUBLE);

			case Opcodes.INT_TO_BYTE:
				return cast(insn, ArgType.INT, ArgType.BYTE);
			case Opcodes.INT_TO_CHAR:
				return cast(insn, ArgType.INT, ArgType.CHAR);
			case Opcodes.INT_TO_SHORT:
				return cast(insn, ArgType.INT, ArgType.SHORT);
			case Opcodes.INT_TO_FLOAT:
				return cast(insn, ArgType.INT, ArgType.FLOAT);
			case Opcodes.INT_TO_DOUBLE:
				return cast(insn, ArgType.INT, ArgType.DOUBLE);
			case Opcodes.INT_TO_LONG:
				return cast(insn, ArgType.INT, ArgType.LONG);

			case Opcodes.FLOAT_TO_INT:
				return cast(insn, ArgType.FLOAT, ArgType.INT);
			case Opcodes.FLOAT_TO_DOUBLE:
				return cast(insn, ArgType.FLOAT, ArgType.DOUBLE);
			case Opcodes.FLOAT_TO_LONG:
				return cast(insn, ArgType.FLOAT, ArgType.LONG);

			case Opcodes.DOUBLE_TO_INT:
				return cast(insn, ArgType.DOUBLE, ArgType.INT);
			case Opcodes.DOUBLE_TO_FLOAT:
				return cast(insn, ArgType.DOUBLE, ArgType.FLOAT);
			case Opcodes.DOUBLE_TO_LONG:
				return cast(insn, ArgType.DOUBLE, ArgType.LONG);

			case Opcodes.LONG_TO_INT:
				return cast(insn, ArgType.LONG, ArgType.INT);
			case Opcodes.LONG_TO_FLOAT:
				return cast(insn, ArgType.LONG, ArgType.FLOAT);
			case Opcodes.LONG_TO_DOUBLE:
				return cast(insn, ArgType.LONG, ArgType.DOUBLE);

			case Opcodes.IF_EQ:
			case Opcodes.IF_EQZ:
				return new IfNode(insn, IfOp.EQ);

			case Opcodes.IF_NE:
			case Opcodes.IF_NEZ:
				return new IfNode(insn, IfOp.NE);

			case Opcodes.IF_GT:
			case Opcodes.IF_GTZ:
				return new IfNode(insn, IfOp.GT);

			case Opcodes.IF_GE:
			case Opcodes.IF_GEZ:
				return new IfNode(insn, IfOp.GE);

			case Opcodes.IF_LT:
			case Opcodes.IF_LTZ:
				return new IfNode(insn, IfOp.LT);

			case Opcodes.IF_LE:
			case Opcodes.IF_LEZ:
				return new IfNode(insn, IfOp.LE);

			case Opcodes.CMP_LONG:
				return cmp(insn, InsnType.CMP_L, ArgType.LONG);
			case Opcodes.CMPL_FLOAT:
				return cmp(insn, InsnType.CMP_L, ArgType.FLOAT);
			case Opcodes.CMPL_DOUBLE:
				return cmp(insn, InsnType.CMP_L, ArgType.DOUBLE);

			case Opcodes.CMPG_FLOAT:
				return cmp(insn, InsnType.CMP_G, ArgType.FLOAT);
			case Opcodes.CMPG_DOUBLE:
				return cmp(insn, InsnType.CMP_G, ArgType.DOUBLE);

			case Opcodes.GOTO:
			case Opcodes.GOTO_16:
			case Opcodes.GOTO_32:
				return new GotoNode(insn.getTarget());

			case Opcodes.THROW:
				return insn(InsnType.THROW, null,
						InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));

			case Opcodes.MOVE_EXCEPTION:
				return insn(InsnType.MOVE_EXCEPTION,
						InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));

			case Opcodes.RETURN_VOID:
				return new InsnNode(InsnType.RETURN, 0);

			case Opcodes.RETURN:
			case Opcodes.RETURN_WIDE:
			case Opcodes.RETURN_OBJECT:
				return insn(InsnType.RETURN,
						null,
						InsnArg.reg(insn, 0, method.getReturnType()));

			case Opcodes.INSTANCE_OF: {
				InsnNode node = new IndexInsnNode(InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
				node.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN));
				node.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
				return node;
			}

			case Opcodes.CHECK_CAST: {
				ArgType castType = dex.getType(insn.getIndex());
				InsnNode node = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
				node.setResult(InsnArg.reg(insn, 0, castType));
				node.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
				return node;
			}

			case Opcodes.IGET:
			case Opcodes.IGET_BOOLEAN:
			case Opcodes.IGET_BYTE:
			case Opcodes.IGET_CHAR:
			case Opcodes.IGET_SHORT:
			case Opcodes.IGET_WIDE:
			case Opcodes.IGET_OBJECT: {
				FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
				InsnNode node = new IndexInsnNode(InsnType.IGET, field, 1);
				node.setResult(InsnArg.reg(insn, 0, field.getType()));
				node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
				return node;
			}

			case Opcodes.IPUT:
			case Opcodes.IPUT_BOOLEAN:
			case Opcodes.IPUT_BYTE:
			case Opcodes.IPUT_CHAR:
			case Opcodes.IPUT_SHORT:
			case Opcodes.IPUT_WIDE:
			case Opcodes.IPUT_OBJECT: {
				FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
				InsnNode node = new IndexInsnNode(InsnType.IPUT, field, 2);
				node.addArg(InsnArg.reg(insn, 0, field.getType()));
				node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
				return node;
			}

			case Opcodes.SGET:
			case Opcodes.SGET_BOOLEAN:
			case Opcodes.SGET_BYTE:
			case Opcodes.SGET_CHAR:
			case Opcodes.SGET_SHORT:
			case Opcodes.SGET_WIDE:
			case Opcodes.SGET_OBJECT: {
				FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
				InsnNode node = new IndexInsnNode(InsnType.SGET, field, 0);
				node.setResult(InsnArg.reg(insn, 0, field.getType()));
				return node;
			}

			case Opcodes.SPUT:
			case Opcodes.SPUT_BOOLEAN:
			case Opcodes.SPUT_BYTE:
			case Opcodes.SPUT_CHAR:
			case Opcodes.SPUT_SHORT:
			case Opcodes.SPUT_WIDE:
			case Opcodes.SPUT_OBJECT: {
				FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
				InsnNode node = new IndexInsnNode(InsnType.SPUT, field, 1);
				node.addArg(InsnArg.reg(insn, 0, field.getType()));
				return node;
			}

			case Opcodes.ARRAY_LENGTH: {
				InsnNode node = new InsnNode(InsnType.ARRAY_LENGTH, 1);
				node.setResult(InsnArg.reg(insn, 0, ArgType.INT));
				node.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN)));
				return node;
			}

			case Opcodes.AGET:
				return arrayGet(insn, ArgType.NARROW);
			case Opcodes.AGET_BOOLEAN:
				return arrayGet(insn, ArgType.BOOLEAN);
			case Opcodes.AGET_BYTE:
				return arrayGet(insn, ArgType.BYTE);
			case Opcodes.AGET_CHAR:
				return arrayGet(insn, ArgType.CHAR);
			case Opcodes.AGET_SHORT:
				return arrayGet(insn, ArgType.SHORT);
			case Opcodes.AGET_WIDE:
				return arrayGet(insn, ArgType.WIDE);
			case Opcodes.AGET_OBJECT:
				return arrayGet(insn, ArgType.UNKNOWN_OBJECT);

			case Opcodes.APUT:
				return arrayPut(insn, ArgType.NARROW);
			case Opcodes.APUT_BOOLEAN:
				return arrayPut(insn, ArgType.BOOLEAN);
			case Opcodes.APUT_BYTE:
				return arrayPut(insn, ArgType.BYTE);
			case Opcodes.APUT_CHAR:
				return arrayPut(insn, ArgType.CHAR);
			case Opcodes.APUT_SHORT:
				return arrayPut(insn, ArgType.SHORT);
			case Opcodes.APUT_WIDE:
				return arrayPut(insn, ArgType.WIDE);
			case Opcodes.APUT_OBJECT:
				return arrayPut(insn, ArgType.UNKNOWN_OBJECT);

			case Opcodes.INVOKE_STATIC:
				return invoke(insn, offset, InvokeType.STATIC, false);

			case Opcodes.INVOKE_STATIC_RANGE:
				return invoke(insn, offset, InvokeType.STATIC, true);

			case Opcodes.INVOKE_DIRECT:
				return invoke(insn, offset, InvokeType.DIRECT, false);
			case Opcodes.INVOKE_INTERFACE:
				return invoke(insn, offset, InvokeType.INTERFACE, false);
			case Opcodes.INVOKE_SUPER:
				return invoke(insn, offset, InvokeType.SUPER, false);
			case Opcodes.INVOKE_VIRTUAL:
				return invoke(insn, offset, InvokeType.VIRTUAL, false);

			case Opcodes.INVOKE_DIRECT_RANGE:
				return invoke(insn, offset, InvokeType.DIRECT, true);
			case Opcodes.INVOKE_INTERFACE_RANGE:
				return invoke(insn, offset, InvokeType.INTERFACE, true);
			case Opcodes.INVOKE_SUPER_RANGE:
				return invoke(insn, offset, InvokeType.SUPER, true);
			case Opcodes.INVOKE_VIRTUAL_RANGE:
				return invoke(insn, offset, InvokeType.VIRTUAL, true);

			case Opcodes.NEW_INSTANCE:
				return insn(InsnType.NEW_INSTANCE,
						InsnArg.reg(insn, 0, dex.getType(insn.getIndex())));

			case Opcodes.NEW_ARRAY:
				ArgType arrType = dex.getType(insn.getIndex());
				return new NewArrayNode(arrType,
						InsnArg.reg(insn, 0, arrType),
						InsnArg.reg(insn, 1, ArgType.INT));

			case Opcodes.FILL_ARRAY_DATA:
				return fillArray(insn);

			case Opcodes.FILLED_NEW_ARRAY:
				return filledNewArray(insn, offset, false);
			case Opcodes.FILLED_NEW_ARRAY_RANGE:
				return filledNewArray(insn, offset, true);

			case Opcodes.PACKED_SWITCH:
				return decodeSwitch(insn, offset, true);

			case Opcodes.SPARSE_SWITCH:
				return decodeSwitch(insn, offset, false);

			case Opcodes.MONITOR_ENTER:
				return insn(InsnType.MONITOR_ENTER,
						null,
						InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));

			case Opcodes.MONITOR_EXIT:
				return insn(InsnType.MONITOR_EXIT,
						null,
						InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
		}

		throw new DecodeException("Unknown instruction: " + OpcodeInfo.getName(insn.getOpcode()));
	}

	private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
		int payloadOffset = insn.getTarget();
		DecodedInstruction payload = insnArr[payloadOffset];
		Object[] keys;
		int[] targets;
		if (packed) {
			PackedSwitchPayloadDecodedInstruction ps = (PackedSwitchPayloadDecodedInstruction) payload;
			targets = ps.getTargets();
			keys = new Object[targets.length];
			int k = ps.getFirstKey();
			for (int i = 0; i < keys.length; i++) {
				keys[i] = k++;
			}
		} else {
			SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload;
			targets = ss.getTargets();
			keys = new Object[targets.length];
			for (int i = 0; i < keys.length; i++) {
				keys[i] = ss.getKeys()[i];
			}
		}
		// convert from relative to absolute offsets
		for (int i = 0; i < targets.length; i++) {
			targets[i] = targets[i] - payloadOffset + offset;
		}
		int nextOffset = getNextInsnOffset(insnArr, offset);
		return new SwitchNode(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset);
	}

	private InsnNode fillArray(DecodedInstruction insn) {
		DecodedInstruction payload = insnArr[insn.getTarget()];
		return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
	}

	private InsnNode filledNewArray(DecodedInstruction insn, int offset, boolean isRange) {
		int resReg = getMoveResultRegister(insnArr, offset);
		ArgType arrType = dex.getType(insn.getIndex());
		ArgType elType = arrType.getArrayElement();
		boolean typeImmutable = elType.isPrimitive();
		int regsCount = insn.getRegisterCount();
		InsnArg[] regs = new InsnArg[regsCount];
		if (isRange) {
			int r = insn.getA();
			for (int i = 0; i < regsCount; i++) {
				regs[i] = InsnArg.reg(r, elType, typeImmutable);
				r++;
			}
		} else {
			for (int i = 0; i < regsCount; i++) {
				int regNum = InsnUtils.getArg(insn, i);
				regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
			}
		}
		InsnNode node = new FilledNewArrayNode(elType, regs.length);
		node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType));
		for (InsnArg arg : regs) {
			node.addArg(arg);
		}
		return node;
	}

	private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) {
		InsnNode inode = new InsnNode(itype, 2);
		inode.setResult(InsnArg.reg(insn, 0, ArgType.INT));
		inode.addArg(InsnArg.reg(insn, 1, argType));
		inode.addArg(InsnArg.reg(insn, 2, argType));
		return inode;
	}

	private InsnNode cast(DecodedInstruction insn, ArgType from, ArgType to) {
		InsnNode inode = new IndexInsnNode(InsnType.CAST, to, 1);
		inode.setResult(InsnArg.reg(insn, 0, to));
		inode.addArg(InsnArg.reg(insn, 1, from));
		return inode;
	}

	private InsnNode invoke(DecodedInstruction insn, int offset, InvokeType type, boolean isRange) {
		int resReg = getMoveResultRegister(insnArr, offset);
		MethodInfo mth = MethodInfo.fromDex(dex, insn.getIndex());
		return new InvokeNode(mth, insn, type, isRange, resReg);
	}

	private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
		InsnNode inode = new InsnNode(InsnType.AGET, 2);
		inode.setResult(InsnArg.reg(insn, 0, argType));
		inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
		inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
		return inode;
	}

	private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
		InsnNode inode = new InsnNode(InsnType.APUT, 3);
		inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
		inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
		inode.addArg(InsnArg.reg(insn, 0, argType));
		return inode;
	}

	private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) {
		return new ArithNode(insn, op, type, false);
	}

	private InsnNode arithLit(DecodedInstruction insn, ArithOp op, ArgType type) {
		return new ArithNode(insn, op, type, true);
	}

	private InsnNode neg(DecodedInstruction insn, ArgType type) {
		InsnNode inode = new InsnNode(InsnType.NEG, 1);
		inode.setResult(InsnArg.reg(insn, 0, type));
		inode.addArg(InsnArg.reg(insn, 1, type));
		return inode;
	}

	private InsnNode insn(InsnType type, RegisterArg res) {
		InsnNode node = new InsnNode(type, 0);
		node.setResult(res);
		return node;
	}

	private InsnNode insn(InsnType type, RegisterArg res, InsnArg arg) {
		InsnNode node = new InsnNode(type, 1);
		node.setResult(res);
		node.addArg(arg);
		return node;
	}

	private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
		int nextOffset = getNextInsnOffset(insnArr, offset);
		if (nextOffset >= 0) {
			DecodedInstruction next = insnArr[nextOffset];
			int opc = next.getOpcode();
			if (opc == Opcodes.MOVE_RESULT
					|| opc == Opcodes.MOVE_RESULT_WIDE
					|| opc == Opcodes.MOVE_RESULT_OBJECT) {
				return next.getA();
			}
		}
		return -1;
	}

	public static int getPrevInsnOffset(Object[] insnArr, int offset) {
		int i = offset - 1;
		while (i >= 0 && insnArr[i] == null) {
			i--;
		}
		if (i < 0) {
			return -1;
		}
		return i;
	}

	public static int getNextInsnOffset(Object[] insnArr, int offset) {
		int i = offset + 1;
		while (i < insnArr.length && insnArr[i] == null) {
			i++;
		}
		if (i >= insnArr.length) {
			return -1;
		}
		return i;
	}
}
