/*******************************************************************************
 * Copyright (c) 2004, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*


 */
package org.eclipse.jem.internal.proxy.remote;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import org.eclipse.jem.internal.proxy.common.CommandException;
import org.eclipse.jem.internal.proxy.common.remote.*;
import org.eclipse.jem.internal.proxy.core.*;
import org.eclipse.jem.internal.proxy.core.IInvokable;
 

/**
 * Remote Invokable. This invokes on the remote vm, but it doesn't use a method proxy. Instead
 * it sends the information to get the method to the remote vm along with the parms. That way
 * it will do the lookup on the remote vm and we don't have the overhead of two calls, one to
 * get the methodproxy and the other to actually do the invoke. This should typically be used
 * only for infrequent calls so that cache of the method proxy is not needed.
 * 
 * @since 1.0.0
 */
class REMInvokable implements IInvokable {

	protected final IREMBeanTypeProxy beanType;	// Beantype to get method from.
	protected final String methodName;	// Method name
	protected final IBeanTypeProxy[] methodArgTypes;	// Argument types (or null if no arguments).
	protected final REMProxyFactoryRegistry factory;	// Factory for this invokable
	
	
	REMInvokable(IREMBeanTypeProxy beanType, String methodName, IBeanTypeProxy[] methodArgTypes) {
		this.beanType = beanType;
		this.methodName = methodName;
		this.methodArgTypes = methodArgTypes;
		this.factory = (REMProxyFactoryRegistry) beanType.getProxyFactoryRegistry();
	}
	
	public IBeanProxy invoke(IBeanProxy subject) throws ThrowableProxy {
		return invoke(subject, (IBeanProxy[]) null);
	}

	public IBeanProxy invoke(IBeanProxy subject, IBeanProxy[] parms) throws ThrowableProxy {
		return invokeWithParms(subject, parms);
	}

	/**
	 * The internal method that allows mixture of constants (e.g. String) with bean proxies.
	 * A parm value can be a REMMethodProxy.ParmArray. This means this parm is an array that
	 * needs to have a ValueRetriever created for it. This is used if the array has ID's in
	 * it and is not an existing array on the server or all constant values.
	 *
	 * NOTE: It is assumed that all IBeanTypeProxies of the parms have already been retrieved.
	 *       It should still work, but it could be asking for them in the middle of the request
	 *       if they are not first gotton.
	 *
	 */
	public IBeanProxy invokeWithParms(IBeanProxy subject, final Object[] parms) throws ThrowableProxy {
		IREMConnection connect = factory.getFreeConnection();
		REMStandardBeanProxyFactory proxyFactory = (REMStandardBeanProxyFactory) factory.getBeanProxyFactory();
		proxyFactory.startTransaction(); // This is definately a transaction, so start it.
		try {
			// First need to send the method info.
			Commands.ValueObject classValue = new Commands.ValueObject();
			beanType.renderBean(classValue);
			
			Commands.ValueObject parmTypesValue = new Commands.ValueObject();
			if (methodArgTypes != null) {
				class Retriever implements Commands.ValueRetrieve {
					int index = 0;
					Object[] array;
					Commands.ValueObject worker = new Commands.ValueObject();

					public Retriever(Object[] anArray) {
						array = anArray;
					}

					public Commands.ValueObject nextValue() {
						Object parm = array[index++];
						((IREMBeanTypeProxy) parm).renderBean(worker);
						return worker;
					}
				};

				parmTypesValue.setArrayIDS(new Retriever(methodArgTypes), methodArgTypes.length, Commands.CLASS_CLASS); // Create Class[].
			}
			
			
			// Now we start building the actual invocation.
			
			Commands.ValueObject subjectValue = new Commands.ValueObject();
			if (subject != null)
				 ((IREMBeanProxy) subject).renderBean(subjectValue);

			Commands.ValueObject parmsValue = new Commands.ValueObject();

			if (parms != null) {
				// Have a local definition of the retriever so that the retriever can create
				// another one of itself if necessary.
				final IStandardBeanTypeProxyFactory typeFactory = factory.getBeanTypeProxyFactory();

				class Retriever implements Commands.ValueRetrieve {
					int index = 0;
					Object[] array;
					Commands.ValueObject worker = new Commands.ValueObject();

					public Retriever(Object[] anArray) {
						array = anArray;
					}

					public Commands.ValueObject nextValue() {
						Object parm = array[index++];
						if (parm != null)
							if (parm instanceof IREMBeanProxy)
								 ((IREMBeanProxy) parm).renderBean(worker);
							else if (parm instanceof TransmitableArray) {
								// It is another array, create a new retriever.
								worker.setArrayIDS(
									new Retriever(((TransmitableArray) parm).array),
									((TransmitableArray) parm).array.length,
									((TransmitableArray) parm).componentTypeID);
							} else {
								// It's an object. Need to get bean type so that we can send it.
								IREMBeanProxy type = (IREMBeanProxy) typeFactory.getBeanTypeProxy(parm.getClass().getName());
								if (type == null)
									throw new IllegalArgumentException();
								int classID = type.getID().intValue();
								worker.setAsObject(parm, classID);
							}
						else
							worker.set();
						return worker; 
					}
				};

				parmsValue.setArrayIDS(new Retriever(parms), parms.length, Commands.OBJECT_CLASS); // Create Object[].
			}

			Commands.ValueObject returnValue = new Commands.ValueObject();
			try {
				invoke(connect, proxyFactory, classValue, parmTypesValue, subjectValue, parmsValue, returnValue);
				return proxyFactory.getBeanProxy(returnValue);
			} catch (CommandException e) {
				if (!e.isRecoverable()) {
					// Close the connection and try again.
					factory.closeConnection(connect);
					connect = null;
					connect = factory.getFreeConnection();
					try {
						invoke(connect, proxyFactory, classValue, parmTypesValue, subjectValue, parmsValue, returnValue);
						return proxyFactory.getBeanProxy(returnValue);
					} catch (CommandException eAgain) {
						// Failed again. Just close and print trace.
						factory.closeConnection(connect);
						connect = null;
						ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", eAgain)); //$NON-NLS-1$
						return null;
					}
				} else {
					// A recoverable error, print trace and return
					ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", e)); //$NON-NLS-1$
					return null;
				}
			}
		} finally {
			proxyFactory.stopTransaction();
			if (connect != null)
				factory.returnConnection(connect);
		}
	}

private void invoke(
		IREMConnection connect,
		REMStandardBeanProxyFactory proxyFactory,
		Commands.ValueObject classTypeValue,
		Commands.ValueObject parmTypesValue,
		Commands.ValueObject subjectValue,
		Commands.ValueObject parmsValue,
		Commands.ValueObject returnValue)
		throws ThrowableProxy, CommandException {
		try {
			connect.invokeMethod(classTypeValue, methodName, parmTypesValue, subjectValue, parmsValue, returnValue);
		} catch (CommandErrorException e) {
			proxyFactory.processErrorReturn(e);
		}
	}

	public IBeanProxy invoke(IBeanProxy subject, IBeanProxy parm) throws ThrowableProxy {
		return invoke(subject, new IBeanProxy[] { parm });
	}

	public IBeanProxy invokeCatchThrowableExceptions(IBeanProxy subject) {
		try {
			return invoke(subject);
		} catch (ThrowableProxy e) {
			ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", e)); //$NON-NLS-1$
			factory.releaseProxy(e); // Since it's no longer needed, get rid of now instead of GC time.
			return null;
		}
	}

	public IBeanProxy invokeCatchThrowableExceptions(IBeanProxy subject, IBeanProxy[] parms) {
		try {
			return invoke(subject, parms);
		} catch (ThrowableProxy e) {
			ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", e)); //$NON-NLS-1$
			factory.releaseProxy(e); // Since it's no longer needed, get rid of now instead of GC time.
			return null;
		}
	}

	public IBeanProxy invokeCatchThrowableExceptions(IBeanProxy subject, IBeanProxy parm) {
		try {
			return invoke(subject, parm);
		} catch (ThrowableProxy e) {
			ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", e)); //$NON-NLS-1$
			factory.releaseProxy(e); // Since it's no longer needed, get rid of now instead of GC time.
			return null;
		}
	}

}
