package com.jmatsuok.random.things;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Main {

	private static ExecutorService executor = Executors.newFixedThreadPool(10);

	public static void main(String[] args) throws Exception {
		// Ensure that Reproducer has it's own ClassLoader
		TestClassLoader c = new TestClassLoader("ClassLoader 1");
		Class dynamicReproducer = c.loadClass(Reproducer.class.getName());

		System.out.println("Waiting for Agent Connection");

		Scanner sc = new Scanner(System.in);
		sc.nextLine();

		for (int i = 0; i < 10; i++) {
			executor.execute(new Wrapper(dynamicReproducer));
		}

		executor.shutdown();
		while (!executor.isTerminated()) {

		}

		System.out.println("Exiting");
	}

		public static byte[] getByteCode(Class<?> c) throws IOException {
			try (InputStream is = c.getClassLoader().getResourceAsStream(c.getName().replace('.', '/') + ".class")) { //$NON-NLS-1$
				return readFully(is, -1, true);
			}
		}

		public static byte[] readFully(InputStream is, int length, boolean readAll) throws IOException {
			byte[] output = {};
			if (length == -1) {
				length = Integer.MAX_VALUE;
			}
			int pos = 0;
			while (pos < length) {
				int bytesToRead;
				if (pos >= output.length) { // Only expand when there's no room
					bytesToRead = Math.min(length - pos, output.length + 1024);
					if (output.length < pos + bytesToRead) {
						output = Arrays.copyOf(output, pos + bytesToRead);
					}
				} else {
					bytesToRead = output.length - pos;
				}
				int cc = is.read(output, pos, bytesToRead);
				if (cc < 0) {
					if (readAll && length != Integer.MAX_VALUE) {
						throw new EOFException("Detect premature EOF"); //$NON-NLS-1$
					} else {
						if (output.length != pos) {
							output = Arrays.copyOf(output, pos);
						}
						break;
					}
				}
				pos += cc;
			}
			return output;
		}

	private static class Wrapper implements Runnable {

		private Class reproducer;

		public Wrapper(Class reproducer) {
			this.reproducer = reproducer;
		}

		@Override
		public void run() {
			int i = 0;
			while (i < 10) {
				try {
					reproducer.getDeclaredMethod("instrumentMe").invoke(reproducer.newInstance());
					reproducer.getDeclaredMethod("instrumentMe2", Boolean.class).invoke(reproducer.newInstance(),true);
				} catch (Exception e) {
					// ignore
				}
				i++;
			}
		}
	}

	private static class TestClassLoader extends ClassLoader {

		private String name;

		public TestClassLoader(String name) {
			super(null);
			this.name = name;
		}

		@Override
		public String getName() {
			return name;
		}


		@Override
		protected Class<?> findClass(String moduleName, String name) {
			try {
				if (name.equals(Reproducer.class.getName())) {
					try {
						return defineClass(Reproducer.class.getName(), Main.getByteCode(Reproducer.class), 0, Main.getByteCode(Reproducer.class).length);
					} catch (IOException e) {
						System.err.println("Could not define class");
						return null;
					}
				} else {
					return loadClass(name, false);
				}
			} catch (ClassNotFoundException e) {
				return null;
			}
		}

		@Override
		public Class<?> loadClass(String name) throws ClassNotFoundException {
			if (name.equals(Reproducer.class.getName())) {
				return findClass("", name);
			}
			return loadClass(name, false);
		}

		@Override
		protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
			return ClassLoader.getPlatformClassLoader().loadClass(name);
		}
	}
}
