/* libsswf_tag_button.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::TagButton 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;



/////////////////////////////////////////// State


/** \class sswf::State
 *
 * \brief The state a button will react to
 *
 * This object is used to define a state to put in a button
 * along with actions.
 */

/** \enum sswf::State::state_flags_t
 *
 * \brief The states of the button
 *
 * These flags describe when a given button state is shown or used.
 */


/** \var sswf::State::STATE_FLAG_UP
 *
 * \brief The flag representing the mouse button not being clicked
 *
 * This flag is used to represent the state of the button when the
 * mouse is not being clicked.
 *
 * \sa enum sswf::State::STATE_FLAG_DOWN
 */


/** \var sswf::State::STATE_FLAG_DOWN
 *
 * \brief The flag representing the mouse button when clicked
 *
 * This flag is used to represent the state of the button when the
 * mouse is clicked over the button hit test area.
 *
 * \sa enum sswf::State::STATE_FLAG_UP
 */


/** \var sswf::State::STATE_FLAG_OVER
 *
 * \brief The flag representing the mouse button when over the hit test area
 *
 * This flag is used to represent the state of the button when the
 * mouse is over the button hit test area.
 *
 * Use this flag with the \e up or \e down flags to get the corresponding
 * state.
 *
 * \sa enum sswf::State::STATE_FLAG_DOWN
 * \sa enum sswf::State::STATE_FLAG_UP
 */


/** \var sswf::State::STATE_FLAG_HIT_TEST
 *
 * \brief The flag representing the hit test area
 *
 * This flag is used to represent that special state of the button used
 * to determine the hit test of the button. The hit test is used to know
 * whether the end-user has his mouse pointer over the button or not and
 * when clicking, whether the end-user really clicked on the button.
 *
 * The hit area is never drawn on the screen. You can put it in some
 * other states if you want the shape to appear on the screen.
 */



/** \var sswf::State::STATE_ALL_FLAGS
 *
 * \brief A mask representing all the flags
 *
 * This value is used to know whether all the flags and only the flags
 * you need were defined.
 */





/** \brief Initialize the state
 *
 * This function calls the State::Reset(void) function.
 *
 * \sa sswf::State::Reset(void)
 */
State::State(ErrorManager& error_manager)
	: f_error_manager(error_manager)
{
	Reset();
}


/** \brief Resets a State to default values
 *
 * This function is used to reset a State object to its initial
 * setup.
 *
 * The referenced shape identifier is set to zero (no shape referenced).
 *
 * The flags are set to zero which represents an invalid set of flags;
 * you will have to initialize them with a call to
 * sswf::State::SetFlags(unsigned char flags)
 *
 * The layer is set to zero.
 *
 * The matrix is reset to the identity.
 *
 * The State is marked as not having a color transformation, color transform
 * which is also reset to have no effect.
 *
 * \sa sswf::State::State(void)
 * \sa sswf::State::HasColorTransform(void) const
 * \sa State::SetObjectID(sswf_id_t id)
 * \sa State::SetFlags(unsigned char flags)
 * \sa State::SetLayer(unsigned short layer)
 * \sa State::SetMatrix(const Matrix& matrix)
 * \sa State::SetColorTransform(const ColorTransform& color_transform)
 */
void State::Reset(void)
{
	f_id = 0;
	f_flags = 0;		// invalid value!
	f_layer = 0;
	f_matrix.Reset();
	f_color_transform.Reset();
	f_has_color_transform = false;
	//f_blend_mode = ...
}


/** \brief Check whether this State has a color transformation defined
 *
 * This function can be used to query the State for a color transformation
 * or not.
 *
 * This function is used by the TagButton to check all the states you
 * saved in said button. If any one has a color transformation matrix,
 * then all the states will be saved with a color transformation matrix
 * even one single state makes use of it (i.e. all the others are identity
 * matrices.)
 *
 * \note
 * If the color transformation has no effect, but it was defined that way,
 * then the function still returns true.
 *
 * \return \e true when the State has a color transformation.
 */
bool State::HasColorTransform(void) const
{
	return f_has_color_transform;
}


/** \brief Set the identification of the shape used in the test
 *
 * The object referenced is used for two purposes: one is to draw a
 * different shape on a mouse movement and/or click event (this renders
 * the button quite dynamic,) and the other is to select an area which
 * is sensitive to mouse clicks.
 *
 * In order for a click to be triggered, it is necessary to know where the
 * user needs to place the mouse to trigger the said event. This is done
 * using the hit test shape (the object identifier needs to be the identifier
 * of a shape for the State to function properly. This has changed in version
 * 8 of SWF [it seems, TBD] and a Sprite can also be specified now.)
 *
 * \param[in] id The identifier of the object referenced
 */
void State::SetObjectID(sswf_id_t id)
{
	f_id = id;
}


/** \brief Set the flags this State represents
 *
 * This function is used to define how the button will react in regard
 * to this State. One or more of the following flags can be defined:
 *
 *		STATE_FLAG_UP
 *		STATE_FLAG_OVER
 *		STATE_FLAG_DOWN
 *		STATE_FLAG_HIT_TEST
 *
 * Use the or (|) operator to define multiple flags at the same time.
 *
 * The Reset() function sets the flags to zero. Thus this function must
 * be called before you try saving the corresponding button.
 *
 * \note
 * At least one flag needs to be set or the Save() function will
 * fail.
 *
 * \bug
 * Flags are defined on the lower 4 bits of the char. Trying to define
 * another bit will generate an assert() in DEBUG mode.
 *
 * The state flags should not be an enumeration since these are flags
 * which can be combined.
 *
 * \param[in] flags A set of STATE_FLAG_... flags
 *
 * \sa sswf::State::Save(Data& data, bool color_transform)
 */
bool State::SetFlags(unsigned char flags)
{
	if((flags & ~STATE_ALL_FLAGS) != 0) {
		f_error_manager.OnError(ErrorManager::ERROR_CODE_BAD_STATE_FLAGS, "Invalid flags (0x%02X) for a State object.", (f_flags & ~STATE_ALL_FLAGS));
		return false;
	}
	if(flags == 0) {
		f_error_manager.OnError(ErrorManager::ERROR_CODE_BAD_STATE_FLAGS, "A State object requires at least one flag.");
		return false;
	}

	f_flags = flags;

	return true;
}


/** \brief Get the flags from this State
 *
 * Use this function to retrieve the flags of a state.
 *
 * \sa sswf::State::SetFlags(unsigned char flags)
 */
unsigned char State::GetFlags(void) const
{
	return f_flags;
}


/** \brief Set the state layer (or depth)
 *
 * Each state has a layer (or depth as in the display list) to define
 * the z position of each shape within the button.
 *
 * There is no known limits to the layer number. The objects with a
 * smaller layer number appear behind the object with a larger layer
 * number.
 *
 * The layer number 0 shall not be used.
 *
 * \param[in] layer The layer at which the object is to be saved
 *
 * \sa sswf::TagPlace::SetDepth(int depth)
 */
void State::SetLayer(unsigned short layer)
{
	f_layer = layer;
}


/** \brief Defines the matrix of the object
 *
 * When placing an object on the screen, you can use a transformation
 * matrix (to rotate, scale, skew and translate the object about.)
 *
 * This is similar to the sswf::TagPlace::SetMatrix(const Matrix& matrix)
 * function.
 *
 * \param[in] matrix The transformation Matrix
 *
 * \sa sswf::TagPlace::SetMatrix(const Matrix& matrix)
 */
void State::SetMatrix(const Matrix& matrix)
{
	f_matrix = matrix;
}


/** \brief Set the color transformation matrix
 *
 * This function is used to define a color transformation matrix.
 * This function has the side effect to setting a flag to know
 * whether the color transformation matrix was defined or not.
 *
 * \param[in] color_transform The color transformation matrix
 *
 * \sa State::HasColorTransform(void) const
 */
void State::SetColorTransform(const ColorTransform& color_transform)
{
	f_color_transform = color_transform;
	f_has_color_transform = true;
}


/** \brief Defines the blending mode of the object referenced by this state
 *
 * This function sets the blending mode to be used to render the object
 * of this state. This is the same as the blending mode of the TagPlace
 * object.
 *
 * \param[in] blend_mode The blending mode to use
 */
void State::SetBlendMode(const BlendMode& blend_mode)
{
	f_blend_mode = blend_mode;
}


/** \brief Save a state in a Data buffer
 *
 * This function can be used to save the State in a Data buffer.
 *
 * The color_transform flag is used to know whether the color
 * transform needs to be saved or not.
 *
 * \param[in] data The data buffer where the State shall be saved
 * \param[in] color_transform Whether the color transform matrix is to be saved
 *
 * \return ERROR_CODE_NONE if no error occur, another error code otherwise
 */
ErrorManager::error_code_t State::Save(Data& data, bool color_transform)
{
	int		flags;

	if(f_flags == 0) {
		f_error_manager.OnError(ErrorManager::ERROR_CODE_BAD_STATE_FLAGS, "A State object requires at least one flag.");
		return ErrorManager::ERROR_CODE_BAD_STATE_FLAGS;
	}


	flags = f_flags;
	if(f_blend_mode.HasBlendMode()) {
		flags |= 0x20;
	}
	data.PutByte(flags);
	data.PutShort(f_id);
	data.PutShort(f_layer);
	f_matrix.Save(data);

	if(color_transform) {
		f_color_transform.Save(data);
	}

	if(f_blend_mode.HasBlendMode()) {
		f_blend_mode.Save(data);
	}

	return ErrorManager::ERROR_CODE_NONE;
}





/////////////////////////////////////////// TagButton


/** \class sswf::TagButton
 *
 * \brief Create an active area.
 *
 * A TagButton is used to create an area which reacts to mouse
 * clicks, tabs and the spacebar. When marked as a menu, it can
 * also react to arrow keys.
 *
 * To fully create a button, one can add actions (what happens
 * when the user clicks on the button), events (what happens, in
 * more details, when the user clicks) and different states
 * (what happens when the mouse moves over and clicks on the
 * button or not.)
 *
 * \sa sswf::TagScalingGrid
 * \sa <a href="../SWFalexref.html#tag_definebutton">SWF Alexis' Reference&mdash;Define Button</a>
 * \sa <a href="../SWFalexref.html#swf_tag">SWF Alexis' Reference&mdash;swf_tag</a>
 */


/** \brief Initializes a button tag object
 *
 * This function initializes the button tag with an empty list
 * of actions and states.
 *
 * \param[in] parent The TagHeader where the button is added
 */
TagButton::TagButton(TagBase *parent)
	: TagBaseID("button", parent)
{
}


/** \brief The flags representing the button tag type
 *
 * A button is of type:
 *
 *	SWF_TYPE_DEFINE
 *	SWF_TYPE_REFERENCE
 *	SWF_TYPE_SCRIPT
 *	SWF_TYPE_HAS_ID
 *
 * \return A set of SWF_TYPE_... flags
 */
TagBase::swf_type_t TagButton::TypeFlags(void) const
{
	return SWF_TYPE_DEFINE | SWF_TYPE_REFERENCE | SWF_TYPE_SCRIPT | SWF_TYPE_HAS_ID;
}


/** \brief Add a state to a button object
 *
 * This function adds a state to a button object.
 *
 * Multiple states with the same flags can be added to the same button.
 * In that case, they all become active at the time the states are
 * satisfied.
 *
 * \bug
 * If you create a state and omit to set a valid set of flags (i.e. not
 * zero and only the 4 allowed flags) then this function fails.
 *
 * \param[in] state The state to add to this button object
 *
 * \return true when the state is added
 */
bool TagButton::SetState(const State& state)
{
	State		*s;

	if(state.GetFlags() == 0) {
		OnError(ErrorManager::ERROR_CODE_BAD_STATE_FLAGS, "A State object requires at least one flag. Cannot insert in button.");
		return false;
	}

	s = new State(state);
	MemAttach(s, sizeof(State), "TagButton::SetState() -- state duplicate in Button tag");

	f_states.Set(-1, s);

	return true;
}


/** \brief Append an action to the button
 *
 * This function adds one action at the end of the list of actions of
 * the button. If you want to manage the list better, use the
 * Actions() function to get access to the array of actions.
 *
 * The input action is copied and thus it can be modified on return.
 *
 * \param[in] action The action to append; it is copied in the array
 *
 * \return TagButton::Actions(void)
 */
void TagButton::SetAction(const Action& action)
{
	Action		*a;

	a = action.Duplicate();
	MemAttach(a, sizeof(Action), "TagButton::SetAction() -- action duplicate in Button tag");

	f_actions.Set(-1, a);
}


/** \brief Returns a reference to the actions array
 *
 * This function returns a Vector reference to the internal array
 * of actions.
 *
 * This can be used to save the actions in any order as you need.
 *
 * \return The reference to the actions array
 *
 * \sa TagButton::SetAction(const Action& action)
 */
Vectors& TagButton::Actions(void)
{
	return f_actions;
}


/** \brief Add the specified event to the button tag
 *
 * This function adds the specified event to the TagButton 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[in] 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 TagButton tag, then the function returns
 *	   false and the event is not added.
 */
bool TagButton::AddEvent(Event *event)
{
	if((event->Events() & ~SSWF_EVENT_CONDITIONS) != 0) {
		return false;
	}

	f_events.Set(-1, event);

	return true;
}


/** \brief Set the menu flag.
 *
 * This function is used to set the menu flag of the button.
 *
 * A button with the menu flag turned on can be used to
 * select an entry as if it were a menu.
 *
 * \param[in] menu The new state of the menu flag (true or false)
 */
void TagButton::SetMenu(bool menu)
{
	f_menu = menu != 0;
}


/** \brief Check that a button can be saved.
 *
 * This function traverse the list of events and actions to determine
 * the minimum version and make sure that the events are valid.
 *
 * \return An error_code_t error or ERROR_CODE_NONE
 */
ErrorManager::error_code_t TagButton::PreSave(void)
{
	int		version, v, max;
	unsigned long	events_all_flags;
	State		*s;
	Event		*e;

// by default we save as a DefineButton (v1.x)
	f_save_button2 = false;
	version = 1;

// check all the actions
	v = Action::MinimumListVersion(f_actions);
	if(v > version) {
		version = v;
	}

// when there are events, we must check each of their actions
	max = f_events.Count();
	if(max > 0) {
		events_all_flags = 0;
		f_save_button2 = true;
		while(max > 0) {
			max--;
			e = dynamic_cast<Event *>(f_events.Get(max));
			events_all_flags |= e->Events();
			v = Action::MinimumListVersion(e->Actions());
			if(v > version) {
				version = v;
			}
		}

		// generate an error on flags which should not be set
		if((events_all_flags & ~SSWF_EVENT_CONDITIONS) != 0) {
			return OnError(ErrorManager::ERROR_CODE_UNEXPECTED_EVENT_FLAG, "invalid condition flag in a button event.");
		}
	}

// the f_menu flag means we want at least v3.x
	if(f_menu) {
		f_save_button2 = true;
	}

// if one or more state has a color transform we need a v3.x
	max = f_states.Count();
	if(max == 0) {
		return OnError(ErrorManager::ERROR_CODE_NO_STATES, "cannot save a Button tag without having at least one state.");
	}
	while(max > 0) {
		max--;
		s = dynamic_cast<State *>(f_states.Get(max));
		if(s->HasColorTransform()) {
			// if we find one, that's it we're done here
			f_save_button2 = true;
			break;
		}
	}

	if(f_save_button2 && version < 3) {
		version = 3;
	}

	MinimumVersion(version);

	return GridPreSave();
}


/** \brief Saves a button in the Data buffer
 *
 * This function saves the button in the specified data buffer.
 *
 * It can save button version 1 and 2.
 *
 * The function will generate an error if it detects an invalid
 * event (i.e. an event with all flags set at zero or invalid
 * flags set.) If no event has the hit test bit set, an error
 * is generated, but the save goes on.
 *
 * \param[in] data The data buffer where the button is saved
 */
ErrorManager::error_code_t TagButton::Save(Data& data)
{
	Action				*action;
	Data				sub_data, extra_data;
	int				idx, max, release;
	ErrorManager::error_code_t	ec;
	unsigned char			state_flags;
	unsigned long			cond;
	State				*s;
	Event				*e;

// this object ID
	SaveID(sub_data);

	if(f_save_button2) {
		// at this time there is only one bit (v6.x),
		// in a later version we may need to change the following line.
		sub_data.PutByte(f_menu);
	}

// save the states
	max = f_states.Count();
	if(max == 0) {
		OnError(ErrorManager::ERROR_CODE_BUTTON_MISSING_STATE, "cannot save a Button tag without having at least one state.");
		return ErrorManager::ERROR_CODE_BUTTON_MISSING_STATE;
	}
	state_flags = 0;
	for(idx = 0; idx < max; idx++) {
		s = dynamic_cast<State *>(f_states.Get(idx));
		ec = s->Save(extra_data, f_save_button2);
		if(ec != ErrorManager::ERROR_CODE_NONE) {
			return ec;
		}
		// used to test whether we have at least one hit test area
		state_flags |= s->GetFlags();
	}
	extra_data.PutByte(0);	/* terminates the list of button states */

	if((state_flags & State::STATE_FLAG_HIT_TEST) == 0) {
		OnError(ErrorManager::ERROR_CODE_BUTTON_MISSING_STATE, "a Button tag needs to have at least one state with the Hit Test flag set.");
		// we manage this one as a warning here
	}

// in DefineButton2 we have a list of conditions + actions
// instead of just a list of actions
	if(f_save_button2) {
// in DefineButton2 we save the size of the states before the states themselves
// (so the player can skip them without having to parse them first)
		sub_data.PutShort((short) (extra_data.ByteSize() + 2));
		sub_data.Append(extra_data);

		// handle the special case where a person uses
		// actions as is in the button by inserting these
		// as a POINTER_RELEASE event
		release = 0;
		max = f_events.Count();
		for(idx = 0; idx < max; idx++) {
			extra_data.Empty();
			e = dynamic_cast<Event *>(f_events.Get(idx));
			cond = e->Conditions();
			extra_data.PutShort((short) cond);
			if((cond & Event::CONDITION_POINTER_RELEASE_INSIDE) != 0 && f_actions.Count() > 0) {
				action = dynamic_cast<Action *>(f_actions.Get(0));
				ec = action->SaveList(&f_actions, extra_data, &e->Actions());
				release = 1;
				if(ec != ErrorManager::ERROR_CODE_NONE) {
					return ec;
				}
			}
			else if(e->Actions().Count() > 0) {
				action = dynamic_cast<Action *>(e->Actions().Get(0));
				ec = action->SaveList(&e->Actions(), extra_data);
				if(ec != ErrorManager::ERROR_CODE_NONE) {
					return ec;
				}
			}
			if(idx + 1 == max && (f_actions.Count() == 0 || release == 1)) {
				// last size is always zero
				sub_data.PutShort(0);
			}
			else {
				sub_data.PutShort((short) (extra_data.ByteSize() + 2));
			}
			sub_data.Append(extra_data);
		}
		extra_data.Empty();
		if(f_actions.Count() > 0 && release == 0) {
			sub_data.PutShort(0);		// last size is always zero
			sub_data.PutShort(Event::CONDITION_POINTER_RELEASE_INSIDE);
			action = dynamic_cast<Action *>(f_actions.Get(0));
			ec = action->SaveList(&f_actions, sub_data);
			if(ec != 0) {
				return ec;
			}
		}
		//sub_data.PutShort(0);	/* terminates the list of button conditions */
	}
	else {
// in DefineButton we save the states as is
		sub_data.Append(extra_data);
		extra_data.Empty();

// save the actions
		if(f_actions.Count() > 0) {
			action = dynamic_cast<Action *>(f_actions.Get(0));
			ec = action->SaveList(&f_actions, sub_data);
			if(ec != ErrorManager::ERROR_CODE_NONE) {
				return ec;
			}
		}
		else {
			// if not actions, we still need an ActionEnd
			sub_data.PutByte(Action::ACTION_END);
		}
	}

// create the tag
	SaveTag(data, f_save_button2 ? SWF_TAG_DEFINE_BUTTON2 : SWF_TAG_DEFINE_BUTTON, sub_data.ByteSize());
	data.Append(sub_data);

// now we can also have a scaling grid
	return GridSave(data, Identification());
}





/* 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
 */
