/*
 * Copyright (c) 2013-2016 Chris Newland.
 * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
 * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
 */
package org.adoptopenjdk.jitwatch.test;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Map;

import org.adoptopenjdk.jitwatch.jarscan.sequencecount.InstructionSequence;
import org.adoptopenjdk.jitwatch.jarscan.sequencecount.SequenceCountOperation;
import org.adoptopenjdk.jitwatch.model.bytecode.MemberBytecode;
import org.adoptopenjdk.jitwatch.model.bytecode.Opcode;
import org.junit.Test;

public class TestJarScan
{
	public static final boolean SHOW_OUTPUT = false;
	
	@Test
	public void testLongBytecodeChain1()
	{
		String[] lines = new String[] {
				"0: aload_1",
				"1: invokevirtual #38                 // Method org/adoptopenjdk/jitwatch/model/Tag.getName:()Ljava/lang/String;",
				"4: astore_3",
				"5: iconst_m1",
				"6: istore        4",
				"8: aload_3",
				"9: invokevirtual #39                 // Method java/lang/String.hashCode:()I",
				"12: lookupswitch  { // 3",
				"   -1067517155: 63",
				"     106437299: 48",
				"     922165544: 78",
				"       default: 90",
				"  }",
				"48: aload_3",
				"49: ldc           #14                 // String parse",
				"51: invokevirtual #40                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z",
				"54: ifeq          90",
				"57: iconst_0",
				"58: istore        4",
				"60: goto          90",
				"63: aload_3",
				"64: ldc           #41                 // String eliminate_allocation",
				"66: invokevirtual #40                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z",
				"69: ifeq          90",
				"72: iconst_1",
				"73: istore        4",
				"75: goto          90",
				"78: aload_3",
				"79: ldc           #42                 // String eliminate_lock",
				"81: invokevirtual #40                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z",
				"84: ifeq          90",
				"87: iconst_2",
				"88: istore        4",
				"90: iload         4",
				"92: tableswitch   { // 0 to 2",
				"             0: 120",
				"             1: 129",
				"             2: 138",
				"       default: 147",
				"  }",
				"120: aload_0",
				"121: aload_1",
				"122: aload_2",
				"123: invokespecial #43                 // Method visitTagParse:(Lorg/adoptopenjdk/jitwatch/model/Tag;Lorg/adoptopenjdk/jitwatch/model/IParseDictionary;)V",
				"126: goto          152",
				"129: aload_0",
				"130: aload_1",
				"131: aload_2",
				"132: invokespecial #44                 // Method visitTagEliminateAllocation:(Lorg/adoptopenjdk/jitwatch/model/Tag;Lorg/adoptopenjdk/jitwatch/model/IParseDictionary;)V",
				"135: goto          152",
				"138: aload_0",
				"139: aload_1",
				"140: aload_2",
				"141: invokespecial #45                 // Method visitTagEliminateLock:(Lorg/adoptopenjdk/jitwatch/model/Tag;Lorg/adoptopenjdk/jitwatch/model/IParseDictionary;)V",
				"144: goto          152",
				"147: aload_0",
				"148: aload_1",
				"149: invokevirtual #46                 // Method handleOther:(Lorg/adoptopenjdk/jitwatch/model/Tag;)V",
				"152: return"
		};

		MemberBytecode memberBytecode = UnitTestUtil.createMemberBytecode(lines);

		SequenceCountOperation counter = new SequenceCountOperation(1);

		counter.processInstructions("Foo", memberBytecode);

		Map<InstructionSequence, Integer> result = counter.getSequenceScores();

		log(result);

		assertEquals(19, result.size());

		checkSequence(result, 5, Opcode.ALOAD_1);
		checkSequence(result, 6, Opcode.INVOKEVIRTUAL);
		checkSequence(result, 1, Opcode.ASTORE_3);
		checkSequence(result, 1, Opcode.ICONST_M1);
		checkSequence(result, 4, Opcode.ISTORE);
		checkSequence(result, 4, Opcode.ALOAD_3);
		checkSequence(result, 1, Opcode.LOOKUPSWITCH);
		checkSequence(result, 3, Opcode.LDC);
		checkSequence(result, 3, Opcode.IFEQ);
		checkSequence(result, 1, Opcode.ICONST_0);
		checkSequence(result, 1, Opcode.ICONST_1);
		checkSequence(result, 1, Opcode.ICONST_2);
		checkSequence(result, 1, Opcode.ILOAD);
		checkSequence(result, 1, Opcode.TABLESWITCH);
		checkSequence(result, 4, Opcode.ALOAD_0);
		checkSequence(result, 3, Opcode.ALOAD_2);
		checkSequence(result, 3, Opcode.INVOKESPECIAL);
		checkSequence(result, 5, Opcode.GOTO);
		checkSequence(result, 1, Opcode.RETURN);
	}

	@Test
	public void testLongBytecodeChain6()
	{
		String[] lines = new String[] {
				"0: aload_1",
				"1: invokevirtual #38                 // Method org/adoptopenjdk/jitwatch/model/Tag.getName:()Ljava/lang/String;",
				"4: astore_3",
				"5: iconst_m1",
				"6: istore        4",
				"8: aload_3",
				"9: invokevirtual #39                 // Method java/lang/String.hashCode:()I",
				"12: lookupswitch  { // 3",
				"   -1067517155: 63",
				"     106437299: 48",
				"     922165544: 78",
				"       default: 90",
				"  }",
				"48: aload_3",
				"49: ldc           #14                 // String parse",
				"51: invokevirtual #40                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z",
				"54: ifeq          90",
				"57: iconst_0",
				"58: istore        4",
				"60: goto          90",
				"63: aload_3",
				"64: ldc           #41                 // String eliminate_allocation",
				"66: invokevirtual #40                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z",
				"69: ifeq          90",
				"72: iconst_1",
				"73: istore        4",
				"75: goto          90",
				"78: aload_3",
				"79: ldc           #42                 // String eliminate_lock",
				"81: invokevirtual #40                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z",
				"84: ifeq          90",
				"87: iconst_2",
				"88: istore        4",
				"90: iload         4",
				"92: tableswitch   { // 0 to 2",
				"             0: 120",
				"             1: 129",
				"             2: 138",
				"       default: 147",
				"  }",
				"120: aload_0",
				"121: aload_1",
				"122: aload_2",
				"123: invokespecial #43                 // Method visitTagParse:(Lorg/adoptopenjdk/jitwatch/model/Tag;Lorg/adoptopenjdk/jitwatch/model/IParseDictionary;)V",
				"126: goto          152",
				"129: aload_0",
				"130: aload_1",
				"131: aload_2",
				"132: invokespecial #44                 // Method visitTagEliminateAllocation:(Lorg/adoptopenjdk/jitwatch/model/Tag;Lorg/adoptopenjdk/jitwatch/model/IParseDictionary;)V",
				"135: goto          152",
				"138: aload_0",
				"139: aload_1",
				"140: aload_2",
				"141: invokespecial #45                 // Method visitTagEliminateLock:(Lorg/adoptopenjdk/jitwatch/model/Tag;Lorg/adoptopenjdk/jitwatch/model/IParseDictionary;)V",
				"144: goto          152",
				"147: aload_0",
				"148: aload_1",
				"149: invokevirtual #46                 // Method handleOther:(Lorg/adoptopenjdk/jitwatch/model/Tag;)V",
				"152: return"
		};

		MemberBytecode memberBytecode = UnitTestUtil.createMemberBytecode(lines);

		SequenceCountOperation counter = new SequenceCountOperation(6);

		counter.processInstructions("Foo", memberBytecode);

		Map<InstructionSequence, Integer> result = counter.getSequenceScores();

		log(result);

		assertEquals(29, result.size());

		checkSequence(result, 1, Opcode.ALOAD_1, Opcode.INVOKEVIRTUAL, Opcode.ASTORE_3, Opcode.ICONST_M1, Opcode.ISTORE, Opcode.ALOAD_3);
		checkSequence(result, 1, Opcode.INVOKEVIRTUAL, Opcode.ASTORE_3, Opcode.ICONST_M1, Opcode.ISTORE, Opcode.ALOAD_3, Opcode.INVOKEVIRTUAL);
		checkSequence(result, 1, Opcode.ASTORE_3, Opcode.ICONST_M1, Opcode.ISTORE, Opcode.ALOAD_3, Opcode.INVOKEVIRTUAL, Opcode.LOOKUPSWITCH);
		checkSequence(result, 1, Opcode.ICONST_M1, Opcode.ISTORE, Opcode.ALOAD_3, Opcode.INVOKEVIRTUAL, Opcode.LOOKUPSWITCH, Opcode.ALOAD_3);
		checkSequence(result, 1, Opcode.ISTORE, Opcode.ALOAD_3, Opcode.INVOKEVIRTUAL, Opcode.LOOKUPSWITCH, Opcode.ALOAD_3, Opcode.LDC);
		checkSequence(result, 1, Opcode.ALOAD_3, Opcode.INVOKEVIRTUAL, Opcode.LOOKUPSWITCH, Opcode.ALOAD_3, Opcode.LDC, Opcode.INVOKEVIRTUAL);
		checkSequence(result, 1, Opcode.INVOKEVIRTUAL, Opcode.LOOKUPSWITCH, Opcode.ALOAD_3, Opcode.LDC, Opcode.INVOKEVIRTUAL, Opcode.IFEQ);
		checkSequence(result, 1, Opcode.LOOKUPSWITCH, Opcode.ALOAD_3, Opcode.LDC, Opcode.INVOKEVIRTUAL, Opcode.IFEQ, Opcode.ICONST_0);
		checkSequence(result, 1, Opcode.ALOAD_3, Opcode.LDC, Opcode.INVOKEVIRTUAL, Opcode.IFEQ, Opcode.ICONST_0, Opcode.ISTORE);
		checkSequence(result, 1, Opcode.LDC, Opcode.INVOKEVIRTUAL, Opcode.IFEQ, Opcode.ICONST_0, Opcode.ISTORE, Opcode.GOTO);
		checkSequence(result, 1, Opcode.INVOKEVIRTUAL, Opcode.IFEQ, Opcode.ICONST_0, Opcode.ISTORE, Opcode.GOTO, Opcode.ILOAD);
		checkSequence(result, 1, Opcode.IFEQ, Opcode.ICONST_0, Opcode.ISTORE, Opcode.GOTO, Opcode.ILOAD, Opcode.TABLESWITCH);
		checkSequence(result, 1, Opcode.ICONST_0, Opcode.ISTORE, Opcode.GOTO, Opcode.ILOAD, Opcode.TABLESWITCH, Opcode.ALOAD_0);
		checkSequence(result, 2, Opcode.ISTORE, Opcode.GOTO, Opcode.ILOAD, Opcode.TABLESWITCH, Opcode.ALOAD_0, Opcode.ALOAD_1);
		checkSequence(result, 2, Opcode.GOTO, Opcode.ILOAD, Opcode.TABLESWITCH, Opcode.ALOAD_0, Opcode.ALOAD_1, Opcode.ALOAD_2);
		checkSequence(result, 1, Opcode.ILOAD, Opcode.TABLESWITCH, Opcode.ALOAD_0, Opcode.ALOAD_1, Opcode.ALOAD_2, Opcode.INVOKESPECIAL);
		checkSequence(result, 1, Opcode.TABLESWITCH, Opcode.ALOAD_0, Opcode.ALOAD_1, Opcode.ALOAD_2, Opcode.INVOKESPECIAL, Opcode.GOTO);
		checkSequence(result, 3, Opcode.ALOAD_0, Opcode.ALOAD_1, Opcode.ALOAD_2, Opcode.INVOKESPECIAL, Opcode.GOTO, Opcode.RETURN);
		checkSequence(result, 1, Opcode.ALOAD_3, Opcode.LDC, Opcode.INVOKEVIRTUAL, Opcode.IFEQ, Opcode.ICONST_1, Opcode.ISTORE);
		checkSequence(result, 1, Opcode.LDC, Opcode.INVOKEVIRTUAL, Opcode.IFEQ, Opcode.ICONST_1, Opcode.ISTORE, Opcode.GOTO);
		checkSequence(result, 1, Opcode.INVOKEVIRTUAL, Opcode.IFEQ, Opcode.ICONST_1, Opcode.ISTORE, Opcode.GOTO, Opcode.ILOAD);
		checkSequence(result, 1, Opcode.IFEQ, Opcode.ICONST_1, Opcode.ISTORE, Opcode.GOTO, Opcode.ILOAD, Opcode.TABLESWITCH);
		checkSequence(result, 1, Opcode.ICONST_1, Opcode.ISTORE, Opcode.GOTO, Opcode.ILOAD, Opcode.TABLESWITCH, Opcode.ALOAD_0);
		checkSequence(result, 1, Opcode.ALOAD_3, Opcode.LDC, Opcode.INVOKEVIRTUAL, Opcode.IFEQ, Opcode.ICONST_2, Opcode.ISTORE);
		checkSequence(result, 1, Opcode.LDC, Opcode.INVOKEVIRTUAL, Opcode.IFEQ, Opcode.ICONST_2, Opcode.ISTORE, Opcode.ILOAD);
		checkSequence(result, 1, Opcode.INVOKEVIRTUAL, Opcode.IFEQ, Opcode.ICONST_2, Opcode.ISTORE, Opcode.ILOAD, Opcode.TABLESWITCH);
		checkSequence(result, 1, Opcode.IFEQ, Opcode.ICONST_2, Opcode.ISTORE, Opcode.ILOAD, Opcode.TABLESWITCH, Opcode.ALOAD_0);
		checkSequence(result, 1, Opcode.ICONST_2, Opcode.ISTORE, Opcode.ILOAD, Opcode.TABLESWITCH, Opcode.ALOAD_0, Opcode.ALOAD_1);
		checkSequence(result, 1, Opcode.ISTORE, Opcode.ILOAD, Opcode.TABLESWITCH, Opcode.ALOAD_0, Opcode.ALOAD_1, Opcode.ALOAD_2);
	}

	private InstructionSequence buildSequence(Opcode... opcodes)
	{
		return new InstructionSequence(Arrays.asList(opcodes));
	}

	private void checkSequence(Map<InstructionSequence, Integer> scoreMap, int count, Opcode... opcodes)
	{
		InstructionSequence key = buildSequence(opcodes);

		int score = scoreMap.get(key);

		assertEquals(count, score);
	}

	private void log(Map<InstructionSequence, Integer> result)
	{
		if (SHOW_OUTPUT)
		{
			for (Map.Entry<InstructionSequence, Integer> entry : result.entrySet())
			{
				System.out.println(entry.getValue() + "\t=>\t" + entry.getKey());
			}
		}
	}

	@Test
	public void testInfiniteLoopChain1()
	{
		String[] lines = new String[] {
				"0: aload_1",
				"1: invokevirtual #38                 // Method org/adoptopenjdk/jitwatch/model/Tag.getName:()Ljava/lang/String;",
				"4: astore_3",
				"5: iconst_m1",
				"6: istore        4",
				"8: goto          0"
		};

		MemberBytecode memberBytecode = UnitTestUtil.createMemberBytecode(lines);

		SequenceCountOperation counter = new SequenceCountOperation(1);

		counter.processInstructions("Foo", memberBytecode);

		Map<InstructionSequence, Integer> result = counter.getSequenceScores();

		log(result);

		assertEquals(6, result.size());

		checkSequence(result, 1, Opcode.ALOAD_1);
		checkSequence(result, 1, Opcode.INVOKEVIRTUAL);
		checkSequence(result, 1, Opcode.ASTORE_3);
		checkSequence(result, 1, Opcode.ICONST_M1);
		checkSequence(result, 1, Opcode.ISTORE);
		checkSequence(result, 1, Opcode.GOTO);
	}

	@Test
	public void testInfiniteLoopChain2()
	{
		String[] lines = new String[] {
				"0: aload_1",
				"1: invokevirtual #38                 // Method org/adoptopenjdk/jitwatch/model/Tag.getName:()Ljava/lang/String;",
				"4: astore_3",
				"5: iconst_m1",
				"6: istore        4",
				"8: goto          0"
		};

		MemberBytecode memberBytecode = UnitTestUtil.createMemberBytecode(lines);

		SequenceCountOperation counter = new SequenceCountOperation(2);

		counter.processInstructions("Foo", memberBytecode);

		Map<InstructionSequence, Integer> result = counter.getSequenceScores();

		log(result);

		assertEquals(6, result.size());

		checkSequence(result, 1, Opcode.ALOAD_1, Opcode.INVOKEVIRTUAL);
		checkSequence(result, 1, Opcode.INVOKEVIRTUAL, Opcode.ASTORE_3);
		checkSequence(result, 1, Opcode.ASTORE_3, Opcode.ICONST_M1);
		checkSequence(result, 1, Opcode.ICONST_M1, Opcode.ISTORE);
		checkSequence(result, 1, Opcode.ISTORE, Opcode.GOTO);
		checkSequence(result, 1, Opcode.GOTO, Opcode.ALOAD_1);
	}

	@Test
	public void testInfiniteLoopChain3()
	{
		String[] lines = new String[] {
				"0: aload_1",
				"1: invokevirtual #38                 // Method org/adoptopenjdk/jitwatch/model/Tag.getName:()Ljava/lang/String;",
				"4: astore_3",
				"5: iconst_m1",
				"6: istore        4",
				"8: goto          0"
		};

		MemberBytecode memberBytecode = UnitTestUtil.createMemberBytecode(lines);

		SequenceCountOperation counter = new SequenceCountOperation(3);

		counter.processInstructions("Foo", memberBytecode);

		Map<InstructionSequence, Integer> result = counter.getSequenceScores();

		log(result);

		assertEquals(6, result.size());

		checkSequence(result, 1, Opcode.ALOAD_1, Opcode.INVOKEVIRTUAL, Opcode.ASTORE_3);
		checkSequence(result, 1, Opcode.INVOKEVIRTUAL, Opcode.ASTORE_3, Opcode.ICONST_M1);
		checkSequence(result, 1, Opcode.ASTORE_3, Opcode.ICONST_M1, Opcode.ISTORE);
		checkSequence(result, 1, Opcode.ICONST_M1, Opcode.ISTORE, Opcode.GOTO);
		checkSequence(result, 1, Opcode.ISTORE, Opcode.GOTO, Opcode.ALOAD_1);
		checkSequence(result, 1, Opcode.GOTO, Opcode.ALOAD_1, Opcode.INVOKEVIRTUAL);

	}

	@Test
	public void testFollowGotoChain1()
	{
		String[] lines = new String[] {
				"0: goto          2",
				"1: goto          3",
				"2: goto          1",
				"3: return        "
		};

		MemberBytecode memberBytecode = UnitTestUtil.createMemberBytecode(lines);

		SequenceCountOperation counter = new SequenceCountOperation(1);

		counter.processInstructions("Foo", memberBytecode);

		Map<InstructionSequence, Integer> result = counter.getSequenceScores();

		log(result);

		assertEquals(2, result.size());

		checkSequence(result, 3, Opcode.GOTO);
		checkSequence(result, 1, Opcode.RETURN);
	}

	@Test
	public void testFollowGotoChain2()
	{
		String[] lines = new String[] {
				"0: goto          2",
				"1: goto          3",
				"2: goto          1",
				"3: return        "
		};

		MemberBytecode memberBytecode = UnitTestUtil.createMemberBytecode(lines);

		SequenceCountOperation counter = new SequenceCountOperation(2);

		counter.processInstructions("Foo", memberBytecode);

		Map<InstructionSequence, Integer> result = counter.getSequenceScores();

		log(result);

		assertEquals(2, result.size());

		checkSequence(result, 2, Opcode.GOTO, Opcode.GOTO);
		checkSequence(result, 1, Opcode.GOTO, Opcode.RETURN);
	}

	@Test
	public void testAthrow()
	{
		String[] lines = new String[] {
				"0: getstatic       #5   // Field socketImplCtor:Ljava/lang/reflect/Constructor;",
				"3: iconst_0        ",
				"4: anewarray       #6   // class java/lang/Object",
				"7: invokevirtual   #7   // Method java/lang/reflect/Constructor.newInstance:([Ljava/lang/Object;)Ljava/lang/Object;",
				"10: checkcast       #8   // class java/net/SocketImpl",
				"13: areturn         ",
				"14: astore_0        ",
				"15: new             #10  // class java/lang/AssertionError",
				"18: dup             ",
				"19: aload_0         ",
				"20: invokespecial   #11  // Method java/lang/AssertionError.\"<init>\":(Ljava/lang/Object;)V",
				"23: athrow          ",
				"24: astore_0        ",
				"25: new             #10  // class java/lang/AssertionError",
				"28: dup             ",
				"29: aload_0         ",
				"30: invokespecial   #11  // Method java/lang/AssertionError.\"<init>\":(Ljava/lang/Object;)V",
				"33: athrow          ",
				"34: astore_0        ",
				"35: new             #10  // class java/lang/AssertionError",
				"38: dup             ",
				"39: aload_0         ",
				"40: invokespecial   #11  // Method java/lang/AssertionError.\"<init>\":(Ljava/lang/Object;)V",
				"43: athrow        "
		};

		MemberBytecode memberBytecode = UnitTestUtil.createMemberBytecode(lines);

		SequenceCountOperation counter = new SequenceCountOperation(3);

		counter.processInstructions("Foo", memberBytecode);

		Map<InstructionSequence, Integer> result = counter.getSequenceScores();

		log(result);

		assertEquals(8, result.size());

		checkSequence(result, 1, Opcode.GETSTATIC, Opcode.ICONST_0, Opcode.ANEWARRAY);
		checkSequence(result, 1, Opcode.ICONST_0, Opcode.ANEWARRAY, Opcode.INVOKEVIRTUAL);
		checkSequence(result, 1, Opcode.ANEWARRAY, Opcode.INVOKEVIRTUAL, Opcode.CHECKCAST);
		checkSequence(result, 1, Opcode.INVOKEVIRTUAL, Opcode.CHECKCAST, Opcode.ARETURN);

		checkSequence(result, 3, Opcode.ASTORE_0, Opcode.NEW, Opcode.DUP);
		checkSequence(result, 3, Opcode.NEW, Opcode.DUP, Opcode.ALOAD_0);
		checkSequence(result, 3, Opcode.DUP, Opcode.ALOAD_0, Opcode.INVOKESPECIAL);
		checkSequence(result, 3, Opcode.ALOAD_0, Opcode.INVOKESPECIAL, Opcode.ATHROW);
	}
}