
var ASMAPI = Java.type('net.minecraftforge.coremod.api.ASMAPI')
var AbstractInsnNode = Java.type('org.objectweb.asm.tree.AbstractInsnNode')
var Opcodes = Java.type('org.objectweb.asm.Opcodes')
var FieldInsnNode = Java.type('org.objectweb.asm.tree.FieldInsnNode')
var VarInsnNode = Java.type('org.objectweb.asm.tree.VarInsnNode')
var MethodInsnNode = Java.type('org.objectweb.asm.tree.MethodInsnNode')
var JumpInsnNode = Java.type('org.objectweb.asm.tree.JumpInsnNode')
var LabelNode = Java.type('org.objectweb.asm.tree.LabelNode')
var InsnNode = Java.type('org.objectweb.asm.tree.InsnNode')
var Label = Java.type('org.objectweb.asm.Label')
var LocalVariableNode = Java.type('org.objectweb.asm.tree.LocalVariableNode')

var opcodesRemapped = {
	'aaload': 50,
	'aastore': 83,
	'aconst_null': 1,
	'aload': 25,
	'aload_0': 42,
	'aload_1': 43,
	'aload_2': 44,
	'aload_3': 45,
	'anewarray': 189,
	'areturn': 176,
	'arraylength': 190,
	'astore': 58,
	'astore_0': 75,
	'astore_1': 76,
	'astore_2': 77,
	'astore_3': 78,
	'athrow': 191,
	'baload': 51,
	'bastore': 84,
	'bipush': 16,
	'breakpoint': 202,
	'caload': 52,
	'castore': 85,
	'checkcast': 192,
	'd2f': 144,
	'd2i': 142,
	'd2l': 143,
	'dadd': 99,
	'daload': 49,
	'dastore': 82,
	'dcmpg': 152,
	'dcmpl': 151,
	'dconst_0': 14,
	'dconst_1': 15,
	'ddiv': 111,
	'dload': 24,
	'dload_0': 38,
	'dload_1': 39,
	'dload_2': 40,
	'dload_3': 41,
	'dmul': 107,
	'dneg': 119,
	'drem': 115,
	'dreturn': 175,
	'dstore': 57,
	'dstore_0': 71,
	'dstore_1': 72,
	'dstore_2': 73,
	'dstore_3': 74,
	'dsub': 103,
	'dup': 89,
	'dup_x1': 90,
	'dup_x2': 91,
	'dup2': 92,
	'dup2_x1': 93,
	'dup2_x2': 94,
	'f2d': 141,
	'f2i': 139,
	'f2l': 140,
	'fadd': 98,
	'faload': 48,
	'fastore': 81,
	'fcmpg': 150,
	'fcmpl': 149,
	'fconst_0': 11,
	'fconst_1': 12,
	'fconst_2': 13,
	'fdiv': 110,
	'fload': 23,
	'fload_0': 34,
	'fload_1': 35,
	'fload_2': 36,
	'fload_3': 37,
	'fmul': 106,
	'fneg': 118,
	'frem': 114,
	'freturn': 174,
	'fstore': 56,
	'fstore_0': 67,
	'fstore_1': 68,
	'fstore_2': 69,
	'fstore_3': 70,
	'fsub': 102,
	'getfield': 180,
	'getstatic': 178,
	'goto': 167,
	'goto_w': 200,
	'i2b': 145,
	'i2c': 146,
	'i2d': 135,
	'i2f': 134,
	'i2l': 133,
	'i2s': 147,
	'iadd': 96,
	'iaload': 46,
	'iand': 126,
	'iastore': 79,
	'iconst_0': 3,
	'iconst_1': 4,
	'iconst_2': 5,
	'iconst_3': 6,
	'iconst_4': 7,
	'iconst_5': 8,
	'iconst_m1': 2,
	'idiv': 108,
	'if_acmpeq': 165,
	'if_acmpne': 166,
	'if_icmpeq': 159,
	'if_icmpge': 162,
	'if_icmpgt': 163,
	'if_icmple': 164,
	'if_icmplt': 161,
	'if_icmpne': 160,
	'ifeq': 153,
	'ifge': 156,
	'ifgt': 157,
	'ifle': 158,
	'iflt': 155,
	'ifne': 154,
	'ifnonnull': 199,
	'ifnull': 198,
	'iinc': 132,
	'iload': 21,
	'iload_0': 26,
	'iload_1': 27,
	'iload_2': 28,
	'iload_3': 29,
	'impdep1': 254,
	'impdep2': 255,
	'imul': 104,
	'ineg': 116,
	'instanceof': 193,
	'invokedynamic': 186,
	'invokeinterface': 185,
	'invokespecial': 183,
	'invokestatic': 184,
	'invokevirtual': 182,
	'ior': 128,
	'irem': 112,
	'ireturn': 172,
	'ishl': 120,
	'ishr': 122,
	'istore': 54,
	'istore_0': 59,
	'istore_1': 60,
	'istore_2': 61,
	'istore_3': 62,
	'isub': 100,
	'iushr': 124,
	'ixor': 130,
	'jsr': 168,
	'jsr_w': 201,
	'l2d': 138,
	'l2f': 137,
	'l2i': 136,
	'ladd': 97,
	'laload': 47,
	'land': 127,
	'lastore': 80,
	'lcmp': 148,
	'lconst_0': 9,
	'lconst_1': 10,
	'ldc': 18,
	'ldc_w': 19,
	'ldc2_w': 20,
	'ldiv': 109,
	'lload': 22,
	'lload_0': 30,
	'lload_1': 31,
	'lload_2': 32,
	'lload_3': 33,
	'lmul': 105,
	'lneg': 117,
	'lookupswitch': 171,
	'lor': 129,
	'lrem': 113,
	'lreturn': 173,
	'lshl': 121,
	'lshr': 123,
	'lstore': 55,
	'lstore_0': 63,
	'lstore_1': 64,
	'lstore_2': 65,
	'lstore_3': 66,
	'lsub': 101,
	'lushr': 125,
	'lxor': 131,
	'monitorenter': 194,
	'monitorexit': 195,
	'multianewarray': 197,
	'new': 187,
	'newarray': 188,
	'nop': 0,
	'pop': 87,
	'pop2': 88,
	'putfield': 181,
	'putstatic': 179,
	'ret': 169,
	'return': 177,
	'saload': 53,
	'sastore': 86,
	'sipush': 17,
	'swap': 95,
	'tableswitch': 170,
	'wide': 196
}

var opcodes = []
opcodes[opcodesRemapped.aaload] = 'aaload'
opcodes[opcodesRemapped.aastore] = 'aastore'
opcodes[opcodesRemapped.aconst_null] = 'aconst_null'
opcodes[opcodesRemapped.aload] = 'aload'
opcodes[opcodesRemapped.aload_0] = 'aload_0'
opcodes[opcodesRemapped.aload_1] = 'aload_1'
opcodes[opcodesRemapped.aload_2] = 'aload_2'
opcodes[opcodesRemapped.aload_3] = 'aload_3'
opcodes[opcodesRemapped.anewarray] = 'anewarray'
opcodes[opcodesRemapped.areturn] = 'areturn'
opcodes[opcodesRemapped.arraylength] = 'arraylength'
opcodes[opcodesRemapped.astore] = 'astore'
opcodes[opcodesRemapped.astore_0] = 'astore_0'
opcodes[opcodesRemapped.astore_1] = 'astore_1'
opcodes[opcodesRemapped.astore_2] = 'astore_2'
opcodes[opcodesRemapped.astore_3] = 'astore_3'
opcodes[opcodesRemapped.athrow] = 'athrow'
opcodes[opcodesRemapped.baload] = 'baload'
opcodes[opcodesRemapped.bastore] = 'bastore'
opcodes[opcodesRemapped.bipush] = 'bipush'
opcodes[opcodesRemapped.breakpoint] = 'breakpoint'
opcodes[opcodesRemapped.caload] = 'caload'
opcodes[opcodesRemapped.castore] = 'castore'
opcodes[opcodesRemapped.checkcast] = 'checkcast'
opcodes[opcodesRemapped.d2f] = 'd2f'
opcodes[opcodesRemapped.d2i] = 'd2i'
opcodes[opcodesRemapped.d2l] = 'd2l'
opcodes[opcodesRemapped.dadd] = 'dadd'
opcodes[opcodesRemapped.daload] = 'daload'
opcodes[opcodesRemapped.dastore] = 'dastore'
opcodes[opcodesRemapped.dcmpg] = 'dcmpg'
opcodes[opcodesRemapped.dcmpl] = 'dcmpl'
opcodes[opcodesRemapped.dconst_0] = 'dconst_0'
opcodes[opcodesRemapped.dconst_1] = 'dconst_1'
opcodes[opcodesRemapped.ddiv] = 'ddiv'
opcodes[opcodesRemapped.dload] = 'dload'
opcodes[opcodesRemapped.dload_0] = 'dload_0'
opcodes[opcodesRemapped.dload_1] = 'dload_1'
opcodes[opcodesRemapped.dload_2] = 'dload_2'
opcodes[opcodesRemapped.dload_3] = 'dload_3'
opcodes[opcodesRemapped.dmul] = 'dmul'
opcodes[opcodesRemapped.dneg] = 'dneg'
opcodes[opcodesRemapped.drem] = 'drem'
opcodes[opcodesRemapped.dreturn] = 'dreturn'
opcodes[opcodesRemapped.dstore] = 'dstore'
opcodes[opcodesRemapped.dstore_0] = 'dstore_0'
opcodes[opcodesRemapped.dstore_1] = 'dstore_1'
opcodes[opcodesRemapped.dstore_2] = 'dstore_2'
opcodes[opcodesRemapped.dstore_3] = 'dstore_3'
opcodes[opcodesRemapped.dsub] = 'dsub'
opcodes[opcodesRemapped.dup] = 'dup'
opcodes[opcodesRemapped.dup_x1] = 'dup_x1'
opcodes[opcodesRemapped.dup_x2] = 'dup_x2'
opcodes[opcodesRemapped.dup2] = 'dup2'
opcodes[opcodesRemapped.dup2_x1] = 'dup2_x1'
opcodes[opcodesRemapped.dup2_x2] = 'dup2_x2'
opcodes[opcodesRemapped.f2d] = 'f2d'
opcodes[opcodesRemapped.f2i] = 'f2i'
opcodes[opcodesRemapped.f2l] = 'f2l'
opcodes[opcodesRemapped.fadd] = 'fadd'
opcodes[opcodesRemapped.faload] = 'faload'
opcodes[opcodesRemapped.fastore] = 'fastore'
opcodes[opcodesRemapped.fcmpg] = 'fcmpg'
opcodes[opcodesRemapped.fcmpl] = 'fcmpl'
opcodes[opcodesRemapped.fconst_0] = 'fconst_0'
opcodes[opcodesRemapped.fconst_1] = 'fconst_1'
opcodes[opcodesRemapped.fconst_2] = 'fconst_2'
opcodes[opcodesRemapped.fdiv] = 'fdiv'
opcodes[opcodesRemapped.fload] = 'fload'
opcodes[opcodesRemapped.fload_0] = 'fload_0'
opcodes[opcodesRemapped.fload_1] = 'fload_1'
opcodes[opcodesRemapped.fload_2] = 'fload_2'
opcodes[opcodesRemapped.fload_3] = 'fload_3'
opcodes[opcodesRemapped.fmul] = 'fmul'
opcodes[opcodesRemapped.fneg] = 'fneg'
opcodes[opcodesRemapped.frem] = 'frem'
opcodes[opcodesRemapped.freturn] = 'freturn'
opcodes[opcodesRemapped.fstore] = 'fstore'
opcodes[opcodesRemapped.fstore_0] = 'fstore_0'
opcodes[opcodesRemapped.fstore_1] = 'fstore_1'
opcodes[opcodesRemapped.fstore_2] = 'fstore_2'
opcodes[opcodesRemapped.fstore_3] = 'fstore_3'
opcodes[opcodesRemapped.fsub] = 'fsub'
opcodes[opcodesRemapped.getfield] = 'getfield'
opcodes[opcodesRemapped.getstatic] = 'getstatic'
opcodes[opcodesRemapped.goto] = 'goto'
opcodes[opcodesRemapped.goto_w] = 'goto_w'
opcodes[opcodesRemapped.i2b] = 'i2b'
opcodes[opcodesRemapped.i2c] = 'i2c'
opcodes[opcodesRemapped.i2d] = 'i2d'
opcodes[opcodesRemapped.i2f] = 'i2f'
opcodes[opcodesRemapped.i2l] = 'i2l'
opcodes[opcodesRemapped.i2s] = 'i2s'
opcodes[opcodesRemapped.iadd] = 'iadd'
opcodes[opcodesRemapped.iaload] = 'iaload'
opcodes[opcodesRemapped.iand] = 'iand'
opcodes[opcodesRemapped.iastore] = 'iastore'
opcodes[opcodesRemapped.iconst_0] = 'iconst_0'
opcodes[opcodesRemapped.iconst_1] = 'iconst_1'
opcodes[opcodesRemapped.iconst_2] = 'iconst_2'
opcodes[opcodesRemapped.iconst_3] = 'iconst_3'
opcodes[opcodesRemapped.iconst_4] = 'iconst_4'
opcodes[opcodesRemapped.iconst_5] = 'iconst_5'
opcodes[opcodesRemapped.iconst_m1] = 'iconst_m1'
opcodes[opcodesRemapped.idiv] = 'idiv'
opcodes[opcodesRemapped.if_acmpeq] = 'if_acmpeq'
opcodes[opcodesRemapped.if_acmpne] = 'if_acmpne'
opcodes[opcodesRemapped.if_icmpeq] = 'if_icmpeq'
opcodes[opcodesRemapped.if_icmpge] = 'if_icmpge'
opcodes[opcodesRemapped.if_icmpgt] = 'if_icmpgt'
opcodes[opcodesRemapped.if_icmple] = 'if_icmple'
opcodes[opcodesRemapped.if_icmplt] = 'if_icmplt'
opcodes[opcodesRemapped.if_icmpne] = 'if_icmpne'
opcodes[opcodesRemapped.ifeq] = 'ifeq'
opcodes[opcodesRemapped.ifge] = 'ifge'
opcodes[opcodesRemapped.ifgt] = 'ifgt'
opcodes[opcodesRemapped.ifle] = 'ifle'
opcodes[opcodesRemapped.iflt] = 'iflt'
opcodes[opcodesRemapped.ifne] = 'ifne'
opcodes[opcodesRemapped.ifnonnull] = 'ifnonnull'
opcodes[opcodesRemapped.ifnull] = 'ifnull'
opcodes[opcodesRemapped.iinc] = 'iinc'
opcodes[opcodesRemapped.iload] = 'iload'
opcodes[opcodesRemapped.iload_0] = 'iload_0'
opcodes[opcodesRemapped.iload_1] = 'iload_1'
opcodes[opcodesRemapped.iload_2] = 'iload_2'
opcodes[opcodesRemapped.iload_3] = 'iload_3'
opcodes[opcodesRemapped.impdep1] = 'impdep1'
opcodes[opcodesRemapped.impdep2] = 'impdep2'
opcodes[opcodesRemapped.imul] = 'imul'
opcodes[opcodesRemapped.ineg] = 'ineg'
opcodes[opcodesRemapped.instanceof] = 'instanceof'
opcodes[opcodesRemapped.invokedynamic] = 'invokedynamic'
opcodes[opcodesRemapped.invokeinterface] = 'invokeinterface'
opcodes[opcodesRemapped.invokespecial] = 'invokespecial'
opcodes[opcodesRemapped.invokestatic] = 'invokestatic'
opcodes[opcodesRemapped.invokevirtual] = 'invokevirtual'
opcodes[opcodesRemapped.ior] = 'ior'
opcodes[opcodesRemapped.irem] = 'irem'
opcodes[opcodesRemapped.ireturn] = 'ireturn'
opcodes[opcodesRemapped.ishl] = 'ishl'
opcodes[opcodesRemapped.ishr] = 'ishr'
opcodes[opcodesRemapped.istore] = 'istore'
opcodes[opcodesRemapped.istore_0] = 'istore_0'
opcodes[opcodesRemapped.istore_1] = 'istore_1'
opcodes[opcodesRemapped.istore_2] = 'istore_2'
opcodes[opcodesRemapped.istore_3] = 'istore_3'
opcodes[opcodesRemapped.isub] = 'isub'
opcodes[opcodesRemapped.iushr] = 'iushr'
opcodes[opcodesRemapped.ixor] = 'ixor'
opcodes[opcodesRemapped.jsr] = 'jsr'
opcodes[opcodesRemapped.jsr_w] = 'jsr_w'
opcodes[opcodesRemapped.l2d] = 'l2d'
opcodes[opcodesRemapped.l2f] = 'l2f'
opcodes[opcodesRemapped.l2i] = 'l2i'
opcodes[opcodesRemapped.ladd] = 'ladd'
opcodes[opcodesRemapped.laload] = 'laload'
opcodes[opcodesRemapped.land] = 'land'
opcodes[opcodesRemapped.lastore] = 'lastore'
opcodes[opcodesRemapped.lcmp] = 'lcmp'
opcodes[opcodesRemapped.lconst_0] = 'lconst_0'
opcodes[opcodesRemapped.lconst_1] = 'lconst_1'
opcodes[opcodesRemapped.ldc] = 'ldc'
opcodes[opcodesRemapped.ldc_w] = 'ldc_w'
opcodes[opcodesRemapped.ldc2_w] = 'ldc2_w'
opcodes[opcodesRemapped.ldiv] = 'ldiv'
opcodes[opcodesRemapped.lload] = 'lload'
opcodes[opcodesRemapped.lload_0] = 'lload_0'
opcodes[opcodesRemapped.lload_1] = 'lload_1'
opcodes[opcodesRemapped.lload_2] = 'lload_2'
opcodes[opcodesRemapped.lload_3] = 'lload_3'
opcodes[opcodesRemapped.lmul] = 'lmul'
opcodes[opcodesRemapped.lneg] = 'lneg'
opcodes[opcodesRemapped.lookupswitch] = 'lookupswitch'
opcodes[opcodesRemapped.lor] = 'lor'
opcodes[opcodesRemapped.lrem] = 'lrem'
opcodes[opcodesRemapped.lreturn] = 'lreturn'
opcodes[opcodesRemapped.lshl] = 'lshl'
opcodes[opcodesRemapped.lshr] = 'lshr'
opcodes[opcodesRemapped.lstore] = 'lstore'
opcodes[opcodesRemapped.lstore_0] = 'lstore_0'
opcodes[opcodesRemapped.lstore_1] = 'lstore_1'
opcodes[opcodesRemapped.lstore_2] = 'lstore_2'
opcodes[opcodesRemapped.lstore_3] = 'lstore_3'
opcodes[opcodesRemapped.lsub] = 'lsub'
opcodes[opcodesRemapped.lushr] = 'lushr'
opcodes[opcodesRemapped.lxor] = 'lxor'
opcodes[opcodesRemapped.monitorenter] = 'monitorenter'
opcodes[opcodesRemapped.monitorexit] = 'monitorexit'
opcodes[opcodesRemapped.multianewarray] = 'multianewarray'
opcodes[opcodesRemapped.new] = 'new'
opcodes[opcodesRemapped.newarray] = 'newarray'
opcodes[opcodesRemapped.nop] = 'nop'
opcodes[opcodesRemapped.pop] = 'pop'
opcodes[opcodesRemapped.pop2] = 'pop2'
opcodes[opcodesRemapped.putfield] = 'putfield'
opcodes[opcodesRemapped.putstatic] = 'putstatic'
opcodes[opcodesRemapped.ret] = 'ret'
opcodes[opcodesRemapped.return] = 'return'
opcodes[opcodesRemapped.saload] = 'saload'
opcodes[opcodesRemapped.sastore] = 'sastore'
opcodes[opcodesRemapped.sipush] = 'sipush'
opcodes[opcodesRemapped.swap] = 'swap'
opcodes[opcodesRemapped.tableswitch] = 'tableswitch'
opcodes[opcodesRemapped.wide] = 'wide'

function includes(array, value) {
	var i

	for (i = 0; i < array.length; i++) {
		if (array[i] == value) {
			return true
		}
	}

	return false
}

function test(instructionList, instructions, offset) {
	var i
	var offset = offset

	for (i = 0; i < instructionList.length && instructions.size() > offset; i++) {
		var instruction = instructions.get(offset)

		// no opcode
		if (instruction.getOpcode() == -1) {
			i--
			offset++
			continue
		}

		if (instructionList[i] == instruction.getOpcode()) {
			offset++
		} else {
			// print("Not matched at " + i + " offset " + offset + " because it has op code of " + opcodes[instruction.getOpcode()] + " when expected " + opcodes[instructionList[i]])
			return -1
		}
	}

	// print("exiting with " + i + " when expecting " + instructionList.length)
	return i == instructionList.length ? offset : -1
}

function method(path, transform) {
	var split = path.split('.')
	var methodName = split.pop()
	var indexOf = methodName.indexOf('(')
	var classname = split.join('.')

	return {
		'target': {
			'type': 'METHOD',
			'class': classname,
			'methodName': ASMAPI.mapMethod(methodName.substring(0, indexOf)),
			'methodDesc': methodName.substring(indexOf)
		},

		'transformer': transform
	}
}

function putInstructions(node, after, instructions) {
	var last = after
	var next

	for (var i = 0; i < instructions.length; i++) {
		next = instructions[i]

		if (last === undefined) {
			node.instructions.insert(next)
		} else {
			node.instructions.insert(last, next)
		}

		last = next
	}
}

function injectAtHead(path, instructions) {
	return method(path, function(node) {
		putInstructions(node, undefined, instructions)
		return node
	})
}

var returnCodes = [
	opcodesRemapped.return,
	opcodesRemapped.areturn,
	opcodesRemapped.freturn,
	opcodesRemapped.ireturn,
	opcodesRemapped.dreturn,
	opcodesRemapped.lreturn,
]

function injectInstructionsAtTail(node, instructions) {
	for (var instr = node.instructions.size() - 1; instr >= 0; instr--) {
		if (includes(returnCodes, node.instructions.get(instr).getOpcode())) {
			putInstructions(node, node.instructions.get(instr > 0 ? --instr : instr), instructions)
			return
		}
	}

	print('[Overdrive That Matters Coremod] Unable to find tail of ' + path + ', injecting at beginning!')
	putInstructions(node, undefined, instructions)
}

function injectAtTail(path, instructions) {
	return method(path, function(node) {
		injectInstructionsAtTail(node, instructions)
		return node
	})
}

function patchBlendFunc(node) {
	var last = new MethodInsnNode(
		opcodesRemapped.invokestatic,
		'ru/dbotthepony/mc/otm/client/render/RenderHelperKt',
		'getLockBlendFunc',
		'()Z',
		false
	)

	var label = new Label()
	var labelNode = new LabelNode(label)

	node.instructions.insert(last)
	node.instructions.insert(last, labelNode)

	var next = new JumpInsnNode(opcodesRemapped.ifeq, labelNode)
	node.instructions.insert(last, next)
	last = next

	next = new InsnNode(opcodesRemapped['return'])
	node.instructions.insert(last, next)
	last = next

	return node
}

function backtrack(instructions, from, opcode, skipAmount) {
	for (var i = from; i >= 0; i--) {
		if (instructions.get(i).getOpcode() === opcode) {
			if (skipAmount > 0) {
				skipAmount--
				continue
			}

			return instructions.get(i)
		}
	}

	return null
}

function initializeCoreMod() {
	return {
		'PhantomSpawner block spawns as android': method('net.minecraft.world.level.levelgen.PhantomSpawner.m_7995_(Lnet/minecraft/server/level/ServerLevel;ZZ)I', function(node) {
			var players = ASMAPI.mapMethod('m_6907_')

			for (var i = 0; i < node.instructions.size(); i++) {
				var instr = node.instructions.get(i)

				if (instr.getOpcode() == opcodesRemapped.invokevirtual && instr.name == players) {
					putInstructions(node, node.instructions.get(i + 1), [
						new MethodInsnNode(
							opcodesRemapped.invokestatic,
							'ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability',
							'phantomSpawnHook',
							'(Ljava/util/Iterator;)Ljava/util/Iterator;'
						),
					])

					return node
				}
			}

			return node
		}),

		'LivingEntity#addEatEffect patch for androids': method('net.minecraft.world.entity.LivingEntity.m_21063_(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/LivingEntity;)V', function(node) {
			for (var i = 0; i < node.instructions.size(); i++) {
				var instr = node.instructions.get(i)

				if (instr.getOpcode() == opcodesRemapped.invokeinterface && instr.name == "iterator") {
					putInstructions(node, node.instructions.get(i), [
						new VarInsnNode(opcodesRemapped.aload, 0),
						new MethodInsnNode(
							opcodesRemapped.invokestatic,
							'ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability',
							'addEatEffectHook',
							'(Ljava/util/Iterator;Lnet/minecraft/world/entity/LivingEntity;)Ljava/util/Iterator;'
						),
					])

					return node
				}
			}

			return node
		}),

		'blend func lock 1': {
			'target': {
				'type': 'METHOD',
				'class': 'com.mojang.blaze3d.systems.RenderSystem',
				'methodName': ASMAPI.mapMethod('m_69478_'), // enableBlend
				'methodDesc': '()V'
			},
			'transformer': patchBlendFunc
		},

		'blend func lock 2': {
			'target': {
				'type': 'METHOD',
				'class': 'com.mojang.blaze3d.systems.RenderSystem',
				'methodName': ASMAPI.mapMethod('m_69461_'), // disableBlend
				'methodDesc': '()V'
			},
			'transformer': patchBlendFunc
		},

		'blend func lock 3': {
			'target': {
				'type': 'METHOD',
				'class': 'com.mojang.blaze3d.systems.RenderSystem',
				'methodName': ASMAPI.mapMethod('m_69405_'), // blendFunc
				'methodDesc': '(II)V'
			},
			'transformer': patchBlendFunc
		},

		'blend func lock 5': {
			'target': {
				'type': 'METHOD',
				'class': 'com.mojang.blaze3d.systems.RenderSystem',
				'methodName': ASMAPI.mapMethod('m_69411_'), // blendFuncSeparate
				'methodDesc': '(IIII)V'
			},
			'transformer': patchBlendFunc
		},

		'blend func lock 6': {
			'target': {
				'type': 'METHOD',
				'class': 'com.mojang.blaze3d.systems.RenderSystem',
				'methodName': ASMAPI.mapMethod('m_69416_'), // blendFuncSeparate
				'methodDesc': '(Lcom/mojang/blaze3d/platform/GlStateManager$SourceFactor;Lcom/mojang/blaze3d/platform/GlStateManager$DestFactor;Lcom/mojang/blaze3d/platform/GlStateManager$SourceFactor;Lcom/mojang/blaze3d/platform/GlStateManager$DestFactor;)V'
			},
			'transformer': patchBlendFunc
		},

		'blend func lock 4': {
			'target': {
				'type': 'METHOD',
				'class': 'com.mojang.blaze3d.systems.RenderSystem',
				'methodName': ASMAPI.mapMethod('m_69408_'), // blendFunc
				'methodDesc': '(Lcom/mojang/blaze3d/platform/GlStateManager$SourceFactor;Lcom/mojang/blaze3d/platform/GlStateManager$DestFactor;)V'
			},
			'transformer': patchBlendFunc
		},

		'LevelRenderer DynamicBufferSource callback': {
			'target': {
				'type': 'METHOD',
				'class': 'net.minecraft.client.renderer.LevelRenderer',
				'methodName': ASMAPI.mapMethod('m_109599_'), // renderLevel
				'methodDesc': '(Lcom/mojang/blaze3d/vertex/PoseStack;FJZLnet/minecraft/client/Camera;Lnet/minecraft/client/renderer/GameRenderer;Lnet/minecraft/client/renderer/LightTexture;Lorg/joml/Matrix4f;)V'
			},

			'transformer': function(node) {
				var endBatchName = ASMAPI.mapMethod('m_109912_') // net/minecraft/client/renderer/MultiBufferSource$BufferSource#endBatch
				var endBatchDesc = '(Lnet/minecraft/client/renderer/RenderType;)V'

				var endBatchName2 = ASMAPI.mapMethod('m_109911_') // net/minecraft/client/renderer/MultiBufferSource$BufferSource#endBatch
				var endBatchDesc2 = '()V'

				for (i = 0; i < node.instructions.size(); i++) {
					var instruction = node.instructions.get(i)

					if (instruction.getOpcode() == opcodesRemapped.invokevirtual && instruction.name == endBatchName && instruction.desc == endBatchDesc) {
						node.instructions.insert(node.instructions.get(i - 1), new InsnNode(opcodesRemapped.dup_x1))
						node.instructions.insert(instruction, new MethodInsnNode(
							opcodesRemapped.invokestatic,
							'ru/dbotthepony/mc/otm/client/render/DynamicBufferSource',
							'renderHook',
							'(Lnet/minecraft/client/renderer/RenderType;)V',
							false
						))

						i += 2
						continue
					}

					if (instruction.getOpcode() == opcodesRemapped.invokevirtual && instruction.name == endBatchName2 && instruction.desc == endBatchDesc2) {
						node.instructions.insert(instruction, new MethodInsnNode(
							opcodesRemapped.invokestatic,
							'ru/dbotthepony/mc/otm/client/render/DynamicBufferSource',
							'renderHook',
							'()V',
							false
						))

						i += 2
                        continue
					}
				}

				return node
			}
		},

		'Mekanism Chargepad Android Charge': {
			'target': {
				'type': 'METHOD',
				'class': 'mekanism.common.tile.TileEntityChargepad',
				'methodName': 'onUpdateServer',
				'methodDesc': '()V'
			},

			'transformer': function(node) {
				var skipLabel = new Label()
				var skipLabelNode = new LabelNode(skipLabel)

				var labelIteratorEndNode

				for (i = 0; i < node.instructions.size(); i++) {
					var insn = node.instructions.get(i)

					if (insn && insn.getOpcode() == opcodesRemapped.getfield && insn.name == 'CuriosLoaded') {
						var nextInsn = node.instructions.get(i + 1) // IFEQ

						labelIteratorEndNode = nextInsn.label

						break
					}
				}

				if (!labelIteratorEndNode) return node

				for (i = 0; i < node.instructions.size(); i++) {
					var insn = node.instructions.get(i)

					if (insn && insn.getType() == AbstractInsnNode.LABEL) {
						if (insn == labelIteratorEndNode) {
							putInstructions(node, insn, [
								new VarInsnNode(opcodesRemapped.aload, 0),
								new FieldInsnNode(
									opcodesRemapped.getfield,
									'mekanism/common/tile/TileEntityChargepad',
									'energyContainer',
									'Lmekanism/common/capabilities/energy/MachineEnergyContainer;'
								),
								new VarInsnNode(opcodesRemapped.aload, 4),
								new MethodInsnNode(
									opcodesRemapped.invokestatic,
									'ru/dbotthepony/mc/otm/compat/mekanism/MekanismHooks',
									'chargeAndroidFromPadHook',
									'(Lmekanism/common/capabilities/energy/MachineEnergyContainer;Lnet/minecraft/world/entity/LivingEntity;)V'
								),
								skipLabelNode
							])

							break
						}
					}
				}

				for (i = 0; i < node.instructions.size(); i++) {
					var insn = node.instructions.get(i)

					if (insn && insn.getOpcode() == opcodesRemapped.invokevirtual && insn.name == 'chargeHandler' && insn.desc == '(Ljava/util/Optional;)Z') {
						var nextInsn = node.instructions.get(i + 1) // curios call is POP, pre-curios check call is IFNE; both need to redirect to loop skip label

						if (nextInsn && (nextInsn.getOpcode() == opcodesRemapped.pop || nextInsn.getOpcode() == opcodesRemapped.ifne && nextInsn.label == labelIteratorEndNode)) {
							node.instructions.set(nextInsn, new JumpInsnNode(opcodesRemapped.ifne, skipLabelNode))

							continue
						}
					}
				}

				return node
			}
		}
	}
}
