/**
 *  Copyright 2012 Douglas Campos <qmx@qmx.me>
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package me.qmx.jitescript;

import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.LocalVariableNode;
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.TryCatchBlockNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import static me.qmx.jitescript.util.CodegenUtils.ci;
import static me.qmx.jitescript.util.CodegenUtils.p;
import static me.qmx.jitescript.util.CodegenUtils.params;
import static me.qmx.jitescript.util.CodegenUtils.sig;

/**
 * @author qmx
 */
public class CodeBlock implements Opcodes {
    private final InsnList instructionList = new InsnList();
    private final List<TryCatchBlockNode> tryCatchBlockList = new ArrayList<TryCatchBlockNode>();
    private final List<LocalVariableNode> localVariableList = new ArrayList<LocalVariableNode>();
    private final List<VisibleAnnotation> annotations = new ArrayList<VisibleAnnotation>();
    private int arity = 0;
    private boolean returns = false;

    public CodeBlock() {
    }

    public CodeBlock(final CodeBlock block) {
        arity = block.arity();
        prepend(block);
    }

    public CodeBlock(final int arity) {
        this.arity = arity;
    }

    public static CodeBlock newCodeBlock() {
        return new CodeBlock();
    }

    public static CodeBlock newCodeBlock(final int arity) {
        return new CodeBlock(arity);
    }

    public static CodeBlock newCodeBlock(final CodeBlock block) {
        return new CodeBlock(block);
    }

    /**
     * Short-hand for specifying a set of aloads
     *
     * @param indices list of aloads you want
     */
    public CodeBlock aloadMany(final int... indices) {
        for (final int index : indices)
            aload(index);

        return this;
    }

    public CodeBlock aload(final int index) {
        instructionList.add(new VarInsnNode(ALOAD, index));
        return this;
    }

    public CodeBlock iload(final int index) {
        instructionList.add(new VarInsnNode(ILOAD, index));
        return this;
    }

    public CodeBlock lload(final int index) {
        instructionList.add(new VarInsnNode(LLOAD, index));
        return this;
    }

    public CodeBlock fload(final int index) {
        instructionList.add(new VarInsnNode(FLOAD, index));
        return this;
    }

    public CodeBlock dload(final int index) {
        instructionList.add(new VarInsnNode(DLOAD, index));
        return this;
    }

    public CodeBlock astore(final int index) {
        instructionList.add(new VarInsnNode(ASTORE, index));
        return this;
    }

    public CodeBlock istore(final int index) {
        instructionList.add(new VarInsnNode(ISTORE, index));
        return this;
    }

    public CodeBlock lstore(final int index) {
        instructionList.add(new VarInsnNode(LSTORE, index));
        return this;
    }

    public CodeBlock fstore(final int index) {
        instructionList.add(new VarInsnNode(FSTORE, index));
        return this;
    }

    public CodeBlock dstore(final int index) {
        instructionList.add(new VarInsnNode(DSTORE, index));
        return this;
    }

    public CodeBlock ldc(final Object value) {
        instructionList.add(new LdcInsnNode(value));
        return this;
    }

    public CodeBlock bipush(final int byteValue) {
        instructionList.add(new IntInsnNode(BIPUSH, byteValue));
        return this;
    }

    public CodeBlock sipush(final int shortValue) {
        instructionList.add(new IntInsnNode(SIPUSH, shortValue));
        return this;
    }

    public CodeBlock pushInt(final int value) {
        if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
            switch (value) {
                case -1:
                    iconst_m1();
                    break;
                case 0:
                    iconst_0();
                    break;
                case 1:
                    iconst_1();
                    break;
                case 2:
                    iconst_2();
                    break;
                case 3:
                    iconst_3();
                    break;
                case 4:
                    iconst_4();
                    break;
                case 5:
                    iconst_5();
                    break;
                default:
                    bipush(value);
                    break;
            }
        } else if (value <= Short.MAX_VALUE && value >= Short.MIN_VALUE) {
            sipush(value);
        } else {
            ldc(value);
        }
        return this;
    }

    public CodeBlock pushBoolean(final boolean bool) {
        if (bool)
            iconst_1();
        else
            iconst_0();

        return this;
    }

    public CodeBlock invokestatic(final String className, final String methodName, final String methodDesc) {
        instructionList.add(new MethodInsnNode(INVOKESTATIC, className, methodName, methodDesc, false));
        return this;
    }

    public CodeBlock invokespecial(final String className, final String methodName, final String methodDesc) {
        instructionList.add(new MethodInsnNode(INVOKESPECIAL, className, methodName, methodDesc, false));
        return this;
    }

    public CodeBlock invokevirtual(final String className, final String methodName, final String methodDesc) {
        instructionList.add(new MethodInsnNode(INVOKEVIRTUAL, className, methodName, methodDesc, false));
        return this;
    }

    public CodeBlock invokeinterface(final String className, final String methodName, final String methodDesc) {
        instructionList.add(new MethodInsnNode(INVOKEINTERFACE, className, methodName, methodDesc, true));
        return this;
    }

    public CodeBlock invokedynamic(final String name, final String descriptor, final Handle bootstrapMethod,
            final Object... bootstrapArguments) {
        instructionList.add(new InvokeDynamicInsnNode(name, descriptor, bootstrapMethod, bootstrapArguments));
        return this;
    }

    public CodeBlock aprintln() {
        dup();
        getstatic(p(System.class), "out", ci(PrintStream.class));
        swap();
        invokevirtual(p(PrintStream.class), "println", sig(void.class, params(Object.class)));
        return this;
    }

    public CodeBlock iprintln() {
        dup();
        getstatic(p(System.class), "out", ci(PrintStream.class));
        swap();
        invokevirtual(p(PrintStream.class), "println", sig(void.class, params(int.class)));
        return this;
    }

    public CodeBlock areturn() {
        returns = true;
        instructionList.add(new InsnNode(ARETURN));
        return this;
    }

    public CodeBlock ireturn() {
        instructionList.add(new InsnNode(IRETURN));
        return this;
    }

    public CodeBlock freturn() {
        instructionList.add(new InsnNode(FRETURN));
        return this;
    }

    public CodeBlock lreturn() {
        instructionList.add(new InsnNode(LRETURN));
        return this;
    }

    public CodeBlock dreturn() {
        instructionList.add(new InsnNode(DRETURN));
        return this;
    }

    public CodeBlock newobj(final String desc) {
        instructionList.add(new TypeInsnNode(NEW, desc));
        return this;
    }

    public CodeBlock dup() {
        instructionList.add(new InsnNode(DUP));
        return this;
    }

    public CodeBlock swap() {
        instructionList.add(new InsnNode(SWAP));
        return this;
    }

    public CodeBlock swap2() {
        dup2_x2();
        pop2();
        return this;
    }

    public CodeBlock getstatic(final String className, final String fieldName, final String fieldDesc) {
        instructionList.add(new FieldInsnNode(GETSTATIC, className, fieldName, fieldDesc));
        return this;
    }

    public CodeBlock putstatic(final String className, final String fieldName, final String fieldDesc) {
        instructionList.add(new FieldInsnNode(PUTSTATIC, className, fieldName, fieldDesc));
        return this;
    }

    public CodeBlock getfield(final String className, final String fieldName, final String fieldDesc) {
        instructionList.add(new FieldInsnNode(GETFIELD, className, fieldName, fieldDesc));
        return this;
    }

    public CodeBlock putfield(final String className, final String fieldName, final String fieldDesc) {
        instructionList.add(new FieldInsnNode(PUTFIELD, className, fieldName, fieldDesc));
        return this;
    }

    public CodeBlock voidreturn() {
        instructionList.add(new InsnNode(RETURN));
        return this;
    }

    public CodeBlock anewarray(final String arrayDesc) {
        instructionList.add(new TypeInsnNode(ANEWARRAY, arrayDesc));
        return this;
    }

    public CodeBlock multianewarray(final String arrayDesc, final int dims) {
        instructionList.add(new MultiANewArrayInsnNode(arrayDesc, dims));
        return this;
    }

    public CodeBlock newarray(final int size) {
        instructionList.add(new IntInsnNode(NEWARRAY, size));
        return this;
    }

    public CodeBlock iconst_m1() {
        instructionList.add(new InsnNode(Opcodes.ICONST_M1));
        return this;
    }

    public CodeBlock iconst_0() {
        instructionList.add(new InsnNode(Opcodes.ICONST_0));
        return this;
    }

    public CodeBlock iconst_1() {
        instructionList.add(new InsnNode(Opcodes.ICONST_1));
        return this;
    }

    public CodeBlock iconst_2() {
        instructionList.add(new InsnNode(Opcodes.ICONST_2));
        return this;
    }

    public CodeBlock iconst_3() {
        instructionList.add(new InsnNode(Opcodes.ICONST_3));
        return this;
    }

    public CodeBlock iconst_4() {
        instructionList.add(new InsnNode(Opcodes.ICONST_4));
        return this;
    }

    public CodeBlock iconst_5() {
        instructionList.add(new InsnNode(Opcodes.ICONST_5));
        return this;
    }

    public CodeBlock lconst_0() {
        instructionList.add(new InsnNode(Opcodes.LCONST_0));
        return this;
    }

    public CodeBlock aconst_null() {
        instructionList.add(new InsnNode(Opcodes.ACONST_NULL));
        return this;
    }

    public CodeBlock label(final LabelNode labelNode) {
        instructionList.add(labelNode);
        return this;
    }

    public CodeBlock nop() {
        instructionList.add(new InsnNode(Opcodes.NOP));
        return this;
    }

    public CodeBlock pop() {
        instructionList.add(new InsnNode(POP));
        return this;
    }

    public CodeBlock pop2() {
        instructionList.add(new InsnNode(POP2));
        return this;
    }

    public CodeBlock arrayload() {
        instructionList.add(new InsnNode(AALOAD));
        return this;
    }

    public CodeBlock arraystore() {
        instructionList.add(new InsnNode(AASTORE));
        return this;
    }

    public CodeBlock iarrayload() {
        instructionList.add(new InsnNode(IALOAD));
        return this;
    }

    public CodeBlock barrayload() {
        instructionList.add(new InsnNode(BALOAD));
        return this;
    }

    public CodeBlock barraystore() {
        instructionList.add(new InsnNode(BASTORE));
        return this;
    }

    public CodeBlock aaload() {
        instructionList.add(new InsnNode(AALOAD));
        return this;
    }

    public CodeBlock aastore() {
        instructionList.add(new InsnNode(AASTORE));
        return this;
    }

    public CodeBlock iaload() {
        instructionList.add(new InsnNode(IALOAD));
        return this;
    }

    public CodeBlock iastore() {
        instructionList.add(new InsnNode(IASTORE));
        return this;
    }

    public CodeBlock laload() {
        instructionList.add(new InsnNode(LALOAD));
        return this;
    }

    public CodeBlock lastore() {
        instructionList.add(new InsnNode(LASTORE));
        return this;
    }

    public CodeBlock baload() {
        instructionList.add(new InsnNode(BALOAD));
        return this;
    }

    public CodeBlock bastore() {
        instructionList.add(new InsnNode(BASTORE));
        return this;
    }

    public CodeBlock saload() {
        instructionList.add(new InsnNode(SALOAD));
        return this;
    }

    public CodeBlock sastore() {
        instructionList.add(new InsnNode(SASTORE));
        return this;
    }

    public CodeBlock caload() {
        instructionList.add(new InsnNode(CALOAD));
        return this;
    }

    public CodeBlock castore() {
        instructionList.add(new InsnNode(CASTORE));
        return this;
    }

    public CodeBlock faload() {
        instructionList.add(new InsnNode(FALOAD));
        return this;
    }

    public CodeBlock fastore() {
        instructionList.add(new InsnNode(FASTORE));
        return this;
    }

    public CodeBlock daload() {
        instructionList.add(new InsnNode(DALOAD));
        return this;
    }

    public CodeBlock dastore() {
        instructionList.add(new InsnNode(DASTORE));
        return this;
    }

    public CodeBlock fcmpl() {
        instructionList.add(new InsnNode(FCMPL));
        return this;
    }

    public CodeBlock fcmpg() {
        instructionList.add(new InsnNode(FCMPG));
        return this;
    }

    public CodeBlock dcmpl() {
        instructionList.add(new InsnNode(DCMPL));
        return this;
    }

    public CodeBlock dcmpg() {
        instructionList.add(new InsnNode(DCMPG));
        return this;
    }

    public CodeBlock dup_x2() {
        instructionList.add(new InsnNode(DUP_X2));
        return this;
    }

    public CodeBlock dup_x1() {
        instructionList.add(new InsnNode(DUP_X1));
        return this;
    }

    public CodeBlock dup2_x2() {
        instructionList.add(new InsnNode(DUP2_X2));
        return this;
    }

    public CodeBlock dup2_x1() {
        instructionList.add(new InsnNode(DUP2_X1));
        return this;
    }

    public CodeBlock dup2() {
        instructionList.add(new InsnNode(DUP2));
        return this;
    }

    public CodeBlock trycatch(final LabelNode scopeStart, final LabelNode scopeEnd, final LabelNode handler, final String exceptionType) {
        tryCatchBlockList.add(new TryCatchBlockNode(scopeStart, scopeEnd, handler, exceptionType));
        return this;
    }

    public CodeBlock trycatch(final String exceptionType, final Runnable body, final Runnable catchBody) {
        final LabelNode before = new LabelNode();
        final LabelNode after = new LabelNode();
        final LabelNode catchStart = new LabelNode();
        final LabelNode done = new LabelNode();

        trycatch(before, after, catchStart, exceptionType);
        label(before);
        body.run();
        label(after);
        go_to(done);
        if (catchBody != null) {
            label(catchStart);
            catchBody.run();
        }
        label(done);
        return this;
    }

    public CodeBlock go_to(final LabelNode label) {
        instructionList.add(new JumpInsnNode(GOTO, label));
        return this;
    }

    public CodeBlock lookupswitch(final LabelNode defaultHandler, final int[] keys, final LabelNode[] handlers) {
        instructionList.add(new LookupSwitchInsnNode(defaultHandler, keys, handlers));
        return this;
    }

    public CodeBlock athrow() {
        instructionList.add(new InsnNode(ATHROW));
        return this;
    }

    public CodeBlock instance_of(final String typeDesc) {
        instructionList.add(new TypeInsnNode(INSTANCEOF, typeDesc));
        return this;
    }

    public CodeBlock ifeq(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IFEQ, jumpLabel));
        return this;
    }

    public CodeBlock iffalse(final LabelNode jumpLabel) {
        ifeq(jumpLabel);
        return this;
    }

    public CodeBlock ifne(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IFNE, jumpLabel));
        return this;
    }

    public CodeBlock iftrue(final LabelNode jumpLabel) {
        ifne(jumpLabel);
        return this;
    }

    public CodeBlock if_acmpne(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IF_ACMPNE, jumpLabel));
        return this;
    }

    public CodeBlock if_acmpeq(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IF_ACMPEQ, jumpLabel));
        return this;
    }

    public CodeBlock if_icmple(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IF_ICMPLE, jumpLabel));
        return this;
    }

    public CodeBlock if_icmpgt(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IF_ICMPGT, jumpLabel));
        return this;
    }

    public CodeBlock if_icmplt(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IF_ICMPLT, jumpLabel));
        return this;
    }

    public CodeBlock if_icmpne(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IF_ICMPNE, jumpLabel));
        return this;
    }

    public CodeBlock if_icmpeq(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IF_ICMPEQ, jumpLabel));
        return this;
    }

    public CodeBlock if_icmpge(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IF_ICMPGE, jumpLabel));
        return this;
    }

    public CodeBlock checkcast(final String typeDesc) {
        instructionList.add(new TypeInsnNode(CHECKCAST, typeDesc));
        return this;
    }

    public CodeBlock line(final int line) {
        visitLineNumber(line, new LabelNode());
        return this;
    }

    public CodeBlock line(final int line, final LabelNode label) {
        visitLineNumber(line, label);
        return this;
    }

    public CodeBlock ifnonnull(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IFNONNULL, jumpLabel));
        return this;
    }

    public CodeBlock ifnull(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IFNULL, jumpLabel));
        return this;
    }

    public CodeBlock iflt(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IFLT, jumpLabel));
        return this;
    }

    public CodeBlock ifle(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IFLE, jumpLabel));
        return this;
    }

    public CodeBlock ifgt(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IFGT, jumpLabel));
        return this;
    }

    public CodeBlock ifge(final LabelNode jumpLabel) {
        instructionList.add(new JumpInsnNode(IFGE, jumpLabel));
        return this;
    }

    public CodeBlock arraylength() {
        instructionList.add(new InsnNode(ARRAYLENGTH));
        return this;
    }

    public CodeBlock ishr() {
        instructionList.add(new InsnNode(ISHR));
        return this;
    }

    public CodeBlock ishl() {
        instructionList.add(new InsnNode(ISHL));
        return this;
    }

    public CodeBlock iushr() {
        instructionList.add(new InsnNode(IUSHR));
        return this;
    }

    public CodeBlock lshr() {
        instructionList.add(new InsnNode(LSHR));
        return this;
    }

    public CodeBlock lshl() {
        instructionList.add(new InsnNode(LSHL));
        return this;
    }

    public CodeBlock lushr() {
        instructionList.add(new InsnNode(LUSHR));
        return this;
    }

    public CodeBlock lcmp() {
        instructionList.add(new InsnNode(LCMP));
        return this;
    }

    public CodeBlock iand() {
        instructionList.add(new InsnNode(IAND));
        return this;
    }

    public CodeBlock ior() {
        instructionList.add(new InsnNode(IOR));
        return this;
    }

    public CodeBlock ixor() {
        instructionList.add(new InsnNode(IXOR));
        return this;
    }

    public CodeBlock land() {
        instructionList.add(new InsnNode(LAND));
        return this;
    }

    public CodeBlock lor() {
        instructionList.add(new InsnNode(LOR));
        return this;
    }

    public CodeBlock lxor() {
        instructionList.add(new InsnNode(LXOR));
        return this;
    }

    public CodeBlock iadd() {
        instructionList.add(new InsnNode(IADD));
        return this;
    }

    public CodeBlock ladd() {
        instructionList.add(new InsnNode(LADD));
        return this;
    }

    public CodeBlock fadd() {
        instructionList.add(new InsnNode(FADD));
        return this;
    }

    public CodeBlock dadd() {
        instructionList.add(new InsnNode(DADD));
        return this;
    }

    public CodeBlock isub() {
        instructionList.add(new InsnNode(ISUB));
        return this;
    }

    public CodeBlock lsub() {
        instructionList.add(new InsnNode(LSUB));
        return this;
    }

    public CodeBlock fsub() {
        instructionList.add(new InsnNode(FSUB));
        return this;
    }

    public CodeBlock dsub() {
        instructionList.add(new InsnNode(DSUB));
        return this;
    }

    public CodeBlock idiv() {
        instructionList.add(new InsnNode(IDIV));
        return this;
    }

    public CodeBlock irem() {
        instructionList.add(new InsnNode(IREM));
        return this;
    }

    public CodeBlock ineg() {
        instructionList.add(new InsnNode(INEG));
        return this;
    }

    public CodeBlock i2d() {
        instructionList.add(new InsnNode(I2D));
        return this;
    }

    public CodeBlock i2l() {
        instructionList.add(new InsnNode(I2L));
        return this;
    }

    public CodeBlock i2f() {
        instructionList.add(new InsnNode(I2F));
        return this;
    }

    public CodeBlock i2s() {
        instructionList.add(new InsnNode(I2S));
        return this;
    }

    public CodeBlock i2c() {
        instructionList.add(new InsnNode(I2C));
        return this;
    }

    public CodeBlock i2b() {
        instructionList.add(new InsnNode(I2B));
        return this;
    }

    public CodeBlock ldiv() {
        instructionList.add(new InsnNode(LDIV));
        return this;
    }

    public CodeBlock lrem() {
        instructionList.add(new InsnNode(LREM));
        return this;
    }

    public CodeBlock lneg() {
        instructionList.add(new InsnNode(LNEG));
        return this;
    }

    public CodeBlock l2d() {
        instructionList.add(new InsnNode(L2D));
        return this;
    }

    public CodeBlock l2i() {
        instructionList.add(new InsnNode(L2I));
        return this;
    }

    public CodeBlock l2f() {
        instructionList.add(new InsnNode(L2F));
        return this;
    }

    public CodeBlock fdiv() {
        instructionList.add(new InsnNode(FDIV));
        return this;
    }

    public CodeBlock frem() {
        instructionList.add(new InsnNode(FREM));
        return this;
    }

    public CodeBlock fneg() {
        instructionList.add(new InsnNode(FNEG));
        return this;
    }

    public CodeBlock f2d() {
        instructionList.add(new InsnNode(F2D));
        return this;
    }

    public CodeBlock f2i() {
        instructionList.add(new InsnNode(F2D));
        return this;
    }

    public CodeBlock f2l() {
        instructionList.add(new InsnNode(F2L));
        return this;
    }

    public CodeBlock ddiv() {
        instructionList.add(new InsnNode(DDIV));
        return this;
    }

    public CodeBlock drem() {
        instructionList.add(new InsnNode(DREM));
        return this;
    }

    public CodeBlock dneg() {
        instructionList.add(new InsnNode(DNEG));
        return this;
    }

    public CodeBlock d2f() {
        instructionList.add(new InsnNode(D2F));
        return this;
    }

    public CodeBlock d2i() {
        instructionList.add(new InsnNode(D2I));
        return this;
    }

    public CodeBlock d2l() {
        instructionList.add(new InsnNode(D2L));
        return this;
    }

    public CodeBlock imul() {
        instructionList.add(new InsnNode(IMUL));
        return this;
    }

    public CodeBlock lmul() {
        instructionList.add(new InsnNode(LMUL));
        return this;
    }

    public CodeBlock fmul() {
        instructionList.add(new InsnNode(FMUL));
        return this;
    }

    public CodeBlock dmul() {
        instructionList.add(new InsnNode(DMUL));
        return this;
    }

    public CodeBlock iinc(final int varIndex, final int increment) {
        instructionList.add(new IincInsnNode(varIndex, increment));
        return this;
    }

    public CodeBlock monitorenter() {
        instructionList.add(new InsnNode(MONITORENTER));
        return this;
    }

    public CodeBlock monitorexit() {
        instructionList.add(new InsnNode(MONITOREXIT));
        return this;
    }

    public CodeBlock jsr(final LabelNode branch) {
        instructionList.add(new JumpInsnNode(JSR, branch));
        return this;
    }

    public CodeBlock ret(final int value) {
        instructionList.add(new IntInsnNode(RET, value));
        return this;
    }

    public CodeBlock visitInsn(final int opcode) {
        instructionList.add(new InsnNode(opcode));
        return this;
    }

    public CodeBlock visitIntInsn(final int opcode, final int operand) {
        instructionList.add(new IntInsnNode(opcode, operand));
        return this;
    }

    public CodeBlock visitInsnNode(final int opcode, final int operand) {
        instructionList.add(new IntInsnNode(opcode, operand));
        return this;
    }

    public CodeBlock visitTypeInsn(final int opcode, final String desc) {
        instructionList.add(new TypeInsnNode(opcode, desc));
        return this;
    }

    public CodeBlock visitFieldInsn(final int opcode, final String className, final String fieldName, final String fieldDesc) {
        instructionList.add(new FieldInsnNode(opcode, className, fieldName, fieldDesc));
        return this;
    }

    public CodeBlock visitMethodInsn(final int opcode, final String className, final String methodName, final String methodDesc) {
        final boolean intf = opcode == INVOKEINTERFACE;
        instructionList.add(new MethodInsnNode(opcode, className, methodName, methodDesc, intf));
        return this;
    }

    public CodeBlock visitInvokeDynamicInsn(final String name, final String desc, final Handle bootstrapMethod,
            final Object... bootstrapArguments) {
        instructionList.add(new InvokeDynamicInsnNode(name, desc, bootstrapMethod, bootstrapArguments));
        return this;
    }

    public CodeBlock visitJumpInsn(final int opcode, final LabelNode node) {
        instructionList.add(new JumpInsnNode(opcode, node));
        return this;
    }

    public CodeBlock visitLabel(final Label label) {
        instructionList.add(new LabelNode(label));
        return this;
    }

    public CodeBlock visitLdcInsn(final Object value) {
        instructionList.add(new LdcInsnNode(value));
        return this;
    }

    public CodeBlock visitIincInsn(final int varIndex, final int increment) {
        instructionList.add(new IincInsnNode(varIndex, increment));
        return this;
    }

    public CodeBlock visitTableSwitchInsn(final int min, final int max, final LabelNode defaultHandler, final LabelNode[] handlers) {
        instructionList.add(new TableSwitchInsnNode(min, max, defaultHandler, handlers));
        return this;
    }

    public CodeBlock visitLookupSwitchInsn(final LabelNode defaultHandler, final int[] keys, final LabelNode[] handlers) {
        instructionList.add(new LookupSwitchInsnNode(defaultHandler, keys, handlers));
        return this;
    }

    public CodeBlock visitMultiANewArrayInsn(final String desc, final int dims) {
        instructionList.add(new MultiANewArrayInsnNode(desc, dims));
        return this;
    }

    public CodeBlock visitTryCatchBlock(final LabelNode scopeStart, final LabelNode scopeEnd, final LabelNode handler,
            final String exceptionType) {
        tryCatchBlockList.add(new TryCatchBlockNode(scopeStart, scopeEnd, handler, exceptionType));
        return this;
    }

    public CodeBlock visitLocalVariable(final String varName, final String varDesc, final String signature, final LabelNode scopeStart,
            final LabelNode scopeEnd, final int varIndex) {
        localVariableList.add(new LocalVariableNode(varName, varDesc, signature, scopeStart, scopeEnd, varIndex));
        return this;
    }

    public CodeBlock visitLineNumber(final int lineNumber, final LabelNode start) {
        instructionList.add(new LineNumberNode(lineNumber, start));
        return this;
    }

    public CodeBlock tableswitch(final int min, final int max, final LabelNode defaultLabel, final LabelNode[] cases) {
        instructionList.add(new TableSwitchInsnNode(min, max, defaultLabel, cases));
        return this;
    }

    public CodeBlock visitFrame(final int opcode, final int nrLocals, final Object[] localTypes, final int nrStackElements,
            final Object[] stackElements) {
        instructionList.add(new FrameNode(opcode, nrLocals, localTypes, nrStackElements, stackElements));
        return this;
    }

    public InsnList getInstructionList() {
        return instructionList;
    }

    public List<TryCatchBlockNode> getTryCatchBlockList() {
        return tryCatchBlockList;
    }

    public List<LocalVariableNode> getLocalVariableList() {
        return localVariableList;
    }

    public List<VisibleAnnotation> getAnnotations() {
        return annotations;
    }

    /**
     * adds a compressed frame to the stack
     *
     * @param stackArguments the argument types on the stack, represented as
     * "class path names" e.g java/lang/RuntimeException
     */
    public CodeBlock frame_same(final Object... stackArguments) {
        final int type;

        switch (stackArguments.length) {
            case 0:
                type = Opcodes.F_SAME;
                break;
            case 1:
                type = Opcodes.F_SAME1;
                break;
            default:
                throw new IllegalArgumentException("same frame should have 0" + " or 1 arguments on stack");
        }

        instructionList.add(new FrameNode(type, 0, null, stackArguments.length, stackArguments));
        return this;
    }

    public CodeBlock prepend(final CodeBlock codeBlock) {
        if (codeBlock.returns())
            returns = true;

        annotations.addAll(codeBlock.annotations);
        instructionList.insert(codeBlock.instructionList);
        return this;
    }

    public CodeBlock append(final CodeBlock codeBlock) {
        if (codeBlock.returns())
            returns = true;

        instructionList.add(codeBlock.instructionList);
        tryCatchBlockList.addAll(codeBlock.tryCatchBlockList);
        annotations.addAll(codeBlock.annotations);
        return this;
    }

    public VisibleAnnotation annotation(final Class<?> type) {
        final VisibleAnnotation annotation = new VisibleAnnotation(ci(type));
        addAnnotation(annotation);
        return annotation;
    }

    public CodeBlock addAnnotation(final VisibleAnnotation annotation) {
        annotations.add(annotation);
        return this;
    }

    public CodeBlock addAll(final InsnList insnList) {
        instructionList.add(insnList);
        return this;
    }

    public int arity() {
        return arity;
    }

    public boolean returns() {
        return returns;
    }
}
