package lilypad.bukkit.connect.injector;

import java.lang.reflect.Modifier;
import java.util.Map;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
import lilypad.bukkit.connect.ConnectPlugin;
import lilypad.bukkit.connect.util.JavassistUtil;
import lilypad.bukkit.connect.util.ReflectionUtils;

import org.bukkit.Server;

public class PacketInjector {
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void injectStringMaxSize(Server server, String protocol, int packetId, final int maxSize) throws Exception {
		Object minecraftServer = ReflectionUtils.getPrivateField(server.getClass(), server, Object.class, "console");
		String minecraftServerClassName = minecraftServer.getClass().getName();
		final String minecraftPackage = minecraftServerClassName.substring(0, minecraftServerClassName.lastIndexOf('.'));
		// get the packet
		Map serverBound;
		Class<?> enumProtocolClass = Class.forName(minecraftPackage + ".EnumProtocol");
		Object enumProtocol = ReflectionUtils.getPrivateField(enumProtocolClass, null, enumProtocolClass, protocol.toUpperCase());		
		if (ConnectPlugin.getProtocol().getGeneralVersion().equalsIgnoreCase("1.7")) {
			serverBound = ReflectionUtils.getPrivateField(enumProtocolClass, enumProtocol, Map.class, ConnectPlugin.getProtocol().getPacketInjectorProtocolDirections());
		} else {
			Map protocolDirections = ReflectionUtils.getPrivateField(enumProtocolClass, enumProtocol, Map.class, ConnectPlugin.getProtocol().getPacketInjectorProtocolDirections());
			Object serverBoundDirection = ReflectionUtils.getPrivateField(Class.forName(minecraftPackage + ".EnumProtocolDirection"), null, Object.class, "SERVERBOUND");
			serverBound = (Map) protocolDirections.get(serverBoundDirection);
		}
		if(!serverBound.containsKey(packetId)) {
			throw new IllegalArgumentException("Packet Id does not exist: " + packetId);
		}
		Class<?> packetClass = (Class<?>) serverBound.get(packetId);
		// create packet proxy
		ClassPool classPool = JavassistUtil.getClassPool();
		CtClass packetCtClass = classPool.getCtClass(packetClass.getName());
		final CtClass packetCtClassProxy = classPool.getAndRename(packetClass.getName(), packetClass.getName() + "$stringMaxSize" + maxSize);
		packetCtClassProxy.setSuperclass(packetCtClass);
		for(CtField field : packetCtClassProxy.getDeclaredFields()) {
			if(Modifier.isPrivate(field.getModifiers())) {
				continue;
			}
			packetCtClassProxy.removeField(field);
		}
		CtMethod decodeCtMethod = packetCtClassProxy.getDeclaredMethod(ConnectPlugin.getProtocol().getPacketInjectorDecodeCtMethod(), new CtClass[] { classPool.getCtClass(minecraftPackage + ".PacketDataSerializer") });
		decodeCtMethod.instrument(new ExprEditor() {
			public void edit(MethodCall methodCall) throws CannotCompileException {
				if(!methodCall.getClassName().equals(minecraftPackage + ".PacketDataSerializer")
						|| !methodCall.getMethodName().equals("c")
						|| !methodCall.getSignature().equals("(I)Ljava/lang/String;")) {
					return;
				}
				methodCall.replace("{ $1 = " + maxSize + "; $_ = $proceed($$); }");
			}
		});
		CtMethod handleCtMethod = packetCtClassProxy.getDeclaredMethod(ConnectPlugin.getProtocol().getPacketInjectorHandleCtMethod(), new CtClass[] { classPool.getCtClass(minecraftPackage + ".PacketListener") });
		handleCtMethod.instrument(new ExprEditor() {
			public void edit(MethodCall methodCall) throws CannotCompileException {
				try {
					methodCall.getMethod().setBody("{ super.a($$); }");
				} catch (NotFoundException exception) {
					exception.printStackTrace();
				}
			}
		});
		Class<?> packetClassProxy = packetCtClassProxy.toClass();
		// replace packet
		serverBound.put(packetId, packetClassProxy);
	}

}
