package openmods.world;

import openmods.Log;
import openmods.api.IResultListener;
import openmods.asm.MappedType;
import openmods.asm.MethodMatcher;
import openmods.asm.VisitorHelper;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

public class MapGenStructureVisitor extends ClassVisitor {

	private final MethodMatcher modifiedMethod;
	private final MethodMatcher markerMethod;
	private final MappedType structureStartCls;
	private final IResultListener listener;

	private class FixerMethodVisitor extends MethodVisitor {
		public FixerMethodVisitor(MethodVisitor mv) {
			super(Opcodes.ASM5, mv);
		}

		private boolean checkcastFound;
		private Integer localVarId;
		private boolean markerMethodFound;

		/*
		 * Default compilator usually creates:
		 * checkcast class net/minecraft/world/gen/structure/StructureStart
		 * astore X
		 * aload X
		 *
		 * We use that to get id of local variable that stores 'structurestart'
		 */

		@Override
		public void visitTypeInsn(int opcode, String type) {
			super.visitTypeInsn(opcode, type);
			if (opcode == Opcodes.CHECKCAST && type.equals(structureStartCls.name())) {
				checkcastFound = true;
				Log.debug("Found checkcast to '%s'", type);
			}
		}

		@Override
		public void visitVarInsn(int opcode, int var) {
			super.visitVarInsn(opcode, var);

			if (checkcastFound && opcode == Opcodes.ASTORE) {
				localVarId = var;
				checkcastFound = false;
				Log.debug("Found var: %d", localVarId);
			}
		}

		/*
		 * Here we are transforming condition
		 * if (structurestart.isSizeableStructure())
		 * to
		 * if (structurestart.isSizeableStructure() &&
		 * !structurestart.getComponents().isEmpty())
		 *
		 * Again, we assume that compilator places IFEQ jump just after calling
		 * isSizeableStructure from first expression. We can then reuse label
		 * for second part
		 */

		@Override
		public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean intf) {
			super.visitMethodInsn(opcode, owner, name, desc, intf);
			if (opcode == Opcodes.INVOKEVIRTUAL && owner.equals(structureStartCls.name()) && markerMethod.match(name, desc)) {
				markerMethodFound = true;
				Log.debug("Found 'StructureStart.isSizeableStructure' (%s.%s) call", owner, name);
			}
		}

		@Override
		public void visitJumpInsn(int opcode, Label label) {
			super.visitJumpInsn(opcode, label);

			if (markerMethodFound && localVarId != null && opcode == Opcodes.IFEQ) {
				Log.debug("All conditions matched, inserting extra condition");
				super.visitVarInsn(Opcodes.ALOAD, localVarId); // hopefully 'structurestart'
				String getComponentsMethodName = VisitorHelper.useSrgNames()? "func_75073_b" : "getComponents";
				super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, structureStartCls.name(), getComponentsMethodName, "()Ljava/util/LinkedList;", false);
				super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/LinkedList", "isEmpty", "()Z", false);
				super.visitJumpInsn(Opcodes.IFNE, label);
				listener.onSuccess();
				markerMethodFound = false;
			}
		}
	}

	public MapGenStructureVisitor(String obfClassName, ClassVisitor cv, IResultListener listener) {
		super(Opcodes.ASM5, cv);

		this.listener = listener;

		structureStartCls = MappedType.of("net/minecraft/world/gen/structure/StructureStart");
		MappedType blockPosCls = MappedType.of("net/minecraft/util/BlockPos");
		MappedType worldCls = MappedType.of("net/minecraft/world/World");

		String descriptor = Type.getMethodDescriptor(blockPosCls.type(), worldCls.type(), blockPosCls.type());

		modifiedMethod = new MethodMatcher(obfClassName, descriptor, "getClosestStrongholdPos", "func_180706_b");
		markerMethod = new MethodMatcher(structureStartCls, "()Z", "isSizeableStructure", "func_75069_d");
	}

	@Override
	public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
		MethodVisitor parent = super.visitMethod(access, name, desc, signature, exceptions);
		return modifiedMethod.match(name, desc)? new FixerMethodVisitor(parent) : parent;
	}

}

@@ -2783,15 +2783,16 @@
 unc_
-75073_b
+186161_c
 %22 : 
@@ -2918,30 +2918,24 @@
 )Ljava/util/
-Linked
 List;%22, fals
@@ -2978,23 +2978,25 @@
 s.INVOKE
-VIRTUAL
+INTERFACE
 , %22java/
@@ -3004,18 +3004,18 @@
 til/
-LinkedList
+Collection
 %22, %22
@@ -3031,20 +3031,19 @@
  %22()Z%22, 
-fals
+tru
 e);%0A%09%09%09%09
@@ -3458,16 +3458,21 @@
 ft/util/
+math/
 BlockPos

Update SRG names