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

/*

Copyright (c) 2002-2009 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.

*/

#define	SSWF_NEED_ASSERT
#include	"sswf.h"
#include	"sswf/libsswf.h"
#include	"sswf/libsswf_asas.h"


#ifdef SSWF_INTERNAL_ASC
#include	"sswf/libsswf_asc.h"
#endif




// Special class so the Import tag looks like
// it is derived from the TagBaseID class!
// This is specific to sswf_save.c++ only and
// it doesn't need to be fully functional.
// (we need to be capable of retrieving the
// proper identifiers at the proper time)
class TagImportID : public sswf::TagBaseID
{
public:
				TagImportID(sswf::TagImport& import, sswf::sswf_id_t id, const char *name = "import-id")
					: TagBaseID(name, import.Parent()), f_import(import), f_id(id)
				{
					// we don't have, want or need
					// any identification for this
					// object!
					NoIdentification();
				}

	virtual sswf::sswf_id_t	Identification(void) const
				{
					return f_id;
				}

	virtual sswf::ErrorManager::error_code_t
				Save(sswf::Data& data)
				{
					// this is not a real tag, there is nothing to save
					return sswf::ErrorManager::ERROR_CODE_NONE;
				}
	virtual sswf::TagBase::swf_type_t
				TypeFlags(void) const
				{
					return 0;
				}

private:
	sswf::TagImport&	f_import;	// the real import tag
	const sswf::sswf_id_t	f_id;		// this specific TagImportID identification
};






struct property_t {
	const char *	name;
	int		value;
};

static const property_t direct_properties[] = {
	{ "X",				 0 },
	{ "Y",				 1 },
	{ "X.SCALE",			 2 },
	{ "Y.SCALE",			 3 },
	{ "CURRENT.FRAME",		 4 },
	{ "NUMBER.OF.FRAMES",		 5 },
	{ "ALPHA",			 6 },
	{ "VISIBILITY",			 7 },
	{ "WIDTH",			 8 },
	{ "HEIGHT",			 9 },
	{ "ROTATION",			10 },
	{ "TARGET",			11 },
	{ "FRAMES.LOADED",		12 },
	{ "NAME",			13 },
	{ "DROP.TARGET",		14 },
	{ "URL",			15 },
	{ "HIGH.QUALITY",		16 },
	{ "SHOW.FOCUS.RECTANGLE",	17 },
	{ "SOUND.BUFFER.TIME",		18 },
	{ "QUALITY",			19 },
	{ "X.MOUSE",			20 },
	{ "Y.MOUSE",			21 },
	{ "WTHIT",			16384 },

	{ NULL, 0 }
};



class SaveState : public sswf::MemoryManager
{
public:
	class ObjectItem : public sswf::ItemBase {
	public:
					ObjectItem(const char *name, sswf::TagBaseID *object)
					{
						f_name = name;
						f_object = object;
					}
					~ObjectItem()
					{
						sswf_free(const_cast<char *>(f_name));
					}
		const char *		Name(void) const { return f_name; }
		const sswf::TagBaseID *	Object(void) const { return f_object; }

	private:
		const char *		f_name;		// name of the object
		sswf::TagBaseID *	f_object;	// only objects with an ID and TagImport are necessary
	};

					SaveState(void)
					{
						f_parent = 0;
						f_d = 0;
						f_last_label = 0;
						f_function_level = 0;
						f_memory_manager = 0;
						f_actions = 0;
					}
	virtual				~SaveState() {}

	void				SetLabel(sswf::TagBase *object, const char *def_label);
	void				RecordRef(sswf::TagBaseID *object, const char *import_name = 0);
	const sswf::TagBaseID *		FindRef(const char *name, const char *type);

	sswf::TagHeader			f_header;	// the header
	sswf::TagBase *			f_parent;	// the current parent (header or sprite)
	const struct data_common_t *	f_d;		// the data to be analyzed
	const struct data_common_t *	f_current_cd;	// the currently analyzed data
	const char *			f_last_label;	// last label found

	// actions related
	unsigned long			f_function_level;// when > 0, we're inside a function (note that a function can include another since a function can define a function member to an object)
	sswf::MemoryManager *		f_memory_manager;// where the actions need to be attached
	sswf::Vectors *			f_actions;	// used to save a list of actions

private:
	sswf::Vectors			f_refs;		// a list of names & objects (struct object_item_t)
};


typedef	int			(*save_child_func)(SaveState& state);

struct tag_list_t {
	enum node_type_t	type;
	save_child_func		func;
};



void SaveState::SetLabel(sswf::TagBase *object, const char *def_label = 0)
{
	// NOTE: labels are used for the Goto's and WaitForFrame's
	//	 within the SWF library; that's it
	if(f_d->name_len > 0) {
		object->SetLabel(f_d->name);
	}
	else if(f_last_label != 0) {
		const_cast<struct data_common_t *>(f_d)->label = f_last_label;
		object->SetLabel(f_last_label);
	}
	else if(def_label != 0) {
		const_cast<struct data_common_t *>(f_d)->label = def_label;
		object->SetLabel(def_label);
	}
	f_last_label = 0;		// use once only
}


void SaveState::RecordRef(sswf::TagBaseID *object, const char *import_name)
{
	ObjectItem		*tl;
	char			*path, *p;

	SetLabel(object);

	// save only those which have a name otherwise we
	// cannot have a reference anyway!
	if(f_d->name_len > 0 || f_d->label != 0) {
		if(f_d->node->data == NULL) {
			// TODO: this is not enough since each parent
			//	 node may likely have a dynamic name!
			f_d->node->data = const_cast<data_common_t *>(f_d);
			path = node_path(f_d->node, 0);
			f_d->node->data = NULL;
		}
		else {
			path = node_path(f_d->node, 0);
		}
		if(import_name != 0) {
			path = sswf_strcat3(p = path, ".", import_name);
			sswf_free(p);
		}
		tl = new ObjectItem(path, object);	// NOTE: the name isn't duplicated!
		MemAttach(tl, sizeof(ObjectItem), "reference structure in sswf_save.c++");
		f_refs.Set(-1, tl);		// append this new entry
	}
}


const sswf::TagBaseID *SaveState::FindRef(const char *name, const char *type)
{
	const ObjectItem	*tl;
	const sswf::TagBaseID	*obj;
	int			max;

	max = f_refs.Count();
	while(max > 0) {
		max--;
		tl = dynamic_cast<const ObjectItem *>(f_refs.Get(max));
		// TODO: a reference to an imported item is accepted as is...
		if(strcmp(tl->Name(), name) == 0 || strcmp(tl->Name(), "imported") == 0) {
			obj = tl->Object();
			/*
			if(strcmp(obj->Name(), "import-id") == 0) {
				dynamic_cast<TagImportID *>(obj)->SelectIdentification(name);
			}
			*/
			if(type != 0 && strcmp(obj->Name(), type) != 0) {
				fprintf(stderr, "ERROR: reference to \"%s\" needs to be to an object of type: %s.\n", name, type);
				errcnt++;
				return 0;
			}
			return obj;
		}
	}

	fflush(stdout);
	fprintf(stderr, "ERROR: the named reference \"%s\" was not found.\n", name);
	errcnt++;

	return 0;
}




/*
 * Lists & blocks call this function so their children are saved
 * as if included as is in the sequence.
 */
static int		save_child(SaveState& state);




static sswf::Action *save_action_branch(SaveState& state, sswf::Action::action_t action, const struct data_list_t *list)
{
	const struct data_common_t	*cd;
	sswf::ActionBranch		*branch;
	int				idx, max;

	branch = new sswf::ActionBranch(state.f_parent, action);
	if(branch == 0) {
		return 0;
	}

	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(cd->type == NODE_TYPE_STRING) {
			branch->SetLabel(cd->name);
		}
		else {
			assert(0, "unknown data type (%d) for an action LABEL", cd->type);
			/*NOTREACHED*/
		}
	}

	return branch;
}



static sswf::Action *save_action_call_frame(SaveState& state, sswf::Action::action_t /*action*/, const struct data_list_t * /*list*/)
{
	return new sswf::ActionCallFrame(state.f_parent);
}



static sswf::Action *save_action_dictionary(SaveState& state, sswf::Action::action_t action, const struct data_list_t *list)
{
	const struct data_common_t	*cd;
	sswf::ActionDictionary		*dictionary;
	int				idx, max;

	dictionary = new sswf::ActionDictionary(state.f_parent);
	if(dictionary == 0) {
		return 0;
	}

	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(cd->type == NODE_TYPE_STRING) {
			dictionary->AddString(cd->name);
		}
		else {
			assert(0, "unknown data type (%d) for a DICTIONARY action, expected a string", cd->type);
			/*NOTREACHED*/
		}
	}

	return dictionary;
}

static sswf::Action *save_action_goto(SaveState& state, sswf::Action::action_t action, const struct data_list_t *list)
{
	struct data_common_t	*cd;
	sswf::ActionGoto	*goto_frame;
	sswf::ActionPushData	*push_data;
	int			idx, max, play;
	char			*frame_name;

	play = -1;		/* undefined */
	frame_name = 0;
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			/* we've got the play flag */
			play = ((struct data_goto_play_t *) cd)->play != 0.0 ? 1 : 0;
		}
		else if(cd->type == NODE_TYPE_STRING){
			/* there is a frame name */
			frame_name = cd->name;
		}
		else {
			assert(0, "unknown data type (%d) for a GOTO action", cd->type);
			/*NOTREACHED*/
		}
	}

	if(frame_name == 0 || action == sswf::Action::ACTION_GOTO_EXPRESSION) {
		if(frame_name != 0) {
			/* we need to push this string on the stack */
			push_data = new sswf::ActionPushData(state.f_parent);
			/* this action won't automatically be registered otherwise! */
			state.f_memory_manager->MemAttach(push_data, sizeof(sswf::Action), "a PUSH DATA action object for a DoAction or a Button");
			push_data->AddString(frame_name);
			state.f_actions->Set(-1, push_data);
		}
		goto_frame = new sswf::ActionGoto(state.f_parent, sswf::Action::ACTION_GOTO_EXPRESSION);
		if(goto_frame != 0) {
			goto_frame->SetPlay(play != 0);
		}
	}
	else {
		if(action == sswf::Action::ACTION_UNKNOWN) {
			action = sswf::Action::ACTION_GOTO_FRAME;
		}
		goto_frame = new sswf::ActionGoto(state.f_parent, action);
		if(goto_frame != 0) {
			goto_frame->SetFrameName(frame_name);
			if(play == 1) {
				/* we stack the action right now and add a PLAY instruction right after */
				state.f_memory_manager->MemAttach(goto_frame, sizeof(sswf::Action), "an action object for a DoAction or a Button");
				state.f_actions->Set(-1, goto_frame);
				return new sswf::Action(state.f_parent, sswf::Action::ACTION_PLAY);
			}
		}
	}

	return goto_frame;
}

static sswf::Action *save_action_label(SaveState& state, sswf::Action::action_t /*action*/, const struct data_list_t *list)
{
	const struct data_common_t	*cd;
	sswf::ActionLabel		*label;
	int				idx, max;

	label = new sswf::ActionLabel(state.f_parent);
	if(label == 0) {
		return 0;
	}

	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(cd->type == NODE_TYPE_STRING) {
			label->SetLabel(cd->name);
		}
		else {
			assert(0, "unknown data type (%d) for a action LABEL", cd->type);
			/*NOTREACHED*/
		}
	}

	return label;
}

static sswf::Action *save_action_pushdata(SaveState& state, sswf::Action::action_t action, const struct data_list_t *list)
{
	const struct data_common_t	*cd;
	const struct data_push_data_t	*data;
	sswf::ActionPushData		*push_data;
	int				idx, max;
	const property_t		*p;
	const char			*name;

	push_data = new sswf::ActionPushData(state.f_parent);
	if(push_data == 0) {
		return 0;
	}

	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			data = (const struct data_push_data_t *) cd;
			switch(static_cast<enum push_data_t>(data->type)) {
			case PUSHDATA_INTEGER:
				push_data->AddInteger((long) rint(data->value));
				break;

			case PUSHDATA_DOUBLE:
				push_data->AddDouble(data->value);
				break;

			case PUSHDATA_FLOAT:
				push_data->AddFloat((float) data->value);
				break;

			case PUSHDATA_BOOLEAN:
				push_data->AddBoolean(data->value != 0);
				break;

			case PUSHDATA_REGISTER:
				push_data->AddRegister((unsigned char) rint(data->value));
				break;

			case PUSHDATA_LOOKUP:
				push_data->AddLookup((unsigned short) rint(data->value));
				break;

			case PUSHDATA_NULL:
				push_data->AddNull();
				break;

			case PUSHDATA_UNDEFINED:
				push_data->AddUndefined();
				break;

			case PUSHDATA_PROPERTY:
				idx++;
				assert(idx < max, "a PROPERTY was specified without a string");
				cd = dc_item(list, idx);
				name = cd->name;
				p = direct_properties;
				while(p->name != NULL) {
					if(strcmp_action(name, p->name) == 0) {
						/* we found the property - add it in Flash V4.0 compatible mode */
						push_data->AddFloat((float) p->value);
						break;
					}
					p++;
				}
				if(p->name == NULL) {
					fprintf(stderr, "ERROR: cannot find property \"%s\".\n", cd->name);
					errcnt++;
				}
				break;

			case PUSHDATA_STRING:
				idx++;
				assert(idx < max, "a PROPERTY was specified without a string");
				cd = dc_item(list, idx);
				if(cd->name_len == 0) {
					// in this case cd->name doesn't always point to a '\0'
					push_data->AddString("");
				}
				else {
					push_data->AddString(cd->name);
				}
				break;

			}
		}
		else {
			assert(0, "unknown data type (%d) for a PUSH DATA action", cd->type);
			/*NOTREACHED*/
		}
	}

	return push_data;
}


static sswf::Action *save_action_store_register(SaveState& state, sswf::Action::action_t action, const struct data_list_t *list)
{
	struct data_common_t		*cd;
	const struct data_register_t	*data;
	sswf::ActionStoreRegister	*store_register;
	int				idx, max, reg;

	store_register = new sswf::ActionStoreRegister(state.f_parent);
	if(store_register == 0) {
		return 0;
	}

	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			data = (const struct data_register_t *) cd;
			reg = (int) rint(data->reg);
			if(reg < 0
			|| (reg >= 4 && state.f_function_level == 0)
			|| (reg > 255)) {
				fprintf(stderr, "ERROR: invalid register number. Outside a function, only registers 0, 1, 2 and 3 are valid. Inside a function, registers 0 to 255 are valid in a version 7 movie.\n");
				errcnt++;
			}
			store_register->SetRegister((unsigned char) reg);
		}
		else {
			assert(0, "unknown data type (%d) for a STORE REGISTER action", cd->type);
			/*NOTREACHED*/
		}
	}

	return store_register;
}


static sswf::Action *save_action_set_target(SaveState& state, sswf::Action::action_t action, const struct data_list_t *list)
{
	struct data_common_t	*cd;
	sswf::ActionSetTarget	*set_target;
	sswf::Action		*a;
	const char		*target_name;
	int			idx, max;

	target_name = 0;
	if(list != 0) {
		max = list->count;
		for(idx = 0; idx < max; idx++) {
			cd = dc_item(list, idx);
			if(cd->type == NODE_TYPE_STRING) {
				if(cd->name_len == 0) {
					target_name = "";
				}
				else {
					target_name = cd->name;
				}
			}
			else {
				assert(0, "unknown data type (%d) for a SET TARGET action", cd->type);
				/*NOTREACHED*/
			}
		}
	}

	if(target_name == 0) {
		a = new sswf::Action(state.f_parent, sswf::Action::ACTION_SET_TARGET2);
	}
	else {
		a = set_target = new sswf::ActionSetTarget(state.f_parent);
		if(set_target != 0) {
			set_target->SetTarget(target_name);
		}
	}

	return a;
}


static sswf::Action *save_action_url(SaveState& state, sswf::Action::action_t action, const struct data_list_t *list)
{
	struct data_common_t	*cd;
	sswf::ActionURL		*url;
	const char		*url_name, *target_name;
	int			idx, max;

	url_name = 0;
	target_name = 0;
	if(list != 0) {
		max = list->count;
		for(idx = 0; idx < max; idx++) {
			cd = dc_item(list, idx);
			if(cd->type == NODE_TYPE_STRING && cd->name_len > 0) {
				if(idx == 0) {
					url_name = cd->name;
				}
				else {
					target_name = cd->name;
				}
			}
			else {
				assert(0, "unknown data type (%d) for a URL action", cd->type);
				/*NOTREACHED*/
			}
		}
	}
	// if no string is specified
	// then we are in the case of a URL2 without variables
	url = new sswf::ActionURL(state.f_parent,
			url_name != 0 && target_name != 0 ? sswf::Action::ACTION_URL : sswf::Action::ACTION_URL2);
	if(url == 0) {
		return 0;
	}

	if(url_name != 0) {
		if(target_name != 0) {
			// both strings defined, assume a direct URL (not URL2)
			url->SetURL(url_name, target_name);
		}
		else {
			// only one string, check for the method
			if(strcasecmp(url_name, "GET") == 0) {
				url->SetMethod(sswf::ActionURL::URL_METHOD_GET);
			}
			else if(strcasecmp(url_name, "POST") == 0) {
				url->SetMethod(sswf::ActionURL::URL_METHOD_GET);
			}
			else if(strncasecmp(url_name, "NOVAR", 5) == 0) {
				url->SetMethod(sswf::ActionURL::URL_METHOD_NOVARIABLE);
			}
			else {
fprintf(stderr, "ERROR: unknown method for a URL instruction; we currently accept GET, POST and NOVAR[IABLE].\n");
				errcnt++;
			}
		}
	}
	//assert(url_name != 0 && target_name != 0, "one of the url or target was not defined for a URL action");

	return url;
}


static int save_actions(SaveState& state);
static sswf::Action *save_action_wait_for_frame(SaveState& state, sswf::Action::action_t action, const struct data_list_t *list)
{
	const struct data_common_t	*cd, *save;
	sswf::Vectors			*previous_actions;
	sswf::ActionWaitForFrame	*wff;
	int				idx, max;
	const char			*frame_name;

// we've got to determine the destination frame name first so we can
// know whether we need a static or dynamic WaitForFrame tag.
	frame_name = 0;
	list = dc_list(state.f_current_cd);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		// use the 1st non-null string as the name of the destination frame
		if(cd->type == NODE_TYPE_STRING && cd->name_len > 0) {
			frame_name = cd->name;
			break;
		}
	}

	wff = new sswf::ActionWaitForFrame(state.f_parent,
			frame_name == 0 ? sswf::Action::ACTION_WAIT_FOR_FRAME2
					: sswf::Action::ACTION_WAIT_FOR_FRAME);
	if(wff == 0) {
		return 0;
	}

	if(frame_name != 0) {
		wff->SetFrameName(frame_name);
	}

	save = state.f_d;
	previous_actions = state.f_actions;
	state.f_d = state.f_current_cd;
	state.f_actions = dynamic_cast<sswf::Action *>(wff)->SubList();
	save_actions(state);
	state.f_d = save;
	state.f_actions = previous_actions;

	return wff;
}



static int action_set_func_name_n_params(sswf::ActionFunction *func, const char *s)
{
	char		*p, *e, *a, c, *end;
	int		reg;

	while(isspace(*s)) {
		s++;
	}

	// make a copy since we cannot modify the input string
	p = func->StrDup(s);

	e = p;
	while(*e != '\0' && *e != '(' && *e != ')' && *e != ',' && !isspace(*e)) {
		e++;
	}
	c = *e;
	*e = '\0';

	// set the function name (only the name here)
	// Note that the name can be empty and that's fine
	func->SetName(p);

	while(c != '\0' && c != ')') {
		do {
			e++;
		} while(isspace(*e));
		end = 0;
		a = e;
		while(*e != '\0' && *e != ')' && *e != ',' && !isspace(*e)) {
			if(*e == ':') {
				end = e;
			}
			e++;
		}
		if(a == e) {
			// empty parameter names are not accepted - this happens
			// when there is one or more spaces before the ')' or
			// simple when there isn't any parameter
			break;
		}
		c = *e;
		*e = '\0';
		if(end != 0) {
			*end = '\0';
			reg = atol(end + 1);
			if(reg < 1 || reg > 255) {
				fprintf(stderr, "ERROR: %d is an invalid register specification for a function parameter (the register number must be between 1 and 255 inclusive).\n", reg);
				errcnt++;
				reg = 6;		// set a somewhat valid number
			}
		}
		else {
			reg = -1;
		}
		if(strcmp(a, "_registers") == 0) {
			func->SetRegistersCount(reg);
		}
		else {
			func->AddParameter(a, reg);	// add one argument name
		}
	}

	return 0;
}




static int action_version_func_name_n_params(const char *s)
{
	unsigned char	version;
	const char	*param, *end;
	long		l;

	version = 5;

// skip spaces at the start
	while(isspace(*s)) {
		s++;
	}

// skip the function name; this has no effect on the version
	while(*s != '\0' && *s != '(' && *s != ')' && *s != ',' && !isspace(*s)) {
		s++;
	}

// go through the list of parameters
	while(*s != '\0' && *s != ')') {
		do {
			s++;
		} while(isspace(*s));
		param = s;
		if(*s == '/') {
			// a suppress register is version 7+
			version = 7;
			s++;
		}
		end = 0;
		while(*s != '\0' && *s != ')' && *s != ',' && !isspace(*s)) {
			if(*s == ':' && end == 0) {
				// a register specification is version 7+
				end = s;
				version = 7;
			}
			s++;
		}
		if(param == s) {
			// empty parameter names are not accepted - this happens
			// when there is one or more spaces before the ')' or
			// simple when there isn't any parameter
			break;
		}
		l = end - param;
		if((l ==  4 && strncmp(param, "this",       l) == 0)
		|| (l ==  5 && strncmp(param, "super",      l) == 0)
		|| (l ==  5 && strncmp(param, "_root",      l) == 0)
		|| (l ==  7 && strncmp(param, "_parent",    l) == 0)
		|| (l ==  7 && strncmp(param, "_global",    l) == 0)
		|| (l ==  9 && strncmp(param, "arguments",  l) == 0)
		|| (l == 10 && strncmp(param, "_registers", l) == 0)) {	// to specify the max # of registers
			version = 7;
		}
	}

	return version;
}




static int action_set_catch_identifier(sswf::ActionTry *try_action, const char *s)
{
	char		*p;
	const char	*e;

	while(isspace(*s)) {
		s++;
	}

	e = s;
	while(isxdigit(*e)) {
		e++;
	}

	while(isspace(*e)) {
		e++;
	}

	if(*e == '\0') {
		// this is a register number
		try_action->SetIdentifier(strtol(s, (char **) &e, 0));
		return 0;
	}

	// this is a variable name
	p = try_action->StrDup(s);
	e = p;
	while(!isspace(*p) && *p != '\0') {
		p++;
	}
	*p = '\0';

	try_action->SetIdentifier(e);

	return 0;
}




typedef	sswf::Action *	(*action_setup_t)(SaveState& state, sswf::Action::action_t action, const struct data_list_t *list);


struct action_table_t {
	const char *		name;
	sswf::Action::action_t	action;
	action_setup_t		func;
};


static int save_actions(SaveState& state)
{
	static const action_table_t	actions[] = {
		{
			"ADD",
			sswf::Action::ACTION_ADD_TYPED,
			0
		},
		{
			"ADD.CAST",
			sswf::Action::ACTION_ADD,
			0
		},
		{
			"AND",
			sswf::Action::ACTION_AND,
			0
		},
		{
			"BRANCH",
			sswf::Action::ACTION_BRANCH_ALWAYS,
			save_action_branch
		},
		{
			"BRANCH.ALWAYS",
			sswf::Action::ACTION_BRANCH_ALWAYS,
			save_action_branch
		},
		{
			"BRANCH.IF.TRUE",
			sswf::Action::ACTION_BRANCH_IF_TRUE,
			save_action_branch
		},
		{
			"CALL.FRAME",
			sswf::Action::ACTION_CALL_FRAME,
			save_action_call_frame
		},
		{
			"CALL.FUNCTION",
			sswf::Action::ACTION_CALL_FUNCTION,
			0
		},
		{
			"CALL.METHOD",
			sswf::Action::ACTION_CALL_METHOD,
			0
		},
		{
			"CAST.OBJECT",
			sswf::Action::ACTION_CAST_OBJECT,
			0
		},
		{
			"CHR",
			sswf::Action::ACTION_CHR,
			0
		},
		{
			"CONCATENATE",
			sswf::Action::ACTION_CONCATENATE,
			0
		},
		{
			"CONCATENATE.STRING",
			sswf::Action::ACTION_CONCATENATE,
			0
		},
		{
			"CONSTANT.POOL",
			sswf::Action::ACTION_DECLARE_DICTIONARY,
			save_action_dictionary
		},
		{
			"DECLARE.ARRAY",
			sswf::Action::ACTION_DECLARE_ARRAY,
			0
		},
		{
			"DECLARE.LOCAL.VARIABLE",
			sswf::Action::ACTION_DECLARE_LOCAL_VARIABLE,
			0
		},
		{
			"DECLARE.OBJECT",
			sswf::Action::ACTION_DECLARE_OBJECT,
			0
		},
		{
			"DECREMENT",
			sswf::Action::ACTION_DECREMENT,
			0
		},
		{
			"DELETE",
			sswf::Action::ACTION_DELETE,
			0
		},
		{
			"DICTIONARY",
			sswf::Action::ACTION_DECLARE_DICTIONARY,
			save_action_dictionary
		},
		{
			"DIVIDE",
			sswf::Action::ACTION_DIVIDE,
			0
		},
		{
			"DUPLICATE",
			sswf::Action::ACTION_DUPLICATE,
			0
		},
		{
			"DUPLICATE.SPRITE",
			sswf::Action::ACTION_DUPLICATE_SPRITE,
			0
		},
		{
			"ENUMERATE",
			sswf::Action::ACTION_ENUMERATE,
			0
		},
		{
			"ENUMERATE.OBJECT",
			sswf::Action::ACTION_ENUMERATE_OBJECT,
			0
		},
		{
			"EQUAL",
			sswf::Action::ACTION_EQUAL_TYPED,
			0
		},
		{
			"EQUAL.CAST",
			sswf::Action::ACTION_EQUAL,
			0
		},
		{
			"EXTENDS",
			sswf::Action::ACTION_EXTENDS,
			0
		},
		{
			"FSCOMMAND2",
			sswf::Action::ACTION_FSCOMMAND2,
			0
		},
		{
			"GET.MEMBER",
			sswf::Action::ACTION_GET_MEMBER,
			0
		},
		{
			"GET.PROPERTY",
			sswf::Action::ACTION_GET_PROPERTY,
			0
		},
		{
			"GET.TARGET",
			sswf::Action::ACTION_GET_TARGET,
			0
		},
		{
			"GET.TIMER",
			sswf::Action::ACTION_GET_TIMER,
			0
		},
		{
			"GET.VARIABLE",
			sswf::Action::ACTION_GET_VARIABLE,
			0
		},
		{
			"GOTO",
			sswf::Action::ACTION_UNKNOWN,
			save_action_goto
		},
		{
			"GOTO.EXPRESSION",
			sswf::Action::ACTION_GOTO_EXPRESSION,
			save_action_goto
		},
		{
			"GOTO.FRAME",
			sswf::Action::ACTION_GOTO_FRAME,
			save_action_goto
		},
		{
			"GOTO.LABEL",
			sswf::Action::ACTION_GOTO_LABEL,
			save_action_goto
		},
		{
			"GREATER.THAN",
			sswf::Action::ACTION_GREATER_THAN_TYPED,
			0
		},
		{
			"IF.TRUE",
			sswf::Action::ACTION_BRANCH_IF_TRUE,
			save_action_branch
		},
		{
			"IMPLEMENTS",
			sswf::Action::ACTION_IMPLEMENTS,
			0
		},
		{
			"INCREMENT",
			sswf::Action::ACTION_INCREMENT,
			0
		},
		{
			"INSTANCE.OF",
			sswf::Action::ACTION_INSTANCE_OF,
			0
		},
		{
			"INT",
			sswf::Action::ACTION_INTEGRAL_PART,
			0
		},
		{
			"INTEGRAL.PART",
			sswf::Action::ACTION_INTEGRAL_PART,
			0
		},
		{
			"LESS.THAN",
			sswf::Action::ACTION_LESS_THAN_TYPED,
			0
		},
		{
			"LESS.THAN.CAST",
			sswf::Action::ACTION_LESS_THAN,
			0
		},
		{
			"LOGICAL.AND",
			sswf::Action::ACTION_LOGICAL_AND,
			0
		},
		{
			"LOGICAL.NOT",
			sswf::Action::ACTION_LOGICAL_NOT,
			0
		},
		{
			"LOGICAL.OR",
			sswf::Action::ACTION_LOGICAL_OR,
			0
		},
		{
			"MB.CHR",
			sswf::Action::ACTION_MBCHR,
			0
		},
		{
			"MB.ORD",
			sswf::Action::ACTION_MBORD,
			0
		},
		{
			"MB.STRING.LENGTH",
			sswf::Action::ACTION_MBSTRING_LENGTH,
			0
		},
		{
			"MODULO",
			sswf::Action::ACTION_MODULO,
			0
		},
		{
			"MULTIPLY",
			sswf::Action::ACTION_MULTIPLY,
			0
		},
		{
			"NEW",
			sswf::Action::ACTION_NEW,
			0
		},
		{
			"NEW.METHOD",
			sswf::Action::ACTION_NEW_METHOD,
			0
		},
		{
			"NEXT.FRAME",
			sswf::Action::ACTION_NEXT_FRAME,
			0
		},
		{
			"NUMBER",
			sswf::Action::ACTION_NUMBER,
			0
		},
		{
			"OR",
			sswf::Action::ACTION_OR,
			0
		},
		{
			"ORD",
			sswf::Action::ACTION_ORD,
			0
		},
		{
			"PLAY",
			sswf::Action::ACTION_PLAY,
			0
		},
		{
			"POP",
			sswf::Action::ACTION_POP,
			0
		},
		{
			"PREVIOUS.FRAME",
			sswf::Action::ACTION_PREVIOUS_FRAME,
			0
		},
		{
			"PUSH.DATA",
			sswf::Action::ACTION_PUSH_DATA,
			save_action_pushdata
		},
		{
			"RANDOM",
			sswf::Action::ACTION_RANDOM,
			0
		},
		{
			"REMOVE.SPRITE",
			sswf::Action::ACTION_REMOVE_SPRITE,
			0
		},
		{
			"RETURN",
			sswf::Action::ACTION_RETURN,
			0
		},
		{
			"SET.LOCAL.VARIABLE",
			sswf::Action::ACTION_SET_LOCAL_VARIABLE,
			0
		},
		{
			"SET.MEMBER",
			sswf::Action::ACTION_SET_MEMBER,
			0
		},
		{
			"SET.PROPERTY",
			sswf::Action::ACTION_SET_PROPERTY,
			0
		},
		{
			"SET.TARGET",
			sswf::Action::ACTION_SET_TARGET2,	/* there are two SET TARGETs! */
			save_action_set_target
		},
		{
			"SET.VARIABLE",
			sswf::Action::ACTION_SET_VARIABLE,
			0
		},
		{
			"SHIFT.LEFT",
			sswf::Action::ACTION_SHIFT_LEFT,
			0
		},
		{
			"SHIFT.RIGHT",
			sswf::Action::ACTION_SHIFT_RIGHT,
			0
		},
		{
			"SHIFT.RIGHT.UNSIGNED",
			sswf::Action::ACTION_SHIFT_RIGHT_UNSIGNED,
			0
		},
		{
			"SHL",
			sswf::Action::ACTION_SHIFT_LEFT,
			0
		},
		{
			"SHR",
			sswf::Action::ACTION_SHIFT_RIGHT,
			0
		},
		{
			"START.DRAG",
			sswf::Action::ACTION_START_DRAG,
			0
		},
		{
			"STOP",
			sswf::Action::ACTION_STOP,
			0
		},
		{
			"STOP.DRAG",
			sswf::Action::ACTION_STOP_DRAG,
			0
		},
		{
			"STOP.SOUND",
			sswf::Action::ACTION_STOP_SOUND,
			0
		},
		{
			"STORE.REGISTER",
			sswf::Action::ACTION_STORE_REGISTER,
			save_action_store_register
		},
		{
			"STRICT.EQUAL",
			sswf::Action::ACTION_STRICT_EQUAL,
			0
		},
		{
			"STRING",
			sswf::Action::ACTION_STRING,
			0
		},
		{
			"STRING.EQUAL",
			sswf::Action::ACTION_STRING_EQUAL,
			0
		},
		{
			"STRING.GREATER",
			sswf::Action::ACTION_STRING_GREATER_THAN,
			0
		},
		{
			"STRING.LENGTH",
			sswf::Action::ACTION_STRING_LENGTH,
			0
		},
		{
			"STRING.LESS.THAN",
			sswf::Action::ACTION_STRING_LESS_THAN,
			0
		},
		{
			"SUB.MBSTRING",
			sswf::Action::ACTION_SUB_MBSTRING,
			0
		},
		{
			"SUB.STRING",
			sswf::Action::ACTION_SUB_STRING,
			0
		},
		{	// TODO: this and the SUB.STRING are not ordered!
			"SUBSTRACT",
			sswf::Action::ACTION_SUBTRACT,
			0
		},
		{	// TODO: this and the SUB.STRING are not ordered!
			"SUBTRACT",
			sswf::Action::ACTION_SUBTRACT,
			0
		},
		{
			"SWAP",
			sswf::Action::ACTION_SWAP,
			0
		},
		{
			"THROW",
			sswf::Action::ACTION_THROW,
			0
		},
		{
			"TOGGLE.QUALITY",
			sswf::Action::ACTION_TOGGLE_QUALITY,
			0
		},
		{
			"TRACE",
			sswf::Action::ACTION_TRACE,
			0
		},
		{
			"TYPE.OF",
			sswf::Action::ACTION_TYPE_OF,
			0
		},
		{
			"URL",
			sswf::Action::ACTION_URL,
			save_action_url
		},
		{
			"USHR",
			sswf::Action::ACTION_SHIFT_RIGHT_UNSIGNED,
			0
		},
		{
			"WAIT.FOR.FRAME",
			sswf::Action::ACTION_WAIT_FOR_FRAME,
			save_action_wait_for_frame
		},
		{
			"XOR",
			sswf::Action::ACTION_XOR,
			0
		}
	};
	const int			max_actions = sizeof(actions) / sizeof(action_table_t);

	const struct data_common_t	*cd, *save;
	const struct data_list_t	*list, *sub_list;
	const char			*name;
	int				r, i, j, p, cnt, idx, max, ec;
	sswf::Action			*a;
	sswf::ActionFunction		*func;
	sswf::ActionTry			*try_action;
	sswf::Vectors			*previous_actions;

#if DEBUG
	// ensures the list of actions is sorted
	static bool check = true;
	if(check) {
		check = false;
		i = max_actions - 1;
		while(i > 0) {
			i--;
			assert(strcasecmp(actions[i + 1].name, actions[i].name) > 0,
				"actions table improperly sorted in \"sswf_save.c++: save_actions()\" (%s > %s)",
							actions[i].name, actions[i + 1].name);
		}
	}
#endif

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		state.f_current_cd = cd;
		a = 0;
		switch(cd->type) {
		case NODE_SUBTYPE_ACTION:
			assert(cd->name_len != 0, "an action must always have a name");
			/* cd is a named action */

			i = 0;
			j = max_actions;
			do {
				p = i + (j - i) / 2;
				r = strcmp_action(cd->name, actions[p].name);

//printf("Compare action [%s] & [%s] -> %d\n", cd->name, actions[p].name, r);

				if(r == 0) {
					/* we found it! */
found_it:
					if(actions[p].func == 0) {
						a = new sswf::Action(state.f_parent, actions[p].action);
					}
					else {
						if(cd->length - cd->name_len == sizeof(struct data_common_t)) {
							sub_list = 0;		// empty list
						}
						else {
							sub_list = dc_list(cd);
						}
						a = (*actions[p].func)(state, actions[p].action, sub_list);
					}
					break;
				}
				if(r > 0) {
					i = p + 1;
				}
				else {
					j = p;
				}
			} while(i < j);
			if(a == 0 && i >= j) {
				/*
				 * This should never happen since the sswf_node.c already checks for valid actions
				 * thus the error is most probably because the name of the action includes a '.'
				 */
				/* check 3 entries before, then 3 after */
				j = p;
				for(cnt = 0; p > 0 && cnt < 3; cnt++) {
					p--;
					r = strcmp_action(cd->name, actions[p].name);
					if(r == 0) {
						goto found_it;
					}
				}
				for(cnt = 0, p = j; p < max_actions - 1 && cnt < 3; cnt++) {
					p++;
					r = strcmp_action(cd->name, actions[p].name);
					if(r == 0) {
						goto found_it;
					}
				}
				assert(0, "cannot find action \"%s\" in sswf_save.c++ -- need fixing\n", cd->name);
				/*NOTREACHED*/
			}
			break;

		case NODE_SUBTYPE_LABEL:
			/* ha! a label for the branch instructions and such */
			a = save_action_label(state, sswf::Action::ACTION_LABEL, dc_list(cd));
			break;

		case NODE_SUBTYPE_FUNCTION:
			/* This was wrong, a function without a name is put on the stack
			 * so later it can be added to an object as a function member.
			 * It is named at that time.
			if(cd->name_len == 0) {
				fprintf(stderr, "ERROR: a function must have a name.\n");
				errcnt++;
				return 1;
			}
			 */
			// In case of a function we need to know if any of the
			// actions within it have a reference to a register > 3.
			// If so, we need to use a version 7 function definition.
			{
			sswf::Vectors actions;

			save = state.f_d;
			previous_actions = state.f_actions;
			state.f_function_level++;
			state.f_d = cd;
			state.f_actions = &actions;	//a->SubList();
			ec = save_actions(state);
			state.f_d = save;
			state.f_actions = previous_actions;
			if(ec != 0) {
				return 1;
			}

			// determine the version now
			if(cd->name_len == 0) {
				name = "";
			}
			else {
				name = cd->name;
			}
			i = action_version_func_name_n_params(name);
			if(i < 7 && sswf::Action::GetMaximumRegister(actions) >= 4) {
				i = 7;
			}

			if(i == 7) {
				// Function2
				func = new sswf::ActionFunction(state.f_parent, sswf::Action::ACTION_DECLARE_FUNCTION2);
			}
			else {
				// Function[1]
				func = new sswf::ActionFunction(state.f_parent);
			}
			if(func == 0) {
				break;
			}
			if(action_set_func_name_n_params(func, name) != 0) {
				delete func;
				return 1;
			}
			// Okay, now we're ready to copy the child actions in the function
			j = actions.Count();
			for(i = 0; i < j; i++) {
				func->AddAction(dynamic_cast<sswf::Action *>(actions.Get(i)));
			}
			a = func;
			}
			break;

		case NODE_SUBTYPE_TRY:
			a = try_action = new sswf::ActionTry(state.f_parent);
			if(try_action == 0) {
				break;
			}
			save = state.f_d;
			previous_actions = state.f_actions;
			state.f_d = cd;
			state.f_actions = try_action->SubListTry();
			ec = save_actions(state);
			state.f_d = save;
			state.f_actions = previous_actions;
			if(ec != 0) {
				delete a;
				return 1;
			}

			idx++;
			if(idx >= max) {
				delete a;
				fprintf(stderr, "ERROR: invalid TRY without any CATCH and FINALLY.\n");
				errcnt++;
				return 1;
			}

			cd = dc_item(list, idx);
			state.f_current_cd = cd;
			if(cd->type != NODE_SUBTYPE_CATCH && cd->type != NODE_SUBTYPE_FINALLY) {
				delete a;
				fprintf(stderr, "ERROR: invalid TRY without any CATCH and FINALLY.\n");
				errcnt++;
				return 1;
			}

			if(cd->type == NODE_SUBTYPE_CATCH) {
				if(cd->name_len == 0) {
					delete a;
					fprintf(stderr, "ERROR: a catch must have a variable name or a register number specified\n");
					errcnt++;
					return 1;
				}
				if(action_set_catch_identifier(try_action, cd->name) != 0) {
					delete a;
					return 1;
				}

				save = state.f_d;
				previous_actions = state.f_actions;
				state.f_d = cd;
				state.f_actions = try_action->SubListCatch();
				ec = save_actions(state);
				state.f_d = save;
				state.f_actions = previous_actions;
				if(ec != 0) {
					delete a;
					return 1;
				}

				idx++;
				if(idx >= max) {
					break;
				}

				cd = dc_item(list, idx);
				state.f_current_cd = cd;
			}

			if(cd->type == NODE_SUBTYPE_FINALLY) {
				save = state.f_d;
				previous_actions = state.f_actions;
				state.f_d = cd;
				state.f_actions = try_action->SubListFinally();
				ec = save_actions(state);
				state.f_d = save;
				state.f_actions = previous_actions;
				if(ec != 0) {
					delete a;
					return 1;
				}
			}
			else {
				// in this case we had a CATCH and no FINALLY
				idx--;
			}
			break;

		case NODE_SUBTYPE_CATCH:
			fprintf(stderr, "ERROR: a CATCH was found without a TRY.\n");
			errcnt++;
			break;

		case NODE_SUBTYPE_FINALLY:
			fprintf(stderr, "ERROR: a FINALLY was found without a TRY.\n");
			errcnt++;
			break;

		case NODE_SUBTYPE_WITH:
			a = new sswf::ActionWith(state.f_parent);
			if(a == 0) {
				break;
			}
			save = state.f_d;
			previous_actions = state.f_actions;
			state.f_d = cd;
			state.f_actions = a->SubList();
			ec = save_actions(state);
			state.f_d = save;
			state.f_actions = previous_actions;
			if(ec != 0) {
				return 1;
			}
			break;

		/* in lists of actions we also accept lists! */
		case NODE_SUBTYPE_BLOCK:
		case NODE_SUBTYPE_LIST:
			save = state.f_d;
			state.f_d = cd;
			ec = save_actions(state);
			state.f_d = save;
			if(ec != 0) {
				return 1;
			}
			break;

		// buttons also accept states and who knows what later!
		default:
			// do nothing here at this time
			break;

		}
		if(a != 0) {
			state.f_memory_manager->MemAttach(a, sizeof(sswf::Action), "an action object for a DoAction or a Button");
			state.f_actions->Set(-1, a);
		}
	}

	return 0;
}



static sswf::Event *save_on_event(SaveState& state, sswf::TagBase *parent, const struct data_common_t *events)
{
	sswf::Event			*event;
	int				idx, max, ec;
	const struct data_common_t	*cd, *save, *str;
	const struct data_list_t	*list;
	const struct data_on_event_t	*evt;

	event = new sswf::Event();
	parent->MemAttach(event, sizeof(sswf::Event), "event & action object");

	list = dc_list(events);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		if(list->offset[idx] < 0) {
			cd = dc_item(list, idx);
			evt = (const struct data_on_event_t *) cd;
			idx++;
			assert(idx < max, "expected a string to follow an ON EVENT item");
			str = dc_item(list, idx);
			assert(str->type == NODE_TYPE_STRING, "expected a string to follow an ON EVENT item");
			if(str->name_len > 0) {
				switch(evt->type) {
				case ON_EVENT_TYPE_EVENT:
					event->SetEvents(sswf::Event::StringToEvents(str->name));
					break;

				case ON_EVENT_TYPE_KEY:
					event->SetKey(sswf::Event::StringToKeyCode(str->name));
					break;

				default:
					assert(0, "unknown ON EVENT type");
					/*NOTREACHED*/

				}
			}
		}
	}

	save = state.f_d;
	state.f_d = events;
	state.f_memory_manager = event;
	state.f_actions = &event->Actions();
	ec = save_actions(state);
	state.f_d = save;

	if(ec != 0) {
		delete event;
		return 0;
	}

	return event;
}


static void save_set_matrix(sswf::Matrix& matrix, const struct data_matrix_t *mx)
{
	if(mx->sy == -1E+128) {
		matrix.SetScale(mx->sx);
	}
	else {
		matrix.SetScale(mx->sx, mx->sy);
	}
	matrix.SetRotate(mx->angle);
	matrix.SetTranslate(PIXEL_TO_TWIPS(mx->tx), PIXEL_TO_TWIPS(mx->ty));
	matrix.SetSkew(mx->skew0, mx->skew1);
}

static int save_button(SaveState& state)
{
	sswf::TagButton			*button;
	sswf::SRectangle		rect;
	sswf::State			s(state.f_header);
	sswf::Matrix			matrix;
	sswf::Event			*event;
	const sswf::TagBaseID *		obj;
	int				m, i, max, idx, ec;
	const struct data_list_t	*list, *sub_list;
	const struct data_common_t	*cd, *sub;
	const struct data_button_t	*b;
	const struct data_button_flag_t	*f;
	const struct data_rect_t	*r;
	const struct data_matrix_t	*mx;
	const char			*obj_name;

	button = new sswf::TagButton(state.f_parent);
	if(button == 0) {
		return -1;
	}

	state.f_memory_manager = button;
	state.f_actions = &button->Actions();

	ec = save_actions(state);
	if(ec != 0) {
		return 1;
	}

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			f = (struct data_button_flag_t *) cd;
			button->SetMenu(f->menu != 0.0);
		}
		else {
			switch(cd->type) {
			case NODE_SUBTYPE_RECT:
				/* transform the data into a rectangle */
				r = dc_rect(cd);
				rect.SetReorder(PIXEL_TO_TWIPS(r->min_x), PIXEL_TO_TWIPS(r->max_x),
						PIXEL_TO_TWIPS(r->min_y), PIXEL_TO_TWIPS(r->max_y));
				/* save it in the button */
				button->SetGrid(rect);
				break;

			case NODE_SUBTYPE_ON_EVENT:
				event = save_on_event(state, button, cd);
				if(event == 0) {
					return -1;
				}
				button->AddEvent(event);
				break;

			case NODE_SUBTYPE_STATE:
				sub_list = dc_list(cd);
				/* cd is a state */
				s.Reset();
				m = sub_list->count;
				for(i = 0; i < m; i++) {
					sub = dc_item(sub_list, i);
					if(sub_list->offset[i] < 0) {
						b = (struct data_button_t *) sub;
						s.SetFlags(static_cast<unsigned char>(b->state));
						s.SetLayer(static_cast<unsigned short>(b->layer));
					}
					else {
						switch(sub->type) {
						case NODE_SUBTYPE_REFERENCE:
							obj = state.FindRef(sub->name, 0);
							if(obj != 0) {
								obj_name = obj->Name();
								if(strcmp(obj_name, "edit")     == 0
								|| strcmp(obj_name, "imported") == 0
								|| strcmp(obj_name, "shape")    == 0
								|| strcmp(obj_name, "sprite")   == 0
								|| strcmp(obj_name, "text")     == 0) {
									s.SetObjectID(obj->Identification());
								}
								else {
fprintf(stderr, "ERROR: a reference to \"%s\" needs to be an object of type: edit, shape, sprite or text (currently: \"%s\").\n", cd->name, obj_name);
									errcnt++;
								}
							}
							break;

						case NODE_SUBTYPE_MATRIX:
							mx = dc_matrix(sub);
							save_set_matrix(matrix, mx);
							s.SetMatrix(matrix);
							break;

						default:
							/* to avoid warnings */
							assert(0, "unknown data type (%d) for a button state object", cd->type);
							/*NOTREACHED*/

						}
					}
				}
				button->SetState(s);
				break;

			default:	// ignore others, these are actions
				break;

			}
		}
	}

	state.RecordRef(button);

	return 0;
}




static int save_edit_text_setup(SaveState& state, sswf::TagEditText *edit_text, const struct data_list_t *list)
{
	const sswf::TagBaseID		*obj;
	sswf::Color			color;
	int				idx, max;
	const struct data_common_t	*cd;
	const struct data_color_t	*c;
	const struct data_text_setup_t	*setup;

	setup = 0;		// makes the reference crash if still undefined (should never happen)
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		// we skip offsets in case we get any
		if(list->offset[idx] < 0) {
			setup = reinterpret_cast<const struct data_text_setup_t *>(cd);
		}
		else {
			switch(cd->type) {
			case NODE_SUBTYPE_COLOR:
				c = dc_color(cd);
				color.Set(CLAMPED_BYTE_COLOR(c->red), CLAMPED_BYTE_COLOR(c->green),
								CLAMPED_BYTE_COLOR(c->blue), CLAMPED_BYTE_COLOR(c->alpha));
				edit_text->SetColor(color);
				break;

			case NODE_SUBTYPE_REFERENCE:
				assert(setup != 0, "found font reference before font setup structure");
				obj = state.FindRef(cd->name, "font");
				if(obj != 0) {
					edit_text->SetFont(dynamic_cast<const sswf::TagFont *>(obj), PIXEL_TO_TWIPS(setup->height));
				}
				break;

			default:
				/* to avoid warnings */
				assert(0, "unknown data type (%d) for a text setup", cd->type);
				/*NOTREACHED*/

			}
		}
	}

	return 0;
}



static int save_edit_text(SaveState& state)
{
	sswf::TagEditText		*edit_text;
	sswf::SRectangle		rect;
	const struct data_list_t	*list;
	const struct data_common_t	*cd, *str;
	const struct data_edit_text_t	*ed;
	const struct data_rect_t	*r;
	int				idx, max, ec;

	edit_text = new sswf::TagEditText(state.f_parent);
	if(edit_text == 0) {
		return -1;
	}
	state.RecordRef(edit_text);

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			switch(* (long *) cd) {		// switch on the type
			case EDIT_TEXT_TYPE_LAYOUT:
				ed = reinterpret_cast<const struct data_edit_text_t *>(cd);
				if(ed->margin_left != -1E+128) {
					edit_text->SetMargins(PIXEL_TO_TWIPS(ed->margin_left),
							PIXEL_TO_TWIPS(ed->margin_right));
				}
				if(ed->indent != -1E+128) {
					edit_text->SetIndent(PIXEL_TO_TWIPS(ed->indent));
				}
				if(ed->leading != -1E+128) {
					edit_text->SetLeading(PIXEL_TO_TWIPS(ed->leading));
				}
				if(ed->max_length != -1E+128) {
					edit_text->SetMaxLength(static_cast<long>(ed->max_length));
				}
				edit_text->SetWordWrap(ed->word_wrap!= 0.0);
				edit_text->SetMultiline(ed->multiline != 0.0);
				edit_text->SetPassword(ed->password != 0.0);
				edit_text->SetReadOnly(ed->readonly != 0.0);
				edit_text->SetNoSelect(ed->no_select != 0.0);
				edit_text->SetBorder(ed->border != 0.0);
				edit_text->SetOutline(ed->outline != 0.0);
				edit_text->SetHTML(ed->html != 0.0);
				edit_text->SetAutoSize(ed->auto_size != 0.0);
				if(ed->thickness != -1E+128) {
					edit_text->SetThickness(ed->thickness);
					edit_text->SetSharpness(ed->sharpness);
				}
				if(ed->renderer != -1E+128) {
					edit_text->SetRenderer(static_cast<sswf::TagCSMTextSettings::csm_text_renderer_t>(static_cast<int>(ed->renderer)));
				}
				if(ed->grid_fit != -1E+128) {
					edit_text->SetGridFit(static_cast<sswf::TagCSMTextSettings::csm_text_gridfit_t>(static_cast<int>(ed->grid_fit)));
				}
				break;

			case EDIT_TEXT_TYPE_VARIABLE:
				idx++;
				assert(idx < max, "expected the string for the \"EditText\" variable name");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item for the \"EditText\" variable name");
				if(str->name_len > 0) {
					edit_text->SetVariableName(str->name);
				}
				if(idx + 1 < max) {
					str = dc_item(list, idx + 1);
					if(str->type == NODE_TYPE_STRING) {
						// we also have the initial text here
						idx++;
						if(str->name_len > 0) {
							edit_text->SetText(str->name);
						}
					}
				}
				break;

			case EDIT_TEXT_TYPE_INITIAL_TEXT:
				idx++;
				assert(idx < max, "expected the string for the \"EditText\" initial text");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item for the \"EditText\" initial text");
				if(str->name_len > 0) {
					edit_text->SetText(str->name);
				}
				break;

			case EDIT_TEXT_TYPE_USED_GLYPHS:
				idx++;
				assert(idx < max, "expected the string for the \"EditText\" used glyphs");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item for the \"EditText\" used glyphs");
				if(str->name_len > 0) {
					edit_text->SetUsedGlyphs(str->name);
				}
				break;

			case EDIT_TEXT_TYPE_USED_STRING:
				idx++;
				assert(idx < max, "expected the string for the \"EditText\" used string");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item for the \"EditText\" used string");
				if(str->name_len > 0) {
					edit_text->AddUsedString(str->name);
				}
				break;

			case EDIT_TEXT_TYPE_ALIGNMENT:
				idx++;
				assert(idx < max, "expected the string for the \"EditText\" alignment");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item for the \"EditText\" alignment");
				if(str->name_len > 0) {
					// TODO: we need to know what alignments are supported!
					if(strcasecmp(str->name, "left") == 0) {
						edit_text->SetAlign(sswf::TagEditText::EDIT_TEXT_ALIGNMENT_LEFT);
					}
					else if(strcasecmp(str->name, "right") == 0) {
						edit_text->SetAlign(sswf::TagEditText::EDIT_TEXT_ALIGNMENT_RIGHT);
					}
					else if(strcasecmp(str->name, "center") == 0) {
						edit_text->SetAlign(sswf::TagEditText::EDIT_TEXT_ALIGNMENT_CENTER);
					}
					else if(strcasecmp(str->name, "justify") == 0) {
						edit_text->SetAlign(sswf::TagEditText::EDIT_TEXT_ALIGNMENT_JUSTIFY);
					}
					else {
fprintf(stderr, "ERROR: invalid alignment \"%s\", only \"left\", \"right\", \"center\" or \"justify\" are understood.\n", str->name);
						errcnt++;
					}
				}
				break;

			default:
				assert(0, "unknown font type (%ld)", * (long *) cd);
				/*NOTREACHED*/

			}
		}
		else switch(cd->type) {
		case NODE_SUBTYPE_RECT:
			/* transform the data into a rectangle */
			r = dc_rect(cd);
			rect.SetReorder(PIXEL_TO_TWIPS(r->min_x), PIXEL_TO_TWIPS(r->max_x),
					PIXEL_TO_TWIPS(r->min_y), PIXEL_TO_TWIPS(r->max_y));
			/* save it in the edit text */
			edit_text->SetBounds(rect);
			break;

		case NODE_SUBTYPE_TEXT_SETUP:
			ec = save_edit_text_setup(state, edit_text, dc_list(cd));
			if(ec != 0) {
				return ec;
			}
			break;

		default:
			/* to avoid warnings */
			assert(0, "unknown data type (%d) for an edit text object", cd->type);
			/*NOTREACHED*/

		}
	}

	return 0;
}


static int save_do_action(SaveState& state)
{
	sswf::TagDoAction		*do_action;
	const sswf::TagBaseID		*obj;
	const struct data_common_t	*cd, *ascd;
	const struct data_list_t	*list, *aslst;
	int				ec, max, idx;
	int				sprite_identification;
	const char			*actionscript;
	bool				has_action, has_actionscript;
	unsigned long			actionscript_line;
	const char *			actionscript_filename;

	has_action = false;
	has_actionscript = false;
	sprite_identification = -1;
	actionscript = 0;
	actionscript_line = 0;
	actionscript_filename = 0;
	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] >= 0) {		// position, depth & clipping
			switch(cd->type) {
			case NODE_SUBTYPE_REFERENCE:
				obj = state.FindRef(cd->name, "sprite");
				if(obj != 0) {
					sprite_identification = obj->Identification();
				}
				break;

			// skip actions here, these are properly parsed in
			// the save_actions() function below
			case NODE_SUBTYPE_ACTION:
			case NODE_SUBTYPE_LABEL:
			case NODE_SUBTYPE_FUNCTION:
			case NODE_SUBTYPE_WITH:
			case NODE_SUBTYPE_TRY:
			case NODE_SUBTYPE_CATCH:
			case NODE_SUBTYPE_FINALLY:
			case NODE_SUBTYPE_BLOCK:
			case NODE_SUBTYPE_LIST:
				has_action = true;
				break;

			case NODE_SUBTYPE_ACTION_SCRIPT:
				if(has_actionscript) {
fprintf(stderr, "ERROR: found two or more actionscripts in the same DO ACTION\n");
					errcnt++;
				}
				else {
					has_actionscript = true;
					actionscript_line = cd->node->line;
					actionscript_filename = cd->node->filename;
					aslst = dc_list(cd);
					ascd = dc_item(aslst, 0);
					assert(ascd->type == NODE_TYPE_STRING, "an action script can only be represented by a string");
					if(ascd->name_len > 0) {
						actionscript = ascd->name;
					}
				}
				break;

			default:
				assert(0, "unknown data type (%d - \"%s\") for a do action", cd->type, node_type_names[cd->type].name);
				/*NOTREACHED*/

			}
		}
	}

	if(has_action && has_actionscript) {
fprintf(stderr, "ERROR: found actions and an actionscript in the same DO ACTION\n");
		errcnt++;
		return 1;
	}

	if(has_actionscript) {
		// we want to compile & assemble this one
		assert(actionscript != 0, "expected the action script variable to be set when ready to use it");
		sswf::asas::Compiler *compiler = new sswf::asas::Compiler();
		sswf::as::NodePtr program;
		sswf::as::Options options;
//fprintf(stderr, "Start the compiler at line #%ld.\n", actionscript_line);

#ifdef SSWF_INTERNAL_ASC
		// must be done before the call to Compile()
		sswf::asc::AscInputRetriever retriever;
		compiler->SetInputRetriever(retriever);
#endif

		compiler->SetOptions(options);
		ec = compiler->Compile(program, actionscript, actionscript_line, actionscript_filename);
//fprintf(stderr, "Compiler returned %d errors!\n", ec);
		if(ec == 0) {
//fprintf(stderr, "And now the assembler.\n");
			sswf::asas::Assembler *assembler = new sswf::asas::Assembler();
			assembler->SetOptions(options);
			ec = assembler->AssembleClasses(program, *state.f_parent, has_action);
			if(ec == 0 && has_action) {
				do_action = new sswf::TagDoAction(state.f_parent);
				if(do_action == 0) {
					return -1;
				}
				state.SetLabel(do_action);
				if(sprite_identification != -1) {
					do_action->SetSprite(sprite_identification);
				}
				ec = assembler->Assemble(program, *do_action, &do_action->Actions());
			}
			if(ec != 0) {
				fprintf(stderr, "ERROR: the assembler failed.\n");
			}
			delete assembler;
		}
		else {
			fprintf(stderr, "ERROR: the compiler failed.\n");
		}
		delete compiler;
//fprintf(stderr, "Everything is fine here?! (ec = %d)\n", ec);
		if(ec != 0) {
			errcnt += ec;
			return 1;
		}
		return 0;
	}

	do_action = new sswf::TagDoAction(state.f_parent);
	if(do_action == 0) {
		return -1;
	}
	state.SetLabel(do_action);
	if(sprite_identification != -1) {
		do_action->SetSprite(sprite_identification);
	}

	state.f_memory_manager = do_action;
	state.f_actions = &do_action->Actions();

	ec = save_actions(state);
	if(ec != 0) {
		return 1;
	}

	return 0;
}


static int save_end(SaveState& state)
{
	sswf::TagEnd		*end;

	end = new sswf::TagEnd(state.f_parent);
	if(end == 0) {
		return -1;
	}

	// NOTE: at this time having the same label multiple times is
	//	 accepted, otherwise it should be marked as being a
	//	 default to avoid duplication errors
	state.SetLabel(end, "end");

	return 0;
}




static int save_export(SaveState& state)
{
	sswf::TagExport			*export_tag;
	const sswf::TagBaseID		*obj;
	const sswf::TagFont		*font;
	const struct data_common_t	*cd, *str, *glyphs;
	const struct data_list_t	*list;
	const char			*used_glyphs;
	int				max, idx;

	export_tag = new sswf::TagExport(state.f_parent);
	if(export_tag == 0) {
		return -1;
	}
	state.SetLabel(export_tag);

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] >= 0) {		// reference + external name
			switch(cd->type) {
			case NODE_SUBTYPE_REFERENCE:
				idx++;
				assert(idx < max, "expected the string for the object name");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				assert(str->name_len != 0, "a reference name cannot be an empty string in export");
				obj = state.FindRef(cd->name, 0);
				if(obj != 0) {
					// Fonts need to have their glyphs marked used to be incorporated
					used_glyphs = 0;
					font = dynamic_cast<const sswf::TagFont *>(obj);
					if(font != 0) {
						// how do we know whether it is part of this one or the next?
						idx++;
						if(idx < max) {
							glyphs = dc_item(list, idx);
							assert(glyphs->type == NODE_TYPE_STRING, "expected a string item for the \"EditText\" used glyphs");
							if(glyphs->name_len > 0) {
								used_glyphs = glyphs->name;
							}
						}
					}
					export_tag->SetObject(obj->Identification(), str->name, used_glyphs);
				}
				break;

			default:
				assert(0, "unknown data type (%d - \"%s\") for an export", cd->type, node_type_names[cd->type].name);
				/*NOTREACHED*/

			}
		}
	}



	return 0;
}




static void set_font_type(sswf::TagFont *font, const char *type)
{
	if(strcasecmp(type, "ascii") == 0 || strcasecmp(type, "ansii") == 0) {
		font->SetType(sswf::TagFont::FONT_TYPE_ASCII);
		return;
	}
	if(strcasecmp(type, "unicode") == 0) {
		font->SetType(sswf::TagFont::FONT_TYPE_UNICODE);
		return;
	}
	if(strcasecmp(type, "shiftjis") == 0) {
		font->SetType(sswf::TagFont::FONT_TYPE_SHIFTJIS);
		return;
	}
	if(strcasecmp(type, "italic") == 0) {
		font->SetItalic(true);
		return;
	}
	if(strcasecmp(type, "bold") == 0) {
		font->SetBold(true);
		return;
	}
	/* the user can force the WIDE flag -- not sure why... */
	if(strcasecmp(type, "wide") == 0) {
		font->SetWide(true);
		return;
	}

	fprintf(stderr, "ERROR: don't understand font type \"%s\".\n", type);
	errcnt++;
}


static sswf::sswf_ucs4_t font_wide_char(int len, const char *str, sswf::sswf_ucs4_t *kern)
{
	sswf::sswf_ucs4_t	*s, c[2];
	size_t			sz;

	if(len == 0 || str == 0) {
		return 0;
	}

	if(*str == '\0') {
		return 0;
	}

	if(*str != '\\') {
		sz = sizeof(c);
		s = c;
		if(sswf::mbtowc(str, len, s, sz) != 0) {
			return 0;
		}
		if(kern != 0) {
			// if it is a kerning definition, we want two chars
			if(sz != 0) {
				return 0;
			}
			*kern = c[1];
		}
		else {
			// if not a kerning definition, we want only one char
			if(sz == 0) {
				return 0;
			}
		}
		return c[0];
	}

	/* a stand alone backslash? */
	if(str[1] == '\0') {
		return (int) '\\';
	}

/* LEGACY CODE, THE NEW CODE ALLOWS FOR WIDE CHARS IN SOURCE FILES
 * AND THAT SHOULD BE USED INSTEAD (i.e. \x276 is prefered to the
 * old \\x276)
 * NOTE: the character '\x276' are automatically transformed in
 *	 the sswf_lexical.c++ file; the following will take care
 *	 of '\\x276' since the lexical will have transformed the
 *	 '\\...' into '\...'.
 */
	if(str[1] != 'x' && str[1] != 'X') {
		return 0;
	}

	/* we have a wide character definition alright */
	return strtol(str + 2, NULL, 16);
}


static int save_font(SaveState& state)
{
	sswf::TagFont			*font;
	const sswf::TagBaseID		*obj;
	sswf::TagFont::font_language_t	lang;
	const struct data_list_t	*list;
	const struct data_common_t	*cd, *str, *ref;
	const struct data_font_t	*f;
	const struct data_font_advance_t*a;
	const struct data_font_kerning_t*k;
	const char			*s;
	long				ascent, descent, leading_height;
	int				idx, max, len;
	sswf::sswf_ucs4_t		c, c2;

	font = new sswf::TagFont(state.f_parent);
	if(font == 0) {
		return -1;
	}
	state.RecordRef(font);

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			switch(* (long *) cd) {		// switch on the type
			case FONT_TYPE_LAYOUT:
				// font information
				f = (const struct data_font_t *) cd;
				if(f->ascent != -1E+128 || f->descent != -1E+128 || f->leading_height != -1E+128) {
					ascent = f->ascent == -1E+128 ? font->DefaultAscent() : (long) PIXEL_TO_TWIPS(f->ascent);
					descent = f->descent == -1E+128 ? font->DefaultDescent() : (long) PIXEL_TO_TWIPS(f->descent);
					leading_height = f->leading_height == -1E+128 ? font->DefaultLeadingHeight() : (long) PIXEL_TO_TWIPS(f->leading_height);
					font->SetLayout(ascent, descent, leading_height);
				}
				if(f->advance != -1E+128) {
					font->SetDefaultAdvance(PIXEL_TO_TWIPS(f->advance));
				}
				if(f->space != -1E+128) {
					font->SetSpaceAdvance(PIXEL_TO_TWIPS(f->space));
				}
				break;

			case FONT_TYPE_ADVANCE:
				// this is when we have a character name, shape reference and specific advance
				idx++;
				assert(idx < max, "expected the character in a string");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				// TODO: the characters larger than 0xFFFF must be supported for
				// chinese, korean and other asian fonts...
				len = strlen(str->name);
				c = font_wide_char(len, str->name, 0);
				if(c == 0) {
					if(len == 0) {
						s = "";
					}
					else {
						s = str->name;
					}
					fprintf(stderr, "ERROR: a character definition requires a string of exactly 1 character (\"%s\" -", s);
					while(*s != '\0') {
						fprintf(stderr, " 0x%02X", (int) *s);
						s++;
					}
					fprintf(stderr, " - refused).\n");
					errcnt++;
					break;
				}
				idx++;
				assert(idx < max, "expected a reference to a shape (not present)");
				ref = dc_item(list, idx);
				assert(ref->type == NODE_SUBTYPE_REFERENCE, "expected a reference to a shape (invalid type: %d)", ref->type);
				obj = state.FindRef(ref->name, "shape");
				if(obj == 0) {
					break;
				}
				a = (const struct data_font_advance_t *) cd;
				if(a->x == -1E+128) {
					font->AddGlyph(c, obj);
				}
				else {
					font->AddGlyph(c, obj, PIXEL_TO_TWIPS(a->x));
				}
				break;

			case FONT_TYPE_LANGUAGE:
				idx++;
				assert(idx < max, "expected the string for the font language");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len > 0) {
					lang = sswf::TagFont::StringToLanguage(str->name);
					if(lang < 0 || lang >= sswf::TagFont::FONT_LANGUAGE_max) {
fprintf(stderr, "ERROR: language \"%s\" is unknown or is not supported.\n", str->name);
						errcnt++;
					}
					else {
						font->SetLanguage(lang);
					}
				}
				break;

			case FONT_TYPE_NAME:
				idx++;
				assert(idx < max, "expected the string for the font name");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len > 0) {
					font->SetName(str->name);
				}
				break;

			case FONT_TYPE_DISPLAY_NAME:
				idx++;
				assert(idx < max, "expected the string for the font display name");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len > 0) {
					font->SetDisplayName(str->name);
				}
				break;

			case FONT_TYPE_COPYRIGHT:
				idx++;
				assert(idx < max, "expected the string for the font copyright");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len > 0) {
					font->SetCopyright(str->name);
				}
				break;

			case FONT_TYPE_TYPE:
				idx++;
				assert(idx < max, "expected one or more strings to describ the font type");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len > 0) {
					set_font_type(font, str->name);
				}
				/* check for more strings */
				while(idx + 1 < max) {
					str = dc_item(list, idx + 1);
					if(str->type != NODE_TYPE_STRING) {
						break;
					}
					idx++;
					if(str->name_len > 0) {
						set_font_type(font, str->name);
					}
				}
				break;

			case FONT_TYPE_KERNING:
				idx++;
				assert(idx < max, "expected the kerning letters in a string");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				// TODO: the characters larger than 0xFFFF must be supported for
				// chinese, korean and other asian fonts...
				len = strlen(str->name);
				c = font_wide_char(len, str->name, &c2);
				if(c == 0) {
fprintf(stderr, "ERROR: a kerning definition requires a string of exactly 2 characters (\"%s\" refused).\n", str->name);
					errcnt++;
					break;
				}
				k = (const struct data_font_kerning_t *) cd;
				font->AddKern(c, c2, PIXEL_TO_TWIPS(k->x));
				break;

			default:
				assert(0, "unknown font type (%ld)", * (long *) cd);
				/*NOTREACHED*/

			}
		}
		// else ... skip all the others since all are preceeded by a structure
	}

	return 0;
}


static int save_frame_label(SaveState& state)
{
	sswf::TagFrameLabel		*frame_label;

	frame_label = new sswf::TagFrameLabel(state.f_parent);
	if(frame_label == 0) {
		return -1;
	}

	assert(state.f_d->name_len > 0, "a frame label must have a name");
	frame_label->SetLabel(state.f_d->name);
	frame_label->SetFrameLabel(state.f_d->name);

	return 0;
}





static int save_image(SaveState& state)
{
	sswf::TagImage			*image;
	const struct data_image_t	*f;
	const struct data_common_t	*cd, *mask;
	const struct data_list_t	*list;
	int				idx, max;
	const char			*mask_name;

	image = new sswf::TagImage(state.f_parent);
	if(image == 0) {
		return -1;
	}
	state.RecordRef(image);

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			// format
			f = (const struct data_image_t *) cd;
			switch(static_cast<enum image_format_t>(f->format)) {
			case IMAGE_FORMAT_LOSSLESS:
				image->SetFormat(sswf::TagImage::IMAGE_FORMAT_LOSSLESS_BEST);
				break;

			case IMAGE_FORMAT_LOSSLESS_8:
				image->SetFormat(sswf::TagImage::IMAGE_FORMAT_LOSSLESS_8);
				break;

			case IMAGE_FORMAT_LOSSLESS_16:
				image->SetFormat(sswf::TagImage::IMAGE_FORMAT_LOSSLESS_16);
				break;

			case IMAGE_FORMAT_LOSSLESS_32:
				image->SetFormat(sswf::TagImage::IMAGE_FORMAT_LOSSLESS_32);
				break;

			case IMAGE_FORMAT_JPEG:
				image->SetFormat(sswf::TagImage::IMAGE_FORMAT_JPEG);
				if(f->quality != -1E+128) {
					image->SetQuality((long) rint(f->quality * 100.0));
				}
				break;

			}
		}
		else if(cd->type == NODE_TYPE_STRING) {
			// image name
			mask_name = 0;
			if(idx + 1 < max) {
				idx++;
				mask = dc_item(list, idx);
				if(mask->type == NODE_TYPE_STRING) {
					// there is a mask definition as well
					mask_name = mask->name;
				}
				else {
					idx--;
				}
			}
			if(image->SetFilename(cd->name, mask_name) != 0) {
				fprintf(stderr, "ERROR: cannot read image \"%s\" (or mask \"%s\"). Are these targa or JPEG files?\n",
						cd->name, mask_name == 0 ? "<no mask>" : mask_name);
				errcnt++;
				return -1;
			}
		}
		else {
			assert(0, "unknown data type (%d) for an image", cd->type);
		}
	}

	return 0;
}


static int save_import(SaveState& state)
{
	sswf::TagImport			*import;
	TagImportID			*import_id;
	const struct data_import_t	*imp;
	const struct data_common_t	*cd, *str, *type;
	const struct data_list_t	*list;
	int				max, idx;
	const char			*type_name;
	sswf::sswf_id_t			id;

	import = new sswf::TagImport(state.f_parent);
	if(import == 0) {
		return -1;
	}
	state.SetLabel(import);

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			imp = (const struct data_import_t *) cd;
			switch(imp->type) {
			case IMPORT_TYPE_URL:
				idx++;
				assert(idx < max, "expected a string with the external name");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				assert(str->name_len != 0 && str->name[0] != '\0', "a movie URL reference name cannot be an empty string in import");
				import->SetURL(str->name);
				break;

			case IMPORT_TYPE_FILENAME:
				idx++;
				assert(idx < max, "expected a string with the SSWF script filename");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				assert(str->name_len != 0 && str->name[0] != '\0', "a script filename cannot be empty");
				// TODO: how can we really deal with this now?!?
				// we may want to look into doing the 'using <filename>;'
				// first and reuse that for this purpose
				import->SetFilename(str->name);
				break;

			case IMPORT_TYPE_NAME:
				idx++;
				assert(idx < max, "expected a string with the external name reference");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				assert(str->name_len != 0 && str->name[0] != '\0', "a reference name cannot be empty in an IMPORT");

				// if there is a 2nd string, that's the type of the item
				type_name = "imported";
				if(idx + 1 < max && list->offset[idx + 1] >= 0) {
					type = dc_item(list, idx + 1);
					if(type->type == NODE_TYPE_STRING) {
						idx++;
						assert(type->name_len != 0 && type->name[0] != '\0', "a reference type cannot be empty in an IMPORT");
						type_name = type->name;
					}
				}

				import->AddName(str->name, type_name);

				// we also need to add a reference here
				// Note that the name needs to include the cd->name too
				// (....<import tag name>.<cd->name>)
				id = import->HasName(str->name);
				import_id = new TagImportID(*import, id, type_name);
				state.RecordRef(import_id, str->name);
				break;

			default:
				assert(0, "undefined import type");
				/*NOTREACHED*/

			}
		}
		else {
			assert(0, "unknown data type (%d - \"%s\") for an IMPORT object", cd->type, node_type_names[cd->type].name);
			/*NOTREACHED*/
		}
	}

	return 0;
}


static int save_metadata(SaveState& state)
{
	sswf::TagMetadata		*metadata;
	const struct data_metadata_t	*met;
	const struct data_common_t	*cd, *str;
	const struct data_list_t	*list;
	int				max, idx;

	metadata = new sswf::TagMetadata(state.f_parent);
	if(metadata == 0) {
		return -1;
	}

	if(state.f_d->length - state.f_d->name_len == sizeof(struct data_common_t)) {
		// use default
		return 0;
	}

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			met = (const struct data_metadata_t *) cd;
			switch(met->type) {
			case METADATA_TYPE_FILENAME:
				idx++;
				assert(idx < max, "expected a string for the filename");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len != 0) {
					metadata->SetFilename(str->name);
				}
				break;

			case METADATA_TYPE_METADATA:
				idx++;
				assert(idx < max, "expected a string of metadata");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len != 0) {
					metadata->SetMetadata(str->name);
				}
				break;

			case METADATA_TYPE_TITLE:
				idx++;
				assert(idx < max, "expected a string for the movie title");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len != 0) {
					metadata->SetTitle(str->name);
				}
				break;

			case METADATA_TYPE_DESCRIPTION:
				idx++;
				assert(idx < max, "expected a string for the movie description");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len != 0) {
					metadata->SetDescription(str->name);
				}
				break;

			case METADATA_TYPE_AUTHOR:
				idx++;
				assert(idx < max, "expected a string for the movie author");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len != 0) {
					metadata->SetAuthor(str->name);
				}
				break;

			case METADATA_TYPE_PUBLISHER:
				idx++;
				assert(idx < max, "expected a string for the movie publisher");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len != 0) {
					metadata->SetPublisher(str->name);
				}
				break;

			case METADATA_TYPE_COPYRIGHT:
				idx++;
				assert(idx < max, "expected a string for the movie copyright");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len != 0) {
					metadata->SetCopyright(str->name);
				}
				break;

			case METADATA_TYPE_URL:
				idx++;
				assert(idx < max, "expected a string for the movie about URL");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item");
				if(str->name_len != 0) {
					metadata->SetURL(str->name);
				}
				break;

			default:
				assert(0, "undefined metadata type");
				/*NOTREACHED*/

			}
		}
		else {
			assert(0, "unknown data type (%d - \"%s\") for a METADATA object", cd->type, node_type_names[cd->type].name);
			/*NOTREACHED*/
		}
	}

	return 0;
}


static int save_place_object(SaveState& state)
{
	sswf::TagPlace				*place;
	sswf::TagSetTabIndex			*set_tab_index;
	sswf::Matrix				matrix;
	sswf::ColorTransform			color_transform;
	sswf::Event				*event;
	sswf::BlendMode				blend_mode;
	const sswf::TagBaseID			*obj;
	const struct data_common_t		*cd, *str;
	const struct data_list_t		*list, *sub_list;
	const struct data_place_object_t	*place_object;
	const struct data_matrix_t		*m;
	const struct data_color_transform_t	*c;
	const struct data_color_t		*col;
	int					idx, max;
	long					v, d, clip;
	const char				*obj_name;

	place = new sswf::TagPlace(state.f_parent);
	if(place == 0) {
		return -1;
	}
	state.SetLabel(place);

	if(state.f_d->type == NODE_SUBTYPE_REPLACE_OBJECT) {
		// we don't want the move flag to be set in this case
		place->Replace();
	}

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {		// position, depth, clipping, tab index & bitmap type
			place_object = (const struct data_place_object_t *) cd;
			switch(place_object->type) {		// switch on the type
			case PLACE_OBJECT_TYPE_VALUE:
				d = (long) rint(place_object->depth);
				assert(d >= 0 && d <= 65535, "invalid depth parameter");
				place->SetDepth(d);
				if(place_object->clip != -1E+128) {
					clip = (long) rint(place_object->clip);
					assert(clip >= 1 && clip <= 65535, "invalid clip depth parameter");
					assert(clip + d <= 65535, "invalid clip depth parameter");
					place->SetClip(clip + d);
				}
				if(place_object->position >= 0.0) {
					// in our script language we expect a position from 0.0 to 1.0
					v = (long) rint(place_object->position * 65535.0);
					if(v > 65535) {
						v = 65535;
					}
					place->SetMorphPosition(v);
				}
				if(place_object->tab_index != -1E+128) {
					// ha! the person also wants to have a SetTabIndex tag
					set_tab_index = new sswf::TagSetTabIndex(state.f_parent);
					if(set_tab_index == 0) {
						return -1;
					}
					state.SetLabel(set_tab_index);
					set_tab_index->SetDepth(d);
					v = (long) rint(place_object->tab_index);
					assert(v >= 0 && v <= 65535, "invalid tab index parameter");
					set_tab_index->SetIndex(v);
				}
				if(place_object->bitmap_caching != 0.0) {
					// at this time I'm assuming you need to set it to "true"...
					place->SetBitmapCaching(1);
				}
				break;

			case PLACE_OBJECT_TYPE_NAME:
				idx++;
				assert(idx < max, "expected the string for the \"PlaceObject\" name");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item for the \"PlaceObject\" name");
				if(str->name_len > 0) {
					place->SetName(str->name);
					//printf("String = [%s]\n", str->name);
				}
				break;

			case PLACE_OBJECT_TYPE_BLEND_MODE:
				idx++;
				assert(idx < max, "expected the string for the \"PlaceObject\" blend mode");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected a string item for the \"PlaceObject\" blend mode");
				if(str->name_len > 0) {
					if(blend_mode.SetBlendModeByName(str->name)) {
						place->SetBlendMode(blend_mode);
					}
					else {
fprintf(stderr, "ERROR: in a PLACE object, unknown blend mode \"%s\".\n", str->name);
					}
					//printf("Blend Mode = [%s]\n", str->name);
				}
				break;

			}
		}
		else {
			/* all sub-data except the rectangle but that way cd points at the right place then */
			sub_list = dc_list(cd);
			switch(cd->type) {
			case NODE_SUBTYPE_REFERENCE:
				obj = state.FindRef(cd->name, 0);
				if(obj != 0) {
					obj_name = obj->Name();
					if(strcmp(obj_name, "button")   == 0
					|| strcmp(obj_name, "edit")     == 0
					|| strcmp(obj_name, "imported") == 0
					|| strcmp(obj_name, "shape")    == 0
					|| strcmp(obj_name, "sprite")   == 0
					|| strcmp(obj_name, "text")     == 0) {
						place->SetObjectID(obj->Identification());
					}
					else {
fprintf(stderr, "ERROR: in a PLACE object, the reference to \"%s\" needs to be to an object of type: button, edit text, imported, shape, sprite or text (current type \"%s\").\n", cd->name, obj_name);
						errcnt++;
					}
				}
				break;

			case NODE_SUBTYPE_MATRIX:
				m = (const struct data_matrix_t *) sub_list;
				save_set_matrix(matrix, m);
				place->SetMatrix(matrix);
				break;

			case NODE_SUBTYPE_COLOR_TRANSFORM:
				c = (const struct data_color_transform_t *) sub_list;
				color_transform.SetAdd(c->add_red, c->add_green, c->add_blue, c->add_alpha);
				color_transform.SetMult(c->mult_red, c->mult_green, c->mult_blue, c->mult_alpha);
				place->SetColorTransform(color_transform);
				break;

			case NODE_SUBTYPE_COLOR:
				col = (const struct data_color_t *) sub_list;
				color_transform.SetAdd(col->red, col->green, col->blue, col->alpha);
				color_transform.SetMult(0, 0, 0, 0);
				place->SetColorTransform(color_transform);
				break;

			case NODE_SUBTYPE_ON_EVENT:
				event = save_on_event(state, place, cd);
				if(event == 0) {
					return -1;
				}
				place->AddEvent(event);
				break;

			default:
				/* to avoid warnings */
				assert(0, "unknown data type (%d) for a place object", cd->type);
				/*NOTREACHED*/

			}
		}
	}

	return 0;
}



static int save_scene_frame_data(SaveState& state)
{
	sswf::TagSceneFrameData		*scene_frame_data;
	const struct data_common_t	*cd;
	const struct data_list_t	*list;
	int				max, idx;

	scene_frame_data = new sswf::TagSceneFrameData(state.f_parent);
	if(scene_frame_data == 0) {
		return -1;
	}

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(cd->type == NODE_TYPE_STRING) {
			// set the filename
			scene_frame_data->SetFileData(cd->name);
		}
		else {
			assert(0, "unknown data type (%d - \"%s\") for a SCENE & FRAME DATA object", cd->type, node_type_names[cd->type].name);
			/*NOTREACHED*/
		}
	}

	return 0;
}


static int save_remove(SaveState& state)
{
	const sswf::TagBaseID		*obj;
	sswf::TagRemove			*remove;
	const struct data_common_t	*cd;
	const struct data_list_t	*list;
	const struct data_remove_t	*depth;
	int				idx, max;

	remove = new sswf::TagRemove(state.f_parent);
	if(remove == 0) {
		return -1;
	}
	state.SetLabel(remove);

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			depth = (const struct data_remove_t *) cd;
			remove->SetDepth(static_cast<int>(depth->depth));
		}
		else {
			switch(cd->type) {
			case NODE_SUBTYPE_REFERENCE:
				obj = state.FindRef(cd->name, 0);
				if(obj != 0) {
					if(strcmp(obj->Name(), "button")   == 0
					|| strcmp(obj->Name(), "edit")     == 0
					|| strcmp(obj->Name(), "imported") == 0
					|| strcmp(obj->Name(), "shape")    == 0
					|| strcmp(obj->Name(), "sprite")   == 0
					|| strcmp(obj->Name(), "text")     == 0) {
						remove->SetObjectID(obj->Identification());
					}
					else {
fprintf(stderr, "ERROR: in a REMOVE object, the reference to \"%s\" needs to be to an object of type: button, edit text, imported, shape, sprite or text.\n", cd->name);
						errcnt++;
					}
				}
				break;

			default:
				/* to avoid warnings */
				assert(0, "unknown data type (%d) for a remove", cd->type);
				/*NOTREACHED*/

			}
		}
	}

	return 0;
}




static int save_script_limits(SaveState& state)
{
	sswf::TagScriptLimits			*sl;
	const struct data_script_limits_t	*script_limits;

	sl = new sswf::TagScriptLimits(state.f_parent);
	if(sl == 0) {
		return -1;
	}
	state.SetLabel(sl);

	script_limits = dc_script_limits(state.f_d);
	sl->SetMaxRecursionDepth(static_cast<int>(ceil(script_limits->max_recursion_depth)));
	sl->SetTimeoutSeconds(static_cast<int>(ceil(script_limits->timeout_seconds)));

	return 0;
}



static int save_set_background_color(SaveState& state)
{
	sswf::TagSetBackgroundColor	*sbc;
	struct data_list_t		*list;
	struct data_color_t		*col;
	struct data_common_t		*cd;

	sbc = new sswf::TagSetBackgroundColor(state.f_parent);
	if(sbc == 0) {
		return -1;
	}
	state.SetLabel(sbc);

	// get to the color data and put it in the sbc object
	list = dc_list(state.f_d);
	if(list->count == 0) {
		// keep the default (white)
		return 0;
	}

	// a direct color definition?
	if(list->offset[0] < 0) {
		// only RGB is defined in this case
		col = dc_item_color(list, 0);
	}
	else {
		// RGBA is defined here - we don't check the alpha it was done before
		cd = dc_item(list, 0);
		col = dc_color(cd);
	}

	sbc->GetColor().Set(CLAMPED_BYTE_COLOR(col->red), CLAMPED_BYTE_COLOR(col->green), CLAMPED_BYTE_COLOR(col->blue));

	return 0;
}



static int save_set_tab_index(SaveState& state)
{
	sswf::TagSetTabIndex			*sti;
	const struct data_set_tab_index_t	*set_tab_index;

	sti = new sswf::TagSetTabIndex(state.f_parent);
	if(sti == 0) {
		return -1;
	}
	state.SetLabel(sti);

	set_tab_index = dc_set_tab_index(state.f_d);
	sti->SetDepth(static_cast<int>(rint(set_tab_index->depth)));
	sti->SetIndex(static_cast<int>(rint(set_tab_index->index)));

	return 0;
}



static const struct data_common_t *save_find_shape(SaveState& state)
{
	sswf::TagBase			*child;
	const struct data_common_t	*cd;

	// note: a shape is a Define and it cannot be in a sprite
	child = state.f_header.Children();
	while(child != 0) {
		if(child->f_userdata != 0) {
			cd = (const struct data_common_t *) child->f_userdata;
			if(cd->length == state.f_d->length) {
				if(memcmp(cd, state.f_d, state.f_d->length) == 0) {
					// ha! it already exists, do insert it again
					return cd;
				}
			}
		}
		child = child->Next();
	}

	return 0;
}




static int save_shape_line(SaveState& state, sswf::TagShape *shape, const struct data_list_t *list)
{
	sswf::Color			color;
	sswf::Style			style(state.f_header);
	const struct data_common_t	*cd;
	const struct data_line_style_t	*w;
	const struct data_color_t	*col, *col_to;
	int				idx, max;
	double				width, to_width;
	bool				morph;

	col = 0;
	col_to = 0;
	morph = false;
	width = 1;
	to_width = 1;
	w = 0;

/* empty lines still have 1 entry since the width is already included! */
	assert(list != 0, "a list always includes at least the default width");

	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			// we got the width and such
			w = (const struct data_line_style_t *) cd;
			width = w->width;
			if(w->to_width != -1.0) {
				morph = true;
				to_width = w->to_width;
			}
			else {
				to_width = width;
			}
		}
		else {
			// here we have a color
			if(col == 0) {
				col = dc_color(cd);
			}
			else {
				col_to = dc_color(cd);
				morph = true;
			}
		}
	}

	if(col == 0) {		// empty line style -> no line style!
		style.SetType(sswf::Style::STYLE_TYPE_NO_LINE);
	}
	else {
		color.Set(CLAMPED_BYTE_COLOR(col->red), CLAMPED_BYTE_COLOR(col->green),
				CLAMPED_BYTE_COLOR(col->blue), CLAMPED_BYTE_COLOR(col->alpha));
		style.SetLine(0, (unsigned short) PIXEL_TO_TWIPS(width), color);
		if(morph) {
			if(col_to != 0) {
				color.Set(CLAMPED_BYTE_COLOR(col_to->red), CLAMPED_BYTE_COLOR(col_to->green),
						CLAMPED_BYTE_COLOR(col_to->blue), CLAMPED_BYTE_COLOR(col_to->alpha));
			}
			style.SetLine(1, (unsigned short) PIXEL_TO_TWIPS(to_width), color);
		}

		// ensure a correct type (and also that so far everything went well)
		style.SetType(sswf::Style::STYLE_TYPE_LINE);

		if(w != 0) {
			if(static_cast<sswf::Style::cap_t>(static_cast<int>(w->cap_start)) != sswf::Style::STYLE_LINE_CAP_SAME) {
				style.SetLineCaps(static_cast<sswf::Style::cap_t>(static_cast<int>(w->cap_start)),
							static_cast<sswf::Style::cap_t>(static_cast<int>(w->cap_end)));
			}
			if(static_cast<sswf::Style::join_t>(static_cast<int>(w->join)) != sswf::Style::STYLE_LINE_JOIN_UNKNOWN) {
				style.SetLineJoin(static_cast<sswf::Style::join_t>(static_cast<int>(w->join)), w->miter);
			}
			if(w->hscale != -1.0) {
				if(w->vscale == -1.0) {
					style.SetLineScale(w->hscale != 0.0, w->hscale != 0.0);
				}
				else {
					style.SetLineScale(w->hscale != 0.0, w->vscale != 0.0);
				}
			}
			if(w->pixel_hinting != -1.0) {
				style.SetLinePixelHinting(w->pixel_hinting != 0.0);
			}
			if(w->no_close != -1.0) {
				style.SetLineNoClose(w->no_close != 0.0);
			}
		}
	}

	shape->AddStyle(style);

	return 0;
}



static int save_shape_fill(SaveState& state, sswf::TagShape *shape, const struct data_list_t *list, sswf::TagShape::fill_t fill_select)
{
	sswf::Color			color;
	sswf::Matrix			matrix;
	sswf::Style			style(state.f_header);
	const sswf::TagBaseID *		obj;
	const struct data_color_t	*c;
	const struct data_matrix_t	*m;
	const struct data_gradient_t	*g;
	const struct data_common_t	*cd;
	const struct data_list_t	*sub_list;
	float				focal;
	int				idx, max, sub_idx, sub_max, colidx, matidx, gradient, graidx, gramat;

	if(list == 0) {
		style.SetType(sswf::Style::STYLE_TYPE_NO_FILL);
	}
	else {
		colidx = 0;
		matidx = 0;
		max = list->count;
		for(idx = 0; idx < max; idx++) {
			cd = dc_item(list, idx);
			switch(cd->type) {
			case NODE_TYPE_STRING:		/* the type */
				if(strcmp(cd->name, "clipped") == 0) {
					style.SetType(sswf::Style::STYLE_TYPE_BITMAP_CLIPPED);
				}
				else if(strcmp(cd->name, "tilled") == 0) {
					style.SetType(sswf::Style::STYLE_TYPE_BITMAP_TILLED);
				}
				else if(strcmp(cd->name, "hard-edge clipped") == 0) {
					style.SetType(sswf::Style::STYLE_TYPE_BITMAP_HARDEDGE_CLIPPED);
				}
				else if(strcmp(cd->name, "hard-edge tilled") == 0) {
					style.SetType(sswf::Style::STYLE_TYPE_BITMAP_HARDEDGE_TILLED);
				}
				else if(strcmp(cd->name, "solid") == 0) {
					style.SetType(sswf::Style::STYLE_TYPE_SOLID);
				}
				else if(strcmp(cd->name, "radial") == 0) {
					style.SetType(sswf::Style::STYLE_TYPE_GRADIENT_RADIAL);
				}
				else if(strcmp(cd->name, "linear") == 0) {
					style.SetType(sswf::Style::STYLE_TYPE_GRADIENT_LINEAR);
				}
				else if(strcmp(cd->name, "focal") == 0) {
					style.SetType(sswf::Style::STYLE_TYPE_GRADIENT_FOCAL);
				}
				else if(strcmp(cd->name, "reset") == 0) {
					style.SetType(sswf::Style::STYLE_TYPE_NO_FILL);
				}
				else {
fprintf(stderr, "ERROR: invalid fill style type (%s).\n", cd->name);
					errcnt++;
				}
				break;

			case NODE_SUBTYPE_COLOR:
				c = dc_color(cd);
				color.Set(CLAMPED_BYTE_COLOR(c->red), CLAMPED_BYTE_COLOR(c->green),
								CLAMPED_BYTE_COLOR(c->blue), CLAMPED_BYTE_COLOR(c->alpha));
				style.SetColor(colidx, color);
				colidx = 1;
				break;

			case NODE_SUBTYPE_GRADIENT:
				if(cd->name_len != 0) {
					if(strcasecmp(cd->name, "linear") == 0) {
						style.SetType(sswf::Style::STYLE_TYPE_GRADIENT_LINEAR);
					}
					else if(strcasecmp(cd->name, "radial") == 0) {
						style.SetType(sswf::Style::STYLE_TYPE_GRADIENT_RADIAL);
					}
					else {
fprintf(stderr, "ERROR: invalid gradient type (%s); expected linear or radial.\n", cd->name);
						errcnt++;
					}
				}
				sub_list = dc_list(cd);
				sub_max = sub_list->count;
				gradient = 0;
				gramat = 0;
				sub_idx = 0;
				while(sub_idx < sub_max) {
					cd = dc_item(sub_list, sub_idx);
					if(sub_list->offset[sub_idx] < 0) {
						g = (const struct data_gradient_t *) cd;
						if(g->type == GRADIENT_TYPE_POSITION) {
							sub_idx++;
							assert(sub_idx < sub_max, "a gradient is always expected to be defined with a color");
							cd = dc_item(sub_list, sub_idx);
							c = dc_color(cd);
							color.Set(CLAMPED_BYTE_COLOR(c->red), CLAMPED_BYTE_COLOR(c->green),
											CLAMPED_BYTE_COLOR(c->blue), CLAMPED_BYTE_COLOR(c->alpha));
							graidx = (int) rint(g->position * 255.0);
							if(graidx > 255) {
								graidx = 255;
							}
							else if(graidx < 0) {
								graidx = 0;
							}
							style.SetGradient(gradient, graidx, color);
							if(g->to_pos != -1E+128) {
								sub_idx++;
								assert(sub_idx < sub_max, "a morph gradient is always expected to be defined with two colors");
								cd = dc_item(sub_list, sub_idx);
								c = dc_color(cd);
								color.Set(CLAMPED_BYTE_COLOR(c->red), CLAMPED_BYTE_COLOR(c->green),
											CLAMPED_BYTE_COLOR(c->blue), CLAMPED_BYTE_COLOR(c->alpha));
								graidx = (int) rint(g->to_pos * 255.0);
								if(graidx > 255) {
									graidx = 255;
								}
								else if(graidx < 0) {
									graidx = 0;
								}
								style.SetGradient(gradient + sswf::Style::MAX_GRADIENTS, graidx, color);
							}
							if(gradient < sswf::Style::MAX_GRADIENTS - 1) {
								gradient++;
							}
						}
						else if(g->type == GRADIENT_TYPE_FOCAL) {
							focal = g->position;
							if(focal > 1.0f) {
								focal = 1.0f;
							}
							else if(focal < -1.0f) {
								focal = -1.0f;
							}
							style.SetGradientFocal(focal);
						}
					}
					else if(cd->type == NODE_SUBTYPE_MATRIX) {
						m = dc_matrix(cd);
						save_set_matrix(matrix, m);
						style.SetMatrix(gramat, matrix);
						gramat = 1;
					}
					else {
						assert(0, "unknown data type (%d) for a gradient", cd->type);
						/*NOTREACHED*/
					}
					sub_idx++;
				}
				break;

			case NODE_SUBTYPE_REFERENCE:
				// TODO...
				if(strcmp(cd->name, "clipping") == 0) {
					/* this is a very special case where we want to have -1 for the ID */
					style.SetClipping();
				}
				else {
					obj = state.FindRef(cd->name, "image");
					if(obj != 0) {
						// valid reference to an image!
						style.SetBitmap(obj->Identification());
					}
				}
				break;

			case NODE_SUBTYPE_MATRIX:
				m = dc_matrix(cd);
				save_set_matrix(matrix, m);
				style.SetMatrix(matidx, matrix);
				matidx = 1;
				break;

			default:
				/* to avoid warnings */
				assert(0, "unknown data type (%d) for a shape", cd->type);
				/*NOTREACHED*/

			}
		}
	}

	shape->AddStyle(style, fill_select);

	return 0;
}



static int save_shape_edges(sswf::TagShape *shape, const struct data_list_t *list, long *x, long *y, long start_x, long start_y, sswf::TagShape::morph_t morph_mode)
{
	const struct data_edges_t	*e;
	int				idx, max;
	long				x1, y1, x2, y2;
	double				s, c;

	/* angle == 0.0 by default */
	s = 0.0;
	c = 1.0;
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		assert(list->offset[idx] < 0, "only direct structures are expected in edges arrays");
		e = dc_item_edges(list, idx);
		if(e->type == EDGE_TYPE_ROTATE) {
			s = sin(e->delta_x1);
			c = cos(e->delta_x1);
			continue;
		}
		x1 = (long) rint(PIXEL_TO_TWIPS(e->delta_x1 * c - e->delta_y1 * s));
		y1 = (long) rint(PIXEL_TO_TWIPS(e->delta_y1 * c + e->delta_x1 * s));
		if(e->delta_x2 == -1E+128 && e->delta_y2 == -1E+128) {
			// in this case we have a line
			if(e->type == EDGE_TYPE_CLOSE) {
				shape->AddEdge(morph_mode, start_x - *x, start_y - *y);
				*x = start_x;
				*y = start_y;
			}
			else {
				shape->AddEdge(morph_mode, x1, y1);
				*x += x1;
				*y += y1;
			}
		}
		else {
			x2 = (long) rint(PIXEL_TO_TWIPS(e->delta_x2 * c - e->delta_y2 * s));
			y2 = (long) rint(PIXEL_TO_TWIPS(e->delta_y2 * c + e->delta_x2 * s));
			if(e->type == EDGE_TYPE_CLOSE) {
				shape->AddEdge(morph_mode, start_x - *x - x1, start_y - *y - y1, x1, y1);
				*x = start_x;
				*y = start_y;
			}
			else {
				shape->AddEdge(morph_mode, x2, y2, x1, y1);
				*x += x1 + x2;
				*y += y1 + y2;
			}
		}
	}

	return 0;
}



static int save_shape_points(sswf::TagShape *shape, const struct data_list_t *list, long *x, long *y, long start_x, long start_y, sswf::TagShape::morph_t morph_mode)
{
	const struct data_points_t	*e;
	int				idx, max;
	long				x1, y1, x2, y2;
	double				s, c;

	/* angle == 0.0 by default */
	s = 0.0;
	c = 1.0;
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		assert(list->offset[idx] < 0, "only direct structures are expected in points arrays");
		e = dc_item_points(list, idx);
		if(e->type == EDGE_TYPE_ROTATE) {
			s = sin(e->x1);
			c = cos(e->x1);
			continue;
		}
		x1 = (long) rint(PIXEL_TO_TWIPS(e->x1 * c - e->y1 * s));
		y1 = (long) rint(PIXEL_TO_TWIPS(e->y1 * c + e->x1 * s));
		if(e->x2 == -1E+128 && e->y2 == -1E+128) {
			// in this case we have a line
			if(e->type == EDGE_TYPE_CLOSE) {
				shape->AddEdge(morph_mode, start_x - *x, start_y - *y);
				*x = start_x;
				*y = start_y;
			}
			else {
				shape->AddEdge(morph_mode, x1 - *x, y1 - *y);
				*x = x1;
				*y = y1;
			}
		}
		else {
			x2 = (long) rint(PIXEL_TO_TWIPS(e->x2 * c - e->y2 * s));
			y2 = (long) rint(PIXEL_TO_TWIPS(e->y2 * c - e->x2 * s));
			if(e->type == EDGE_TYPE_CLOSE) {
				shape->AddEdge(morph_mode, start_x - x1, start_y - y1, x1 - *x, y1 - *y);
				*x = start_x;
				*y = start_y;
			}
			else {
				shape->AddEdge(morph_mode, x2 - x1, y2 - y1, x1 - *x, y1 - *y);
				*x = x2;
				*y = y2;
			}
		}
	}

	return 0;
}



struct save_shape_info {
	sswf::TagShape *	shape;
	bool			sb;
	bool			so;
	int			rect_cnt;
	sswf::TagShape::fill_t	fill_select;
	sswf::TagShape::morph_t	morph_mode;
	long			x;
	long			y;
	long			start_x;
	long			start_y;
};



static int save_shape_list(SaveState& state, save_shape_info& info, const struct data_list_t *list)
{
	sswf::SRectangle		rect;
	const struct data_common_t	*cd;
	const struct data_list_t	*sub_list;
	const struct data_rect_t	*r;
	const struct data_move_t	*move;
	int				idx, max;

	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			// switch on the type which happens to be the fill selection
			switch(((const struct data_fill_style_t *) cd)->select) {
			case 0:
				info.fill_select = sswf::TagShape::SHAPE_FILL_EVEN;
				break;

			case 1:
				info.fill_select = sswf::TagShape::SHAPE_FILL_ODD;
				break;

			case 2:
				info.sb = ((const struct data_shape_t *) cd)->show != 0.0;
				break;

			case 3:
				move = (const struct data_move_t *) cd;
				info.start_x = info.x = (long) rint(PIXEL_TO_TWIPS(move->tx));
				info.start_y = info.y = (long) rint(PIXEL_TO_TWIPS(move->ty));
				info.shape->AddMove(info.morph_mode, info.x, info.y);
				break;

			case 4:
				info.so = ((const struct data_shape_t *) cd)->show != 0.0;
				break;

			case 5:
				// mode is a double and cl cannot cast that to
				// an emuneration type without an error...
				// so we force an int cast in between
				info.morph_mode = (sswf::TagShape::morph_t) (int) ((const struct data_morph_t *) cd)->mode;
				break;

			}
		}
		else {
			/* all sub-data except the rectangle but that way sub_list points at the right place then */
			if(cd->length - cd->name_len == sizeof(struct data_common_t)) {
				sub_list = 0;		// empty list
			}
			else {
				sub_list = dc_list(cd);
			}
			switch(cd->type) {
			case NODE_SUBTYPE_RECT:
				/* transform the data into a rectangle */
				r = (const data_rect_t *) sub_list;
				/* there should be at most 2 rectangles but we still ensure a good index
				 * at all times for our own sake...
				 */
				rect.SetReorder(PIXEL_TO_TWIPS(r->min_x), PIXEL_TO_TWIPS(r->max_x),
						PIXEL_TO_TWIPS(r->min_y), PIXEL_TO_TWIPS(r->max_y));
				/* save it in the shape */
				info.shape->SetBounds(info.rect_cnt, rect);
				info.rect_cnt = 1;
				break;

			case NODE_SUBTYPE_FILL_STYLE:
				save_shape_fill(state, info.shape, sub_list, info.fill_select);
				info.fill_select = sswf::TagShape::SHAPE_FILL_EVEN;
				break;

			case NODE_SUBTYPE_LINE_STYLE:
				save_shape_line(state, info.shape, sub_list);
				break;

			case NODE_SUBTYPE_EDGES:
				save_shape_edges(info.shape, sub_list, &info.x, &info.y, info.start_x, info.start_y, info.morph_mode);
				break;

			case NODE_SUBTYPE_POINTS:
				save_shape_points(info.shape, sub_list, &info.x, &info.y, info.start_x, info.start_y, info.morph_mode);
				break;

			case NODE_SUBTYPE_LIST:
			case NODE_SUBTYPE_BLOCK:
				save_shape_list(state, info, sub_list);
				break;

			default:
				/* to avoid warnings */
				assert(0, "unknown data type (%d) for a shape", cd->type);
				/*NOTREACHED*/

			}
		}
	}

	return 0;
}


static int save_shape(SaveState& state)
{
	int				ec;
	bool				is_glyph;
	save_shape_info			info;
	const struct data_common_t	*cd;

	is_glyph = state.f_d->type == NODE_SUBTYPE_GLYPH;

	if(!is_glyph) {		// not sure this is right, but I think it is...
		cd = save_find_shape(state);
		if(cd != 0) {
			// ha! it already exists, don't insert it again
			return 0;
		}
	}

	info.shape = new sswf::TagShape(state.f_parent);
	if(info.shape == 0) {
		return -1;
	}
	state.RecordRef(info.shape);

	if(is_glyph) {
		info.shape->Glyph();
	}

	// save the data pointer so we can compare shapes quickly later
	info.shape->f_userdata = const_cast<data_common_t *>(state.f_d);

	info.sb = false;
	info.so = false;
	info.rect_cnt = 0;
	info.fill_select = sswf::TagShape::SHAPE_FILL_EVEN;
	info.morph_mode = sswf::TagShape::MORPH_MODE_SHAPE0;
	info.start_x = info.x = 0;		// current position kept in x/y so we can use Edges or Points
	info.start_y = info.y = 0;

	ec = save_shape_list(state, info, dc_list(state.f_d));

	/* now we know for sure whether the user wants to show the bounds and/or origin of this shape */
	info.shape->ShowBounds(show_bounds || info.sb);
	info.shape->ShowOrigin(show_origin || info.so);

	return ec;
}


static int save_show_frame(SaveState& state)
{
	sswf::TagShowFrame		*sf;
	const struct data_show_frame_t	*show_frame;
	int				cnt;

	show_frame = dc_show_frame(state.f_d);
	cnt = (int) rint(show_frame->wait) - 1;

	/*
	 * The first one only is given a label so we do it outside the
	 * loop - also in this way we make sure we have at least one
	 * show frame inserted
	 */
	sf = new sswf::TagShowFrame(state.f_parent);
	// this tag is not returned so we have to register it here
	state.SetLabel(sf);

	while(cnt > 0) {
		cnt--;
		sf = new sswf::TagShowFrame(state.f_parent);
		// the following tags cannot have labels, only the 1st
		// one can.
	}

	return 0;
}



static int save_sound(SaveState& state)
{
	sswf::TagSound			*sound;
	int				idx, max;
	const struct data_list_t	*list;
	const struct data_common_t	*cd, *str;
	const struct data_sound_t	*snd, *flags;

	sound = new sswf::TagSound(state.f_parent);
	if(sound == 0) {
		return -1;
	}
	state.RecordRef(sound);

	flags = 0;
	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			// we simply define snd with this and use it
			// once we get out of the loop; there should
			// be only one anyway
			snd = (struct data_sound_t *) cd;
			switch(snd->type) {
			case SOUND_TYPE_SETUP:
				flags = snd;
				break;

			case SOUND_TYPE_FILENAME:
				idx++;
				assert(idx < max, "expected a string for the sound filename");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected an object of type string as the sound filename");
				if(str->name_len > 0) {
					if(sound->SetFilename(str->name) != 0) {
						// TODO: for these we need a better decimation of the actual error
						// (i.e. whether the file exists, the format isn't supported, the compression
						// isn't supported, the # of channels, etc.)
fprintf(stderr, "ERROR: cannot read sound file \"%s\". Is this an uncompressed WAVE file or an MP3?\n", str->name);
						errcnt++;
						return -1;
					}
				}
				break;

			case SOUND_TYPE_FORMAT:
				idx++;
				assert(idx < max, "expected a string for the sound type");
				str = dc_item(list, idx);
				assert(str->type == NODE_TYPE_STRING, "expected an object of type string as the sound type");
				if(str->name_len > 0) {
					if(strcasecmp(str->name, "RAW") == 0) {
						sound->SetFormat(sswf::TagSound::SOUND_FORMAT_RAW);
						break;
					}
					else if(strcasecmp(str->name, "UNCOMPRESSED") == 0) {
						sound->SetFormat(sswf::TagSound::SOUND_FORMAT_UNCOMPRESSED);
						break;
					}
					else if(strcasecmp(str->name, "MP3") == 0) {
						sound->SetFormat(sswf::TagSound::SOUND_FORMAT_MP3);
						break;
					}
				}
				fprintf(stderr, "ERROR: unknown/unsupported Flash sound compression \"%s\".\n", str->name);
				errcnt++;
				break;

			}

		}
		else {
			assert(0, "unknown data type (%d) for a sound", cd->type);
			/*NOTREACHED*/
		}
	}

	if(flags != 0) {
		if(flags->mono) {
			sound->SetMono();
		}
		if(flags->set_8bits) {
			sound->Set8Bits();
		}
	}

	return 0;
}



static int save_sound_info(SaveState& state)
{
	const sswf::TagBaseID		*sound;
	const sswf::TagSound		*sound_ptr;
	sswf::TagStartSound		*start_sound;
	sswf::SoundInfo			*sound_info;
	int				idx, max;
	const struct data_list_t	*list;
	const struct data_common_t	*cd;
	const struct data_sound_info_t	*sndinf;
	size_t				samples;
	long				start, end, swap;

	sound = NULL;
	sndinf = NULL;
	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			sndinf = (const struct data_sound_info_t *) cd;
		}
		else {
			switch(cd->type) {
			case NODE_SUBTYPE_REFERENCE:
				sound = state.FindRef(cd->name, 0);
				if(sound != 0) {
					if(strcmp(sound->Name(), "sound") != 0
					&& strcmp(sound->Name(), "imported") != 0) {
fprintf(stderr, "ERROR: in a SOUND INFO object, the reference to \"%s\" needs to be to an object of type: sound.\n", cd->name);
						errcnt++;
						return 1;
					}
				}
				break;

			default:
				/* to avoid warnings */
				assert(0, "unknown data type (%d) for a sound info", cd->type);
				/*NOTREACHED*/

			}
		}
	}

	if(sound == NULL) {
		fprintf(stderr, "ERROR: a SOUND INFO needs to have a reference to an existing sound.\n");
		errcnt++;
		return 1;
	}

	/* now that we have all the info, initialize the sound info object */
	// TODO: this is a memory leak
	sound_info = new sswf::SoundInfo(state.f_header);

	sound_info->SetSoundID(sound->Identification());

	sound_ptr = dynamic_cast<const sswf::TagSound *>(sound);
	if(sound_ptr == 0) {
		// 100 Mb should be a large enough limit for now...
		samples = 100 * 1024 * 1024;
	}
	else {
		samples = sound_ptr->GetSamplesCount();
		if(samples <= 0) {
			fprintf(stderr, "ERROR: a SOUND INFO is referencing a SOUND object without any audio samples.\n");
			errcnt++;
			return 1;
		}
	}

	if(sndinf == NULL) {
		sound_info->SetLoop(1);
		start = 0;
		end = 0;
	}
	else {
		sound_info->StopSound(sndinf->stop != 0.0);
		sound_info->NoMultiple(sndinf->no_multiple != 0.0);
		sound_info->SetLoop(sndinf->loop == -1E+128 ? 1 : (unsigned short) sndinf->loop);
		start = (long) sndinf->start;
		end = (long) sndinf->end;
		if(sndinf->end == -1E+128) {
			end = 0;
		}
		if(end >= (long) samples) {
			end = samples - 1;
		}
		else if(end < 0) {
			end = 0;
		}
		if(start < 0) {
			start = 0;
		}
		if(start > end) {
			// we cannot play backward (we could create a 2nd DEFINE_SOUND...)
			swap = start;
			start = end;
			end = swap;
		}
	}
	sound_info->SetRange(start, end);

	start_sound = new sswf::TagStartSound(state.f_parent);
	if(start_sound == 0) {
		return -1;
	}
	start_sound->SetInfo(sound_info);

	return 0;
}



static int save_sprite(SaveState& state)
{
	sswf::TagBase			*parent;
	sswf::TagSprite			*sprite;
	const struct data_common_t	*sprite_info;
	const struct data_list_t	*list;
	int				idx, max, ec;

	// note: though a sprite (like many other tags) cannot be in another sprite
	// this is a bit doggy...
	sprite = new sswf::TagSprite(state.f_parent);
	if(sprite == 0) {
		return -1;
	}
	state.RecordRef(sprite);

	sprite_info = state.f_d;
	parent = state.f_parent;		// save the current parent
	state.f_parent = sprite;		// and make us the new one

	ec = 0;
	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max && ec == 0; idx++) {
		state.f_d = dc_item(list, idx);
		ec = save_child(state);
	}
	state.f_parent = parent;
	state.f_d = sprite_info;

	return ec;

}



static int save_text_setup(SaveState& state, sswf::TagText *text, const struct data_list_t *list)
{
	const sswf::TagBaseID		*obj;
	sswf::Color			color;
	int				idx, max;
	const struct data_common_t	*cd;
	const struct data_color_t	*c;
	const struct data_text_setup_t	*setup;

	setup = 0;		// makes the reference crash if still undefined (should never happen)
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			setup = (const struct data_text_setup_t *) cd;
			if(idx != 0 || setup->x != 0 || setup->y != 0) {
				// we got offsets (otherwise we just got some setups)
				text->AddOffsets(PIXEL_TO_TWIPS(setup->x), PIXEL_TO_TWIPS(setup->y));
			}
		}
		else {
			switch(cd->type) {
			case NODE_SUBTYPE_COLOR:
				c = dc_color(cd);
				color.Set(CLAMPED_BYTE_COLOR(c->red), CLAMPED_BYTE_COLOR(c->green),
								CLAMPED_BYTE_COLOR(c->blue), CLAMPED_BYTE_COLOR(c->alpha));
				text->AddColor(color);
				break;

			case NODE_SUBTYPE_REFERENCE:
				assert(setup != 0, "found font reference before font setup structure");
				obj = state.FindRef(cd->name, "font");
				if(obj != 0) {
					text->AddFont(dynamic_cast<const sswf::TagFont *>(obj), PIXEL_TO_TWIPS(setup->height));
				}
				break;

			default:
				/* to avoid warnings */
				assert(0, "unknown data type (%d) for a text setup", cd->type);
				/*NOTREACHED*/

			}
		}
	}

	return 0;
}



static int save_text(SaveState& state)
{
	sswf::TagText			*text;
	sswf::SRectangle		rect;
	sswf::Matrix			matrix;
	const struct data_common_t	*cd, *str;
	const struct data_list_t	*list;
	const struct data_rect_t	*r;
	const struct data_matrix_t	*mx;
	const struct data_text_t	*txt;
	int				idx, max, ec;

	text = new sswf::TagText(state.f_parent);
	if(text == 0) {
		return -1;
	}
	state.RecordRef(text);

	list = dc_list(state.f_d);
	max = list->count;
	for(idx = 0; idx < max; idx++) {
		cd = dc_item(list, idx);
		if(list->offset[idx] < 0) {
			txt = reinterpret_cast<const struct data_text_t *>(cd);
			switch(* (long *) cd) {		// switch on the type
			case TEXT_TYPE_ADVANCE:
				// this is a text entry with advance
				idx++;
				assert(idx < max, "expected a string following the advance information in this text");
				str = dc_item(list, idx);
				if(str->name_len > 0) {
					if(txt->advance == -1E+128) {
						text->AddText(str->name);
					}
					else {
						text->AddText(str->name, PIXEL_TO_TWIPS(txt->advance));
					}
				}
				break;

			case TEXT_TYPE_INFO:
				if(txt->thickness != -1E+128) {
					text->SetThickness(txt->thickness);
					text->SetSharpness(txt->sharpness);
				}
				if(txt->renderer != -1E+128) {
					text->SetRenderer(static_cast<sswf::TagCSMTextSettings::csm_text_renderer_t>(static_cast<int>(txt->renderer)));
				}
				if(txt->grid_fit != -1E+128) {
					text->SetGridFit(static_cast<sswf::TagCSMTextSettings::csm_text_gridfit_t>(static_cast<int>(txt->grid_fit)));
				}
				break;

			default:
				assert(0, "unknown font type (%ld)", * (long *) cd);
				/*NOTREACHED*/

			}
		}
		else {
			/* all sub-data except the rectangle but that way cd points at the right place then */
			switch(cd->type) {
			case NODE_SUBTYPE_RECT:
				/* transform the data into a rectangle */
				r = dc_rect(cd);
				rect.SetReorder(PIXEL_TO_TWIPS(r->min_x), PIXEL_TO_TWIPS(r->max_x),
						PIXEL_TO_TWIPS(r->min_y), PIXEL_TO_TWIPS(r->max_y));
				/* save it in the text */
				text->SetBounds(rect);
				break;

			case NODE_SUBTYPE_MATRIX:
				mx = dc_matrix(cd);
				save_set_matrix(matrix, mx);
				text->SetMatrix(matrix);
				break;

			case NODE_SUBTYPE_TEXT_SETUP:
				ec = save_text_setup(state, text, dc_list(cd));
				if(ec != 0) {
					return ec;
				}
				break;

			// NOTE: the NODE_TYPE_STRING is caught with the TEXT_TYPE_ADVANCE
			default:
				/* to avoid warnings */
				assert(0, "unknown data type (%d) for a text", cd->type);
				/*NOTREACHED*/

			}
		}
	}

	return 0;
}


static int save_label(SaveState& state)
{
	const struct data_list_t	*list;
	const struct data_common_t	*cd;

	list = dc_list(state.f_d);
	if(list->count > 0) {
		cd = dc_item(list, 0);
		assert(cd->type == NODE_TYPE_STRING, "a label can only accept a string");
		if(cd->name_len > 0) {
			state.f_last_label = cd->name;
		}
	}

	return 0;
}


static int save_list(SaveState& state)
{
	const struct data_list_t	*list;
	const struct data_common_t	*save;
	int				idx, ec;

	// save all the children now that we have the proper header info
	save = state.f_d;
	list = dc_list(save);
	ec = 0;
	for(idx = 0; idx < (int) list->count && ec == 0; idx++) {
		state.f_d = dc_item(list, idx);
		ec = save_child(state);
	}
	state.f_d = save;

	return ec;
}


static int save_child(SaveState& state)
{
	static const struct tag_list_t	children[] = {
		{
			NODE_SUBTYPE_BLOCK,
			save_list
		},
		{
			NODE_SUBTYPE_BUTTON,
			save_button
		},
		{
			NODE_SUBTYPE_DO_ACTION,
			save_do_action
		},
		{
			NODE_SUBTYPE_EDIT_TEXT,
			save_edit_text
		},
		{
			NODE_SUBTYPE_END,
			save_end
		},
		{
			NODE_SUBTYPE_EXPORT,
			save_export
		},
		{
			NODE_SUBTYPE_FONT,
			save_font
		},
		{
			NODE_SUBTYPE_FRAME_LABEL,
			save_frame_label
		},
		{
			NODE_SUBTYPE_GLYPH,
			save_shape
		},
		{
			NODE_SUBTYPE_IMAGE,
			save_image
		},
		{
			NODE_SUBTYPE_IMPORT,
			save_import
		},
		{
			NODE_SUBTYPE_LABEL,
			save_label
		},
		{
			NODE_SUBTYPE_LIST,
			save_list
		},
		{
			NODE_SUBTYPE_METADATA,
			save_metadata
		},
		{
			NODE_SUBTYPE_PLACE_OBJECT,
			save_place_object
		},
		{
			NODE_SUBTYPE_REMOVE,
			save_remove
		},
		{
			NODE_SUBTYPE_REPLACE_OBJECT,
			save_place_object
		},
		{
			NODE_SUBTYPE_SCENE_FRAME_DATA,
			save_scene_frame_data
		},
		{
			NODE_SUBTYPE_SCRIPT_LIMITS,
			save_script_limits
		},
		{
			NODE_SUBTYPE_SET_BACKGROUND_COLOR,
			save_set_background_color
		},
		{
			NODE_SUBTYPE_SET_TAB_INDEX,
			save_set_tab_index
		},
		{
			NODE_SUBTYPE_SHAPE,
			save_shape
		},
		{
			NODE_SUBTYPE_SHOW_FRAME,
			save_show_frame
		},
		{
			NODE_SUBTYPE_SOUND,
			save_sound
		},
		{
			NODE_SUBTYPE_SOUND_INFO,
			save_sound_info
		},
		{
			NODE_SUBTYPE_SPRITE,
			save_sprite
		},
		{
			NODE_SUBTYPE_TEXT,
			save_text
		},

		{		/* end the table */
			NODE_SUBTYPE_UNKNOWN,
			NULL
		}
	};
	const struct tag_list_t		*c;

	// search for this child
	c = children;
	do {
		if(c->type == state.f_d->type) {
			return (*c->func)(state);
		}
		c++;
	} while(c->type != NODE_SUBTYPE_UNKNOWN);

	return 0;
}


int save_data(const struct data_common_t *data, struct global_t *g)
{
	SaveState			state;
	const struct data_common_t	*d;
	const struct data_list_t	*list;
	int				idx, ec;
	sswf::SRectangle		rect;
	const struct data_rect_t	*frame_rect;
	sswf::Data			result;
	size_t				size;
	void				*buffer;
	FILE				*f;
	const char			*name;

	list = (const struct data_list_t *) ((char *) (data + 1) + data->name_len);

	// search for the frame rectangle (the 1st rectangle)
	rect.Set(-1000, 1000, -1000, 1000);	/* a default setup of 100 pixels in each direction */
	for(idx = 0; idx < (int) list->count; idx++) {
		// NOTE: (at least for now) the offsets cannot be negative in a sequence
		d = dc_item(list, idx);
		if(d->type == NODE_SUBTYPE_RECT) {
			frame_rect = dc_rect(d);
			rect.SetReorder(PIXEL_TO_TWIPS(frame_rect->min_x), PIXEL_TO_TWIPS(frame_rect->max_x),
					PIXEL_TO_TWIPS(frame_rect->min_y), PIXEL_TO_TWIPS(frame_rect->max_y));
			break;
		}
	}

	state.f_header.SetOutputEncoding(g->f_output_encoding);
	state.f_header.SetFrame(rect);
	state.f_header.SetRate((float) g->f_frame_rate);
	state.f_parent = &state.f_header;

	if((g->f_flags & GLOBAL_FLAG_PROTECT) != 0) {
		if(new sswf::TagProtect(state.f_parent) == 0) {
			fprintf(stderr, "ERROR: cannot create the Protect tag.\n");
			errcnt++;
			return -1;
		}
	}
	state.f_header.SetUseNetwork((g->f_flags & GLOBAL_FLAG_USE_NETWORK) != 0);
	if((g->f_flags & GLOBAL_FLAG_COMPRESS) != 0) {
		state.f_header.SetCompress();
	}
//printf("Version = %d (Max. %d)\n", g->f_version, g->f_maximum_version);
	if(g->f_version > 0) {
		state.f_header.SetVersion(g->f_version);
	}
	if(g->f_minimum_version > 0) {
		state.f_header.SetMinimumVersion(g->f_minimum_version);
	}
	if(g->f_maximum_version > 0) {
		state.f_header.SetMaximumVersion(g->f_maximum_version);
	}

#if 0
{	// the following prevents the movie from being played back by macromedia...
	sswf::TagInfo	*info;
	char		*version;
	int		v;

	info = new sswf::TagInfo(state.f_parent);
	if(info == 0) {
		return -1;
	}
	// this is what I have in a movie...
	//info->SetInfo("com.macromedia.generator.commands.MovieSetCommand width=\"100\" height=\"100\" rate=\"12\"");
	// and here is what I wanted to do
	// either way it doesn't work (a genereal error occurs)
	info->SetInfo("com.SSWF.generator");
	version = TO_STR(SSWF_VERSION);
	v = strtol(version, &version, 10);
	version++;
	v = strtol(version, &version, 10) + v * 256;
	version++;
	v = strtol(version, NULL, 10) + v * 256;
	info->SetVersion(v);	// in the other movie v == 8
}
#endif
	{
		sswf::TagProductInfo	*info;
		const char		*version(TO_STR(SSWF_VERSION));
		char			*min_string, *rel_string;
		long			major, minor, release;
#ifndef WIN32
		struct tm		compile_date;
#endif
		time_t			compile_time;

		info = new sswf::TagProductInfo(state.f_parent);
		if(info == 0) {
			return -1;
		}
		info->SetProduct(0xF055);
		info->SetEdition(1);
		major = strtol(version, &min_string, 10);
		minor = strtol(min_string + 1, &rel_string, 10);
		release = strtol(rel_string + 1, 0, 10);
		info->SetVersion(major, minor);
		info->SetBuild(((int64_t) major << 32) + (minor << 16) + release);

#ifdef WIN32
		/* under Win32, we currently cheat big time 8-) -- version 1.8.1 -- need a fix! */
		compile_time = time(NULL);
#else
		/* the following works great under Linux */
		strptime(__DATE__ " " __TIME__, "%b %d %Y %H:%M:%S", &compile_date);
		compile_time = mktime(&compile_date);
#endif
		info->SetCompileDate(compile_time);	// needs to use __DATE__ & correct unit...
	}

	if(g->f_show_steps > 0) {
		printf("+ Save each child in the TagHeader object\n");
	}

	// save all the children now that we have the proper header info
	for(idx = 0; idx < (int) list->count; idx++) {
		d = dc_item(list, idx);
		if(d->type != NODE_SUBTYPE_RECT) {
#if 0
printf("Save child %p (%s)\n", d, d->name_len == 0 ? "<no name>" : d->name);
#endif
			if(d->name_len > 0) {
				name = d->name;
			}
			else {
				name = "<no name>";
			}
			if(g->f_show_steps > 1) {
				printf("  + Saving child: \"%s\" (%p)\n", name, d);
			}

			state.f_d = d;
			ec = save_child(state);
			if(ec != 0) {
				printf("ERROR: error #%d occured while saving the tag \"%s\".\n", ec, name);
				errcnt++;
				return ec;
			}
		}
	}

	if(g->f_show_steps > 0) {
		printf("+ Save the TagHeader in \"%s\".\n", g->f_output_filename);
	}

	ec = state.f_header.Save(result);

	// NOTE: even if there is an error, try to save what was generated so far; this
	// enables for debugging of what could have generated a problem in the saving
	// process!
	result.Read(buffer, size);

	f = fopen(g->f_output_filename, "wb");
	if(f == 0) {
		fprintf(stderr, "ERROR: cannot create file \"%s\". (errno: %d - %s)\n", g->f_output_filename, errno, strerror(errno));
		errcnt++;
		return -1;
	}
	fwrite(buffer, size, 1, f);
	fclose(f);

	if(ec != 0) {
		fprintf(stderr, "ERROR: an error occured while saving the result (maybe a version problem?)\n");
		errcnt++;
	}

	if(state.f_header.Version() < 8 && (g->f_flags & GLOBAL_FLAG_USE_NETWORK) == 0) {
		fprintf(stderr, "WARNING: the \"use_network\" flag was set to false but the movie was saved in an SWF version V%d, which does not enforce that flag.\n",
				(int) state.f_header.Version());
	}

	return ec;
}


