package eu.the5zig.mod.asm.transformers;

import eu.the5zig.mod.asm.LogUtil;
import eu.the5zig.mod.asm.Names;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

import static org.objectweb.asm.Opcodes.*;

/**
 * Created by 5zig.
 * All rights reserved © 2015
 */
public class PatchMinecraft implements IClassTransformer {

	@Override
	public byte[] transform(String s, String s1, byte[] bytes) {
		LogUtil.startClass("Minecraft (%s)", Names.minecraft.getName());

		ClassReader reader = new ClassReader(bytes);
		ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
		ClassPatcher visitor = new ClassPatcher(writer);
		reader.accept(visitor, 0);
		LogUtil.endClass();
		return writer.toByteArray();
	}

	public class ClassPatcher extends ClassVisitor {

		public ClassPatcher(ClassVisitor visitor) {
			super(ASM5, visitor);
		}

		@Override
		public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
			if (Names.startGame.equals(name, desc)) {
				LogUtil.startMethod(Names.startGame.getName() + "(%s)", Names.startGame.getDesc());
				return new PatchStartGame(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			if (Names.dispatchKeypresses.equals(name, desc)) {
				LogUtil.startMethod(Names.dispatchKeypresses.getName() + "(%s)", Names.dispatchKeypresses.getDesc());
				return new PatchDispatchKeypresses(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			if (Names.shutdown.equals(name, desc)) {
				LogUtil.startMethod(Names.shutdown.getName() + "(%s)", Names.shutdown.getDesc());
				return new PatchShutdown(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			if (Names.displayCrashReport.equals(name, desc)) {
				LogUtil.startMethod(Names.displayCrashReport.getName() + "(%s)", Names.displayCrashReport.getDesc());
				return new PatchDisplayCrashReport(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			if (Names.tick.equals(name, desc)) {
				LogUtil.startMethod(Names.tick.getName() + "(%s)", Names.tick.getDesc());
				return new PatchTick(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			if (Names.leftClickMouse.equals(name, desc)) {
				LogUtil.startMethod(Names.leftClickMouse.getName() + "(%s)", Names.leftClickMouse.getDesc());
				return new PatchLeftClickMouse(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			if (Names.rightClickMouse.equals(name, desc)) {
				LogUtil.startMethod(Names.rightClickMouse.getName() + "(%s)", Names.rightClickMouse.getDesc());
				return new PatchRightClickMouse(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			if (Names.displayScreen.equals(name, desc)) {
				LogUtil.startMethod(Names.displayScreen.getName() + "(%s)", Names.displayScreen.getDesc());
				return new PatchDisplayScreen(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			LogUtil.endMethod();
			return super.visitMethod(access, name, desc, signature, exceptions);
		}

	}

	public class PatchStartGame extends MethodVisitor {

		public PatchStartGame(MethodVisitor methodVisitor) {
			super(ASM5, methodVisitor);
		}

		@Override
		public void visitCode() {
			LogUtil.log("add resource pack");
			mv.visitVarInsn(ALOAD, 0);
			mv.visitFieldInsn(GETFIELD, Names.minecraft.getName(), Names.resourcePacks.getName(), "Ljava/util/List;");
			mv.visitTypeInsn(NEW, "The5zigModResourcePack");
			mv.visitInsn(DUP);
			mv.visitMethodInsn(INVOKESPECIAL, "The5zigModResourcePack", "<init>", "()V", false);
			mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
			mv.visitInsn(POP);
			super.visitCode();
		}
	}

	public class PatchDispatchKeypresses extends MethodVisitor {

		public PatchDispatchKeypresses(MethodVisitor methodVisitor) {
			super(ASM5, methodVisitor);
		}

		@Override
		public void visitCode() {
			LogUtil.log("dispatchKeypresses");
			mv.visitMethodInsn(INVOKESTATIC, "BytecodeHook", "onDispatchKeyPresses", "()V", false);
			super.visitCode();
		}

		@Override
		public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean ifc) {
			super.visitMethodInsn(opcode, owner, name, desc, ifc);
			if (opcode == INVOKESTATIC && "org/lwjgl/input/Keyboard".equals(owner) && "getEventCharacter".equals(name) && "()C".equals(desc) && !ifc) {
				LogUtil.log("fix keys");
				mv.visitIntInsn(SIPUSH, 256);
				mv.visitInsn(IADD);
			}
		}
	}

	public class PatchShutdown extends MethodVisitor {

		public PatchShutdown(MethodVisitor methodVisitor) {
			super(ASM5, methodVisitor);
		}

		@Override
		public void visitCode() {
			LogUtil.log("shutdown");
			mv.visitMethodInsn(INVOKESTATIC, "BytecodeHook", "onShutdown", "()V", false);
			super.visitCode();
		}
	}

	public class PatchDisplayCrashReport extends MethodVisitor {

		private int count;

		public PatchDisplayCrashReport(MethodVisitor methodVisitor) {
			super(ASM5, methodVisitor);
		}

		@Override
		public void visitCode() {
			super.visitCode();
			mv.visitVarInsn(ALOAD, 1);
			mv.visitMethodInsn(INVOKESTATIC, "BytecodeHook", "appendCrashCategory", "(Ljava/lang/Object;)V", false);
		}

		@Override
		public void visitInsn(int opcode) {
			if (opcode == ICONST_M1) {
				count++;
				if (count == 1) {
					LogUtil.log("display crash report #" + count);
					mv.visitVarInsn(ALOAD, 1);
					mv.visitMethodInsn(INVOKEVIRTUAL, Names.crashReport.getName(), "b", "()Ljava/lang/Throwable;", false);
					mv.visitVarInsn(ALOAD, 1);
					mv.visitMethodInsn(INVOKEVIRTUAL, Names.crashReport.getName(), "f", "()Ljava/io/File;", false);
					mv.visitMethodInsn(INVOKESTATIC, "BytecodeHook", "onDisplayCrashReport", "(Ljava/lang/Throwable;Ljava/io/File;)V", false);
				} else if (count == 2) {
					mv.visitVarInsn(ALOAD, 1);
					mv.visitMethodInsn(INVOKEVIRTUAL, Names.crashReport.getName(), "b", "()Ljava/lang/Throwable;", false);
					mv.visitVarInsn(ALOAD, 3);
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/File", "getAbsoluteFile", "()Ljava/io/File;", false);
					mv.visitMethodInsn(INVOKESTATIC, "BytecodeHook", "onDisplayCrashReport", "(Ljava/lang/Throwable;Ljava/io/File;)V", false);
				}
			}
			super.visitInsn(opcode);
		}

	}

	public class PatchTick extends MethodVisitor {

		public PatchTick(MethodVisitor methodVisitor) {
			super(ASM5, methodVisitor);
		}

		@Override
		public void visitCode() {
			super.visitCode();
			LogUtil.log("tick");
			mv.visitMethodInsn(INVOKESTATIC, "BytecodeHook", "onRealTick", "()V", false);
		}

	}

	public class PatchLeftClickMouse extends MethodVisitor {

		public PatchLeftClickMouse(MethodVisitor methodVisitor) {
			super(ASM5, methodVisitor);
		}

		@Override
		public void visitCode() {
			super.visitCode();
			LogUtil.log("left click mouse");
			mv.visitMethodInsn(INVOKESTATIC, "BytecodeHook", "onLeftClickMouse", "()V", false);
		}

	}

	public class PatchRightClickMouse extends MethodVisitor {

		public PatchRightClickMouse(MethodVisitor methodVisitor) {
			super(ASM5, methodVisitor);
		}

		@Override
		public void visitCode() {
			super.visitCode();
			LogUtil.log("right click mouse");
			mv.visitMethodInsn(INVOKESTATIC, "BytecodeHook", "onRightClickMouse", "()V", false);
		}

	}

	public class PatchDisplayScreen extends MethodVisitor {

		public PatchDisplayScreen(MethodVisitor methodVisitor) {
			super(ASM5, methodVisitor);
		}

		@Override
		public void visitCode() {
			super.visitCode();
			LogUtil.log("displayScreen");
			mv.visitMethodInsn(INVOKESTATIC, "BytecodeHook", "onDisplayScreen", "()V", false);
		}

	}

}