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

/**
 * FSClipEvent is used to define the actions that a movie clip will execute in
 * response to a particular event.
 * 
 * <p>
 * FSClipEvent objects are added to an FSPlaceObject2 object and the actions are
 * registered with the Flash Player when the movie clip is added to the display
 * list.
 * </p>
 * 
 * <table class="datasheet">
 * 
 * <tr>
 * <th align="left" colspan="2">Attributes</th>
 * </tr>
 * 
 * <tr>
 * <td>event</td>
 * <td>A code representing one or more events that the movie clip will respond
 * to.</td>
 * </tr>
 * <tr>
 * <td>actions</td>
 * <td>An array of actions that will be executed when one or more of the
 * specified events occur.</td>
 * </tr>
 * 
 * <tr>
 * <td>encodedActions</td>
 * <td>An array of bytes containing encoded actions can also be set. The
 * encoded actions are typically generated by the parser in the Translate
 * framework. The actions array and encodedActions cannot both be valid at the
 * same time. Accessor methods used to set either of the attributes will set the
 * other to null.</td>
 * </tr>
 * 
 * </table>
 * 
 * <p>
 * The events that a movie clip responds to are:
 * </p>
 * 
 * <table class="datasheet">
 * <tr bgcolor="#CCCCFF" valign="top">
 * <th align="left">Event</th>
 * <th align="left">Description</th>
 * </tr>
 * <tr>
 * <td>Load</td>
 * <td>occurs when the movie clip is finished loading.</td>
 * </tr>
 * <tr>
 * <td>Unload</td>
 * <td>occurs when the movie clip is unloaded from the parent movie.</td>
 * </tr>
 * <tr>
 * <td>EnterFrame</td>
 * <td>occurs when each frame in the movie clip is played.</td>
 * </tr>
 * <tr>
 * <td>MouseMove</td>
 * <td>occurs when the mouse pointer is moved.</td>
 * </tr>
 * <tr>
 * <td>MouseDown</td>
 * <td>occurs when the left mouse button is pressed while the cursor is outside
 * of the bounding rectangle of the movie clip.</td>
 * </tr>
 * <tr>
 * <td>MouseUp</td>
 * <td>occurs when the left mouse button is pressed and released while the
 * cursor is outside of the bounding rectangle of the movie clip.</td>
 * </tr>
 * <tr>
 * <td>KeyDown</td>
 * <td>occurs when any key is pressed on the keyboard. From Flash 6 a key code
 * can be specified to identify a specific key rather than testing for the value
 * inside the actions that are executed in response to the event.</td>
 * </tr>
 * <tr>
 * <td>KeyUp</td>
 * <td>occurs when any key being pressed on the keyboard is released.</td>
 * </tr>
 * <tr>
 * <td>Data</td>
 * <td>occurs when an FSGetUrl2 action is executed with the movie clip
 * specified as a target.</td>
 * </tr>
 * </table>
 * 
 * <p>
 * Starting with Flash 6 movie clips also respond to the same set of events that
 * buttons do:
 * </p>
 * 
 * <table class="datasheet">
 * <tr>
 * <td>RollOver</td>
 * <td>occurs when the mouse cursor moves over the movie clip.</td>
 * </tr>
 * <tr>
 * <td>RollOut</td>
 * <td>occurs when the mouse cursor moves out of the bounding rectangle of the
 * movie clip.</td>
 * </tr>
 * <tr>
 * <td>Press</td>
 * <td>occurs when the mouse button is clicked while the mouse cursor is inside
 * bounding rectangle of the movie clip.</td>
 * </tr>
 * <tr>
 * <td>Release</td>
 * <td>occurs when the mouse button is clicked and released while the mouse
 * cursor is inside bounding rectangle of the movie clip.</td>
 * </tr>
 * <tr>
 * <td>ReleaseOut</td>
 * <td>occurs when the mouse button is clicked and the mouse cursor is released
 * outside of the bounding rectangle of the movie clip.</td>
 * </tr>
 * <tr>
 * <td>DragOut</td>
 * <td>occurs when the mouse button is clicked and the mouse cursor is dragged
 * out of the bounding rectangle of the movie clip.</td>
 * </tr>
 * <tr>
 * <td>DragOver</td>
 * <td>occurs when the mouse button is clicked, the mouse cursor is dragged
 * into the bounding rectangle of the movie clip and the mouse button is
 * released.</td>
 * </tr>
 * <tr>
 * <td>Initialize</td>
 * <td>occurs when a movie clip is initialized using the FSInitialize class.</td>
 * </tr>
 * </td>
 * </tr>
 * </table>
 * 
 * <p>
 * Movie clips now also respond to keys being pressed on the keyboard. Keyboard
 * events are defined by the character key being pressed, e.g. "t", "T", "$",
 * etc. The ASCII code for the key is used to identify which key was pressed.
 * Note that while multiple mouse events can be defined for a button only one
 * keyboard event can be defined.
 * </p>
 * 
 * <p>
 * In Flash 7 a new construct event was added.
 * </p>
 * 
 * <table class="datasheet">
 * <tr>
 * <td>Construct</td>
 * <td>The function of this event is undocumented.</td>
 * </tr>
 * </td>
 * </tr>
 * </table>
 * 
 * <p>
 * IMPORTANT: The FSClipEvent object supports both the Flash 5, Flash 6 and
 * Flash 7 event models. The events that are encoded to a file are determined by
 * the version of the FSMovie in which the object is contained. The codes
 * assigned to the different types of event ensure that the same value can be
 * used to encode the object for each version of Flash.
 * </p>
 * 
 * <p>
 * For the KeyDown and KeyUp events, Flash ActionScript provides the Key object
 * which contains the getCode() and isDown() functions that allow code to be
 * written to test which key was pressed. Note that ActionScript is a high-level
 * interpreted language similar to JavaScript. Transform supports actions that
 * represent the compiled version of the ActionScript code. To create and
 * compile ActionScript code then use Transform's sister product, Translate.
 * </p>
 * 
 * <p>
 * Each type of event is defined by a constant, for example, Load, EnterFrame,
 * etc. An FSClipEvent object can define the actions that will be executed in
 * response to multiple events. There are two ways to respond to multiple
 * events. If the same set of actions should be executed then the event code
 * that flags which events should be responded to can be generated by bitwise
 * OR-ing together the individual constants:
 * </p>
 * 
 * <pre>
 * int loadAndMouseMove = FSClipEvent.Load | FSClipEvent.MouseMove;
 * </pre>
 * 
 * <p>
 * If different actions should be executed then an FSClipEvent object is created
 * for each different set of events. The FSPlaceObject2 object that is used to
 * register the actions for a movie clip with the Flash Player supports an array
 * of FSClipEvent objects.
 * </p>
 * 
 * <p>
 * The array of actions may be empty. Although this situation does not perform
 * any useful operation it is valid and may be encountered when parsing Flash
 * files generated by a third party.
 * </p>
 * 
 * <h1 class="datasheet">Examples</h1>
 * 
 * <p>
 * The following simplified code fragments illustrate how the FSClipEvent class
 * can be used.
 * </p>
 * 
 * <p>
 * 1. Defining the actions for a single event.
 * </p>
 * 
 * <pre>
 *  FSDefineMovieClip movieClip = new FSDefineMovieClip(movie.newIdentifier(), commands);
 * 
 *  FSClipEvent clipEvent(FSClipEvent.MouseDown);
 * 
 *  clipEvent.add(anAction);
 *  ...
 * 
 *  FSPlaceObject2 placeClip = new FSPlaceObject2(movieClip.getIdentifier(), 1, events, 400, 400);
 * 
 *  placeClip.add(clipEvent);
 * 
 *  movie.add(movieClip);
 *  movie.add(placeClip);
 * </pre>
 * 
 * <p>
 * 2. Defining the actions for a compound event.<br/> If a movie clip should
 * execute the same set of actions for different types of event then the code
 * for the compound event can be created by bitwise-OR'ing individual event
 * codes.
 * </p>
 * 
 * <pre>
 *  FSDefineMovieClip movieClip = new FSDefineMovieClip(movie.newIdentifier(), commands);
 * 
 *  FSClipEvent clipEvent(FSClipEvent.MouseDown | FSClipEvent.KeyDown, actions);
 *  ...
 *  FSPlaceObject2* placeClip = new FSPlaceObject2(movieClip.getIdentifier(), 1, events, 400, 400)
 * 
 *  placeClip.add(clipEvent);
 * 
 *  movie.add(movieClip);
 *  movie.add(placeClip);
 * </pre>
 * 
 * <p>
 * 3. Defining different sets of actions for events.<br/> An FSClipEvent object
 * is created for each set of events that a movie clip must respond to.
 * </p>
 * 
 * <pre>
 *  FSDefineMovieClip* movieClip = new FSDefineMovieClip(movie.newIdentifier(), commands);
 *  FSPlaceObject2* placeClip = new FSPlaceObject2(movieClip.getIdentifier(), 1, events, 400, 400)
 * 
 *  FSClipEvent mouseEvent(FSClipEvent.MouseDown, mouseActions);
 *  ...
 *  placeClip.add(clipEvent);
 * 
 *  FSClipEvent keyEvent(FSClipEvent.KeyDown, keyActions);
 *  ...
 *  placeClip.add(keyEvent);
 * 
 *  movie.add(movieClip);
 *  movie.add(placeClip);
 * </pre>
 * 
 * <h1 class="datasheet">History</h1>
 * 
 * <p>
 * The FSClipEvent class represents the ClipEvent data structure tag from the
 * Macromedia Flash (SWF) File Format Specification. It was introduced in Flash
 * 5. The event model was extended in Flash 6 to support the set of events that
 * Buttons respond to. In Flash 7 the Construct event was added.
 * </p>
 */
public class FSClipEvent extends FSTransformObject
{
	/** Code for a load event. */
	public static final int Load = 1;

	/** Code for an enter frame event. */
	public static final int EnterFrame = 2;

	/** Code for an unload event. */
	public static final int Unload = 4;

	/** Code for a mouse move event. */
	public static final int MouseMove = 8;

	/** Code for a mouse down event. */
	public static final int MouseDown = 16;

	/** Code for a mouse up event. */
	public static final int MouseUp = 32;

	/** Code for a key down event. */
	public static final int KeyDown = 64;

	/** Code for a key up event. */
	public static final int KeyUp = 128;

	/** Code for a data event. */
	public static final int Data = 256;

	/** Code for an initialise event. */
	public static final int Initialize = 512;

	/** Code for a press event. */
	public static final int Press = 1024;

	/** Code for a release event. */
	public static final int Release = 2048;

	/** Code for a release outside event. */
	public static final int ReleaseOut = 4096;

	/** Code for a roll over event. */
	public static final int RollOver = 8192;

	/** Code for a roll out event. */
	public static final int RollOut = 16384;

	/** Code for a drag over event. */
	public static final int DragOver = 32768;

	/** Code for a drag out event. */
	public static final int DragOut = 65536;

	/** Code for a key press event, where the code for the key is specified. */
	public static final int KeyPress = 131072;

	/** Code for a construct event. */
	public static final int Construct = 262144;

	private int event = 0;
	private int keyCode = 0; // Flash 6+
	private ArrayList actions = null;
	private byte[] encodedActions = null;

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

	/**
	 * Constructs an FSClipEvent object that defines the array of actions that
	 * will be executed when a particular event occurs.
	 * 
	 * @param eventCode
	 *            the code representing one or more events.
	 * @param anArray
	 *            the array of actions that will be executed when the specified
	 *            event occurs.
	 */
	public FSClipEvent(int eventCode, ArrayList anArray)
	{
		setEvent(eventCode);
		setActions(anArray);
	}

	/**
	 * Constructs an FSClipEvent object that defines the array of actions that
	 * will be executed when a particular event occurs.
	 * 
	 * @param eventCode
	 *            the event code.
	 * @param bytes
	 *            an array of encoded action objects.
	 */
	public FSClipEvent(int eventCode, byte[] bytes)
	{
		setEvent(eventCode);
		setEncodedActions(bytes);
	}

	// Flash 6
	/**
	 * Constructs an FSClipEvent object that defines the array of actions that
	 * will be executed when a particular event occurs or when the specified key
	 * is pressed.
	 * 
	 * @param eventCode
	 *            the code representing one or more events.
	 * @param keyCode
	 *            the ASCII code for the key pressed on the keyboard.
	 * @param anArray
	 *            the array of actions that will be executed when the specified
	 *            event occurs.
	 */
	public FSClipEvent(int eventCode, int keyCode, ArrayList anArray)
	{
		setEvent(eventCode);
		setKeyCode(keyCode);
		setActions(anArray);
	}

	/**
	 * Constructs an FSClipEvent object that defines the array of actions that
	 * will be executed when a particular event occurs or when the specified key
	 * is pressed. The array of bytes contained the encoded actions and is
	 * typically generated using the classes in the Translate framework.
	 * 
	 * @param eventCode
	 *            the code representing one or more events.
	 * @param keyCode
	 *            the ASCII code for the key pressed on the keyboard.
	 * @param bytes
	 *            an array of encoded action objects.
	 */
	public FSClipEvent(int eventCode, int keyCode, byte[] bytes)
	{
		setEvent(eventCode);
		setKeyCode(keyCode);
		setEncodedActions(bytes);
	}

	// End Flash 6
	/**
	 * Constructs an FSCall object by copying values from an existing object.
	 * 
	 * @param obj
	 *            an FSCall object.
	 */
	public FSClipEvent(FSClipEvent obj)
	{
		event = obj.event;
		keyCode = obj.keyCode;

		if (actions != null)
		{
			actions = new ArrayList();

			for (Iterator i = obj.actions.iterator(); i.hasNext();)
				actions.add(((FSActionObject) i.next()).clone());
		} else
		{
			encodedActions = Transform.clone(obj.encodedActions);
		}
	}

	/**
	 * Adds an action to the array of actions.
	 * 
	 * @param anAction
	 *            a pointer to an action object.
	 */
	public void add(FSActionObject anAction)
	{
		if (encodedActions != null)
		{
			actions = FSMovie.decodeActions(encodedActions);
			encodedActions = null;
		}
		actions.add(anAction);
	}

	/**
	 * Sets the event code that this FSClipEvent defines actions for.
	 * 
	 * @param aNumber
	 *            the code representing one or more events.
	 */
	public void setEvent(int aNumber)
	{
		event = aNumber;
	}

	/**
	 * Gets the event code that this FSClipEvent defines actions for.
	 * 
	 * @return the eventCode representing the events that the FSClipEvent object
	 *         will respond to.
	 */
	public int getEvent()
	{
		return event;
	}

	// Flash 6
	/**
	 * Gets the code for the key that triggers the event when pressed. The code
	 * is typically the ASCII code for standard western keyboards.
	 * 
	 * @return the code for the key that triggers the event.
	 */
	public int getKeyCode()
	{
		return keyCode;
	}

	/**
	 * Sets the code for the key that triggers the event when pressed. The code
	 * is typically the ASCII code for standard western keyboards.
	 * 
	 * @param code
	 *            the ASCII code for the key that triggers the event.
	 */
	public void setKeyCode(int code)
	{
		keyCode = code;
	}

	// End Flash 6

	/**
	 * Sets the array of actions that are executed by the movie clip in response
	 * to specified event(s).
	 * 
	 * @param anArray
	 *            the array of actions that will be executed when the specified
	 *            event occurs.
	 */
	public void setActions(ArrayList anArray)
	{
		actions = anArray;
		encodedActions = null;
	}

	/**
	 * Gets the array of actions that are executed by the movie clip in response
	 * to specified event(s).
	 * 
	 * @return the array of actions that will be executed when the specified
	 *         event occurs.
	 */
	public ArrayList getActions()
	{
		if (encodedActions != null)
		{
			actions = FSMovie.decodeActions(encodedActions);
			encodedActions = null;
		}
		return actions;
	}

	/**
	 * Get the array of encoded actions that are executed when the frame is
	 * displayed.
	 * 
	 * @return the array of action objects or null if the actions have been
	 *         decoded.
	 */
	public byte[] getEncodedActions()
	{
		return encodedActions;
	}

	/**
	 * Set the array of encoded actions generated by the classes in the
	 * Translate framework. If the object already contains an array of actions
	 * then they will be deleted.
	 * 
	 * @param bytes
	 *            the array of encoded actions.
	 */
	public void setEncodedActions(byte[] bytes)
	{
		encodedActions = bytes;
		actions = null;
	}

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

		if (actions != null)
		{
			anObject.actions = new ArrayList();

			for (Iterator i = actions.iterator(); i.hasNext();)
				anObject.actions.add(((FSActionObject) i.next()).clone());
		} else
		{
			anObject.encodedActions = Transform.clone(encodedActions);
		}
		return anObject;
	}

	/**
	 * Returns true if anObject is equal to this one. Objects are considered
	 * equal if they would generate identical binary data when they are encoded
	 * to a Flash file.
	 * 
	 * @return true if this object would be identical to anObject when encoded.
	 */
	public boolean equals(Object anObject)
	{
		boolean result = false;

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

			result = event == typedObject.event;
			// Flash 6
			result = result && keyCode == typedObject.keyCode;
			// End Flash 6
			if (actions != null)
				result = actions.equals(typedObject.actions);
			else
				result = Transform.equals(encodedActions,
								typedObject.encodedActions);
		}
		return result;
	}

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

		if (depth > 0)
		{
			buffer.append(": { ");
			Transform.append(buffer, "event", event);
			// Flash 6
			Transform.append(buffer, "keyCode", keyCode);
			// End Flash 6
			if (actions != null)
				Transform.append(buffer, "actions", actions, depth);
			else
				buffer.append("actions = <data>; ");

			buffer.append("}");
		}
	}

	public int length(FSCoder coder)
	{
		int length = 4 + ((coder.context[FSCoder.Version] > 5) ? 4 : 2);

		// Flash 6
		length += ((event & KeyPress) != 0) ? 1 : 0;
		// End Flash 6

		if (actions != null)
		{
			FSActionObject currentAction = null;

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

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

			if ((actions.size() == 0)
							|| (currentAction.getType() != FSAction.End))
			{
				length += 1;
			}
		} else
		{
			length += encodedActions.length;

			if ((encodedActions.length == 0)
							|| (encodedActions[encodedActions.length - 1] != FSAction.End))
			{
				length += 1;
			}
		}
		return length;
	}

	public void encode(FSCoder coder)
	{
		int eventSize = (coder.context[FSCoder.Version] > 5) ? 4 : 2;

		int offset = 0;

		if (actions != null)
		{
			FSActionObject currentAction = null;

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

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

			if ((actions.size() == 0)
							|| (currentAction.getType() != FSAction.End))
			{
				offset += 1;
			}
		} else
		{
			offset += encodedActions.length;

			if ((encodedActions.length == 0)
							|| (encodedActions[encodedActions.length - 1] != FSAction.End))
			{
				offset += 1;
			}
		}

		// Flash 6
		offset += ((event & KeyPress) != 0) ? 1 : 0;
		// End Flash 6

		coder.writeWord(event, eventSize);
		coder.writeWord(offset, 4);

		// Flash 6
		if ((event & KeyPress) != 0)
			coder.writeWord(keyCode, 1);
		// End Flash 6

		if (actions != null)
		{
			FSActionObject action = null;

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

				int objStart = coder.getPointer();
				int length = action.getLength();
				int start = coder.getPointer()
								+ ((action.getType() > 128) ? 24 : 8);
				int next = start + (length << 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 ((actions.size() == 0) || (action.getType() != FSAction.End))
			{
				coder.writeWord(0, 1);
			}
		} else
		{
			coder.writeBytes(encodedActions);

			if ((encodedActions.length == 0)
							|| (encodedActions[encodedActions.length - 1] != FSAction.End))
			{
				coder.writeWord(0, 1);
			}
		}
	}

	public void decode(FSCoder coder)
	{
		int eventSize = (coder.context[FSCoder.Version] > 5) ? 4 : 2;

		event = coder.readWord(eventSize, false);
		int length = coder.readWord(4, false);

		// Flash 6
		if ((event & KeyPress) != 0)
		{
			keyCode = coder.readWord(1, false);
			length -= 1;
		}
		// End Flash 6

		if (coder.context[FSCoder.DecodeActions] == 1)
		{
			FSActionObject action;
			int start;

			actions = new ArrayList();

			while (length > 0)
			{
				start = coder.getPointer();

				action = FSMovie.decodeAction(coder);
				actions.add(action);
				length -= (coder.getPointer() - start) >>> 3;

				/*
				 * The following code is to fix an apparent bug in Flash CS3
				 * where the offset to the next clip event is 1 byte larger than
				 * it should be.
				 */
				
				if (length == 0)
				{
					if (coder.getContext(FSCoder.Version) == 9 && 
									coder.scanWord(eventSize-1, false) == 0 && 
									coder.scanWord(eventSize, false) != 0)
					{
						coder.adjustPointer(-8);
						break;
					}
				}
			}
		} else
		{
			encodedActions = new byte[length];
			coder.readBytes(encodedActions);
		}
	}
}
