package gtPlusPlus.xmod.gregtech.loaders;

import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

import gregtech.api.enums.Materials;
import gregtech.api.enums.OrePrefixes;

import gtPlusPlus.api.objects.Logger;
import gtPlusPlus.api.objects.data.AutoMap;
import gtPlusPlus.core.lib.CORE;
import gtPlusPlus.core.util.Utils;
import gtPlusPlus.core.util.minecraft.MaterialUtils;
import gtPlusPlus.core.util.reflect.ReflectionUtils;

public class GT_Material_Loader {

	private volatile static GT_Material_Loader instance = new GT_Material_Loader();
	private volatile Object mProxyObject;
	private static AutoMap<Materials> mMaterials = new AutoMap<Materials>();
	private static volatile boolean mHasRun = false;

	public synchronized GT_Material_Loader getInstance(){
		return GT_Material_Loader.instance;
	}
	
	public synchronized boolean getRunAbility(){
		return (mHasRun ? false : true);
	}
	public synchronized void setRunAbility(boolean b){
		mHasRun = Utils.invertBoolean(b);
	}

	public GT_Material_Loader() {
		if (getRunAbility()){
		//Set Singleton Instance
		instance = this;				 

		//Try Reflectively add ourselves to the GT loader.
			Class mInterface = ReflectionUtils.getClass("gregtech.api.interfaces.IMaterialHandler");
			if (CORE.MAIN_GREGTECH_5U_EXPERIMENTAL_FORK && mInterface != null){

				//Make this class Dynamically implement IMaterialHandler
				if (mProxyObject == null){
					mProxyObject = Proxy.newProxyInstance(
							mInterface.getClassLoader(), new Class[] { mInterface }, 
							new MaterialHandler(getInstance()));		
				}

				if (ReflectionUtils.invoke(Materials.class, "add", new Class[]{ReflectionUtils.getClass("gregtech.api.interfaces.IMaterialHandler")}, new Object[]{mProxyObject})){
					Logger.REFLECTION("Successfully invoked add, on the proxied object implementing IMaterialHandler.");


					Logger.REFLECTION("Examining Proxy to ensure it implements the correct Interface.");
					Class[] i = mProxyObject.getClass().getInterfaces();
					for (int r=0;r<i.length;r++){
						Logger.REFLECTION("Contains "+i[r].getCanonicalName()+".");
						if (i[r] == mInterface){
							Logger.REFLECTION("Found gregtech.api.interfaces.IMaterialHandler. This Proxy is valid.");
						}
					}
				}
				else {
					Logger.REFLECTION("Failed to invoke add, on the proxied object implementing IMaterialHandler.");
				}
			}	
		//Materials.add(this);
		
		//Stupid shit running twice, I don't think so.
		setRunAbility(false);
		}
	}

	public void onMaterialsInit() {
		Logger.DEBUG_MATERIALS("onMaterialsInit()");
	}

	public void onComponentInit() {
		Logger.DEBUG_MATERIALS("onComponentInit()");
		if (!mMaterials.isEmpty()){
			Logger.DEBUG_MATERIALS("Found "+mMaterials.size()+" materials to re-enable.");
			for (Materials M : mMaterials.values()){
				String name = MaterialUtils.getMaterialName(M);
				Logger.DEBUG_MATERIALS("Trying to enable "+name+".");
				boolean success = tryEnableAllComponentsForMaterial(M);
				if (success){
					Logger.DEBUG_MATERIALS("Success! Enabled "+name+".");
				}
				else {
					Logger.DEBUG_MATERIALS("Failure... Did not enable "+name+".");					
				}
			}
		}
	}

	public void onComponentIteration(Materials aMaterial) {
		Logger.DEBUG_MATERIALS("onComponentIteration()");
	}

	public synchronized boolean enableMaterial(Materials m){
		if (mMaterials.setValue(m)){
			Logger.DEBUG_MATERIALS("Added "+MaterialUtils.getMaterialName(m)+" to internal Map.");
			return true;
		}
		Logger.DEBUG_MATERIALS("Failed to add "+MaterialUtils.getMaterialName(m)+" to internal Map.");
		return false;
	}






	/*
	 * Static internal handler methods
	 */

	private static synchronized boolean tryEnableMaterial(Materials mMaterial){
		if (!CORE.MAIN_GREGTECH_5U_EXPERIMENTAL_FORK){
			return false;
		}			

		boolean value = ReflectionUtils.setField(mMaterial, "mHasParentMod", true);	
		if (value){
			Logger.DEBUG_MATERIALS("Set mHasParentMod true for "+mMaterial.mDefaultLocalName);
		}
		else {
			Logger.DEBUG_MATERIALS("Failed to set mHasParentMod true for "+mMaterial.mDefaultLocalName);
		}
		return value; 
	}

	private static synchronized boolean tryEnableMaterialPart(OrePrefixes prefix, Materials mMaterial){
		if (!CORE.MAIN_GREGTECH_5U_EXPERIMENTAL_FORK){
			return false;
		}		
		try {
			Method enableComponent = ReflectionUtils.getClass("gregtech.api.enums.OrePrefixes").getDeclaredMethod("enableComponent", Materials.class);
			enableComponent.invoke(prefix, mMaterial);
			Logger.DEBUG_MATERIALS("Enabled "+prefix.name()+" for "+mMaterial.mDefaultLocalName+".");
			return true;
		}
		catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException error) {
			Logger.DEBUG_MATERIALS("Failed to enabled "+prefix.name()+" for "+mMaterial.mDefaultLocalName+". Caught "+error.getCause().toString()+".");
			error.printStackTrace();			
		}		
		Logger.DEBUG_MATERIALS("Did not enable "+prefix.name()+" for "+mMaterial.mDefaultLocalName+". Report this error to Alkalus on Github.");
		return false;
	}

	private static synchronized boolean tryEnableAllComponentsForMaterial(Materials material){
		if (!CORE.MAIN_GREGTECH_5U_EXPERIMENTAL_FORK){
			return false;
		}		
		try {
			tryEnableMaterial(material);
			int mValid = 0;
			for(OrePrefixes ore:OrePrefixes.values()){
				if (tryEnableMaterialPart(ore, material)){
					mValid++;
				}
			}
			if (mValid > 0){
				Logger.DEBUG_MATERIALS("Success - Re-enabled all components for "+MaterialUtils.getMaterialName(material));
			}
			else {
				Logger.DEBUG_MATERIALS("Failure - Did not enable any components for "+MaterialUtils.getMaterialName(material));
			}
			return mValid > 0;			
		}
		catch (SecurityException | IllegalArgumentException e) {
			Logger.DEBUG_MATERIALS("Total Failure - Unable to re-enable "+MaterialUtils.getMaterialName(material)+". Most likely an IllegalArgumentException, but small chance it's a SecurityException.");
			return false;
		}		
	}









	/**
	 * Special Dynamic Interface Class
	 */

	public class MaterialHandler implements InvocationHandler {

		private final Map<String, Method> methods = new HashMap<String, Method>();
		private Object target;

		public MaterialHandler(Object target) {
			Logger.REFLECTION("Created a Proxy Interface which implements IMaterialHandler.");
			this.target = target;	 
			for(Method method: target.getClass().getDeclaredMethods()) {
				Logger.REFLECTION("Adding "+method.getName()+" to internal method map.");
				this.methods.put(method.getName(), method);
			}
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) 
				throws Throwable {
			long start = System.nanoTime();
			Object result = methods.get(method.getName()).invoke(target, args);
			long elapsed = System.nanoTime() - start;	 
			Logger.INFO("[Debug] Executed "+method.getName()+" in "+elapsed+" ns");	 
			return result;
		}
	}


	/*
	public static class ProxyListener implements java.lang.reflect.InvocationHandler {		

		public static Object IMaterialHandlerProxy;

		ProxyListener(){

			Logger.REFLECTION("Failed setting IMaterialHandler Proxy instance.");
		}

		//Loading the class at runtime
		public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException {
		    Class<?> someInterface = ReflectionUtils.getClass("gregtech.api.interfaces.IMaterialHandler");
		    Object instance = Proxy.newProxyInstance(someInterface.getClassLoader(), new Class<?>[]{someInterface}, new InvocationHandler() {

		        @Override
		        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		            //Handle the invocations
		            if(method.getName().equals("onMaterialsInit")){
		            	Logger.REFLECTION("Invoked onMaterialsInit() via IMaterialHandler proxy");
		                return 1;
		            }
		            else if(method.getName().equals("onComponentInit")){
		            	Logger.REFLECTION("Invoked onComponentInit() via IMaterialHandler proxy");
		                return 2;
		            }
		            else if(method.getName().equals("onComponentIteration")){
		            	Logger.REFLECTION("Invoked onComponentIteration() via IMaterialHandler proxy");
		                return 3;
		            }
		            else {
		            	return -1;
		            }
		        }
		    }); 
		    System.out.println(instance.getClass().getDeclaredMethod("someMethod", (Class<?>[])null).invoke(instance, new Object[]{}));
		}

		private static class MaterialHandler implements InvocationHandler {
	        private final Object original;

	        public MaterialHandler(Object original) {
	            this.original = original;
	        }

	        @Override
			public Object invoke(Object proxy, Method method, Object[] args)
	                throws IllegalAccessException, IllegalArgumentException,
	                InvocationTargetException {
	            System.out.println("BEFORE");
	            method.invoke(original, args);
	            System.out.println("AFTER");
	            return null;
	        }
	    }

	    public static void init(){ 

	    	Class<?> someInterface = ReflectionUtils.getClass("gregtech.api.interfaces.IMaterialHandler");		   
	        GT_Material_Loader original = GT_Material_Loader.instance;
	        MaterialHandler handler = new MaterialHandler(original);

	        Object f = Proxy.newProxyInstance(someInterface.getClassLoader(),
	                new Class[] { someInterface },
	                handler);

	        f.originalMethod("Hallo");
	    }



	}

	 */
}