package org.squiddev.cctweaks.lua.asm.binary;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.TypeInsnNode;
import org.squiddev.patcher.transformer.ClassMerger;
import org.squiddev.patcher.transformer.TransformationChain;
import org.squiddev.patcher.visitors.FindingVisitor;

import java.util.Arrays;

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

/**
 * Utilities for injecting binary patches
 */
public final class BinaryUtils {
	public static final String BINARY_CONVERTER = "org/squiddev/cctweaks/lua/lib/BinaryConverter";
	public static final String BINARY_OBJECT = "org/squiddev/cctweaks/api/lua/IBinaryHandler";

	private BinaryUtils() {
		throw new RuntimeException("Cannot create instance of BinaryUtils");
	}

	private static String[] addInterface(String[] interfaces) {
		String[] newInterfaces = Arrays.copyOf(interfaces, interfaces.length + 1);
		newInterfaces[interfaces.length] = BINARY_OBJECT;
		return newInterfaces;
	}

	/**
	 * Add a binary interface to a class
	 *
	 * @param visitor The original visitor
	 * @return The new visitor
	 */
	public static ClassVisitor withBinaryInterface(ClassVisitor visitor) {
		return new ClassVisitor(ASM5, visitor) {
			@Override
			public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
				super.visit(version, access, name, signature, superName, addInterface(interfaces));
			}
		};
	}

	/**
	 * Replace string casts with byte array casts then string creation
	 *
	 * @param visitor The original visitor
	 * @return The new visitor
	 */
	public static ClassVisitor withStringCasts(ClassVisitor visitor, String... methodNames) {
		FindingVisitor finder = new FindingVisitor(
			visitor,
			new TypeInsnNode(INSTANCEOF, "java/lang/String")
		) {
			@Override
			public void handle(InsnList nodes, MethodVisitor visitor) {
				visitor.visitTypeInsn(INSTANCEOF, "[B");
			}
		}.onMethod("callMethod");
		for (String name : methodNames) {
			finder.onMethod(name);
		}

		finder = new FindingVisitor(
			finder,
			new TypeInsnNode(CHECKCAST, "java/lang/String")
		) {
			@Override
			public void handle(InsnList nodes, MethodVisitor visitor) {
				visitor.visitTypeInsn(CHECKCAST, "[B");
				visitor.visitMethodInsn(INVOKESTATIC, BINARY_CONVERTER, "decodeString", "([B)Ljava/lang/String;", false);
			}
		}.onMethod("callMethod");
		for (String name : methodNames) {
			finder.onMethod(name);
		}

		return finder;
	}

	/**
	 * Inject all binary patches into the chain
	 *
	 * @param chain The chain to inject
	 * @return The chain that has been injected
	 */
	public static TransformationChain inject(TransformationChain chain) {
		chain.add(new BinaryGeneric());
		chain.add(new BinaryMachine());
		chain.add(new BinaryFS());

		// HTTP rewrite
		chain.add(new ClassMerger("dan200.computercraft.core.apis.HTTPAPI", "org.squiddev.cctweaks.lua.patch.HTTPAPI_Patch"));

		// FS handle rewrites
		chain.add(new ClassMerger(BinaryFS.READER_OBJECT, BinaryFS.READER_OBJECT));
		chain.add(new ClassMerger(BinaryFS.WRITER_OBJECT, BinaryFS.WRITER_OBJECT));
		chain.add(new ClassMerger("dan200.computercraft.core.filesystem.FileSystem", "org.squiddev.cctweaks.lua.patch.binfs.FileSystem_Patch"));

		return chain;
	}
}

@@ -2833,24 +2833,186 @@
 naryFS());%0A%0A
+%09%09// PeripheralAPI rewrites%0A%09%09chain.add(new ClassMerger(%22dan200.computercraft.core.apis.PeripheralAPI%22, %22org.squiddev.cctweaks.lua.patch.PeripheralAPI_Patch%22));%0A%0A
 %09%09// HTTP re

Fix Peripheral API not being patched