package squeek.asmhelper;

import java.io.IOException;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.minecraft.launchwrapper.LaunchClassLoader;
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;

public class ObfHelper
{
	private static Boolean obfuscated = null;
	private static HashMap<String, String> classNameToObfClassNameCache = new HashMap<String, String>();
	private static HashMap<String, String> obfClassNameToClassNameCache = new HashMap<String, String>();

	/**
	 * Can be initialized by a core mod in {@link cpw.mods.fml.relauncher.IFMLLoadingPlugin#injectData} by 
	 * using the value of "runtimeDeobfuscationEnabled" to
	 * avoid the class loader lookup in isObfuscated.<br>
	 * <br>
	 * <b>Example:</b>
	 * <pre>
	 * public void injectData(Map<String, Object> data)
	 * {
	 *     ObfHelper.setObfuscated((Boolean) data.get("runtimeDeobfuscationEnabled"));
	 * }
	 * </pre>
	 */
	public static void setObfuscated(boolean obfuscated)
	{
		ObfHelper.obfuscated = obfuscated;
	}

	/**
	 * @return Whether or not the current environment contains obfuscated Minecraft code
	 */
	public static boolean isObfuscated()
	{
		if (obfuscated == null)
		{
			try
			{
				byte[] bytes = ((LaunchClassLoader) ObfHelper.class.getClassLoader()).getClassBytes("net.minecraft.world.World");
				ObfHelper.setObfuscated(bytes == null);
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
		return obfuscated;
	}

	private static void cacheObfClassMapping(String obfClassName, String className)
	{
		obfClassNameToClassNameCache.put(obfClassName, className);
		classNameToObfClassNameCache.put(className, obfClassName);
	}

	/**
	 * Deobfuscates an obfuscated class name if {@link #isObfuscated()}.
	 */
	public static String toDeobfClassName(String obfClassName)
	{
		if (isObfuscated())
			return forceToDeobfClassName(obfClassName);
		else
			return obfClassName;
	}

	/**
	 * Deobfuscates an obfuscated class name regardless of {@link #isObfuscated()}.
	 */
	public static String forceToDeobfClassName(String obfClassName)
	{
		if (!obfClassNameToClassNameCache.containsKey(obfClassName))
		{
			String mapped = FMLDeobfuscatingRemapper.INSTANCE.map(obfClassName.replace('.', '/')).replace('/', '.');
			if (!mapped.equals(obfClassName))
				cacheObfClassMapping(mapped, obfClassName);
			else
				return obfClassName;
		}

		return obfClassNameToClassNameCache.get(obfClassName);
	}

	/**
	 * Obfuscates a deobfuscated class name if {@link #isObfuscated()}.
	 */
	public static String toObfClassName(String deobfClassName)
	{
		if (isObfuscated())
			return forceToObfClassName(deobfClassName);
		else
			return deobfClassName;
	}

	/**
	 * Obfuscates a deobfuscated class name regardless of {@link #isObfuscated()}.
	 */
	public static String forceToObfClassName(String deobfClassName)
	{
		if (!classNameToObfClassNameCache.containsKey(deobfClassName))
		{
			String unmapped = FMLDeobfuscatingRemapper.INSTANCE.unmap(deobfClassName.replace('.', '/')).replace('/', '.');
			if (!unmapped.equals(deobfClassName))
				cacheObfClassMapping(unmapped, deobfClassName);
			else
				return deobfClassName;
		}

		return classNameToObfClassNameCache.get(deobfClassName);
	}

	/**
	 * Converts a class name to an internal class name, obfuscating the class name if {@link #isObfuscated()}.
	 * @return internal/class/name
	 */
	public static String getInternalClassName(String className)
	{
		return toObfClassName(className).replace('.', '/');
	}

	/**
	 * Converts a class name to a descriptor, obfuscating the class name if {@link #isObfuscated()}.
	 * @return Linternal/class/name;
	 */
	public static String getDescriptor(String className)
	{
		return "L" + getInternalClassName(className) + ";";
	}

	/**
	 * Processes a descriptor, obfuscating class names if {@link #isObfuscated()}.
	 */
	public static String desc(String deobfDesc)
	{
		if (isObfuscated())
		{
			// for each internal name, replace with the obfuscated version
			Matcher classNameMatcher = Pattern.compile("L([^;]+);").matcher(deobfDesc);
			StringBuffer obfDescBuffer = new StringBuffer(deobfDesc.length());
			while (classNameMatcher.find())
			{
				classNameMatcher.appendReplacement(obfDescBuffer, getDescriptor(classNameMatcher.group(1).replace('/', '.')));
			}
			classNameMatcher.appendTail(obfDescBuffer);
			return obfDescBuffer.toString();
		}
		else
			return deobfDesc;
	}
}

@@ -52,34 +52,8 @@
 on;%0A
-import java.util.HashMap;%0A
 impo
@@ -76,24 +76,24 @@
 ex.Matcher;%0A
+
 import java.
@@ -314,212 +314,8 @@
 ull;
-%0A%09private static HashMap%3CString, String%3E classNameToObfClassNameCache = new HashMap%3CString, String%3E();%0A%09private static HashMap%3CString, String%3E obfClassNameToClassNameCache = new HashMap%3CString, String%3E();
 %0A%0A%09/
@@ -1274,218 +1274,8 @@
 %09%7D%0A%0A
-%09private static void cacheObfClassMapping(String obfClassName, String className)%0A%09%7B%0A%09%09obfClassNameToClassNameCache.put(obfClassName, className);%0A%09%09classNameToObfClassNameCache.put(className, obfClassName);%0A%09%7D%0A%0A
 %09/**
@@ -1682,91 +1682,14 @@
 %7B%0A%09%09
-if (!obfClassNameToClassNameCache.containsKey(obfClassName))%0A%09%09%7B%0A%09%09%09String mapped =
+return
  FML
@@ -1778,188 +1778,8 @@
 ');%0A
-%09%09%09if (!mapped.equals(obfClassName))%0A%09%09%09%09cacheObfClassMapping(mapped, obfClassName);%0A%09%09%09else%0A%09%09%09%09return obfClassName;%0A%09%09%7D%0A%0A%09%09return obfClassNameToClassNameCache.get(obfClassName);%0A
 %09%7D%0A%0A
@@ -2190,95 +2190,14 @@
 %7B%0A%09%09
-if (!classNameToObfClassNameCache.containsKey(deobfClassName))%0A%09%09%7B%0A%09%09%09String unmapped =
+return
  FML
@@ -2290,200 +2290,8 @@
 ');%0A
-%09%09%09if (!unmapped.equals(deobfClassName))%0A%09%09%09%09cacheObfClassMapping(unmapped, deobfClassName);%0A%09%09%09else%0A%09%09%09%09return deobfClassName;%0A%09%09%7D%0A%0A%09%09return classNameToObfClassNameCache.get(deobfClassName);%0A
 %09%7D%0A%0A

Remove buggy caching of obf/deobf class names in ObfHelper * Breaks more than its worth at the moment (was causing null pointer errors in ObfRemappingClassWriter.getCommonSuperClass Class.forName calls)