package lilypad.bukkit.connect.injector;

import lilypad.bukkit.connect.util.JavassistUtil;
import sun.reflect.ReflectionFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import lilypad.bukkit.connect.ConnectPlugin;
import lilypad.bukkit.connect.util.ReflectionUtils;

import org.bukkit.Server;

@SuppressWarnings("restriction")
public class OfflineInjector {

	private static Object offlineMinecraftServer;

	public static void inject(Server server) throws Exception {
		Method serverGetHandle = server.getClass().getDeclaredMethod("getServer");
		Object minecraftServer = serverGetHandle.invoke(server);
		// create offline minecraftServer
		ClassPool classPool = JavassistUtil.getClassPool();
		CtClass minecraftServerClass = classPool.getCtClass(minecraftServer.getClass().getName());
		CtClass offlineMinecraftServerClass = classPool.makeClass(minecraftServer.getClass().getName() + "$offline");
		offlineMinecraftServerClass.setSuperclass(minecraftServerClass);
		// ... create delegate field
		CtField delegateField = new CtField(minecraftServerClass, "delegate", offlineMinecraftServerClass);
		offlineMinecraftServerClass.addField(delegateField);
		// ... add our special getOfflineMode
		CtMethod getOnlineModeMethod = new CtMethod(minecraftServerClass.getSuperclass().getDeclaredMethod("getOnlineMode").getReturnType(), "getOnlineMode", new CtClass[] { }, offlineMinecraftServerClass);
		getOnlineModeMethod.setBody("{ return false; }");
		offlineMinecraftServerClass.addMethod(getOnlineModeMethod);
		// ... proxy all declared methods recursively
		CtClass cursorClass = minecraftServerClass;
		while (true) {
			for (CtMethod method : cursorClass.getDeclaredMethods()) {
				if(Modifier.isFinal(method.getModifiers())) {
					continue;
				}
				if(Modifier.isPrivate(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) {
					continue;
				}
				try {
					offlineMinecraftServerClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
					continue;
				} catch(NotFoundException exception) {
					// proceed
				}
				CtMethod proxyMethod = new CtMethod(method.getReturnType(), method.getName(), method.getParameterTypes(), offlineMinecraftServerClass);
				proxyMethod.setBody("{ return ($r)this.delegate." + method.getName() + "($$); }");
				offlineMinecraftServerClass.addMethod(proxyMethod);
			}
			cursorClass = cursorClass.getSuperclass();
			if (cursorClass == null) {
				break;
			}
			if (cursorClass.getName().equals("java.lang.Object")) {
				break;
			}
		}
		// ... make a blank constructor
		// don't need to make a blank constructor for 1.9
		if (ConnectPlugin.getProtocol().isOfflineBlankConstructor()) {
			CtConstructor constructor = new CtConstructor(new CtClass[] { }, offlineMinecraftServerClass);
			constructor.setBody("{ super(null); }");
			offlineMinecraftServerClass.addConstructor(constructor);
		}

		// ... create our class
		Class<?> offlineMinecraftServerJClass = offlineMinecraftServerClass.toClass();
		// ... create an instance of our class without calling the constructor
		ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
		Constructor<?> objectConstructor = Object.class.getDeclaredConstructor();
		Constructor<?> serializeConstructor = reflectionFactory.newConstructorForSerialization(offlineMinecraftServerJClass, objectConstructor);
		offlineMinecraftServer = serializeConstructor.newInstance();
		// ... set our delegate, among other stuff
		ReflectionUtils.setFinalField(offlineMinecraftServer.getClass(), offlineMinecraftServer, "delegate", minecraftServer);
		ReflectionUtils.setFinalField(offlineMinecraftServer.getClass().getSuperclass().getSuperclass(), offlineMinecraftServer, "server", server);
		ReflectionUtils.setFinalField(offlineMinecraftServer.getClass().getSuperclass().getSuperclass(), offlineMinecraftServer, "processQueue", ReflectionUtils.getPrivateField(minecraftServer.getClass().getSuperclass(), minecraftServer, Object.class, "processQueue"));
		// get server connection
		Method serverConnectionMethod = null;
		for (Method method : minecraftServer.getClass().getSuperclass().getDeclaredMethods()) {
			if (!method.getReturnType().getSimpleName().equals("ServerConnection")) {
				continue;
			}
			serverConnectionMethod = method;
			break;
		}
		Object serverConnection = serverConnectionMethod.invoke(minecraftServer);
		// set server connection minecraftServer
		ReflectionUtils.setFinalField(serverConnection.getClass(), serverConnection, ConnectPlugin.getProtocol().getOfflineInjectorServerConnection(), offlineMinecraftServer);
	}

	public static Object getOfflineMinecraftServer() {
		return offlineMinecraftServer;
	}
	
}
