package me.nallar.tickthreading.patcher;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.expr.Cast;
import javassist.expr.ConstructorCall;
import javassist.expr.ExprEditor;
import javassist.expr.FieldAccess;
import javassist.expr.Handler;
import javassist.expr.Instanceof;
import javassist.expr.MethodCall;
import javassist.expr.NewArray;
import javassist.expr.NewExpr;
import me.nallar.tickthreading.Log;

public class Patches {
	@Patch
	public void synchronize(CtMethod method) {
		int currentModifiers = method.getModifiers();
		if (Modifier.isSynchronized(currentModifiers)) {
			method.setModifiers(Modifier.clear(currentModifiers, Modifier.SYNCHRONIZED));
		} else {
			Log.warning("Method: " + method.getLongName() + " is already synchronized");
		}
	}

	@Patch
	public void replaceInstantiations(CtClass clazz, Map<String, String> attributes) {

	}

	@Patch
	public void replaceInstantiations(CtMethod method, Map<String, String> attributes) {

	}

	public void replaceInstantiationsImplementation(CtMethod method, final Map<String, List<String>> replacementClasses) throws CannotCompileException {
		// TODO: Learn to use ASM, javassist isn't nice. :(
		final Map<Integer, String> newExprType = new HashMap<Integer, String>();
		method.instrument(new ExprEditor() {
			NewExpr lastNewExpr;
			int newPos = 0;

			@Override
			public void edit(NewExpr e) {
				lastNewExpr = null;
				newPos++;
				if (replacementClasses.containsKey(e.getClassName())) {
					lastNewExpr = e;
				}
			}

			@Override
			public void edit(FieldAccess e) {
				NewExpr myLastNewExpr = lastNewExpr;
				lastNewExpr = null;
				if (myLastNewExpr != null) {
					Log.fine("(" + myLastNewExpr.getSignature() + ") " + myLastNewExpr.getClassName() + " at " + myLastNewExpr.getFileName() + ":" + myLastNewExpr.getLineNumber() + ":" + newPos);
					newExprType.put(newPos, classSignatureToName(e.getSignature()));
				}
			}

			@Override
			public void edit(MethodCall e) {
				lastNewExpr = null;
			}

			@Override
			public void edit(NewArray e) {
				lastNewExpr = null;
			}

			@Override
			public void edit(Cast e) {
				lastNewExpr = null;
			}

			@Override
			public void edit(Instanceof e) {
				lastNewExpr = null;
			}

			@Override
			public void edit(Handler e) {
				lastNewExpr = null;
			}

			@Override
			public void edit(ConstructorCall e) {
				lastNewExpr = null;
			}
		});
		method.instrument(new ExprEditor() {
			int newPos = 0;

			@Override
			public void edit(NewExpr e) throws CannotCompileException {
				newPos++;
				try {
					Log.fine(e.getFileName() + ":" + e.getLineNumber() + ", pos: " + newPos);
					if (newExprType.containsKey(newPos)) {
						String replacementType = null, assignedType = newExprType.get(newPos);
						Log.fine(assignedType + " at " + e.getFileName() + ":" + e.getLineNumber());
						Class<?> assignTo = Class.forName(assignedType);
						for (String replacementClass : replacementClasses.get(e.getClassName())) {
							if (assignTo.isAssignableFrom(Class.forName(replacementClass))) {
								replacementType = replacementClass;
								break;
							}
						}
						if (replacementType == null) {
							return;
						}
						String block = "{$_=new " + replacementType + "($$);}";
						Log.fine("Replaced with " + block + ", " + replacementType.length() + ":" + assignedType.length());
						e.replace(block);
					}
				} catch (ClassNotFoundException el) {
					Log.severe("Could not replace instantiation, class not found.", el);
				}
			}
		});
	}

	private static String classSignatureToName(String signature) {
		//noinspection HardcodedFileSeparator
		return signature.substring(1, signature.length() - 1).replace("/", ".");
	}
}

@@ -770,176 +770,176 @@
 %0A%09%09%09
-method.setModifiers(Modifier.clear(currentModifiers, Modifier.SYNCHRONIZED));%0D%0A%09%09%7D else %7B%0D%0A%09%09%09Log.warning(%22Method: %22 + method.getLongName() + %22 is already synchronized%22
+Log.warning(%22Method: %22 + method.getLongName() + %22 is already synchronized%22);%0D%0A%09%09%7D else %7B%0D%0A%09%09%09method.setModifiers(Modifier.clear(currentModifiers, Modifier.SYNCHRONIZED)
 );%0D%0A

Synchronize now works - inverted logic fixed