/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

package org.teiid.translator.coherence.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.teiid.core.util.ReflectionHelper;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.coherence.CoherencePlugin;

/**
 * ObjectSourceMethodManager caches the methods for the classes that
 * are being utilized, and provides utility methods for finding a method
 * and executing.
 */
public class ObjectSourceMethodManager {

	private static ObjectSourceMethodManager osmm = new ObjectSourceMethodManager();

	private Map<Class<?>, Method[]> classToMethodMap = new ConcurrentHashMap<Class<?>, Method[]>();

	public ObjectSourceMethodManager() {

	}

	/**
	 * Call to execute the method
	 * 
	 * @param m
	 *            is the method to execute
	 * @param api
	 *            is the object to execute the method on
	 * @param parms
	 *            are the parameters to pass when the method is executed
	 * @return Object return value
	 * @throws Exception
	 */
	public static Object executeMethod(Method m, Object api, Object[] parms)
			throws TranslatorException {
		try {
			return m.invoke(api, parms);
		} catch (InvocationTargetException x) {
			x.printStackTrace();
			Throwable cause = x.getCause();
			System.err.format("invocation of %s failed: %s%n",
					"set" + m.getName(), cause.getMessage());
			LogManager.logError(LogConstants.CTX_CONNECTOR, "Error calling "
					+ m.getName() + ":" + cause.getMessage());
			throw new TranslatorException(x.getMessage());
		} catch (Exception e) {
			e.printStackTrace();
			throw new TranslatorException(e.getMessage());
		}
	}

	/**
	 * Call to execute the method
	 * 
	 * @param m
	 *            is the method to execute
	 * @param api
	 *            is the object to execute the method on
	 * @param parms
	 *            are the parameters to pass when the method is executed
	 * @return Object return value
	 * @throws Exception
	 */
	public static Object executeMethod(Method m, Object api, List parms)
			throws TranslatorException {
		try {
			return m.invoke(api, parms.toArray());
		} catch (InvocationTargetException x) {
			x.printStackTrace();
			Throwable cause = x.getCause();
			System.err.format("invocation of %s failed: %s%n",
					"set" + m.getName(), cause.getMessage());
			LogManager.logError(LogConstants.CTX_CONNECTOR, "Error calling "
					+ m.getName() + ":" + cause.getMessage());
			throw new TranslatorException(x.getMessage());
		} catch (Exception e) {
			e.printStackTrace();
			throw new TranslatorException(e.getMessage());
		}
	}

	public static Object createObject(String objectClassName)
			throws TranslatorException {
		try {

			return ReflectionHelper.create(objectClassName, null, null);
		} catch (Exception e1) {
			throw new TranslatorException(e1);
		}

	}

	public static Object getValue(String methodName, Object object)
			throws TranslatorException {
		try {
			final Method m = osmm
					.getMethod(object.getClass(), methodName, null);

			return m.invoke(object, (Object[]) null);
		} catch (TranslatorException t) {
			throw t;
		} catch (InvocationTargetException x) {
			Throwable cause = x.getCause();
			System.err.format("invocation of %s failed: %s%n", "get"
					+ methodName, cause.getMessage());
			LogManager.logError(LogConstants.CTX_CONNECTOR, "Error calling get"
					+ methodName + ":" + cause.getMessage());
			throw new TranslatorException(x.getMessage());
		} catch (Exception e) {
			e.printStackTrace();
			throw new TranslatorException(e.getMessage());
		}

	}

	public static Method getMethod(Class api, String methodName, List argumentsClasses)
			throws TranslatorException {
		
		try {
			Method[] namedMethods = findMethods(methodName, getMethodsFromAPI(api));
			if (namedMethods != null && namedMethods.length == 1) {
				return namedMethods[0];
			}
	
			Method m = findBestMethod(api, methodName, argumentsClasses);
	
			if (m == null) {
				throw new NoSuchMethodException(
						CoherencePlugin.Util
								.getString(
										"ObjectSourceMethodManager.No_method_implemented_for", new Object[] {methodName, api.getName()})); //$NON-NLS-1$           
			}
			return m;
		
		} catch (NoSuchMethodException nsme) {
			String msg = CoherencePlugin.Util
			.getString(
					"ObjectSourceMethodManager.No_method_implemented_for", new Object[] {methodName, api.getName()});
			throw new TranslatorException(msg); //$NON-NLS-1$           
		} catch (Exception e) {
	        final String msg = CoherencePlugin.Util.getString("ObjectSourceMethodManager.No_method_implemented_for", new Object[] {methodName, api.getName()}); //$NON-NLS-1$
			throw new TranslatorException(msg);	
		}		
	}

	private static Method[] findMethods(String methodName, Method[] methods) {
		if (methods == null || methods.length == 0) {
			return null;
		}
		final ArrayList result = new ArrayList();
		for (int i = 0; i < methods.length; i++) {
			if (methods[i].getName().equals(methodName)) {
				result.add(methods[i]);
			}
		}
		return (Method[]) result.toArray(new Method[result.size()]);
	}

	private static synchronized Method[] getMethodsFromAPI(Class api)
			throws SecurityException {
		Method[] ms = osmm.classToMethodMap.get(api);
		if (ms != null) {
			return ms;
		}
		Method[] methods = api.getMethods();
		osmm.classToMethodMap.put(api, methods);
		return methods;
	}
	/**
	 * Call to find the best method on a class, by passing the 
	 * cache
	 * @param objectClass
	 * @param methodName
	 * @param argumentsClasses
	 * @return
	 * @throws TranslatorException
	 */
	private static Method findBestMethod(Class objectClass, String methodName,
			List argumentsClasses) throws SecurityException, NoSuchMethodException {
		
		ReflectionHelper rh = new ReflectionHelper(objectClass);

		if (argumentsClasses == null) {
			argumentsClasses = Collections.EMPTY_LIST;
		}
		return rh.findBestMethodWithSignature(methodName, argumentsClasses);

	}

}
