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

/**
 * FSDefineButton2 defines the appearance and actions of push and menu buttons.
 * 
 * <p>
 * It provides a more sophisticated model for creating buttons:
 * </p>
 * 
 * <ul>
 * <li>Two types of button are supported, <B>Push</B> and <B>Menu</B>.</li>
 * <li>The number of events that a button can respond to is increased.</li>
 * <li>Actions can be executed for any button event.</li>
 * </ul>
 * 
 * <p>
 * Push and Menu buttons behave slightly differently in tracking mouse movements
 * when the button is clicked. A Push button 'captures' the mouse so if the
 * cursor is dragged outside of the active area of the button and the mouse
 * click is released then the Release Outside event is still sent to the button.
 * A Menu button does not 'capture' the mouse so if the cursor is dragged out of
 * the active area the button returns to its 'inactive' state.
 * </p>
 * 
 * <table class="datasheet">
 * 
 * <tr>
 * <th align="left" colspan="2">Attributes</th>
 * </tr>
 * 
 * <tr>
 * <td>type</td>
 * <td>Identifies the data structure when it is encoded. Read-only.</td>
 * </tr>
 * 
 * <tr>
 * <td>identifier</td>
 * <td>An unique identifier for this object in the range 1..65535.</td>
 * </tr>
 * 
 * <tr>
 * <td>buttonType</td>
 * <td>Whether the button is a Push button or a Menu button.</td>
 * </tr>
 * 
 * <tr>
 * <td>buttonRecords</td>
 * <td>An array of FSButton objects that describe the appearance of the button
 * in each of its states.</td>
 * </tr>
 * 
 * <tr>
 * <td>buttonEvents</td>
 * <td>An array of FSButtonEvent objects that define the actions that are
 * executed for each type of button event.</td>
 * </tr>
 * 
 * <tr>
 * <td><a name="FSDefineButton2_5">encodedEvents</a></td>
 * <td>An array of bytes containing encoded button events can also be set. The
 * encoded objects are typically generated by the parser in the Translate
 * framework. The buttonEvents array and encodedEvents 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>
 * An FSDefineButton2 object must contain at least one FSButton object. If more
 * than one button record is defined for a given button state then each shape
 * will be displayed by the button. The order in which the shapes are displayed
 * is determined by the layer assigned to each button record.
 * </p>
 * 
 * <p>
 * Each FSButton object can contain an FSColorTransform object which can be used
 * to change the colour of the shape being displayed without changing the
 * original definition.
 * </p>
 * 
 * <p>
 * Actions do not need to be specified for every button event. Indeed actions do
 * not need to be specified at all.
 * </p>
 * 
 * <h1 class="datasheet">Examples</h1>
 * 
 * 1. Define a button that highlights itself when the mouse is move over it and
 * actions are executed when the button is clicked.
 * 
 * <pre>
 * FSDefineShape upShape = new FSDefineShape(movie.newIdentifier(), bounds,
 * 				fillStyles, lineStyles, shape1);
 * FSDefineShape overShape = new FSDefineShape(movie.newIdentifier(), bounds,
 * 				fillStyles, lineStyles, shape2);
 * FSDefineShape downShape = new FSDefineShape(movie.newIdentifier(), bounds,
 * 				fillStyles, lineStyles, shape3);
 * 
 * ArrayList records = new ArrayList();
 * ArrayList events = new ArrayList();
 * 
 * records.add(FSButtonRecord(FSButton.Up, upShape.getIdentifier(), 1));
 * records.add(FSButtonRecord(FSButton.Over, overShape.getIdentifier(), 2));
 * records.add(FSButtonRecord(FSButton.Down, downShape.getIdentifier(), 3));
 * 
 * ArrayList actions = new ArrayList();
 * 
 * actions.add(action);
 * actions.add(action);
 * actions.add(action);
 * 
 * events.add(FSButtonEvent(FSButtonEvent.Press, actions));
 * 
 * FSDefineButton2 button = new FSDefineButton2(movie.newIdentifier(),
 * 				FSDefineButton2.Push, records, events);
 * </pre>
 * 
 * <h1 class="datasheet">History</h1>
 * 
 * <p>
 * The FSDefineButton2 class represents the DefineButton2 structure from the
 * Macromedia Flash (SWF) File Format Specification. It was introduced in Flash
 * 3.
 * </p>
 */
public class FSDefineButton2 extends FSDefineObject
{
	/**
	 * Defines whether a FSDefineButton2 object is configured as a momentary,
	 * push button.
	 */
	public static final int Push = 0;

	/** 
	 * Defines whether a FSDefineButton2 object is configured as a menu button. 
	 * */
	public static final int Menu = 1;

	private int buttonType = Push;
	private ArrayList buttonRecords = null;
	private ArrayList buttonEvents = null;
	private byte[] encodedEvents = null;

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

	/**
	 * Constructs an FSDefineButton object, specifying the unique identifier,
	 * the type of button to be created, the button records that describe the
	 * button's appearance and the actions that are performed in response to
	 * each button event.
	 * 
	 * @param anIdentifier
	 *            a unique identifier for this button.
	 * @param buttonType
	 *            the type of button - push or menu.
	 * @param buttonRecordArray
	 *            an array of FSButton objects.
	 * @param buttonEventArray
	 *            an array of FSButtonEvent objects.
	 */
	public FSDefineButton2(int anIdentifier, int buttonType,
							ArrayList buttonRecordArray,
							ArrayList buttonEventArray)
	{
		super(DefineButton2, anIdentifier);
		setButtonType(buttonType);
		setButtonRecords(buttonRecordArray);
		setButtonEvents(buttonEventArray);
	}

	/**
	 * Constructs an FSDefineButton object, specifying the unique identifier,
	 * the type of button to be created, the button records that describe the
	 * button's appearance and the encoded actions that are performed in
	 * response to each button event.
	 * 
	 * @param anIdentifier
	 *            a unique identifier for this button.
	 * @param buttonType
	 *            the type of button - push or menu.
	 * @param buttonRecordArray
	 *            an array of FSButton objects.
	 * @param bytes
	 *            an array containing the encoded FSButtonEvent objects.
	 */
	public FSDefineButton2(int anIdentifier, int buttonType,
							ArrayList buttonRecordArray, byte[] bytes)
	{
		super(DefineButton2, anIdentifier);
		setButtonType(buttonType);
		setButtonRecords(buttonRecordArray);
		setEncodedEvents(bytes);
	}

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

		buttonRecords = new ArrayList();

		for (Iterator i = obj.buttonRecords.iterator(); i.hasNext();)
			buttonRecords.add(((FSButton) i.next()).clone());

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

			for (Iterator i = obj.buttonEvents.iterator(); i.hasNext();)
				buttonEvents.add(((FSButtonEvent) i.next()).clone());
		} else
		{
			encodedEvents = Transform.clone(obj.encodedEvents);
		}
	}

	/**
	 * Adds an FSButton to the array of button records.
	 * 
	 * @param aButton
	 *            a button record.
	 */
	public void add(FSButton aButton)
	{
		buttonRecords.add(aButton);
	}

	/**
	 * Adds a button event object to the array of button events.
	 * 
	 * @param aButtonEvent
	 *            a button event.
	 */
	public void add(FSButtonEvent aButtonEvent)
	{
		if (buttonEvents == null)
			buttonEvents = new ArrayList();

		buttonEvents.add(aButtonEvent);
		encodedEvents = null;
	}

	/**
	 * Gets the button type - push or menu.
	 * 
	 * @return the type of button.
	 */
	public int getButtonType()
	{
		return buttonType;
	}

	/**
	 * Gets the array of button records defined for this button.
	 * 
	 * @return the array of button records.
	 */
	public ArrayList getButtonRecords()
	{
		return buttonRecords;
	}

	/**
	 * Gets the array of button records defined for this button.
	 * 
	 * @return the array of button events.
	 */
	public ArrayList getButtonEvents()
	{
		return buttonEvents;
	}

	/**
	 * Sets the button type.
	 * 
	 * @param aType
	 *            the type of button - push or menu.
	 */
	public void setButtonType(int aType)
	{
		buttonType = aType;
	}

	/**
	 * Sets the array of button records defined for this button.
	 * 
	 * @param anArray
	 *            an array of FSButtonRecord objects.
	 */
	public void setButtonRecords(ArrayList anArray)
	{
		buttonRecords = anArray;
	}

	/**
	 * Sets the array of button events defined for this button. If the object
	 * contains encodedEvents then they will be deleted.
	 * 
	 * @param anArray
	 *            and array of FSButtonEvent objects.
	 */
	public void setButtonEvents(ArrayList anArray)
	{
		buttonEvents = anArray;

		if (encodedEvents != null)
			encodedEvents = null;
	}

	/**
	 * Set the encoded button event objects generated by the classes in the
	 * Translate framework. If the object already contains an array of button
	 * events objects then they will be deleted.
	 * 
	 * @param bytes
	 *            the array of encoded button events.
	 */
	public void setEncodedEvents(byte[] bytes)
	{
		encodedEvents = bytes;
		buttonEvents = null;
	}

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

		anObject.buttonRecords = new ArrayList();

		for (Iterator i = buttonRecords.iterator(); i.hasNext();)
			anObject.buttonRecords.add(((FSButton) i.next()).clone());

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

			for (Iterator i = buttonEvents.iterator(); i.hasNext();)
				anObject.buttonEvents.add(((FSButtonEvent) i.next()).clone());
		} else
		{
			anObject.encodedEvents = Transform.clone(encodedEvents);
		}
		return anObject;
	}

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

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

			result = buttonType == typedObject.buttonType;

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

			if (buttonEvents != null)
				result = buttonEvents.equals(typedObject.buttonEvents);
			else
				result = Transform.equals(encodedEvents,
								typedObject.encodedEvents);
		}
		return result;
	}

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

		if (depth > 0)
		{
			buffer.append(": { ");
			Transform.append(buffer, "buttonRecords", buttonRecords, depth);

			if (buttonEvents != null)
				Transform.append(buffer, "buttonEvents", buttonEvents, depth);
			else
				buffer.append("buttonEvents = <data>; ");

			buffer.append("}");
		}
	}

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

		coder.context[FSCoder.TransparentColors] = 1;

		length += 3;

		for (Iterator buttonIterator = buttonRecords.iterator(); buttonIterator
						.hasNext();)
			length += ((FSTransformObject) buttonIterator.next()).length(coder);

		length += 1;

		if (buttonEvents != null)
		{
			for (Iterator eventIterator = buttonEvents.iterator(); eventIterator
							.hasNext();)
			{
				length += 2;
				length += ((FSTransformObject) eventIterator.next())
								.length(coder);
			}
		} else
		{
			length += encodedEvents.length;
		}
		coder.context[FSCoder.TransparentColors] = 0;

		return length;
	}

	public void encode(FSCoder coder)
	{
		int offsetStart = 0;
		int offsetEnd = 0;
		int currentCursor = 0;

		super.encode(coder);

		coder.context[FSCoder.TransparentColors] = 1;

		coder.writeWord(buttonType, 1);

		offsetStart = coder.getPointer();
		coder.writeWord(0, 2);

		for (Iterator buttonIterator = buttonRecords.iterator(); buttonIterator
						.hasNext();)
			((FSTransformObject) buttonIterator.next()).encode(coder);

		coder.writeWord(0, 1);

		// Write actions offset

		currentCursor = coder.getPointer();
		offsetEnd = (currentCursor - offsetStart) >> 3;
		coder.setPointer(offsetStart);
		coder.writeWord(offsetEnd, 2);
		coder.setPointer(currentCursor);

		if (buttonEvents != null)
		{
			for (Iterator eventIterator = buttonEvents.iterator(); eventIterator
							.hasNext();)
			{
				FSButtonEvent anEvent = (FSButtonEvent) eventIterator.next();

				offsetStart = coder.getPointer();
				coder.writeWord(anEvent.length(coder) + 2, 2);
				anEvent.encode(coder);
			}
			// Write offset of zero for last Action Condition

			currentCursor = coder.getPointer();
			coder.setPointer(offsetStart);
			coder.writeWord(0, 2);
			coder.setPointer(currentCursor);
		} else
		{
			coder.writeBytes(encodedEvents);
		}
		coder.context[FSCoder.TransparentColors] = 0;
		coder.endObject(name());
	}

	public void decode(FSCoder coder)
	{
		int offsetToNext = 0;

		buttonEvents = new ArrayList();

		super.decode(coder);

		int start = coder.getPointer() - 16;

		coder.context[FSCoder.TransparentColors] = 1;

		buttonType = coder.readWord(1, false);
		offsetToNext = coder.readWord(2, false);

		buttonRecords = new ArrayList();

		while (coder.scanWord(1, false) != 0)
			buttonRecords.add(new FSButton(coder));

		coder.readWord(1, false);

		if (offsetToNext != 0)
		{
			buttonEvents = new ArrayList();

			do
			{
				offsetToNext = coder.readWord(2, false);

				if (offsetToNext != 0)
				{
					buttonEvents
									.add(new FSButtonEvent(coder,
													offsetToNext - 2));
				} else
				{
					buttonEvents.add(new FSButtonEvent(coder, length
									- ((coder.getPointer() - start) >>> 3)));
				}
			} while (offsetToNext != 0);

		}
		coder.context[FSCoder.TransparentColors] = 0;
		coder.endObject(name());
	}
}
