/* libsswf_tag_place.c++ -- written by Alexis WILKE for Made to Order Software Corp. (c) 2002-2008 */

/*

Copyright (c) 2002-2008 Made to Order Software Corp.

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and
associated documentation files (the "Software"), to
deal in the Software without restriction, including
without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

/** \file
 *
 * \brief The implementation of the sswf::TagPlace class
 *
 * This file declares the body of the functions which are not
 * inline. It is part of the SSWF library.
 */

#include	"sswf/libsswf.h"

using namespace sswf;


/////////////////////////////////////////// TagPlace


/** \class sswf::TagPlace
 *
 * \brief Tag used to place (or remove) an object in the display list
 *
 * This tag can be used to place or remove an object in the display list.
 *
 * The default behavior is to replace the object at the specified depth.
 *
 * \sa <a href="../SWFalexref.html#tag_placeobject">SWF Alexis' Reference&mdash;Place Object</a>
 * \sa <a href="../SWFalexref.html#tag_placeobject2">SWF Alexis' Reference&mdash;Place Object 2 &amp; 3</a>
 * \sa <a href="../SWFalexref.html#swf_tag">SWF Alexis' Reference&mdash;swf_tag</a>
 */





/** \brief Initialize a TagPlace object
 *
 * The constructor initializes the TagPlace to defaults. The following
 * gives you the default value and the function to use to change the default
 * value.
 *
 *	\li No reference defined -- sswf::TagPlace::SetObjectID(sswf_id_t id)
 *	\li Replace any existing object at that depth -- sswf::TagPlace::Replace(void)
 *		and sswf::TagPlace::Place(void)
 *	\li Undefined depth (-1) -- sswf::TagPlace::SetDepth(int depth)
 *	\li No clipping -- sswf::TagPlace::SetClip(int depth)
 *	\li Unnamed -- sswf::TagPlace::SetName(const char *name)
 *	\li No morphing position -- sswf::TagPlace::SetMorphPosition(int position)
 *	\li No blending -- sswf::TagPlace::SetBlendMode(blend_mode_t blend_mode)
 *	\li No bitmap caching -- sswf::TagPlace::SetBitmapCaching(int bitmap_caching)
 *		and SetBlendModeName(const char *blend_mode_name)
 *	\li No transformation matrix -- sswf::TagPlace::SetMatrix(const Matrix& matrix)
 *	\li No color matrix -- sswf::TagPlace::SetColorTransform(const ColorTransform& color_transform)
 *	\li No events -- sswf::TagPlace::AddEvent(Event *event)
 */
TagPlace::TagPlace(TagBase *parent)
	: TagBase("place", parent)
{
	assert(parent != 0, "a Place tag must have a parent");

	f_id_defined = false;
	f_id = 0;		// not defined yet
	f_replace = 1;
	f_depth = -1;
	f_clip_depth = 0;
	f_name = 0;
	f_events_all_flags = 0;	// not necessary, it is reset in PreSave()
	f_position = -1;	// not defined yet
	//f_blend_mode = ...
	f_bitmap_caching = -1;
	f_has_matrix = false;
	//f_matrix = ...
	//f_color_transform = ...
	//f_events = ...
}


/** \brief This function returns the flags representing the type of the TagPlace tag
 *
 * This function returns the flag CONTROL, this tag is used to control
 * the display list.
 *
 * This function returns the flag SCRIPT, it can include a script executed
 * when responding to events.
 *
 * This function returns the flag REFERENCE, it references one object which
 * it places in the display list.
 *
 * \return The CONTROL, SCRIPT and REFERENCE bits
 */
TagBase::swf_type_t TagPlace::TypeFlags(void) const
{
	return SWF_TYPE_CONTROL | SWF_TYPE_SCRIPT | SWF_TYPE_REFERENCE;
}


/** \brief Change the identifier of the object to place
 *
 * This function defines the object that this TagPlace inserts in the
 * display list.
 *
 * Note that in newer SWF versions it is possible to not define any
 * object identifier to use the TagPlace to remove an object at the
 * specified depth. Thus calling this function is not mandatory.
 * Also, if you do call this function, you lose that other functionality.
 *
 * \param id The identifier of the object to place
 */
void TagPlace::SetObjectID(sswf_id_t id)
{
	f_id = id;
	f_id_defined = true;
}


/** \brief The depth at which the object is placed
 *
 * The sswf::TagPlace object is used to place an object in the
 * display list. The display list is organized as an array of
 * objects sorted by depth. Each object should have a distinct
 * depth (though older versions of the Flash player would accept
 * multiple objects at the same depth, it may not last forever!)
 *
 * The depth 0 should not be used for your objects.
 *
 * Bit 15 (0x8000) of the depth is used in a special way (thought
 * I cannot find the docs about that right now...). So you really
 * only can use depth 1 to 32767 which is likely to be enough
 * anyway!
 *
 * \param depth The new object depth
 */
void TagPlace::SetDepth(int depth)
{
	f_depth = depth;
}


/** \brief Insert a mask in the display list, not a shape
 *
 * When defining a clipping, this TagPlace must reference
 * either a shape or a text. Other objects will not work
 * (at least up to version 6 of SWF). The referenced object
 * defines a mask to use as a clipping mask.
 *
 * The sswf::TagPlace::SetDepth(int depth) and sswf::TagPlace::SetClip(int depth)
 * functions must both be called to define the range of depths affected by the
 * clipping. All the objects placed in that range will be affected by this
 * clipping mask. The depths defined in the Depth and Clip are inclusive.
 *
 * \bug The depth and clipping depth are hard coded and thus this feature
 * is not that practical to use.
 *
 * \param depth The top of the clipping range
 */
void TagPlace::SetClip(int depth)
{
	f_clip_depth = depth;
}


/** \brief Set the geometric transformation matrix
 *
 * This function defines the transformation matrix used to move, scale,
 * rotate and skew the referenced object on the screen.
 *
 * \note
 * A flag is used to know whether the matrix was changed. Unfortunately
 * this means setting an identity matrix will result in incorporating
 * the matrix in the result even thought it is useless. But it seemed
 * also that without incorporating the matrix in the TagPlace, you could
 * not tweak it with an ActionScript. This may have been fixed since I
 * checked (or maybe my test was bogus...). So it could be useful to
 * have a matrix even if it is the identity.
 *
 * \param matrix The matrix to apply to the object
 */
void TagPlace::SetMatrix(const Matrix& matrix)
{
	f_matrix = matrix;
	f_has_matrix = true;
}


/** \brief Set the color transformation matrix
 *
 * This function sets the transformation matrix to apply on the color.
 *
 * This matrix can affect the alpha channel. It can be used to
 * transform the colors by scaling the components and adding a bias
 * to each component. It is not a full matrix. To tweak the full
 * matrix, you need to use the color matrix filter instead (which
 * requires version 8 of SWF also).
 *
 * \param color_transform The color matrix to save in this PlaceObject
 */
void TagPlace::SetColorTransform(const ColorTransform& color_transform)
{
	f_color_transform = color_transform;
}


/** \brief Defines the name of a TagPlace object
 *
 * This function is used to define the name of the TagPlace object.
 *
 * The name can later be used to reference the object from an ActionScript.
 *
 * By default, an object being placed has no name.
 *
 * You can set the name to NULL or "" to remove the name.
 *
 * \param name The name of the object
 */
void TagPlace::SetName(const char *name)
{
	MemFree(f_name);
	f_name = StrDup(name);
}


/** \brief Set the morphing position
 *
 * This function is used to change the position of the morph.
 *
 * This is a static setup in a TagPlace object.
 *
 * The position of 0 represents the first object and a position of
 * 65535 represents the last object.
 *
 * By default the position is set to -1 meaning that you are not
 * referencing a morph object.
 *
 * Placing a morph object without a position is similar to placing
 * that object with a position of 0, thought, for forward compatibility,
 * it is a good idea to always use a corresponding place.
 *
 * \param position The morph position
 */
void TagPlace::SetMorphPosition(int position)
{
	f_position = position;
}


/** \brief Defines a blending filter
 *
 * This function defines the filter to use with this TagPlace object.
 * It determines how the object is rendered on the screen.
 *
 * By specifying a blend mode, TagPlace generates one of the
 * SWF_TAG_PLACE_OBJECT2 or SWF_TAG_PLACE_OBJECT3.
 *
 * \sa sswf::TagPlace::SetBlendModeName(const char *blend_mode_name)
 */
void TagPlace::SetBlendMode(const BlendMode& blend_mode)
{
	f_blend_mode = blend_mode;
}




/** \brief Determine whether bitmap caching can be done
 *
 * This function sets the flag \e bitmap \e caching to true or false.
 *
 * Newer versions of SWF can save the bitmaps in buffers and not re-render
 * everything on each frame. This is particularly useful if you create a
 * complex but inanimated sprite which can therefore be drawn once in a
 * buffer and any number of time from that buffer to the final display
 * screen.
 *
 * \param bitmap_caching \em needs testing, use 0 or 1 to not set or set the bitmap caching
 */
void TagPlace::SetBitmapCaching(int bitmap_caching)
{
	f_bitmap_caching = bitmap_caching;
}


/** \brief Mark the TagPlace as a replacement
 *
 * The tag is used to replace another object at the specified depth.
 *
 * If no object reference is defined, then this becomes a "remove" instead
 * (in other words, it always removes, and may insert)
 *
 * \sa sswf::TagPlace::Place(void)
 * \sa sswf::TagPlace::SetObjectID(sswf_id_t id)
 * \sa sswf::TagRemove
 */
void TagPlace::Replace(void)
{
	f_replace = 0;
}


/** \brief Mark the TagPlace as an additional placement
 *
 * This function marks this TagPlace as an 'add' function. The object
 * referenced will be added to the display list whether there is already
 * an object at the same depth.
 *
 * \note
 * When you add new objects at the same depth, the first ones stay on
 * top and the last go below the other objects. This was true in older
 * versions and is likely working the same way.
 *
 * \sa sswf::TagPlace::Replace(void)
 * \sa sswf::TagPlace::SetObjectID(sswf_id_t id)
 */
void TagPlace::Place(void)
{
	f_replace = 1;
}


/** \brief Add the specified event to the place tag
 *
 * This function adds the specified event to the TagPlace object.
 *
 * An event is a set of flags which determine when the event is
 * triggered and a set of actions to execute then.
 *
 * As many events as you need can be added. However, the first
 * event matching has its ActionScript executed and then the
 * process stops (to be confirmed!)
 *
 * \param event The event to add to this TagPlace object
 *
 * \return This function returns true if the event is accepted
 * as is. If the event has flags which are not compatible with
 * the TagPlace tag, then the function returns false and the
 * event is not added.
 */
bool TagPlace::AddEvent(Event *event)
{
	if((event->Events() & ~(SSWF_EVENT_V5 | SSWF_EVENT_V6 | SSWF_EVENT_V7)) != 0) {
		return false;
	}

	f_events.Set(-1, event);

	return true;
}


/** \brief Prepare the object to be saved
 *
 * This function checks all the parameters to determine the minimum
 * version required to save this TagPlace object.
 *
 * By default, the minimum is considered to be 1.
 *
 * The use of events forces the version to be 5, 6 or 7 depending on
 * the actions and flags used.
 *
 * The use of a name, no identifier, a color transform with an alpha
 * channel, a clip depth forces the version to a minimum of 3.
 *
 * \return Zero when no error occurs, non-zero otherwise
 */
ErrorManager::error_code_t TagPlace::PreSave(void)
{
	int		idx;
	long		v, version;
	Event		*event;

	f_events_all_flags = 0;

	if(f_blend_mode.HasBlendMode()
	|| f_bitmap_caching != -1) {
		version = 8;
	}
	else {
		version = 3;
	}

	if(version >= 8
	|| f_replace == 0
	|| !f_id_defined
	|| f_position != -1
	|| f_name != 0
	|| !f_color_transform.IsSolidCompatible()
	|| f_clip_depth != 0) {
		/* in this case the order in which we check the events doesn't matter */
		idx = f_events.Count();
		while(idx > 0) {
			idx--;
			event = dynamic_cast<Event *>(f_events.Get(idx));
			f_events_all_flags |= event->Events();
			v = Action::MinimumListVersion(event->Actions());
			if(v > version) {
				version = v;
			}
		}
		if(f_events_all_flags != 0) {
			// generate an error on flags which should not be set
			if((f_events_all_flags & ~(SSWF_EVENT_V5 | SSWF_EVENT_V6 | SSWF_EVENT_V7)) != 0) {
				return ErrorManager::ERROR_CODE_UNEXPECTED_EVENT_FLAG;
			}
			if((f_events_all_flags & SSWF_EVENT_V7) != 0) {
				v = 7;
			}
			else if((f_events_all_flags & SSWF_EVENT_V6) != 0) {
				v = 6;
			}
			else {
				v = 5;
			}
		}
		else {
			v = 3;
		}
		if(v > version) {
			version = v;
		}
		MinimumVersion((unsigned char) version);
	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief Save the TagPlace in the specified Data buffer
 *
 * This function saves the TagPlace in the specified Data buffer.
 *
 * The tag is saved only if the depth is correct.
 *
 * \param data The Data buffer where the tag is saved
 *
 * \return Zero when no error occurs, non-zero otherwise
 */
ErrorManager::error_code_t TagPlace::Save(Data& data)
{
	Data				sub_data, action_data;
	swf_tag_t			tag;
	int				idx, max;
	unsigned long			flags;
	Action				*action;
	Event				*event;
	ErrorManager::error_code_t	ec;

	if(f_depth < 0 || f_depth > 65535) {
		// bad depth
		return OnError(ErrorManager::ERROR_CODE_INVALID_DEPTH, "depth out of bounds (0 <= %d <= 65535 not satisfied)", f_depth);
	}

	if(f_blend_mode.HasBlendMode()
	|| f_bitmap_caching != -1
	|| f_replace == 0
	|| !f_id_defined
	|| f_position != -1
	|| f_name != 0
	|| !f_color_transform.IsSolidCompatible()
	|| f_clip_depth != 0
	|| f_events_all_flags != 0) {
		if(f_blend_mode.HasBlendMode()
		|| f_bitmap_caching != -1) {
			tag = SWF_TAG_PLACE_OBJECT3;
			sub_data.WriteBits(0, 5);
			sub_data.WriteBits(f_bitmap_caching != -1 ? 1 : 0, 1);
			sub_data.WriteBits(f_blend_mode.HasBlendMode() ? 1 : 0, 1);
			sub_data.WriteBits(0, 1);
		}
		else {
			tag = SWF_TAG_PLACE_OBJECT2;
		}
		sub_data.WriteBits(f_events_all_flags != 0, 1);	// TODO: events + actions (v5+)
		sub_data.WriteBits(f_clip_depth != 0, 1);
		sub_data.WriteBits(f_name != 0, 1);
		sub_data.WriteBits(f_position != -1, 1);
		sub_data.WriteBits(!f_color_transform.IsNull(true), 1);
		sub_data.WriteBits(f_has_matrix, 1);
		sub_data.WriteBits(f_id_defined, 1);
		/* note that if both f_replace and f_id_defined are zero, then the Macromedia plugin crashes! */
		sub_data.WriteBits(f_replace == 0 && !f_id_defined ? 1 : f_replace, 1);
		sub_data.PutShort(f_depth);
		if(f_id_defined) {
			sub_data.PutShort(f_id);
		}
		if(f_has_matrix) {
			// we have to save null matrices in a PlaceObject2 because we
			// often need the translate of (0, 0)!!!
			f_matrix.Save(sub_data);
		}
		if(!f_color_transform.IsNull(true)) {
			f_color_transform.Save(sub_data, true);
		}
		if(f_position != -1) {
			sub_data.PutShort(f_position);
		}
		if(f_name != 0) {
			ec = SaveString(sub_data, f_name);
			if(ec != ErrorManager::ERROR_CODE_NONE) {
				return ec;
			}
		}
		if(f_clip_depth != 0) {
			sub_data.PutShort((unsigned short) f_clip_depth);
		}
		if(f_blend_mode.HasBlendMode()) {
			f_blend_mode.Save(sub_data);
		}
		if(f_bitmap_caching != -1) {
			sub_data.PutByte(f_bitmap_caching);
		}
		if(f_events_all_flags != 0) {
			max = f_events.Count();
			if(Version() == 5) {
				/* version 5 uses U16 for event masks (to be confirmed) */
				sub_data.PutShort(0);
				sub_data.PutShort((unsigned short) f_events_all_flags);
				for(idx = 0; idx < max; idx++) {
					event = dynamic_cast<Event *>(f_events.Get(idx));
					sub_data.PutShort((unsigned short) event->Events());
					action_data.Empty();
					if(event->Actions().Count() > 0) {
						action = dynamic_cast<Action *>(event->Actions().Get(0));
						action->SaveList(&event->Actions(), action_data);
					}
					sub_data.PutLong(action_data.ByteSize());
					sub_data.Append(action_data);
				}
				sub_data.PutShort(0);
			}
			else {
				/* version 6+ uses U32 for event masks */
				sub_data.PutShort(0);
				sub_data.PutLong(f_events_all_flags);
				for(idx = 0; idx < max; idx++) {
					event = dynamic_cast<Event *>(f_events.Get(idx));
					flags = event->Events();
					sub_data.PutLong(flags);
					action_data.Empty();
					if((flags & Event::EVENT_KEY_PRESS) != 0) {
						action_data.PutByte(event->Key());
					}
					if(event->Actions().Count() > 0) {
						action = dynamic_cast<Action *>(event->Actions().Get(0));
						action->SaveList(&event->Actions(), action_data);
					}
					sub_data.PutLong(action_data.ByteSize());
					sub_data.Append(action_data);
				}
				sub_data.PutLong(0);
			}
		}
	}
	else {
		// V1.0 is sufficent for this one
		tag = SWF_TAG_PLACE_OBJECT;
		sub_data.PutShort(f_id);
		sub_data.PutShort(f_depth);
		f_matrix.Save(sub_data);
		if(!f_color_transform.IsNull(false)) {		// save the Color Transformation only if required
			f_color_transform.Save(sub_data, false);
		}
	}

	SaveTag(data, tag, sub_data.ByteSize());
	data.Append(sub_data);

	return ErrorManager::ERROR_CODE_NONE;
}






/* The following options fold the documentation; use 'zi' to turn on and off
 *
 * vim: foldexpr=getline(v\:lnum)!~'^/\\*\\*'&&getline(v\:lnum)!~'^\ \\*'?0\:1 foldcolumn=2 foldmethod=expr
 */
