/*******************************************************************************
 * 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.common.remote;

import java.io.*;

import org.eclipse.jem.internal.proxy.common.CommandException;

 
/**
 * 
 * @since 1.0.0
 */
public class ExpressionCommands {

	// These are the sub-commands under EXPRESSION_TREE_COMMANDS that can be send.
	public static final byte
		START_EXPRESSION_TREE_PROCESSING = 0,
		PUSH_EXPRESSION = 1,
		END_EXPRESSION_TREE_PROCESSING = 2,
		SYNC_REQUEST = 3,
		PULL_VALUE_REQUEST = 4,
		TRANSFER_EXPRESSION_REQUEST = 5,
		RESUME_EXPRESSION_REQUEST = 6;
		
	// These are the expression specific error codes (it can also send back general ones. See SYNC_REQUEST docs lower down).
	public static final int
		EXPRESSION_NOEXPRESSIONVALUE_EXCEPTION = Commands.MAX_ERROR_CODE+1;	// No expression value occurred.
	
	// These are the flag values sent in proxy resolution when doesn't resolve to proxy.
	public static final int
		EXPRESSIONPROXY_VOIDTYPE = 0,	// Expression proxy resolves to void type.
		EXPRESSIONPROXY_NOTRESOLVED = 1;	// Expression proxy not resolved.
	
	// These are the trace values sent in START_EXPRESSION_TREE_PROCESSING
	public static final byte
		TRACE_DEFAULT = -1,
		TRACE_OFF = 0,
		TRACE_ON = 1;

	public static final String EXPRESSIONTRACE = "proxyvm.expressionTrace";	// The system property for turning on expression tracing. //$NON-NLS-1$
	public static final String EXPRESSIONTRACE_TIMER_THRESHOLD = "proxyvm.expressionTraceTimerThreshold";	// The system property for timer threshold. //$NON-NLS-1$
	
	/*
	 * The format of the commands are:
	 * 		Note: Most of the commands will not being doing a os.flush() at the end. We are just going to
	 * 		be streaming the data over the line. At the end we will flush and then catch up. That way
	 * 		we aren't waiting for the other side as we send the data.
	 * 
	 * START_EXPRESSION_TREE_PROCESSING: 0b, trace
	 * 	Start processing.
	 *  byte(trace): -1 : do default
	 *                0 : no trace
	 *                1 : do trace
	 * 
	 * PUSH_EXPRESSION: 1b, b
	 * 	Push an expression. Where "b" is the expression type from IInternalExpressionConstants. 
	 * 	The actual data that follows is expression type dependent and will be
	 * 	sent in a following call to ExpressionCommands as it builds up the actual command.
	 * 	See REMExpression and each type of push call method within it to see the actual
	 * 	sent data.
	 * 
	 * END_EXPRESSION_TREE_PROCESSING: 2b
	 * 	End the processing and clean up.
	 * 
	 * SYNC_REQUEST: 3b
	 * 	This will return the current status. The reason it is called sync is because the
	 * 	IDE will wait for it to complete and read back the value. It will send back:
	 * 		1: VALUE command with boolean true as the value.
	 * 		2: ERROR command with code of ExpressionClassNotFound, with value of String with message from exception.
	 * 		3: ERROR command with code of EXPRESSION_NOEXPRESSIONVALUE_EXCEPTION, with value of String with message from exception.
	 * 		4: THROWABLE command with the actual exception that occurred.
	 * 
	 *
	 * PULL_VALUE_REQUEST: 4b
	 * 	This will do a sync up and return the value from the expression.
	 * 	IDE will wait for it to compleate and read back the value. It will send back:
	 * 		1: VALUE command with the result as the value.
	 * 		2: ERROR or EXCEPTION if there were errors, see SYNC_REQUEST with the format they are sent back.
	 * 
	 * TRANSFER_EXPRESSION_REQUEST: 5b
	 *  This will do a sync up, and return the ExpressionProcessorController that the request is for. And remove
	 *  the controller from its list of active expression controllers. 
	 *  
	 * RESUME_EXPRESSION_REQUEST: 6b, anExpressionProcessorController
	 *  This will take the given controller and add it to the list of controllers this connection is handling. It returns nothing.  
	 *  
	 * @see org.eclipse.jem.internal.proxy.initParser.tree.IInternalExpressionConstants
	 * @see org.eclipse.jem.internal.proxy.remote.REMExpression
	 * 
	 */
	
	/**
	 * Send the start expression processing command.
	 * @param expressionID
	 * @param trace 
	 * @param os
	 * 
	 * @throws IOException
	 * 
	 * @since 1.0.0
	 */
	public static void sendStartExpressionProcessingCommand(int expressionID, byte trace, DataOutputStream os) throws IOException {
		os.writeByte(Commands.EXPRESSION_TREE_COMMAND);
		os.writeInt(expressionID);
		os.writeByte(START_EXPRESSION_TREE_PROCESSING);
		os.writeByte(trace);
	}
	
	/**
	 * Send the end expression processing command.
	 * @param expressionID 
	 * @param os
	 * 
	 * @throws IOException
	 * 
	 * @since 1.0.0
	 */
	public static void sendEndExpressionProcessingCommand(int expressionID, DataOutputStream os) throws IOException {
		os.writeByte(Commands.EXPRESSION_TREE_COMMAND);
		os.writeInt(expressionID);
		os.writeByte(END_EXPRESSION_TREE_PROCESSING);
		os.flush();	// Flushing because we are done and want to make sure everything goes out.
	}
	
	/**
	 * Send an expression subcommand.
	 * @param expressionID 
	 * @param os
	 * @param subcommand
	 * 
	 * @throws IOException
	 * 
	 * @since 1.0.0
	 */
	public static void sendExpressionCommand(int expressionID, DataOutputStream os, byte subcommand) throws IOException {
		os.writeByte(Commands.EXPRESSION_TREE_COMMAND);
		os.writeInt(expressionID);
		os.writeByte(PUSH_EXPRESSION);
		os.writeByte(subcommand);
	}
	
	/**
	 * Send just a byte.
	 * 
	 * @param os
	 * @param aByte
	 * @throws IOException
	 * 
	 * @since 1.0.0
	 */
	public static void sendByte(DataOutputStream os, byte aByte) throws IOException {
		os.writeByte(aByte);
	}
	
	/**
	 * Send just an int.
	 * 
	 * @param os
	 * @param anInt
	 * @throws IOException
	 * 
	 * @since 1.0.0
	 */
	public static void sendInt(DataOutputStream os, int anInt) throws IOException {
		os.writeInt(anInt);
	}
	
	/**
	 * Send just a string.
	 * 
	 * @param os
	 * @param aString
	 * @throws IOException
	 * 
	 * @since 1.0.0
	 */
	public static void sendString(DataOutputStream os, String aString) throws IOException {
		Commands.sendStringData(os, aString);
	}
	
	/**
	 * Send just a boolean.
	 * 
	 * @param os
	 * @param aBool
	 * @throws IOException
	 * 
	 * @since 1.0.0
	 */
	public static void sendBoolean(DataOutputStream os, boolean aBool) throws IOException {
		os.writeBoolean(aBool);
	}
	
	/**
	 * Send the pull value command. After processing the proxies, caller should call getFinalValue() to get the value.
	 * @param expressionID 
	 * @param os
	 * @param is
	 * @param proxyids if not <code>null</code>, then this is the list of expression proxy ids that need to be returned as rendered.
	 * @param sender sender used to process the resolved proxy ids, or <code>null</code> if no proxy ids.
	 * @throws CommandException
	 * 
	 * @since 1.0.0
	 */
	public static void sendPullValueCommand(int expressionID, DataOutputStream os, DataInputStream is, Commands.ValueObject proxyids, Commands.ValueSender sender) throws CommandException {
		try {
			os.writeByte(Commands.EXPRESSION_TREE_COMMAND);
			os.writeInt(expressionID);
			os.writeByte(PULL_VALUE_REQUEST);
			sendProxyIDs(os, proxyids);
			os.flush();
			if (proxyids != null) {
				Commands.readBackValue(is, proxyids, Commands.ARRAY_IDS);	// Read the array header.
				sender.initialize(proxyids);
				Commands.readArray(is, proxyids.anInt, sender, proxyids, true);	// Read the array.
			}
		} catch (CommandException e) {
			// rethrow this exception since we want these to go on out.
			throw e;
		} catch (IOException e) {
			// Wrapper this one.
			throw new UnexpectedExceptionCommandException(false, e);
		}		
	}
	
	/**
	 * Send a sync command. This does a synchronize with the remote expression processor. It makes sure that the
	 * stream is caught and doesn't return until everything on the stream has been processed. Should then call
	 * getFinalValue() to verify the expression is valid.
	 * @param expressionID 
	 * @param os
	 * @param is
	 * @param proxyids if not <code>null</code>, then this is the list of expression proxy ids that need to be returned as rendered.
	 * @param sender the sender used for reading back the proxyid resolutions, or <code>null</code> if no proxy ids.
	 * 
	 * @throws CommandException
	 * 
	 * @since 1.0.0
	 */
	public static void sendSyncCommand(int expressionID, DataOutputStream os, DataInputStream is, Commands.ValueObject proxyids, Commands.ValueSender sender) throws CommandException {
		try {
			os.writeByte(Commands.EXPRESSION_TREE_COMMAND);
			os.writeInt(expressionID);
			os.writeByte(SYNC_REQUEST);
			sendProxyIDs(os, proxyids);
			os.flush();
			if (proxyids != null) {
				Commands.readBackValue(is, proxyids, Commands.ARRAY_IDS);	// Read the array header.
				sender.initialize(proxyids);
				Commands.readArray(is, proxyids.anInt, sender, proxyids, true);	// Read the array.
			}
		} catch (CommandException e) {
			// rethrow this exception since we want these to go on out.
			throw e;
		} catch (Exception e) {
			// Wrapper this one.
			throw new UnexpectedExceptionCommandException(false, e);
		}		
	}	
	
	/**
	 * Send the proxyids (if not null) as part of a command. Used by sync and pullValue.
	 * @param os
	 * @param proxyids <code>null</code> if not requesting proxy ids.
	 * @throws IOException
	 * @throws CommandException
	 * 
	 * @since 1.1.0
	 */
	private static void sendProxyIDs(DataOutputStream os, Commands.ValueObject proxyids) throws IOException, CommandException {
		if (proxyids != null) {
			os.writeBoolean(true);	// Indicates proxy ids being sent.
			Commands.writeValue(os, proxyids, false, false);
		} else
			os.writeBoolean(false);	// No proxyids being sent.
	}
	
	/**
	 * Send the transfer expression command and receive back the expression processor controller.
	 * 
	 * @param expressionID
	 * @param os
	 * @param is
	 * @param expressionProcesserController
	 * @throws CommandException
	 * 
	 * @since 1.1.0
	 */
	public static void sendTransferExpressionCommand(int expressionID, DataOutputStream os, DataInputStream is, Commands.ValueObject expressionProcesserController) throws CommandException {
		try {
			os.writeByte(Commands.EXPRESSION_TREE_COMMAND);
			os.writeInt(expressionID);
			os.writeByte(TRANSFER_EXPRESSION_REQUEST);
			os.flush();
			Commands.readBackValue(is, expressionProcesserController, Commands.NO_TYPE_CHECK); // Read the expression processor controller
		} catch (CommandException e) {
			throw e;
		} catch (Exception e) {
			// Wrapper this one.
			throw new UnexpectedExceptionCommandException(false, e);
		}
	}

	/**
	 * Send the resume expression command with given expression processor controller.
	 * 
	 * @param expressionID
	 * @param os
	 * @param expressionProcessorController
	 * @throws CommandException
	 * 
	 * @since 1.1.0
	 */
	public static void sendResumeExpressionCommand(int expressionID, DataOutputStream os, Commands.ValueObject expressionProcessorController) throws CommandException {
		try {
			os.writeByte(Commands.EXPRESSION_TREE_COMMAND);
			os.writeInt(expressionID);
			os.writeByte(RESUME_EXPRESSION_REQUEST);
			Commands.writeValue(os, expressionProcessorController, false, false);
		} catch (CommandException e) {
			throw e;
		} catch (Exception e) {
			// Wrapper this one.
			throw new UnexpectedExceptionCommandException(false, e);
		}			
	}
	
	private ExpressionCommands() {
		// Never intended to be instantiated.
	}
	
}
