package com.teamwizardry.wizardry.api.spell.module;

import com.teamwizardry.wizardry.api.spell.SpellRing;
import com.teamwizardry.wizardry.api.spell.annotation.ContextRing;
import com.teamwizardry.wizardry.api.spell.annotation.ContextSuper;
import com.teamwizardry.wizardry.api.spell.annotation.ModuleOverride;
import com.teamwizardry.wizardry.api.spell.annotation.ModuleOverrideInterface;
import com.teamwizardry.wizardry.api.spell.module.ModuleRegistry.OverrideDefaultMethod;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.WrongMethodTypeException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;

/**
 * A handler to call overwritten methods within a spell chain using a consumer interface.
 *
 * @author Avatair
 */
@SuppressWarnings("Duplicates")
public class ModuleOverrideHandler {

	private HashMap<String, OverridePointer> overridePointers = new HashMap<>();
	private HashMap<String, Object> cachedProxies = new HashMap<>();
	private final SpellRing spellChain;

	/**
	 * The constructor. Called only within {@link SpellRing#getOverrideHandler}.
	 *
	 * @param spellChain the spell chain head element owning this handler.
	 * @throws ModuleOverrideException if some incompatible method signatures have been found.
	 */
	public ModuleOverrideHandler(SpellRing spellChain) throws ModuleOverrideException {
		this.spellChain = spellChain;
		if (spellChain.getParentRing() != null)
			throw new IllegalArgumentException("passed spellRing is not a root.");

		// Apply default overrides
		for (OverrideDefaultMethod methodEntry : ModuleRegistry.INSTANCE.getDefaultOverrides().values())
			applyDefaultOverride(methodEntry);

		// Apply overrides from spell chain
		SpellRing[] spellSequence = getSequenceFromSpellChain(spellChain);
		for (SpellRing curRing : spellSequence)
			applyModuleOverrides(curRing);
	}

	/**
	 * Retrieves an array of spell chain elements ordered by their occurrence in the chain,
	 * which implement at least one override.
	 *
	 * @param spellRing the head element of the spell chain.
	 * @return the array containing all elements having an override.
	 */
	private static SpellRing[] getSequenceFromSpellChain(SpellRing spellRing) {
		SpellRing cur = spellRing;
		LinkedList<SpellRing> instances = new LinkedList<>();
		while (cur != null) {
			ModuleInstance module = cur.getModule();
			if (module == null) continue;

			if (module.getFactory().hasOverrides()) {
				instances.add(cur);
			}
			cur = cur.getChildRing();
		}

		return instances.toArray(new SpellRing[instances.size()]);
	}

	/**
	 * Returns whether the second method has a compatible signature to override the base method.
	 * Special parameters, annotated with {@link ContextRing} or {@link ContextSuper} are ignored.
	 *
	 * @param baseMtd     the first method being overridden or the interface method.
	 * @param overrideMtd the second method overriding the first method.
	 * @return <code>true</code> iff yes.
	 */
	private static boolean areMethodsCompatible(Method baseMtd, Method overrideMtd) {
		// WARNING: Update this method, if language conventions in java change.

		// Check compatibility of return types
		Class<?> baseReturnType = baseMtd.getReturnType();
		Class<?> overrideReturnType = overrideMtd.getReturnType();
		if (baseReturnType == null) {
			if (overrideReturnType != null)
				return false;
		} else {
			if (overrideReturnType == null)
				return false;
			if (!baseReturnType.isAssignableFrom(overrideReturnType))
				return false;
		}

		// Check compatibility of parameters
		Parameter[] baseParams = baseMtd.getParameters();
		Parameter[] overrideParams = overrideMtd.getParameters();

		int i = 0, j = 0;
		while (i < baseParams.length || j < baseParams.length) {
			if (i >= baseParams.length) {
				while (j < overrideParams.length) {
					Parameter overrideParam = overrideParams[j];
					if (!isExtraParameter(overrideParam))
						return false;    // Unmappable extra parameter.
					j++;
				}
				break;
			}

			if (j >= overrideParams.length) {
				while (i < overrideParams.length) {
					Parameter baseParam = baseParams[i];
					if (!isExtraParameter(baseParam))
						return false;    // Unmappable extra parameter.
					i++;
				}
				break;
			}

			//
			Parameter baseParam = baseParams[i];
			if (isExtraParameter(baseParam)) {
				i++;
				continue;    // Ignore parameters taking values from context
			}

			Parameter overrideParam = overrideParams[j];
			if (isExtraParameter(overrideParam)) {
				j++;
				continue;    // Ignore parameters taking values from context
			}

			if (!baseParam.getType().isAssignableFrom(overrideParam.getType()))
				return false;

			i++;
			j++;
		}

		// Check compatibility of exceptions
		Class<?>[] baseExcps = baseMtd.getExceptionTypes();
		Class<?>[] overrideExcps = overrideMtd.getExceptionTypes();

		// For every checked exception at the interface method
		// there should exist an exception type at base
		// which is assignable from the interface method exception
		for (Class<?> overrideExcp : overrideExcps) {
			if (RuntimeException.class.isAssignableFrom(overrideExcp))
				continue;

			boolean found = false;
			for (Class<?> baseExcp : baseExcps) {
				if (RuntimeException.class.isAssignableFrom(baseExcp))
					continue;
				if (baseExcp.isAssignableFrom(overrideExcp)) {
					found = true;
					break;
				}
			}
			if (!found)
				return false;
		}

		return true;
	}

	/**
	 * Returns whether a parameter is a special one, taking values from the given caller context.
	 *
	 * @param param the parameter.
	 * @return <code>true</code> iff yes.
	 */
	private static boolean isExtraParameter(Parameter param) {
		return param.isAnnotationPresent(ContextRing.class) ||
				param.isAnnotationPresent(ContextSuper.class);
	}

	/**
	 * Returns a list of override methods from a given type, implementing them.
	 *
	 * @param clazz      the type.
	 * @param hasContext if <code>true</code> then the method may contain context parameters.
	 * @return a collection, mapping override names to their according methods.
	 * @throws ModuleInitException if a method exists having invalid argument types or if some issues with reflection occurred.
	 */
	static HashMap<String, OverrideMethod> getOverrideMethodsFromClass(Class<?> clazz, boolean hasContext) throws ModuleInitException {
		HashMap<String, OverrideMethod> overridableMethods = new HashMap<>();
		// Determine overriden methods via reflection
		// FIXME: Separation of concerns: Overridable methods are not part of factory. Move them or rename factory class appropriately.
		for (Method method : clazz.getMethods()) {
			ModuleOverride ovrd = method.getDeclaredAnnotation(ModuleOverride.class);
			if (ovrd == null)
				continue;

			if (overridableMethods.containsKey(ovrd.value()))
				throw new ModuleInitException("Multiple methods exist in class '" + clazz + "' with same override name '" + ovrd.value() + "'.");

			try {
				method.setAccessible(true);
			} catch (SecurityException e) {
				throw new ModuleInitException("Failed to aquire reflection access to method '" + method.toString() + "', annotated by @ModuleOverride.", e);
			}

			// Search for context parameters
			int idxContextParamRing = -1;
			int idxContextParamSuper = -2;
			Parameter[] params = method.getParameters();
			for (int i = 0; i < params.length; i++) {
				Parameter param = params[i];
				if (param.isAnnotationPresent(ContextRing.class)) {
					if (idxContextParamRing >= 0)
						throw new ModuleInitException("Method '" + method.toString() + "' has invalid @ContextRing annotated parameter. It is not allowed on multiple parameters.");
					idxContextParamRing = i;
				}
				if (param.isAnnotationPresent(ContextSuper.class)) {
					if (idxContextParamSuper >= 0)
						throw new ModuleInitException("Method '" + method.toString() + "' has invalid @ContextSuper annotated parameter. It is not allowed on multiple parameters.");
					idxContextParamSuper = i;
				}
			}

			if (!hasContext) {
				if (idxContextParamRing >= 0 || idxContextParamSuper >= 0)
					throw new ModuleInitException("Context parameters are not allowed.");
			}

			if (idxContextParamRing == idxContextParamSuper)
				throw new ModuleInitException("Method '" + method.toString() + "' has a parameter which is annotated with multiple roles.");

			OverrideMethod ovrdMethod = new OverrideMethod(method, idxContextParamRing, idxContextParamSuper);
			overridableMethods.put(ovrd.value(), ovrdMethod);
		}

		return overridableMethods;
	}

	/////////////////

	/**
	 * Returns a list of override methods from a given interface type.
	 *
	 * @param clazz the interface type to retrieve methods from.
	 * @return a collection, mapping override names to their according methods.
	 * @throws ModuleOverrideException if some issues with the reflection occurred.
	 */
	public static Map<String, Method> getInterfaceMethods(Class<?> clazz) throws ModuleOverrideException {
		HashMap<String, Method> overridableMethods = new HashMap<>();

		for (Method method : clazz.getMethods()) {
			ModuleOverrideInterface ovrd = method.getDeclaredAnnotation(ModuleOverrideInterface.class);
			if (ovrd == null)
				continue;

			if (overridableMethods.containsKey(ovrd.value()))
				throw new ModuleOverrideException("Multiple methods exist in class '" + clazz + "' with same override name '" + ovrd.value() + "'.");

			try {
				method.setAccessible(true);
			} catch (SecurityException e) {
				throw new ModuleOverrideException("Failed to aquire reflection access to method '" + method.toString() + "', annotated by @ModuleOverrideInterface.", e);
			}

			overridableMethods.put(ovrd.value(), method);
		}

		return overridableMethods;
	}

	/**
	 * Returns an object implementing the given consumer interface class. Invoking methods from it will invoke
	 * override methods accordingly.
	 *
	 * @param interfaceClass the interface type.
	 * @return an object implementing the passed interface type.
	 * @throws ModuleOverrideException if the passed interface type has at least one override method with incompatible signature.
	 */
	@SuppressWarnings("unchecked")
	public synchronized <T> T getConsumerInterface(Class<T> interfaceClass) throws ModuleOverrideException {
		String className = interfaceClass.getName();
		Object obj = cachedProxies.get(className);
		if (obj == null) {
			T newProxy = createConsumerInterface(interfaceClass);
			cachedProxies.put(className, newProxy);
			return newProxy;
		}

		// check for interface compatibility
		if (!interfaceClass.isInstance(obj))
			throw new IllegalStateException("Incompatible interface class with matching name. Class loader different?");
		return (T) obj;
	}

	/**
	 * Allocates a new object for the consumer interface. <br/>
	 * <b>NOTE</b>: This method should not be called directly, use {@link #getConsumerInterface} instead.
	 *
	 * @param interfaceClass the interface type.
	 * @return an object implementing the passed interface type.
	 * @throws ModuleOverrideException if the passed interface type has at least one override method with incompatible signature.
	 */
	private <T> T createConsumerInterface(Class<T> interfaceClass) throws ModuleOverrideException {
		// Retrieve all overridable methods and check them for compatibility with base class
		Map<String, Method> overridableMethods = getInterfaceMethods(interfaceClass);

		// Create invocation handler. All interface methods are mapped to their base method pendants
		OverrideInvoker invocationHandler = new OverrideInvoker(overridableMethods, interfaceClass.getName());

		//
		ClassLoader myClassLoader = getClass().getClassLoader();    // Inherit class loader from this class
		Class<?>[] proxyInterfaces = new Class<?>[]{interfaceClass};

		@SuppressWarnings("unchecked")
		T proxy = (T) Proxy.newProxyInstance(myClassLoader, proxyInterfaces, invocationHandler);

		return proxy;
	}

	/**
	 * Applies a new spell ring on the override stack. <br/>
	 * <b>NOTE</b>: Is called in the order of the spell chain elements, starting with the head element.
	 *
	 * @param spellRing the actual spell chain element.
	 * @throws ModuleOverrideException if some incompatible method signatures have been found.
	 */
	private void applyModuleOverrides(SpellRing spellRing) throws ModuleOverrideException {
		ModuleInstance module = spellRing.getModule();
		if (module == null) return;

		Map<String, OverrideMethod> overrides = module.getFactory().getOverrides();

		for (Entry<String, OverrideMethod> entry : overrides.entrySet()) {
			OverridePointer ptr = overridePointers.get(entry.getKey());
			if (ptr == null) {
				ptr = new OverridePointer(spellRing, null, entry.getKey(), entry.getValue());
			} else {
				if (!areMethodsCompatible(ptr.getBaseMethod().getMethod(), entry.getValue().getMethod()))
					throw new ModuleOverrideException("Method '" + ptr.getClass().getName() + "." + ptr.getBaseMethod().getMethod().getName() + "' can't be overridden by '" + entry.getValue().getClass().getName() + "." + entry.getValue().getMethod().getName() + "' due to incompatible signature.");
				ptr = new OverridePointer(spellRing, ptr, entry.getKey(), entry.getValue());
			}

			overridePointers.put(entry.getKey(), ptr);
		}
	}

	/**
	 * Applies a default override method to the override stack. <br/>
	 * <b>NOTE</b>: Is called before any spell chain element is processed.
	 *
	 * @param methodEntry the default method.
	 */
	private void applyDefaultOverride(OverrideDefaultMethod methodEntry) {
		if (overridePointers.containsKey(methodEntry.getOverrideName()))
			throw new IllegalStateException("Duplicate override found.");    // Should not happen, as duplication cases are catched in ModuleRegistry.registerOverrideDefaults()
		OverridePointer ptr = new OverridePointer(methodEntry);
		overridePointers.put(methodEntry.getOverrideName(), ptr);
	}

	/////////////////

	/**
	 * A pointer object pointing to a discovered override implementation in the context of the spell chain.
	 *
	 * @author Avatair
	 */
	static class OverridePointer {
		private final Object object;
		private final SpellRing spellRingWithOverride;
		private final String overrideName;
		private final OverrideMethod baseMethod;
		private final OverridePointer prev;

		/**
		 * The constructor which is called in case of a spell module override implementation.
		 *
		 * @param spellRingWithOverride the ring referring to the module containing the override implementation.
		 * @param prev                  the overridden method or <code>null</code> if no such method exists. Is used to refer to a super method.
		 * @param overrideName          the override name.
		 * @param baseMethod            the override implementation.
		 */
		OverridePointer(SpellRing spellRingWithOverride, OverridePointer prev, String overrideName, OverrideMethod baseMethod) {
			this.spellRingWithOverride = spellRingWithOverride;
			this.baseMethod = baseMethod;
			this.overrideName = overrideName;
			this.prev = prev;
			this.object = getModule().getModuleClass();
		}

		/**
		 * The constructor which is called in case of a default override implementation.
		 *
		 * @param methodEntry the reference to the default implementation.
		 */
		public OverridePointer(OverrideDefaultMethod methodEntry) {
			this.spellRingWithOverride = null;
			this.baseMethod = methodEntry.getMethod();
			this.overrideName = methodEntry.getOverrideName();
			this.prev = null;
			this.object = methodEntry.getObj();
		}

		/**
		 * Returns the spell chain element referring to a module containing the override implementation.
		 *
		 * @return the spell chain element.
		 */
		SpellRing getSpellRingWithOverride() {
			return spellRingWithOverride;
		}

		/**
		 * Returns the override method implementation reference.
		 *
		 * @return the override method reference.
		 */
		OverrideMethod getBaseMethod() {
			return this.baseMethod;
		}

		/**
		 * Returns the module implementing the override method in case this pointer points to a spell chain.
		 *
		 * @return the module or <code>null</code> if the implementation is a default implementation.
		 */
		ModuleInstance getModule() {
			if (spellRingWithOverride == null)
				return null;
			return spellRingWithOverride.getModule();
		}

		/**
		 * Returns the pointer referring to an implementation which got immediately overridden
		 * or <code>null</code> if no such method exists.
		 *
		 * @return the override pointer for the previous method.
		 */
		OverridePointer getPrev() {
			return prev;
		}

		/**
		 * Returns the override name, as declared in the {@link ModuleOverrideHandler} or {@link ModuleOverrideInterface} annotation.
		 *
		 * @return the override name.
		 */
		String getOverrideName() {
			return overrideName;
		}

		/**
		 * Invokes the override implementation.
		 *
		 * @param args passed arguments
		 * @return the return value from the override call.
		 * @throws Throwable any occurred exception thrown by the override implementation or by the Java Method Handler.
		 */
		Object invoke(Object[] args) throws Throwable {
			int idxContextParamRing = baseMethod.getIdxContextParamRing();
			int idxContextParamSuper = baseMethod.getIdxContextParamSuper();

			Object passedArgs[] = args;
			int countExtra = 1;
			if (idxContextParamRing >= 0)
				countExtra++;
			if (idxContextParamSuper >= 0)
				countExtra++;

			// Add extra arguments like this pointer a.s.o.
			passedArgs = new Object[args.length + countExtra];
			int i = 0;
			int j = 0;
			while (i < passedArgs.length) {
				if (i == 0) {
					passedArgs[i] = object;
				} else if (i == idxContextParamRing + 1) {
					passedArgs[i] = spellRingWithOverride;
				} else if (i == idxContextParamSuper + 1) {
					passedArgs[i] = new ModuleOverrideSuper(prev);
				} else {
					passedArgs[i] = args[j];
					j++;
				}
				i++;
			}

			try {
				return baseMethod.getMethodHandle().invokeWithArguments(passedArgs);
			} catch (WrongMethodTypeException | ClassCastException e) {
				// NOTE: If this happens, then correctness of checks like "areMethodsCompatible()" a.s.o. need to be debugged.
				throw new IllegalStateException("Couldn't invoke call. See cause.", e);
			}
		}
	}

	////////////////////////

	/**
	 * An object represents a linkage of a interface method to its corresponding override pointer.
	 *
	 * @author Avatair
	 */
	private static class OverrideInterfaceMethod {
		private final OverridePointer overridePointer;
		private final Method interfaceMethod;

		/**
		 * The constructor.
		 *
		 * @param overridePointer the given override pointer.
		 * @param interfaceMethod the linked interface method.
		 */
		OverrideInterfaceMethod(OverridePointer overridePointer, Method interfaceMethod) {
			super();
			this.overridePointer = overridePointer;
			this.interfaceMethod = interfaceMethod;
		}

		/**
		 * Returns the associated override pointer.
		 *
		 * @return the override pointer.
		 */
		OverridePointer getOverridePointer() {
			return overridePointer;
		}

		/**
		 * Returns the associated override interface method.
		 *
		 * @return the interface method.
		 */
		Method getInterfaceMethod() {
			return interfaceMethod;
		}

		/**
		 * Returns the key to be used to retrieve this object from a {@link HashMap}.
		 *
		 * @return the key.
		 */
		String getKey() {
			return interfaceMethod.getName();
		}
	}

	/**
	 * A method registry entry for an override implementation. Can be either an implementation on a module
	 * or a default implementation.
	 *
	 * @author Avatair
	 */
	static class OverrideMethod {
		private final Method method;
		private final MethodHandle methodHandle;
		private final int idxContextParamRing;
		private final int idxContextParamSuper;

		/**
		 * The constructor. Is called from {@link ModuleOverrideHandler#getOverrideMethodsFromClass}.
		 *
		 * @param method               the override implementation method.
		 * @param idxContextParamRing  index of the parameter containing the actual spell chain element containing the override.
		 *                             Is negative if no such parameter exists.
		 * @param idxContextParamSuper index of the parameter containing the actual spell chain element containing the override.
		 *                             Is negative if no such parameter exists.
		 * @throws ModuleInitException
		 */
		OverrideMethod(Method method, int idxContextParamRing, int idxContextParamSuper) throws ModuleInitException {
			super();

			try {
				this.method = method;
				this.methodHandle = MethodHandles.lookup().unreflect(method);

				// NOTE: Parameter indices should be less or equal to -2 if missing to make ModuleOverrideHandler.OverridePointer.invoke work correctly
				this.idxContextParamRing = idxContextParamRing >= 0 ? idxContextParamRing : -2;
				this.idxContextParamSuper = idxContextParamSuper >= 0 ? idxContextParamSuper : -2;
			} catch (Exception e) {
				throw new ModuleInitException("Couldn't initialize override method binding. See cause.", e);
			}
		}

		/**
		 * Returns the implementation method.
		 *
		 * @return the implementation method.
		 */
		Method getMethod() {
			return method;
		}

		/**
		 * Returns the method handle pointing to the implementation method. Is used for invocation at {@link OverridePointer#invoke}.
		 *
		 * @return the method handle.
		 */
		MethodHandle getMethodHandle() {
			return methodHandle;
		}

		/**
		 * Returns the index of the parameter for the referenced ring if existing.
		 *
		 * @return the index of the parameter for the referenced ring or a negative value if no such parameter exists.
		 */
		int getIdxContextParamRing() {
			return idxContextParamRing;
		}

		/**
		 * Returns the index of the parameter for the super method handler if existing.
		 *
		 * @return the index of the parameter for the super method handler or a negative value if no such parameter exists.
		 */
		int getIdxContextParamSuper() {
			return idxContextParamSuper;
		}
	}

	/**
	 * An invocation handler for the reflection PROXY object implementing a consumer interface.
	 *
	 * @author Avatair
	 */
	private class OverrideInvoker implements InvocationHandler {
		private final HashMap<String, OverrideInterfaceMethod> callMap = new HashMap<>();
		private final String displayedInterfaceName;

		/**
		 * The constructor.
		 *
		 * @param interfaceMethods       a map containing all interface methods.
		 * @param displayedInterfaceName name of the interface. Is used only to return messages for thrown exceptions.
		 * @throws ModuleOverrideException if some method has an incompatible signature.
		 */
		public OverrideInvoker(Map<String, Method> interfaceMethods, String displayedInterfaceName) throws ModuleOverrideException {
			this.displayedInterfaceName = displayedInterfaceName;

			for (Entry<String, Method> interfaceMethod : interfaceMethods.entrySet()) {
				OverridePointer ptr = overridePointers.get(interfaceMethod.getKey());
				if (ptr == null)
					continue;    // Ignore unmapped methods. invoke() will throw a proper exception on attempt to call them.
				if (!areMethodsCompatible(interfaceMethod.getValue(), ptr.getBaseMethod().getMethod()))
					throw new ModuleOverrideException("Interface method signature of '" + interfaceMethod.getValue() + "' is incompatible with '" + ptr.getBaseMethod().getMethod() + "'.");

				OverrideInterfaceMethod intfMethodEntry = new OverrideInterfaceMethod(ptr, interfaceMethod.getValue());
				callMap.put(intfMethodEntry.getKey(), intfMethodEntry);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			String name = method.getName();
			OverrideInterfaceMethod intfMethod = callMap.get(name);
			if (intfMethod == null) {
				// Check for default methods
				// NOTE: Hopefully monitor method calls are not redirected to this invoke handler.
				if (isMethodToString(method)) {
					return "Proxy for '" + displayedInterfaceName + "' on " + spellChain;
				} else if (isMethodHashCode(method)) {
					return 0;
				} else if (isMethodEquals(method)) {
					return proxy == args[0];
				} else if (isMethodClone(method)) {
					throw new CloneNotSupportedException("Override handler has no clone ability.");
				} else {
					// Nothing found. Throw an exception.
					ModuleOverrideInterface annot = method.getDeclaredAnnotation(ModuleOverrideInterface.class);
					if (annot != null)
						throw new UnsupportedOperationException("Override method for '" + annot.value() + "' invoke via '" + method + "' is not implemented or not public.");
					else
						throw new UnsupportedOperationException("Method '" + method + "' is not an override. Annotation @ModuleOverrideInterface must be supplied.");
				}
			}

			OverridePointer ptr = intfMethod.getOverridePointer();
			return ptr.invoke(args);
		}

		/**
		 * Returns whether the method is {@link Object#hashCode}.
		 *
		 * @param method the method to test.
		 * @return <code>true</code> iff yes.
		 */
		private boolean isMethodHashCode(Method method) {
			// TODO: Move to utils.
			if (method == null)
				return false;

			if (method.getParameterCount() != 0)
				return false;
			if (!int.class.equals(method.getReturnType()))
				return false;
			return "hashCode".equals(method.getName());
		}

		/**
		 * Returns whether the method is {@link Object#equals}.
		 *
		 * @param method the method to test.
		 * @return <code>true</code> iff yes.
		 */
		private boolean isMethodEquals(Method method) {
			// TODO: Move to utils.
			if (method == null)
				return false;

			if (method.getParameterCount() != 1)
				return false;
			if (!method.getParameterTypes()[0].equals(Object.class))
				return false;
			if (!boolean.class.equals(method.getReturnType()))
				return false;
			return "equals".equals(method.getName());
		}

		/**
		 * Returns whether the method is {@link Object#toString}.
		 *
		 * @param method the method to test.
		 * @return <code>true</code> iff yes.
		 */
		private boolean isMethodToString(Method method) {
			// TODO: Move to utils.
			if (method == null)
				return false;

			if (method.getParameterCount() != 0)
				return false;
			if (!String.class.equals(method.getReturnType()))
				return false;
			return "toString".equals(method.getName());
		}

		/**
		 * Returns whether the method is {@link Object#clone}.
		 *
		 * @param method the method to test.
		 * @return <code>true</code> iff yes.
		 */
		private boolean isMethodClone(Method method) {
			// TODO: Move to utils.
			if (method == null)
				return false;

			if (method.getParameterCount() != 0)
				return false;
			if (!Object.class.equals(method.getReturnType()))
				return false;
			return "clone".equals(method.getName());
		}
	}
}