package me.coley.recaf.bytecode.insn;

import me.coley.recaf.bytecode.AccessFlag;
import me.coley.recaf.util.Parse;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;

import java.util.*;

/**
 * Extended instructions with names to indicate placeholder variable locations.
 *
 * @author Matt
 */
public interface NamedVarRefInsn {
	/**
	 * @return Placeholder identifier.
	 */
	String getVarName();

	/**
	 * @param v
	 * 		Variable data to use
	 *
	 * @return Cloned instruction without serialization information. Uses the standard instruction
	 * isntance.
	 */
	AbstractInsnNode clone(Var v);

	// ========================= UTILITY METHODS ========================================= //

	/**
	 * Replace named instructions with the standard implementations.
	 *
	 * @param access
	 * 		Method access flags.
	 * @param insns
	 * 		Method instructions.
	 *
	 * @return Updated method instructions.
	 */
	static InsnList clean(int access, InsnList insns) {
		// Map of names to vars
		Map<String, Var> varMap = new HashMap<>();
		Set<Integer> usedIndices = new HashSet<>();
		// Set of opcodes to replace
		Set<NamedVarRefInsn> replaceSet = new HashSet<>();
		// Pass to find used names
		for(AbstractInsnNode ain : insns.toArray()) {
			if(ain instanceof NamedVarRefInsn) {
				// Add to opcode set to replace
				NamedVarRefInsn named = (NamedVarRefInsn) ain;
				replaceSet.add(named);
				// Add to varMap
				String key = named.getVarName();
				varMap.putIfAbsent(key, new Var(key));
			}
		}
		// Generate indices
		boolean isStatic = AccessFlag.isStatic(access);
		if(!isStatic) {
			Var v = varMap.get("this");
			if(v != null) {
				usedIndices.add(0);
				v.index = 0;
			}
		}
		for(Map.Entry<String, Var> e : varMap.entrySet()) {
			String key = e.getKey();
			Var v = e.getValue();
			// Check if the key is actually a number literal
			if(Parse.isInt(key)) {
				int index = Integer.parseInt(key);
				v.index = index;
				usedIndices.add(index);
				continue;
			}
			// Find the first unused int to apply
			int index = isStatic ? 0 : 1;
			while(usedIndices.contains(index))
				index++;
			v.index = index;
			usedIndices.add(index);
		}
		// Replace insns
		for(NamedVarRefInsn nvri : replaceSet) {
			AbstractInsnNode index = (AbstractInsnNode) nvri;
			Var v = varMap.get(nvri.getVarName());
			insns.set(index, nvri.clone(v));
		}
		return insns;
	}

	/**
	 * Wrapper for variable.
	 */
	class Var {
		final String key;
		int index = -1;

		Var(String key) {
			this.key = key;
		}
	}
}

@@ -32,16 +32,62 @@
 .insn;%0A%0A
+import jregex.Matcher;%0Aimport jregex.Pattern;%0A
 import m
@@ -1922,16 +1922,35 @@
  literal
+ or parameter value
 %0A%09%09%09if(P
@@ -1964,24 +1964,51 @@
 Int(key)) %7B%0A
+%09%09%09%09// Literal index given%0A
 %09%09%09%09int inde
@@ -2097,16 +2097,329 @@
 ntinue;%0A
+%09%09%09%7D else if (key.matches(%22p%5C%5Cd%5C%5Cw*%22)) %7B%0A%09%09%09%09// Parameter index given p%3Cindex%3E%3Cname%3E%0A%09%09%09%09Matcher m = new Pattern(%22p(%7BINDEX%7D%5C%5Cd+)%5C%5Cw*%22).matcher(key);%0A%09%09%09%09m.find();%0A%09%09%09%09int index = Integer.parseInt(m.group(%22INDEX%22));%0A%09%09%09%09if (isStatic)%0A%09%09%09%09%09index -= 1;%0A%09%09%09%09v.index = index;%0A%09%09%09%09usedIndices.add(index);%0A%09%09%09%09continue;%0A
 %09%09%09%7D%0A%09%09%09

Support for indicating parameter values via variable names 'pN<name' where N is the Nth variable, starting at 1