/*
 * FSExceptionHandler.java
 * Transform
 * 
 * Copyright (c) 2001-2008 Flagstone Software Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Flagstone Software Ltd. nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software 
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.flagstone.transform;

import java.util.*;

/**
 * The FSExceptionHandler class is used to specify try..catch blocks so
 * exceptions can be thrown and handled when executing a series of actions.
 * 
 * <table class="datasheet">
 * <tr>
 * <th align="left" colspan="2">Attributes</th>
 * </tr>
 * 
 * <tr>
 * <td><a>type</a></td>
 * <td>Identifies the data structure when it is encoded. Read-only.</td>
 * </tr>
 * 
 * <tr>
 * <td><a>register</a></td>
 * <td>The number of the register that the thrown object will be assigned to.
 * Optional, Omitted if the variable attribute is set.</td>
 * </tr>
 * 
 * <tr>
 * <td><a>variable</a></td>
 * <td>The variable in memory that the thrown object will be assigned to.
 * Optional, Omitted if the register attribute is set.</td>
 * </tr>
 * 
 * <tr>
 * <td><a>tryActions</a></td>
 * <td>The actions executed in the try block of the exception.</td>
 * </tr>
 * 
 * <tr>
 * <td><a>catchActions </a></td>
 * <td>The actions executed in the catch block of the exception. Defining
 * actions for the catch block is optional and may be set to the null object or
 * an empty array if no actions will be executed.</td>
 * </tr>
 * 
 * <tr>
 * <td><a>finalActions</a></td>
 * <td>The actions executed in a finally block of the exception. Defining
 * actions for the finally block is optional and may be set to the null object
 * or an empty array if no actions will be executed.</td>
 * </tr>
 * 
 * </table>
 * 
 * <p>
 * The thrown object can be assigned to either one of the Flash Player's 256
 * internal registers or to a variable in memory. If a register number is set
 * the variable name is set to null. Similarly if a variable name is set then
 * the register number is set to zero.
 * </p>
 * 
 * <p>
 * The FSExceptionHandler class contains three arrays of actions supporting the
 * standard syntax for an exception with try, catch and finally blocks. An
 * exception is thrown by a Throw action which pops a value off the stack and
 * assigns it to either a named variable or one of the Flash Player's internal
 * registered so it can be processed by the actions in the catch block.
 * </p>
 * 
 * <p>
 * Exceptions may be nest to any level if a thrown exception is not handled by
 * the immediate catch block it is propagated to the next handler in the
 * exception hierarchy and so on until it is caught.
 * </p>
 * 
 * <p>
 * Both the catch and finally blocks are optional when defining an exception,
 * the corresponding arguments in constructors and accessor methods may be set
 * to null.
 * </p>
 * 
 * <h1 class="datasheet">History</h1>
 * 
 * <p>
 * The FSExceptionHandler represents the Try action introduced in Flash 7 and is
 * used to directly support the exception handling syntax defined in
 * ActionScript 2.0.
 * </p>
 */
public class FSExceptionHandler extends FSActionObject
{
	private int register = 0;
	private String variable = null;
	private ArrayList tryActions = null;
	private ArrayList catchActions = null;
	private ArrayList finalActions = null;

	/**
	 * Construct an FSExceptionHandler object, initialising it with values
	 * decoded from an encoded object.
	 * 
	 * @param coder
	 *            an FSCoder containing the binary data.
	 */
	public FSExceptionHandler(FSCoder coder)
	{
		super(ExceptionHandler);
		decode(coder);
	}

	/**
	 * Constructs a new exception handler with the thrown object assigned to a
	 * local variable.
	 * 
	 * @param name
	 *            the name of the variable that the thrown object will be
	 *            assigned to.
	 * @param tryArray
	 *            actions that will be executed in the try block of the
	 *            exception.
	 * @param catchArray
	 *            actions that will be executed in the catch block of the
	 *            exception, if one is defined. This may be null is no catch
	 *            block is required - the exception will be handled by another
	 *            catche block higher in the exception tree.
	 * @param finallyArray
	 *            actions that will be executed in the finally block of the
	 *            exception, if one is defined. This may be null is no finally
	 *            block is required.
	 */
	public FSExceptionHandler(String name, ArrayList tryArray,
								ArrayList catchArray, ArrayList finallyArray)
	{
		super(ExceptionHandler);

		register = 0;
		variable = name;
		tryActions = tryArray;
		catchActions = catchArray;
		finalActions = finallyArray;
	}

	/**
	 * Constructs a new exception handler with the thrown object assigned to one
	 * of the Flash Player's internal registers.
	 * 
	 * @param index
	 *            the number of the register that the thrown object will be
	 *            assigned to.
	 * @param tryArray
	 *            actions that will be executed in the try block of the
	 *            exception.
	 * @param catchArray
	 *            actions that will be executed in the catch block of the
	 *            exception, if one is defined. This may be null is no catch
	 *            block is required - the exception will be handled by another
	 *            catche block higher in the exception tree.
	 * @param finallyArray
	 *            actions that will be executed in the finally block of the
	 *            exception, if one is defined. This may be null is no finally
	 *            block is required.
	 */
	public FSExceptionHandler(int index, ArrayList tryArray,
								ArrayList catchArray, ArrayList finallyArray)
	{
		super(ExceptionHandler);

		register = index;
		variable = null;
		tryActions = tryArray;
		catchActions = catchArray;
		finalActions = finallyArray;
	}

	/**
	 * Constructs an FSExceptionHandler object by copying values from an
	 * existing object.
	 * 
	 * @param obj
	 *            an FSExceptionHandler object.
	 */
	public FSExceptionHandler(FSExceptionHandler obj)
	{
		super(obj);
		register = obj.register;

		if (obj.variable != null)
			variable = new String(obj.variable);

		tryActions = new ArrayList(obj.tryActions.size());

		for (Iterator i = obj.tryActions.iterator(); i.hasNext();)
			tryActions.add(((FSActionObject) i.next()).clone());

		if (obj.catchActions != null)
		{
			catchActions = new ArrayList(obj.catchActions.size());

			for (Iterator i = obj.catchActions.iterator(); i.hasNext();)
				catchActions.add(((FSActionObject) i.next()).clone());
		}

		if (obj.finalActions != null)
		{
			finalActions = new ArrayList(obj.finalActions.size());

			for (Iterator i = obj.finalActions.iterator(); i.hasNext();)
				finalActions.add(((FSActionObject) i.next()).clone());
		}
	}

	/**
	 * Adds the action object to the array of actions for the try block.
	 * 
	 * @param anAction
	 *            an action.
	 */
	public void addToTry(FSActionObject anAction)
	{
		tryActions.add(anAction);
	}

	/**
	 * Adds the action object to the array of actions for the catch block.
	 * 
	 * @param anAction
	 *            an action.
	 */
	public void addToCatch(FSActionObject anAction)
	{
		if (catchActions == null)
			catchActions = new ArrayList();

		catchActions.add(anAction);
	}

	/**
	 * Adds the action object to the array of actions for the finally block.
	 * 
	 * @param anAction
	 *            an action.
	 */
	public void addToFinally(FSActionObject anAction)
	{
		if (catchActions == null)
			finalActions = new ArrayList();

		finalActions.add(anAction);
	}

	/**
	 * Gets the name of the variable which the exception object is assigned to.
	 * 
	 * @return the name of the function. Returns null if the exception object
	 *         will be assigned to a register.
	 */
	public String getVariable()
	{
		return variable;
	}

	/**
	 * Sets the name of the variable that the exception object is assigned to.
	 * 
	 * @param name
	 *            the name of the variable. May be null if the exception object
	 *            will be signed to a register.
	 */
	public void setVariable(String name)
	{
		variable = name;
		register = 0;
	}

	/**
	 * Gets the index of the register that the exception object is assigned to.
	 * 
	 * @return the number of register. Returns 0 if the exception object will be
	 *         assigned to a local variable.
	 */
	public int getRegister()
	{
		return register;
	}

	/**
	 * Sets the index of the register that the exception object is assigned to.
	 * 
	 * @param index
	 *            the number of the register. May be 0 if the exception object
	 *            will be assigned to a local variable.
	 */
	public void setRegister(int index)
	{
		register = index;
		variable = null;
	}

	/**
	 * Gets the array of actions executed in the try block.
	 * 
	 * @return the array of actions for the try block.
	 */
	public ArrayList getTryActions()
	{
		return tryActions;
	}

	/**
	 * Sets the array of actions executed in the try block.
	 * 
	 * @param array
	 *            the array of actions for the try block.
	 */
	public void setTryActions(ArrayList array)
	{
		tryActions = array;
	}

	/**
	 * Gets the array of actions executed in the catch block.
	 * 
	 * @return the array of actions for the catch block.
	 */
	public ArrayList getCatchActions()
	{
		return catchActions;
	}

	/**
	 * Sets the array of actions executed in the catch block.
	 * 
	 * @param array
	 *            the array of actions for the catch block.
	 */
	public void setCatchActions(ArrayList array)
	{
		catchActions = array;
	}

	/**
	 * Gets the array of actions executed in the finally block.
	 * 
	 * @return the array of actions for the finally block.
	 */
	public ArrayList getFinalActions()
	{
		return finalActions;
	}

	/**
	 * Sets the array of actions executed in the final block.
	 * 
	 * @param array
	 *            the array of actions for the final block.
	 */
	public void setFinalActions(ArrayList array)
	{
		finalActions = array;
	}

	public Object clone()
	{
		FSExceptionHandler anObject = (FSExceptionHandler) super.clone();

		anObject.tryActions = new ArrayList();
		anObject.catchActions = new ArrayList();
		anObject.finalActions = new ArrayList();

		for (Iterator i = tryActions.iterator(); i.hasNext();)
			anObject.tryActions.add(((FSActionObject) i.next()).clone());

		for (Iterator i = catchActions.iterator(); i.hasNext();)
			anObject.catchActions.add(((FSActionObject) i.next()).clone());

		for (Iterator i = finalActions.iterator(); i.hasNext();)
			anObject.finalActions.add(((FSActionObject) i.next()).clone());

		return anObject;
	}

	public boolean equals(Object anObject)
	{
		boolean result = false;

		if (super.equals(anObject))
		{
			FSExceptionHandler typedObject = (FSExceptionHandler) anObject;

			if (variable != null)
				result = variable.equals(typedObject.variable);
			else
				result = variable == typedObject.variable;

			result = result && register == typedObject.register;

			if (tryActions != null)
				result = result && tryActions.equals(typedObject.tryActions);
			else
				result = result && tryActions == typedObject.tryActions;

			if (catchActions != null)
				result = result
								&& catchActions
												.equals(typedObject.catchActions);
			else
				result = result && catchActions == typedObject.catchActions;

			if (finalActions != null)
				result = result
								&& finalActions
												.equals(typedObject.finalActions);
			else
				result = result && finalActions == typedObject.finalActions;
		}
		return result;
	}

	public void appendDescription(StringBuffer buffer, int depth)
	{
		buffer.append(name());

		if (depth > 0)
		{
			buffer.append(": { ");
			Transform.append(buffer, "variable", variable);
			Transform.append(buffer, "register", register);
			Transform.append(buffer, "tryActions", tryActions, depth);
			Transform.append(buffer, "catchActions", catchActions, depth);
			Transform.append(buffer, "finalActions", finalActions, depth);
			buffer.append("}");
		}
	}

	public int length(FSCoder coder)
	{
		super.length(coder);

		length += 7;
		length += (variable != null) ? coder.strlen(variable, true) : 1;

		for (Iterator i = tryActions.iterator(); i.hasNext();)
		{
			FSActionObject currentAction = (FSActionObject) i.next();

			length += currentAction.length(coder);
			length += (currentAction.getType() > 128) ? 3 : 1;
		}

		if (catchActions != null)
		{
			for (Iterator i = catchActions.iterator(); i.hasNext();)
			{
				FSActionObject currentAction = (FSActionObject) i.next();

				length += currentAction.length(coder);
				length += (currentAction.getType() > 128) ? 3 : 1;
			}
		}

		if (finalActions != null)
		{
			for (Iterator i = finalActions.iterator(); i.hasNext();)
			{
				FSActionObject currentAction = (FSActionObject) i.next();

				length += currentAction.length(coder);
				length += (currentAction.getType() > 128) ? 3 : 1;
			}
		}
		return length;
	}

	public void encode(FSCoder coder)
	{
		super.encode(coder);

		coder.writeBits(0, 5);
		coder.writeBits(variable == null ? 1 : 0, 1);
		coder.writeBits(finalActions != null && finalActions.size() > 0 ? 1 : 0, 1);
		coder.writeBits(catchActions != null && catchActions.size() > 0 ? 1 : 0, 1);

		int tryLength = 0;

		for (Iterator i = tryActions.iterator(); i.hasNext();)
		{
			FSActionObject currentAction = (FSActionObject) i.next();

			tryLength += currentAction.getLength();
			tryLength += (currentAction.getType() > 128) ? 3 : 1;
		}

		coder.writeWord(tryLength, 2);

		int catchLength = 0;

		if (catchActions != null)
		{
			for (Iterator i = catchActions.iterator(); i.hasNext();)
			{
				FSActionObject currentAction = (FSActionObject) i.next();

				catchLength += currentAction.getLength();
				catchLength += (currentAction.getType() > 128) ? 3 : 1;
			}
		}

		coder.writeWord(catchLength, 2);

		int finalLength = 0;

		if (finalActions != null)
		{
			for (Iterator i = finalActions.iterator(); i.hasNext();)
			{
				FSActionObject currentAction = (FSActionObject) i.next();

				finalLength += currentAction.getLength();
				finalLength += (currentAction.getType() > 128) ? 3 : 1;
			}
		}

		coder.writeWord(finalLength, 2);

		if (variable != null)
			coder.writeString(variable);
		else
			coder.writeWord(register, 1);

		for (Iterator i = tryActions.iterator(); i.hasNext();)
		{
			FSActionObject action = (FSActionObject) i.next();

			int objStart = coder.getPointer();
			int start = coder.getPointer()
							+ ((action.getType() > 128) ? 24 : 8);
			int next = start + (action.getLength() << 3);

			action.encode(coder);
			coder.setPointer(next);

			int delta = (coder.getPointer() - next) >> 3;

			if (delta != 0)
			{
				coder.context[FSCoder.CodingError] = 1;
				coder.context[FSCoder.TypeInError] = action.getType();
				coder.context[FSCoder.StartOfError] = objStart >>> 3;
				coder.context[FSCoder.ExpectedLength] = (next - objStart) >>> 3;
				coder.context[FSCoder.Delta] = delta;
			}
		}

		if (catchActions != null)
		{
			for (Iterator i = catchActions.iterator(); i.hasNext();)
			{
				FSActionObject action = (FSActionObject) i.next();

				int objStart = coder.getPointer();
				int start = coder.getPointer()
								+ ((action.getType() > 128) ? 24 : 8);
				int next = start + (action.getLength() << 3);

				action.encode(coder);
				coder.setPointer(next);

				int delta = (coder.getPointer() - next) >> 3;

				if (delta != 0)
				{
					coder.context[FSCoder.CodingError] = 1;
					coder.context[FSCoder.TypeInError] = action.getType();
					coder.context[FSCoder.StartOfError] = objStart >>> 3;
					coder.context[FSCoder.ExpectedLength] = (next - objStart) >>> 3;
					coder.context[FSCoder.Delta] = delta;
				}
			}
		}

		if (finalActions != null)
		{
			for (Iterator i = finalActions.iterator(); i.hasNext();)
			{
				FSActionObject action = (FSActionObject) i.next();

				int objStart = coder.getPointer();
				int start = coder.getPointer()
								+ ((action.getType() > 128) ? 24 : 8);
				int next = start + (action.getLength() << 3);

				action.encode(coder);
				coder.setPointer(next);

				int delta = (coder.getPointer() - next) >> 3;

				if (delta != 0)
				{
					coder.context[FSCoder.CodingError] = 1;
					coder.context[FSCoder.TypeInError] = action.getType();
					coder.context[FSCoder.StartOfError] = objStart >>> 3;
					coder.context[FSCoder.ExpectedLength] = (next - objStart) >>> 3;
					coder.context[FSCoder.Delta] = delta;
				}
			}
		}

		coder.endObject(name());
	}

	public void decode(FSCoder coder)
	{
		boolean containsVariable = false;
		boolean containsCatch = false;
		boolean containsFinal = false;

		super.decode(coder);

		coder.readBits(5, false);
		containsVariable = coder.readBits(1, false) != 1;
		containsFinal = coder.readBits(1, false) == 1;
		containsCatch = coder.readBits(1, false) == 1;

		int tryLength = coder.readWord(2, false);
		int catchLength = coder.readWord(2, false);
		int finalLength = coder.readWord(2, false);
		
		if (length == 8)
		{
			length += tryLength;
			length += catchLength;
			length += finalLength;
		}

		tryActions = new ArrayList();

		if (containsCatch)
			catchActions = new ArrayList();

		if (containsFinal)
			finalActions = new ArrayList();

		/*
		 * Both the variable name and register index are represented in Describe
		 * so pretend fields are consecutive.
		 */
		if (containsVariable)
		{
			variable = coder.readString();
		} else
		{
			register = coder.readWord(1, false);				
		}

		while (tryLength > 0)
		{
			FSActionObject anAction = FSMovie.decodeAction(coder);

			if (anAction != null)
			{
				tryLength -= anAction.getLength() + ((anAction.getType() >= 128) ? 3 : 1);
				tryActions.add(anAction);
			}
			else
			{
				coder.adjustPointer(8);
				tryLength = -1;
			}
		}
		
		if (containsCatch)
		{
			while (catchLength > 0)
			{
				FSActionObject anAction = FSMovie.decodeAction(coder);

				if (anAction != null)
				{
					catchLength -= anAction.getLength() + ((anAction.getType() >= 128) ? 3 : 1);
					catchActions.add(anAction);
				} 
				else
				{
					coder.adjustPointer(8);
					catchLength = -1;
				}
			}
		}
		if (containsFinal)
		{
			while (finalLength > 0)
			{
				FSActionObject anAction = FSMovie.decodeAction(coder);

				if (anAction != null)
				{
					finalLength -= anAction.getLength() + ((anAction.getType() >= 128) ? 3 : 1);
					finalActions.add(anAction);
				} 
				else
				{
					coder.adjustPointer(8);
					finalLength = -1;
				}
			}
		}

		coder.endObject(name());
	}
}
