/***
 * ASM tests
 * Copyright (c) 2000-2011 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.objectweb.asm.tree;

import java.util.ListIterator;
import java.util.NoSuchElementException;

import junit.framework.TestCase;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * InsnList unit tests.
 * 
 * @author Eric Bruneton
 * @author Eugene Kuleshov
 */
public class InsnListUnitTest extends TestCase {

    InsnList l1;

    InsnList l2;

    InsnNode in1;

    InsnNode in2;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        l1 = new CheckedInsnList();
        l2 = new CheckedInsnList();
        in1 = new InsnNode(0);
        in2 = new InsnNode(0);
        l2.add(in1);
        l2.add(in2);
    }

    protected void assertEquals(final AbstractInsnNode[] expected,
            final AbstractInsnNode[] value) {
        assertEquals(expected.length, value.length);
        for (int i = 0; i < value.length; ++i) {
            assertEquals(expected[i], value[i]);
        }
    }

    public void testSize() {
        assertEquals(0, l1.size());
    }

    public void testGetFirst() {
        assertEquals(null, l1.getFirst());
    }

    public void testGetLast() {
        assertEquals(null, l1.getLast());
    }

    public void testInvalidGet() {
        try {
            l1.get(0);
            fail();
        } catch (IndexOutOfBoundsException e) {
        }
    }

    public void testContains() {
        assertEquals(false, l1.contains(new InsnNode(0)));
    }

    public void testIterator() {
        InsnNode insn = new InsnNode(0);

        // iteration
        ListIterator<AbstractInsnNode> it = l2.iterator();
        assertTrue(it.hasNext());
        assertEquals(in1, it.next());
        assertTrue(it.hasNext());
        assertEquals(in2, it.next());
        assertFalse(it.hasNext());
        assertTrue(it.hasPrevious());
        assertEquals(in2, it.previous());
        assertTrue(it.hasPrevious());
        assertEquals(in1, it.previous());
        assertFalse(it.hasPrevious());

        l2.add(insn);

        // remove()
        it = l2.iterator();
        assertTrue(it.hasNext());
        assertEquals(in1, it.next());
        assertTrue(it.hasNext());
        assertEquals(in2, it.next());
        assertTrue(it.hasNext());
        it.remove(); // remove in2
        assertTrue(it.hasNext());
        assertEquals(insn, it.next());
        assertFalse(it.hasNext());
        assertTrue(it.hasPrevious());

        it = l2.iterator();
        assertTrue(it.hasNext());
        assertEquals(in1, it.next());
        assertTrue(it.hasNext());
        assertEquals(insn, it.next());
        assertFalse(it.hasNext());

        l2.remove(insn);
        l2.insert(in1, in2);

        // add() then next()
        it = l2.iterator();
        assertTrue(it.hasNext());
        assertEquals(in1, it.next());
        it.add(insn);
        assertEquals(in2, it.next());

        l2.remove(insn);

        // add() then previous()
        it = l2.iterator();
        assertTrue(it.hasNext());
        assertEquals(in1, it.next());
        it.add(insn);
        assertEquals(insn, it.previous());
        assertEquals(insn, it.next());
        assertTrue(it.hasNext());
        assertEquals(in2, it.next());
        assertFalse(it.hasNext());

        l2.remove(insn);

        // set() then previous()
        it = l2.iterator();
        assertTrue(it.hasNext());
        assertEquals(in1, it.next());
        it.set(insn);
        assertEquals(insn, it.previous());
        assertEquals(insn, it.next());
        assertTrue(it.hasNext());

        l2.remove(insn);
        l2.insertBefore(in2, in1);

        // add() then next()
        it = l2.iterator();
        assertTrue(it.hasNext());
        assertEquals(in1, it.next());
        it.set(insn);
        assertEquals(in2, it.next());
    }

    public void testIterator2() {
        ListIterator<AbstractInsnNode> it = l2.iterator(l2.size());

        assertFalse(it.hasNext());
        assertTrue(it.hasPrevious());
        assertEquals(1, it.previousIndex());
        assertEquals(in2, it.previous());
        assertTrue(it.hasPrevious());
        assertEquals(0, it.previousIndex());
        assertEquals(in1, it.previous());
        assertFalse(it.hasPrevious());

        assertEquals(-1, it.previousIndex());

        assertTrue(it.hasNext());
        assertEquals(0, it.nextIndex());
        assertEquals(in1, it.next());
        assertTrue(it.hasNext());
        assertEquals(1, it.nextIndex());

        InsnNode insn = new InsnNode(0);
        it.add(insn);

        assertEquals(2, it.nextIndex());
        assertEquals(in2, it.next());
        assertFalse(it.hasNext());
        assertEquals(3, it.nextIndex());
    }

    public void testIterator3() {
        try {
            new InsnList().iterator().next();
            fail();
        } catch (NoSuchElementException e) {
        }
    }

    public void testInvalidIndexOf() {
        try {
            l1.indexOf(new InsnNode(0));
            fail();
        } catch (IllegalArgumentException e) {
        }
    }

    public void testToArray() {
        assertEquals(0, l1.toArray().length);
    }

    public void testInvalidSet() {
        try {
            l1.set(new InsnNode(0), new InsnNode(0));
            fail();
        } catch (IllegalArgumentException e) {
        }
    }

    public void testSet() {
        l1.add(new InsnNode(0));
        AbstractInsnNode insn = new InsnNode(0);
        l1.set(l1.getFirst(), insn);
        assertEquals(1, l1.size());
        assertEquals(insn, l1.getFirst());

        l1.remove(insn);
        l1.add(new InsnNode(0));

        l1.set(l1.get(0), insn);
        assertEquals(1, l1.size());
        assertEquals(insn, l1.getFirst());

        l1.remove(insn);
        l1.add(new InsnNode(0));
        l1.add(new InsnNode(0));

        l1.set(l1.get(1), insn);
        assertEquals(2, l1.size());
        assertEquals(insn, l1.get(1));
    }

    public void testInvalidAdd() {
        try {
            l1.add(in1);
            fail();
        } catch (IllegalArgumentException e) {
        }
    }

    public void testAddEmpty() {
        InsnNode insn = new InsnNode(0);
        l1.add(insn);
        assertEquals(1, l1.size());
        assertEquals(insn, l1.getFirst());
        assertEquals(insn, l1.getLast());
        assertEquals(insn, l1.get(0));
        assertEquals(true, l1.contains(insn));
        assertEquals(0, l1.indexOf(insn));
        assertEquals(new AbstractInsnNode[] { insn }, l1.toArray());
        assertEquals(null, insn.getPrevious());
        assertEquals(null, insn.getNext());
    }

    public void testAddNonEmpty() {
        InsnNode insn = new InsnNode(0);
        l1.add(new InsnNode(0));
        l1.add(insn);
        assertEquals(2, l1.size());
        assertEquals(insn, l1.getLast());
        assertEquals(1, l1.indexOf(insn));
        assertEquals(insn, l1.get(1));
        assertEquals(true, l1.contains(insn));
    }

    public void testAddEmptyList() {
        l1.add(new InsnList());
        assertEquals(0, l1.size());
        assertEquals(null, l1.getFirst());
        assertEquals(null, l1.getLast());
        assertEquals(new AbstractInsnNode[0], l1.toArray());
    }

    public void testInvalidAddAll() {
        try {
            l1.add(l1);
            fail();
        } catch (IllegalArgumentException e) {
        }
    }

    public void testAddAllEmpty() {
        l1.add(l2);
        assertEquals(2, l1.size());
        assertEquals(in1, l1.getFirst());
        assertEquals(in2, l1.getLast());
        assertEquals(in1, l1.get(0));
        assertEquals(true, l1.contains(in1));
        assertEquals(true, l1.contains(in2));
        assertEquals(0, l1.indexOf(in1));
        assertEquals(1, l1.indexOf(in2));
        assertEquals(new AbstractInsnNode[] { in1, in2 }, l1.toArray());
    }

    public void testAddAllNonEmpty() {
        InsnNode insn = new InsnNode(0);
        l1.add(insn);
        l1.add(l2);
        assertEquals(3, l1.size());
        assertEquals(insn, l1.getFirst());
        assertEquals(in2, l1.getLast());
        assertEquals(insn, l1.get(0));
        assertEquals(true, l1.contains(insn));
        assertEquals(true, l1.contains(in1));
        assertEquals(true, l1.contains(in2));
        assertEquals(0, l1.indexOf(insn));
        assertEquals(1, l1.indexOf(in1));
        assertEquals(2, l1.indexOf(in2));
        assertEquals(new AbstractInsnNode[] { insn, in1, in2 }, l1.toArray());
    }

    public void testInvalidInsert() {
        try {
            l1.insert(in1);
            fail();
        } catch (IllegalArgumentException e) {
        }
    }

    public void testInsertEmpty() {
        InsnNode insn = new InsnNode(0);
        l1.insert(insn);
        assertEquals(1, l1.size());
        assertEquals(insn, l1.getFirst());
        assertEquals(insn, l1.getLast());
        assertEquals(insn, l1.get(0));
        assertEquals(true, l1.contains(insn));
        assertEquals(0, l1.indexOf(insn));
        assertEquals(new AbstractInsnNode[] { insn }, l1.toArray());
    }

    public void testInsertNonEmpty() {
        InsnNode insn = new InsnNode(0);
        l1.add(new InsnNode(0));
        l1.insert(insn);
        assertEquals(2, l1.size());
        assertEquals(insn, l1.getFirst());
        assertEquals(insn, l1.get(0));
        assertEquals(true, l1.contains(insn));
        assertEquals(0, l1.indexOf(insn));
    }

    public void testInvalidInsertAll() {
        try {
            l1.insert(l1);
            fail();
        } catch (IllegalArgumentException e) {
        }
    }

    public void testInsertAllEmptyList() {
        l1.insert(new InsnList());
        assertEquals(0, l1.size());
        assertEquals(null, l1.getFirst());
        assertEquals(null, l1.getLast());
        assertEquals(new AbstractInsnNode[0], l1.toArray());
    }

    public void testInsertAllEmpty() {
        l1.insert(l2);
        assertEquals(2, l1.size(), 2);
        assertEquals(in1, l1.getFirst());
        assertEquals(in2, l1.getLast());
        assertEquals(in1, l1.get(0));
        assertEquals(true, l1.contains(in1));
        assertEquals(true, l1.contains(in2));
        assertEquals(0, l1.indexOf(in1));
        assertEquals(1, l1.indexOf(in2));
        assertEquals(new AbstractInsnNode[] { in1, in2 }, l1.toArray());
    }

    public void testInsertAllNonEmpty() {
        InsnNode insn = new InsnNode(0);
        l1.add(insn);
        l1.insert(l2);
        assertEquals(3, l1.size());
        assertEquals(in1, l1.getFirst());
        assertEquals(insn, l1.getLast());
        assertEquals(in1, l1.get(0));
        assertEquals(true, l1.contains(insn));
        assertEquals(true, l1.contains(in1));
        assertEquals(true, l1.contains(in2));
        assertEquals(0, l1.indexOf(in1));
        assertEquals(1, l1.indexOf(in2));
        assertEquals(2, l1.indexOf(insn));
        assertEquals(new AbstractInsnNode[] { in1, in2, insn }, l1.toArray());
    }

    public void testInvalidInsert2() {
        try {
            l1.insert(new InsnNode(0), new InsnNode(0));
            fail();
        } catch (IllegalArgumentException e) {
        }
    }

    public void testInsert2NotLast() {
        InsnNode insn = new InsnNode(0);
        l2.insert(in1, insn);
        assertEquals(3, l2.size());
        assertEquals(in1, l2.getFirst());
        assertEquals(in2, l2.getLast());
        assertEquals(in1, l2.get(0));
        assertEquals(true, l2.contains(insn));
        assertEquals(1, l2.indexOf(insn));
        assertEquals(new AbstractInsnNode[] { in1, insn, in2 }, l2.toArray());
    }

    public void testInsert2Last() {
        InsnNode insn = new InsnNode(0);
        l2.insert(in2, insn);
        assertEquals(3, l2.size());
        assertEquals(in1, l2.getFirst());
        assertEquals(insn, l2.getLast());
        assertEquals(in1, l2.get(0));
        assertEquals(true, l2.contains(insn));
        assertEquals(2, l2.indexOf(insn));
        assertEquals(new AbstractInsnNode[] { in1, in2, insn }, l2.toArray());
    }

    public void testInsertBefore() {
        InsnNode insn = new InsnNode(0);
        l2.insertBefore(in2, insn);
        assertEquals(3, l2.size());
        assertEquals(in1, l2.getFirst());
        assertEquals(in2, l2.getLast());
        assertEquals(insn, l2.get(1));
        assertEquals(true, l2.contains(insn));
        assertEquals(1, l2.indexOf(insn));
        assertEquals(new AbstractInsnNode[] { in1, insn, in2 }, l2.toArray());
    }

    public void testInsertBeforeFirst() {
        InsnNode insn = new InsnNode(0);
        l2.insertBefore(in1, insn);
        assertEquals(3, l2.size());
        assertEquals(insn, l2.getFirst());
        assertEquals(in2, l2.getLast());
        assertEquals(insn, l2.get(0));
        assertEquals(true, l2.contains(insn));
        assertEquals(0, l2.indexOf(insn));
        assertEquals(new AbstractInsnNode[] { insn, in1, in2 }, l2.toArray());
    }

    public void testInvalidInsertBefore() {
        try {
            l1.insertBefore(new InsnNode(0), new InsnNode(0));
            fail();
        } catch (IllegalArgumentException e) {
        }
    }

    public void testInvalidInsertAll2() {
        try {
            l1.insert(new InsnNode(0), new InsnList());
            fail();
        } catch (IllegalArgumentException e) {
        }
    }

    public void testInsertAll2EmptyList() {
        InsnNode insn = new InsnNode(0);
        l1.add(insn);
        l1.insert(insn, new InsnList());
        assertEquals(1, l1.size());
        assertEquals(insn, l1.getFirst());
        assertEquals(insn, l1.getLast());
        assertEquals(new AbstractInsnNode[] { insn }, l1.toArray());
    }

    public void testInsertAll2NotLast() {
        InsnNode insn = new InsnNode(0);
        l1.add(insn);
        l1.add(new InsnNode(0));
        l1.insert(insn, l2);
        assertEquals(4, l1.size());
        assertEquals(insn, l1.getFirst());
        assertEquals(insn, l1.get(0));
        assertEquals(true, l1.contains(insn));
        assertEquals(true, l1.contains(in1));
        assertEquals(true, l1.contains(in2));
        assertEquals(0, l1.indexOf(insn));
        assertEquals(1, l1.indexOf(in1));
        assertEquals(2, l1.indexOf(in2));
    }

    public void testInsertAll2Last() {
        InsnNode insn = new InsnNode(0);
        l1.add(insn);
        l1.insert(insn, l2);
        assertEquals(3, l1.size());
        assertEquals(insn, l1.getFirst());
        assertEquals(in2, l1.getLast());
        assertEquals(insn, l1.get(0));
        assertEquals(true, l1.contains(insn));
        assertEquals(true, l1.contains(in1));
        assertEquals(true, l1.contains(in2));
        assertEquals(0, l1.indexOf(insn));
        assertEquals(1, l1.indexOf(in1));
        assertEquals(2, l1.indexOf(in2));
        assertEquals(new AbstractInsnNode[] { insn, in1, in2 }, l1.toArray());
    }

    public void testInvalidInsertBeforeAll() {
        try {
            l1.insertBefore(new InsnNode(0), new InsnList());
            fail();
        } catch (IllegalArgumentException e) {
        }
    }

    public void testInsertBeforeAll2EmptyList() {
        InsnNode insn = new InsnNode(0);
        l1.add(insn);
        l1.insertBefore(insn, new InsnList());
        assertEquals(1, l1.size());
        assertEquals(insn, l1.getFirst());
        assertEquals(insn, l1.getLast());
        assertEquals(new AbstractInsnNode[] { insn }, l1.toArray());
    }

    public void testInsertBeforeAll2NotLast() {
        InsnNode insn = new InsnNode(0);
        l1.add(new InsnNode(0));
        l1.add(insn);
        l1.insertBefore(insn, l2);
        assertEquals(4, l1.size());
        assertEquals(in1, l1.get(1));
        assertEquals(in2, l1.get(2));
        assertEquals(true, l1.contains(insn));
        assertEquals(true, l1.contains(in1));
        assertEquals(true, l1.contains(in2));
        assertEquals(3, l1.indexOf(insn));
        assertEquals(1, l1.indexOf(in1));
        assertEquals(2, l1.indexOf(in2));
    }

    public void testInsertBeforeAll2First() {
        InsnNode insn = new InsnNode(0);
        l1.insert(insn);
        l1.insertBefore(insn, l2);
        assertEquals(3, l1.size());
        assertEquals(in1, l1.getFirst());
        assertEquals(insn, l1.getLast());
        assertEquals(in1, l1.get(0));
        assertEquals(true, l1.contains(insn));
        assertEquals(true, l1.contains(in1));
        assertEquals(true, l1.contains(in2));
        assertEquals(2, l1.indexOf(insn));
        assertEquals(0, l1.indexOf(in1));
        assertEquals(1, l1.indexOf(in2));
        assertEquals(new AbstractInsnNode[] { in1, in2, insn }, l1.toArray());
    }

    public void testInvalidRemove() {
        try {
            l1.remove(new InsnNode(0));
        } catch (IllegalArgumentException e) {
        }
    }

    public void testRemoveSingle() {
        InsnNode insn = new InsnNode(0);
        l1.add(insn);
        l1.remove(insn);
        assertEquals(0, l1.size());
        assertEquals(null, l1.getFirst());
        assertEquals(null, l1.getLast());
        assertEquals(false, l1.contains(insn));
        assertEquals(new AbstractInsnNode[0], l1.toArray());
        assertEquals(null, insn.getPrevious());
        assertEquals(null, insn.getNext());
    }

    public void testRemoveFirst() {
        InsnNode insn = new InsnNode(0);
        l1.add(insn);
        l1.add(new InsnNode(0));
        l1.remove(insn);
        assertEquals(false, l1.contains(insn));
        assertEquals(null, insn.getPrevious());
        assertEquals(null, insn.getNext());
    }

    public void testRemoveMiddle() {
        InsnNode insn = new InsnNode(0);
        l1.add(new InsnNode(0));
        l1.add(insn);
        l1.add(new InsnNode(0));
        l1.remove(insn);
        assertEquals(false, l1.contains(insn));
        assertEquals(null, insn.getPrevious());
        assertEquals(null, insn.getNext());
    }

    public void testRemoveLast() {
        InsnNode insn = new InsnNode(0);
        l1.add(new InsnNode(0));
        l1.add(insn);
        l1.remove(insn);
        assertEquals(false, l1.contains(insn));
        assertEquals(null, insn.getPrevious());
        assertEquals(null, insn.getNext());
    }

    public void testClear() {
        InsnNode insn = new InsnNode(0);
        l1.add(new InsnNode(0));
        l1.add(insn);
        l1.add(new InsnNode(0));
        l1.clear();
        assertEquals(0, l1.size());
        assertEquals(null, l1.getFirst());
        assertEquals(null, l1.getLast());
        assertEquals(false, l1.contains(insn));
        assertEquals(new AbstractInsnNode[0], l1.toArray());
        assertEquals(null, insn.getPrevious());
        assertEquals(null, insn.getNext());
    }

    public void testAcceptor1() {
        l1.add(new InsnNode(55));
        l1.add(new InsnNode(77));

        final InsnList lst = new InsnList();
        l1.accept(new MethodVisitor(Opcodes.ASM4) {
            @Override
            public void visitInsn(int opcode) {
                lst.add(new InsnNode(opcode));
            }
        });

        assertEquals(55, lst.get(0).opcode);
        assertEquals(77, lst.get(1).opcode);
    }

    public void testResetLabels() throws Exception {
        LabelNode labelNode = new LabelNode();

        l1.add(new InsnNode(55));
        l1.add(labelNode);
        l1.add(new InsnNode(55));

        Label label = labelNode.getLabel();
        assertNotNull(label);

        l1.resetLabels();

        assertNotSame(label, labelNode.getLabel());
    }
}

class CheckedInsnList extends InsnList {

    @Override
    public int indexOf(final AbstractInsnNode insn) {
        if (!contains(insn)) {
            throw new IllegalArgumentException();
        }
        return super.indexOf(insn);
    }

    @Override
    public void set(final AbstractInsnNode location, final AbstractInsnNode insn) {
        if (!(contains(location) && insn.index == -1)) {
            throw new IllegalArgumentException();
        }
        super.set(location, insn);
    }

    @Override
    public void add(final AbstractInsnNode insn) {
        if (insn.index != -1) {
            throw new IllegalArgumentException();
        }
        super.add(insn);
    }

    @Override
    public void add(final InsnList insns) {
        if (insns == this) {
            throw new IllegalArgumentException();
        }
        super.add(insns);
    }

    @Override
    public void insert(final AbstractInsnNode insn) {
        if (insn.index != -1) {
            throw new IllegalArgumentException();
        }
        super.insert(insn);
    }

    @Override
    public void insert(final InsnList insns) {
        if (insns == this) {
            throw new IllegalArgumentException();
        }
        super.insert(insns);
    }

    @Override
    public void insert(final AbstractInsnNode location,
            final AbstractInsnNode insn) {
        if (!(contains(location) && insn.index == -1)) {
            throw new IllegalArgumentException();
        }
        super.insert(location, insn);
    }

    @Override
    public void insert(final AbstractInsnNode location, final InsnList insns) {
        if (!(contains(location) && insns != this)) {
            throw new IllegalArgumentException();
        }
        super.insert(location, insns);
    }

    @Override
    public void insertBefore(final AbstractInsnNode location,
            final AbstractInsnNode insn) {
        if (!(contains(location) && insn.index == -1)) {
            throw new IllegalArgumentException();
        }
        super.insertBefore(location, insn);
    }

    @Override
    public void insertBefore(final AbstractInsnNode location,
            final InsnList insns) {
        if (!(contains(location) && insns != this)) {
            throw new IllegalArgumentException();
        }
        super.insertBefore(location, insns);
    }

    @Override
    public void remove(final AbstractInsnNode insn) {
        if (!contains(insn)) {
            throw new IllegalArgumentException();
        }
        super.remove(insn);
    }

    @Override
    public void clear() {
        super.removeAll(true);
    }
}
