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

import com.chrisnewland.freelogj.Logger;
import com.chrisnewland.freelogj.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

// Demo class to generate an example hotspot.log
// run with VM arguments
// -XX:+UnlockDiagnosticVMOptions
// -XX:+LogCompilation 
// -XX:+PrintAssembly

// If you prefer Intel assembly syntax to AT&T
// -XX:PrintAssemblyOptions=intel

// Disable TieredCompilation on Java 8 (optional)
// -XX:-TieredCompilation

public class MakeHotSpotLog
{
	private static final Logger logger = LoggerFactory.getLogger(MakeHotSpotLog.class);

	public MakeHotSpotLog(int iterations)
	{
		addVariable(iterations);
		addConstant(iterations);
		randomBranchTest(iterations);
		changingBranchTest(iterations);
		intrinsicTest(iterations);
		intrinsicTestMin(iterations);
		tooBigToInline(iterations);
		testSort(iterations);
		testCallChain(iterations);
		testOptimizedVCall(iterations);

		long result = testCallChainReturn(iterations);

		// ensure code not eliminated by using result
		System.out.println("testCallChainReturn: " + result);

		testCallChain3();
		testLeaf(iterations);
		testToUpperCase(iterations);
		testLoopUnrolling(iterations);
		padMethod();
	}

	private void addVariable(int iterations)
	{
		long count = 0;

		for (int i = 0; i < iterations; i++)
		{
			count = add(count, i);
		}

		System.out.println("addVariable: " + count);
	}

	private void addConstant(int iterations)
	{
		long count = 0;

		for (int i = 0; i < iterations; i++)
		{
			count = add(count, 1);
		}

		System.out.println("addConstant: " + count);
	}

	private void randomBranchTest(int iterations)
	{
		long count = 0;
		int adds = 0;
		int subs = 0;

		for (int i = 0; i < iterations; i++)
		{
			if (Math.random() < 0.5)
			{
				count = add(count, 1);
				adds++;
			}
			else
			{
				count = sub(count, 1);
				subs++;
			}
		}

		System.out.println("randomBranchTest: " + count + " " + adds + " " + subs);
	}

	private void changingBranchTest(int iterations)
	{
		long count = 0;
		int adds = 0;
		int subs = 0;

		for (int i = 0; i < iterations * 2; i++)
		{
			if (i < iterations)
			{
				count = add(count, 1);
				adds++;
			}
			else
			{
				count = sub(count, 1);
				subs++;
			}
		}

		System.out.println("changingBranchTest: " + count + " " + adds + " " + subs);
	}

	private void intrinsicTest(int iterations)
	{

		long dstSum = 0;
		int[] src = new int[] { 1, 2, 3, 4, 5 };
		int[] dst = new int[src.length];

		for (int i = 0; i < iterations; i++)
		{
			// x86 has intrinsic for System.arrayCopy
			System.arraycopy(src, 0, dst, 0, src.length);

			for (int dstVal : dst)
			{
				dstSum += add(dstSum, dstVal);
			}
		}

		System.out.println("intrinsicTest: " + dstSum);
	}

	// http://openjdk.5641.n7.nabble.com/Intrinsics-for-Math-min-and-max-td183747.html
	private void intrinsicTestMin(int iterations)
	{
		long sum = 0;

		for (int i = 0; i < iterations; i++)
		{
			// x86 has intrinsic for Math.min

			sum = Math.min(i, i + 1);
		}

		System.out.println("intrinsicTest: " + sum);
	}

	private long add(long a, long b)
	{
		return a + b;
	}

	private long sub(long a, long b)
	{
		return a - b;
	}

	public void tooBigToInline(int iterations)
	{
		long count = 0;

		for (int i = 0; i < iterations; i++)
		{
			count = bigMethod(count, i);
		}

		System.out.println("tooBigToInline: " + count);
	}

	private long bigMethod(long count, int i)
	{
		long a, b, c, d, e, f, g;

		a = count;
		b = count;
		c = count;
		d = count;
		e = count;
		f = count;
		g = count;

		a += i;
		b += i;
		c += i;
		d += i;
		e += i;
		f += i;
		g += i;

		a += 1;
		b += 2;
		c += 3;
		d += 4;
		e += 5;
		f += 6;
		g += 7;

		a += i;
		b += i;
		c += i;
		d += i;
		e += i;
		f += i;
		g += i;

		a -= 7;
		b -= 6;
		c -= 5;
		d -= 4;
		e -= 3;
		f -= 2;
		g -= 1;

		a++;
		b++;
		c++;
		d++;
		e++;
		f++;
		g++;

		a /= 2;
		b /= 2;
		c /= 2;
		d /= 2;
		e /= 2;
		f /= 2;
		g /= 2;

		long result = a + b + c + d + e + f + g;

		return result;
	}

	private void testSort(long iterations)
	{

		long sum = 0;

		// ensure sort is JIT compiled
		for (int i = 0; i < iterations; i++)
		{
			Random random = new Random();

			int count = 1000;

			List<Integer> list = new ArrayList<>();

			for (int j = 0; j < count; j++)
			{
				list.add(random.nextInt());
			}

			Collections.sort(list);

			for (int j = 0; j < count; j++)
			{
				sum += list.get(j);
			}
		}

		System.out.println("list sum: " + sum);
	}

	private void testCallChain(long iterations)
	{
		long count = 0;

		for (int i = 0; i < iterations; i++)
		{
			count = chainA1(count);
			count = chainB1(count);
		}

		System.out.println("testCallChain: " + count);
	}

	/*
	 * Test bimorphic dispatch is inlined
	 */
	private void testOptimizedVCall(long iterations)
	{
		List<Integer> list1 = new ArrayList<>();
		List<Integer> list2 = new LinkedList<>();

		List<Integer> ref;

		for (int i = 0; i < iterations; i++)
		{
			if (i % 2 == 0)
			{
				ref = list1;
			}
			else
			{
				ref = list2;
			}

			ref.add(i);
			// list2.add(i);
		}

		System.out.println("list sizes: " + list1.size() + "/" + list2.size());

	}

	private long chainA1(long count)
	{
		return 1 + chainA2(count);
	}

	private long chainA2(long count)
	{
		return 2 + chainA3(count);
	}

	private long chainA3(long count)
	{
		return 3 + chainA4(count);
	}

	private long chainA4(long count)
	{
		// last link will not be inlined
		return bigMethod(count, 4);
	}

	private long chainB1(long count)
	{
		return chainB2(count) - 1;
	}

	private long chainB2(long count)
	{
		return chainB3(count) - 2;
	}

	private long chainB3(long count)
	{
		return count - 3;
	}

	public long testCallChainReturn(long iterations)
	{
		long count = 0;

		for (int i = 0; i < iterations; i++)
		{
			count = chainA1(count);
			count = chainB1(count);
		}

		return count;
	}

	private boolean test(int count, int iterations)
	{
		return count < (0.9 * iterations);
	}

	private void testCallChain3()
	{
		long count = 0;

		int iterations = 100_000;

		for (int i = 0; i < iterations; i++)
		{
			if (test(i, iterations))
			{
				count = chainC1(count);
			}
			else
			{
				count = chainC2(count);
			}
		}

		System.out.println("testCallChain3: " + count);
	}

	private long chainC1(long inCount)
	{
		long count = inCount;
		count += chainC2(count);
		return chainC3(count);
	}

	private long chainC2(long count)
	{
		return 2 + count;
	}

	private long chainC3(long count)
	{
		return 3 + count;
	}

	private void testLeaf(long iterations)
	{
		long count = 0;

		for (int i = 0; i < iterations; i++)
		{
			count = leaf1(count);
			count = leaf2(count);
			count = leaf3(count);
			count = leaf4(count);
		}

		System.out.println("testLeaf: " + count);
	}

	private long leaf1(long count)
	{
		return count + 1;
	}

	private long leaf2(long count)
	{
		return count + 2;
	}

	private long leaf3(long count)
	{
		return count + 3;
	}

	private long leaf4(long count)
	{
		return count + 4;
	}

	private void testToUpperCase(long iterations)
	{
		String sentence = "The quick brown fox jumps over the lazy dog\n";

		String[] lcWords = sentence.split(" ");

		int wordCount = lcWords.length;

		String[] ucWords = new String[wordCount];

		for (long l = 0; l < iterations; l++)
		{
			toUpper(lcWords, ucWords, wordCount);
		}
	}

	private void toUpper(String[] lcWords, String[] ucWords, int wordCount)
	{
		for (int w = 0; w < wordCount; w++)
		{
			ucWords[w] = lcWords[w].toUpperCase();
		}
	}

	private void testLoopUnrolling(long iterations)
	{
		long result = 0;
		int toAdd = 4;

		for (long l = 0; l < iterations; l++)
		{
			result += timesTen(toAdd);
		}

		for (long l = 0; l < iterations; l++)
		{
			result += timesHundred(toAdd);
		}

		System.out.println("testLoopUnrolling: " + result);
	}

	private int timesTen(int number)
	{
		int result = 0;

		for (int i = 0; i < 10; i++)
		{
			result += number;
		}

		return result;
	}

	private int timesHundred(int number)
	{
		int result = 0;

		for (int i = 0; i < 100; i++)
		{
			result += number;
		}

		return result;
	}

	// sacrificial dummy method
	// in case hotspot truncates the LogCompilation output
	// as it exits and produces a <fragment>
	private void padMethod()
	{
		try
		{
			Thread.sleep(500);
			System.out.println("done");
		}
		catch (InterruptedException ie)
		{
			logger.error("", ie);
		}
	}

	public static void main(String[] args)
	{
		int iterations = 20_000;

		if (args.length == 1)
		{
			try
			{
				iterations = Integer.parseInt(args[0]);
			}
			catch (NumberFormatException nfe)
			{
				System.out.println("usage: MakeHotSpotLog [iterations]");
			}
		}

		new MakeHotSpotLog(iterations);
	}
}
