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

/**
 * FSShape is a container for the shape objects (FSLine, FSCurve and
 * FSShapeStyle objects) that describe how a particular shape is drawn.
 * 
 * <table class="datasheet">
 * 
 * <tr>
 * <th align="left" colspan="2">Attributes</th>
 * </tr>
 * 
 * <tr>
 * <td>objects</td>
 * <td>An array of shape (FSLine, FSCurve and FSShapeStyle) objects which are
 * used to draw the outline of the shape.</td>
 * </tr>
 * 
 * </table>
 * </p>
 * 
 * <p>
 * FSShapes are used in shape and font definitions. The FSShape class is used to
 * simplify the design of these classes and provides no added functionality
 * other than acting as a container class.
 * </p>
 * 
 * <h1 class="datasheet">History</h1>
 * 
 * <p>
 * The FSShape class represents the Shape record from the Macromedia Flash (SWF)
 * File Format Specification. It was introduced in Flash 1.
 * </p>
 */
public class FSShape extends FSTransformObject
{
	private ArrayList objects = null;
	private byte[] encodedObjects = null;

	/*
	 * This method is only used when lazily decoding shapes.
	 */
	FSShape(FSCoder coder, int length)
	{
		/*
		 * This test is used to overcome a bug in SWFTool's pdf2swf where empty
		 * glyphs are only encoded using 1 byte - should be 2.
		 */
		if (length > 1)
			encodedObjects = new byte[length];

		decode(coder);
	}

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

	/** Constructs an FSShape object with no shape objects. */
	public FSShape()
	{
		objects = new ArrayList();
	}

	/**
	 * Constructs an FSShape object, specifying the Objects that describe how
	 * the shape is drawn.
	 * 
	 * @param anArray
	 *            the array of shape records.
	 */
	public FSShape(ArrayList anArray)
	{
		setObjects(anArray);
	}

	/**
	 * Constructs an FSShape object by copying values from an existing object.
	 * 
	 * @param obj
	 *            an FSShape object.
	 */
	public FSShape(FSShape obj)
	{
		if (objects != null)
		{
			objects = new ArrayList();

			for (Iterator i = obj.objects.iterator(); i.hasNext();)
				objects.add(((FSTransformObject) i.next()).clone());
		} else
		{
			encodedObjects = Transform.clone(obj.encodedObjects);
		}
	}

	/**
	 * Adds the object to the array of shape records.
	 * 
	 * @param anObject
	 *            an instance of FSShapeStyle, FSLine or FSCurve.
	 */
	public void add(FSTransformObject anObject)
	{
		if (encodedObjects != null)
		{
			objects = FSMovie.decodeShape(encodedObjects);
			encodedObjects = null;
		}
		objects.add(anObject);
	}

	/**
	 * Gets the array of shape records that define the shape.
	 * 
	 * @return the array of shape records.
	 */
	public ArrayList getObjects()
	{
		if (encodedObjects != null)
		{
			objects = FSMovie.decodeShape(encodedObjects);
			encodedObjects = null;
		}
		return objects;
	}

	/**
	 * Sets the array of shape records.
	 * 
	 * @param anArray
	 *            the array of shape records.
	 */
	public void setObjects(ArrayList anArray)
	{
		objects = anArray;
	}

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

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

			for (Iterator i = objects.iterator(); i.hasNext();)
				anObject.objects.add(((FSTransformObject) i.next()).clone());
		} else
		{
			anObject.encodedObjects = Transform.clone(encodedObjects);
		}

		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))
		{
			FSShape typedObject = (FSShape) anObject;

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

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

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

	public int length(FSCoder coder)
	{
		int numberOfBits = 0;

		coder.context[FSCoder.NumberOfShapeBits] = numberOfBits;

		if (objects != null)
		{
			numberOfBits += 8;

			for (Iterator shapeIterator = objects.iterator(); shapeIterator
							.hasNext();)
				numberOfBits += ((FSTransformObject) shapeIterator.next())
								.length(coder);

			numberOfBits += 6; // Add size of end of shape

			numberOfBits += (numberOfBits % 8 > 0) ? 8 - (numberOfBits % 8) : 0;
		} else
		{
			numberOfBits += encodedObjects.length << 3;
		}
		return numberOfBits >> 3;
	}

	public void encode(FSCoder coder)
	{
		if (objects != null)
		{
			coder.writeBits(coder.context[FSCoder.NumberOfFillBits], 4);
			coder.writeBits(coder.context[FSCoder.NumberOfLineBits], 4);

			for (Iterator shapeIterator = objects.iterator(); shapeIterator
							.hasNext();)
				((FSTransformObject) shapeIterator.next()).encode(coder);

			coder.writeBits(0, 6); // End of shape
			coder.alignToByte();
		} else
		{
			coder.writeBytes(encodedObjects);
		}
	}

	public void decode(FSCoder coder)
	{
		if (encodedObjects != null)
		{
			coder.readBytes(encodedObjects);
		} else
		{
			objects = FSMovie.decodeShape(coder);
		}
	}
}
