package jadx.tests.api.compiler;

import jadx.core.dex.nodes.ClassNode;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import static javax.tools.JavaCompiler.CompilationTask;

public class DynamicCompiler {

	private final ClassNode clsNode;

	private JavaFileManager fileManager;

	private Object instance;

	public DynamicCompiler(ClassNode clsNode) {
		this.clsNode = clsNode;
	}

	public boolean compile() throws Exception {
		String fullName = clsNode.getFullName();
		String code = clsNode.getCode().toString();

		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));

		List<JavaFileObject> jFiles = new ArrayList<JavaFileObject>(1);
		jFiles.add(new CharSequenceJavaFileObject(fullName, code));

		CompilationTask compilerTask = compiler.getTask(null, fileManager, null, null, null, jFiles);
		return Boolean.TRUE.equals(compilerTask.call());
	}

	private ClassLoader getClassLoader() {
		return fileManager.getClassLoader(null);
	}

	private void makeInstance() throws Exception {
		String fullName = clsNode.getFullName();
		instance = getClassLoader().loadClass(fullName).newInstance();
		if (instance == null) {
			throw new NullPointerException("Instantiation failed");
		}
	}

	private Object getInstance() throws Exception {
		if (instance == null) {
			makeInstance();
		}
		return instance;
	}

	public Method getMethod(String method, Class[] types) throws Exception {
		for (Class<?> type : types) {
			checkType(type);
		}
		return getInstance().getClass().getMethod(method, types);
	}

	public Object invoke(Method mth, Object... args) throws Exception {
		return mth.invoke(getInstance(), args);
	}

	private Class<?> checkType(Class<?> type) throws ClassNotFoundException {
		if (type.isPrimitive()) {
			return type;
		}
		if (type.isArray()) {
			return checkType(type.getComponentType());
		}
		Class<?> decompiledCls = getClassLoader().loadClass(type.getName());
		if (type != decompiledCls) {
			throw new IllegalArgumentException("Internal test class cannot be used in method invoke");
		}
		return decompiledCls;
	}
}
