// hey
// a small scripting utility
// written by Attila Mezei (attila.mezei@mail.datanet.hu)
// contributions by Sander Stoks, Peter Folk, Chris Herborth, Marco Nelissen, Scott Lindsey and others
//
// public domain, use it at your own risk
//
// 1.2.6:	syntax extended by Sander Stoks <sander@adamation.com> to allow:
//		"hey Application let Specifier do ..."
//		allowing you to send messages directly to other handlers than the app itself.
//		In cooperation with the new Application defined commands (note that some
//		Be classes, e.g. BWindow, publish commands as well) this allows, for example:
//		"hey NetPositive let Window 0 do MoveBy BPoint[10,10]"
//		Note that this is partly redundant, since
//		"hey NetPositive let Window 0 do get Title"
//		duplicates "hey NetPositive get Title of Window 0"
//		But with the old system,
//		"hey NetPositive MoveBy of Window 0 with data=BPoint[10,10]"
//		couldn't work ("MoveBy" is not defined by the Application itself).
//
// 1.2.5:   value_info is supported in BPropertyInfo. This means when you send GETSUITES (B_GET_SUPPORTED_SUITES)
//	the value info is printed after the property infos, like this:
//		   "messages" (B_PROPERTY_INFO_TYPE) :
//		        property   commands                            specifiers
//		--------------------------------------------------------------------------------
//		          Suites   B_GET_PROPERTY                      DIRECT
//		       Messenger   B_GET_PROPERTY                      DIRECT
//		    InternalName   B_GET_PROPERTY                      DIRECT
//		
//		            name   value                               kind
//		--------------------------------------------------------------------------------
//		          Backup   0x6261636B ('back')                 COMMAND
//		                   Usage: This command backs up your hard drive.
//		           Abort   0x61626F72 ('abor')                 COMMAND
//		                   Usage: Stops the current operation...
//		       Type Code   0x74797065 ('type')                 TYPE CODE
//		                   Usage: Type code info...
// 
//	You can also use the application defined commands (as the verb) with hey:
//		hey MyBackupApp Backup "Maui"
//
// 1.2.4:   the syntax is extended by Peter Folk <pfolk@uni.uiuc.edu> to contain:
//      do the x of y -3 of z '"1"'
//      I.e. "do" => B_EXECUTE_PROPERTY, optional "the" makes direct specifiers
//      more like english, bare reverse-index-specifiers are now handled, and
//      named specifiers can contain only digits by quoting it (but make sure the
//      shell passed the quotes through).
//      
//      Hey(target,const char*,reply) was previously limited to 100 tokens.  It
//      now uses a vector<> so it's only limited by available memory.
//
//      Also, the archive name is now Y2K compliant =)
//
// 1.2.3:	new option: -s for silent processing (no reply or errors printed) AM
//
// 1.2.2:	Fixes by Marco Nelissen (marcone@xs4all.nl)
//      - fixed parsing of negative numbers
//		- fixed "with" syntax, which was broken (after a create, "with" would be taken as a specifier)
//
// 1.2.1:	compiled for x86, R4 with minor modifications at BPropertyInfo
//
// 1.2.0:	the syntax is extended by Sander Stoks (sander@adamation.com) to contain
//		with name=<value> [and name=<value> [...]]
//		at the end of the command which will add additional data to the scripting message. E.g:
//		hey Becasso create Canvas with name=MyCanvas and size=BRect(100,100,300,300)
//		Also a small interpreter is included.
//
//		Detailed printout of B_PROPERTY_INFO in BMessages. Better than BPropertyInfo::PrintToStream().
//		Also prints usage info for a property if defined.
//
// 1.1.1:	minor change from chrish@qnx.com to return -1 if an error is
//		sent back in the reply message; also added B_COUNT_PROPERTIES support
//
//		The range specifier sent to the target was 1 greater than it should've been. Fixed.
//
//		'hey' made the assumption that the first thread in a team will be the 
//		application thread (and therefore have the application's name).  
//		This was not always the case. Fix from Scott Lindsey <wombat@gobe.com>.
//
//v1.1.0:	Flattened BPropertyInfo is printed if found in the reply of B_GET_SUPPORTED_SUITES 
//		1,2,3 and 4 character message constant is supported (e.g. '1', '12', '123', '1234') 
//		Alpha is sent with rgb_color 
//
//v1.0.0	First public release 


#include "hey.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <AppKit.h>
#include <Path.h>

const char VERSION[]="v1.2.6";

#define DEBUG_HEY 0		// 1: prints the script message to be sent to the target application, 0: prints only the reply


// test, these should be zero for normal operation
#define TEST_VALUEINFO 0


// flag for silent mode
bool silent;


int main(int argc, char *argv[])
{
	BApplication app("application/x-amezei-hey");

	if (argc < 2) {
		fprintf(stderr, "hey %s, written by Attila Mezei (attila.mezei@mail.datanet.hu)\n" \
		"usage: hey [-s] <app|signature> [let <specifier> do] <verb> <specifier_1> <of\n" \
		"           <specifier_n>>* [to <value>] [with name=<value> [and name=<value>]*]\n" \
		"where  <verb> : DO|GET|SET|COUNT|CREATE|DELETE|GETSUITES|QUIT|SAVE|LOAD|'what'\n" \
		"  <specifier> : [the] <property_name> [ <index> | name | \"name\" | '\"name\"' ]\n" \
		"      <index> : int | -int | '['int']' | '['-int']' | '['startint to end']'\n" \
		"      <value> : \"string\" | <integer> | <float> | bool(value) | int8(value)\n" \
		"                | int16(value) | int32(value) | float(value) | double(value)\n" \
		"                | BPoint(x,y) | BRect(l,t,r,b) | rgb_color(r,g,b,a) | file(path)\n" \
		"options: -s: silent\n\n", VERSION);
		// Updated Usage string to reflect "do", "the", bare -index, and '"name"' changes below
		//   -- pfolk@uni.uiuc.edu 1999-11-03
		
		return -1;
	}
	
	int32 argapp = 1;
	silent=false;
	
	if(strcmp(argv[1], "-s")==0 || strcmp(argv[1], "-S")==0){
		silent=true;
		argapp++;
	}

	// find the application
	BMessenger the_application;
	BList team_list;
	team_id teamid;
	app_info appinfo;
	
	be_roster->GetAppList(&team_list);
	for(int32 i=0;i<team_list.CountItems();i++){
		teamid=(team_id)team_list.ItemAt(i);
		be_roster->GetRunningAppInfo(teamid, &appinfo);
		if(strcmp(appinfo.signature, argv[argapp])==0){
			the_application=BMessenger(appinfo.signature);
			break;
		}else{
			if(strcmp(appinfo.ref.name, argv[argapp])==0){
				the_application=BMessenger(0, teamid);
				break;
			}
		}
	}
	
	if(!the_application.IsValid()){
		if(!silent) fprintf(stderr, "Cannot find the application (%s)\n", argv[argapp]);
		return -1;
	}

	if (argc < 3) {
		if(!silent) fprintf(stderr, "Cannot find the verb!\n");
		return -1;
	}

	
	BMessage the_reply;
	int32 argx = argapp+1;
//	const char *test_string = "set File of Window Sample to file(/boot/home/media/images/BeLogo.psd)";
//	status_t err = Hey(&the_application, test_string, &the_reply);
	status_t err = Hey(&the_application, argv, &argx, argc, &the_reply);

	if (err!=B_OK) {
		if(!silent) fprintf(stderr, "Error when sending message to %s!\n", argv[argapp]);
		return -1;
	} else {
		if(the_reply.what==(uint32)B_MESSAGE_NOT_UNDERSTOOD || the_reply.what==(uint32)B_ERROR){	// I do it myself
			if(the_reply.HasString("message")){
				if(!silent) printf("%s (error 0x%8lX)\n", the_reply.FindString("message"), the_reply.FindInt32("error"));
			}else{
				if(!silent) printf("error 0x%8lX\n", the_reply.FindInt32("error"));
			}
		}else{
			if(!silent){
				printf("Reply ");
				print_message(&the_reply);
				printf("\n");
			}
		}
	}
	
	return 0;
}


int32 HeyInterpreterThreadHook(void* arg)
{
	if (arg) {
		BMessage environment(*(BMessage*) arg);
		char* prompt = "Hey";
		if (environment.HasString("prompt")) environment.FindString("prompt", (const char **)&prompt);
		printf("%s> ", prompt);
		
		BMessenger target;
		if (environment.HasMessenger("Target")) environment.FindMessenger("Target", &target);

		char command[1024];
		status_t err;
		BMessage reply;
		while (gets(command)) {
			reply.MakeEmpty();
			err = Hey(&target, command, &reply);
			if (!err) {
				print_message(&reply);
			} else {
				printf("Error!\n");
			}
			printf("%s> ", prompt);
		}
		
		return 0;

	} else {
		return 1;
	}
}

status_t Hey(BMessenger* target, const char* arg, BMessage* reply)
{
	vector<char*> argv; // number of tokens is now limited only by memory  -- pfolk@uni.uiuc.edu 1999-11-03
	char* tokens = new char[strlen(arg)*2];
	char* currentToken = tokens;
	int32 tokenNdex = 0;
	int32 argNdex = 0;
	bool inquotes = false;
	
	while (arg[argNdex] != 0) { // for each character in arg
		if (arg[argNdex] == '\"') inquotes = !inquotes;
		if (!inquotes && isSpace(arg[argNdex])) { // if the character is white space
			if (tokenNdex!=0) { //  close off currentToken token
				currentToken[tokenNdex] = 0; 
				argv.push_back(currentToken);
				currentToken += tokenNdex+1;
				tokenNdex=0;
				argNdex++;
			} else { // just skip the whitespace
				argNdex++; 
			}
		} else { // copy char into current token
			currentToken[tokenNdex] = arg[argNdex];
			tokenNdex++;
			argNdex++;
		}
	}
	
	if (tokenNdex!=0) { //  close off currentToken token
		currentToken[tokenNdex] = 0; 
		argv.push_back(currentToken);
	}
	argv.push_back(NULL);
	
	int32 argx = 0;
	status_t ret = Hey(target, argv.begin(), &argx, argv.size()-1, reply);
	  // This used to be "return Hey(...);"---so tokens wasn't delete'd.  -- pfolk@uni.uiuc.edu 1999-11-03
	delete tokens;
	return ret;
}

bool isSpace(char c)
{
	switch (c) {
		case ' ':
		case '\t':
			return true;

		default:
			return false;
	}
}

status_t Hey(BMessenger* target, char* argv[], int32* argx, int32 argc, BMessage* reply)
{
	bool direct_what = false;
	BMessage the_message;
	if(strcasecmp(argv[*argx], "let")==0){			// added "let" -- sander@adamation.com 31may2000
		BMessage get_target (B_GET_PROPERTY);
		get_target.AddSpecifier ("Messenger");
		// parse the specifiers
		(*argx)++;
		status_t result=B_OK;
		while((result=add_specifier(&get_target, argv, argx, argc))==B_OK){};
		
		if(result!=B_ERROR){	// bad syntax
			if(!silent) fprintf(stderr, "Bad specifier syntax!\n");
			return result;
		}
		BMessage msgr;
		if (target && target->IsValid()) {
			result = target->SendMessage(&get_target, &msgr);
			if (result!=B_OK) return result;
			result = msgr.FindMessenger ("result", target);
			if (result!=B_OK) {
				if (!silent) fprintf(stderr, "Couldn't retrieve the BMessenger!\n");
				return result;
			}
		}
		if (!argv[*argx]) {
			if (!silent) fprintf(stderr, "Syntax error - forgot \"do\"?\n");
			return B_ERROR;
		}
	}
	if(strcasecmp(argv[*argx], "do")==0){			// added "do"  -- pfolk@uni.uiuc.edu  1999-11-03
		the_message.what=B_EXECUTE_PROPERTY;
	}else if(strcasecmp(argv[*argx], "get")==0){
		the_message.what=B_GET_PROPERTY;
	}else if(strcasecmp(argv[*argx], "set")==0){
		the_message.what=B_SET_PROPERTY;
	}else if(strcasecmp(argv[*argx], "create")==0){
		the_message.what=B_CREATE_PROPERTY;
	}else if(strcasecmp(argv[*argx], "delete")==0){
		the_message.what=B_DELETE_PROPERTY;
	}else if(strcasecmp(argv[*argx], "quit")==0){
		the_message.what=B_QUIT_REQUESTED;
	}else if(strcasecmp(argv[*argx], "save")==0){
		the_message.what=B_SAVE_REQUESTED;
	}else if(strcasecmp(argv[*argx], "load")==0){
		the_message.what=B_REFS_RECEIVED;
	}else if(strcasecmp(argv[*argx], "count")==0){
		the_message.what=B_COUNT_PROPERTIES;
	}else if(strcasecmp(argv[*argx], "getsuites")==0){
		the_message.what=B_GET_SUPPORTED_SUITES;
	}else{
		switch(strlen(argv[*argx])){	// can be a message constant if 1,2,3 or 4 chars
			case 1:
						the_message.what=(int32)argv[*argx][0];
						break;
			case 2:
						the_message.what=(((int32)argv[*argx][0])<<8)|(((int32)argv[*argx][1]));
						break;
			case 3:
						the_message.what=(((int32)argv[*argx][0])<<16)|(((int32)argv[*argx][1])<<8)|(((int32)argv[*argx][2]));
						break;
			case 4:			
						the_message.what=(((int32)argv[*argx][0])<<24)|(((int32)argv[*argx][1])<<16)|(((int32)argv[*argx][2])<<8)|(((int32)argv[*argx][3]));
						break;
			default:
						// maybe this is a user defined command, ask for the supported suites
						bool found=false;
						if (target && target->IsValid()) {
							BMessage rply;
							BMessage req(B_GET_SUPPORTED_SUITES);
							if(target->SendMessage(&req, &rply)==B_OK){
								// if all goes well, rply contains all kinds of property infos
								int32 j=0;
								void *voidptr;
								int32 sizefound;
								BPropertyInfo propinfo;
								const value_info *vinfo;
								int32 vinfo_index, vinfo_count;
								
//								const char *str;
//								while (rply.FindString("suites", j++, &str) == B_OK)
//									printf ("Suite %ld: %s\n", j, str);
//									
//								j = 0;
								while(rply.FindData("messages", B_PROPERTY_INFO_TYPE, j++, (const void **)&voidptr, &sizefound)==B_OK && !found){
									if(propinfo.Unflatten(B_PROPERTY_INFO_TYPE, (const void *)voidptr, sizefound)==B_OK){
										vinfo=propinfo.Values();
										vinfo_index=0;
										vinfo_count=propinfo.CountValues();
#if TEST_VALUEINFO>0
										value_info vinfo[10]={	{"Backup", 'back', B_COMMAND_KIND, "This command backs up your hard drive."},
																{"Abort", 'abor', B_COMMAND_KIND, "Stops the current operation..."},
																{"Type Code", 'type', B_TYPE_CODE_KIND, "Type code info..."}	
															};
										vinfo_count=3;
#endif
				
										while(vinfo_index<vinfo_count){
											if(strcmp(vinfo[vinfo_index].name, argv[*argx])==0){
												found=true;
												the_message.what=vinfo[vinfo_index].value;
#if TEST_VALUEINFO>0
												printf("FOUND COMMAND \"%s\" = %lX\n", vinfo[vinfo_index].name, the_message.what);
#endif
												break;
											}
											vinfo_index++;
										}
										
									}
								}
							}
						}


						if(!found){
							if(!silent) fprintf(stderr, "Bad verb (\"%s\")\n", argv[*argx]);
							return -1;
						}
		}
		direct_what = true;
	}
	
	status_t result=B_OK;
	(*argx)++;

	// One exception: Single data item at end of line.
	if (direct_what && *argx == argc - 1 && argv[*argx] != NULL) {
		add_data(&the_message, argv, argx);
	}
	else {
		// parse the specifiers
		if(the_message.what!=B_REFS_RECEIVED){	// LOAD has no specifier
			while((result=add_specifier(&the_message, argv, argx, argc))==B_OK){};
		
			if(result!=B_ERROR){	// bad syntax
				if(!silent) fprintf(stderr, "Bad specifier syntax!\n");
				return result;
			}
		}
	}
	
	// if verb is SET or LOAD, there should be a to <value>
	if((the_message.what==B_SET_PROPERTY || the_message.what==B_REFS_RECEIVED) && argv[*argx]!=NULL){
		if(strcasecmp(argv[*argx], "to")==0){
			(*argx)++;
		}
		result=add_data(&the_message, argv, argx);
		if(result!=B_OK){
			if(result==B_FILE_NOT_FOUND){
				if(!silent) fprintf(stderr, "File not found!\n");
			}else{
				if(!silent) fprintf(stderr, "Invalid 'to...' value format!\n");
			}
			return result;
		}
	}

	add_with(&the_message, argv, argx, argc);
	
#if DEBUG_HEY>0
	fprintf(stderr, "Send ");
	print_message(&the_message);
	fprintf(stderr, "\n");
#endif

	if (target && target->IsValid()) {
		if (reply) {
			result = target->SendMessage(&the_message, reply);
		} else {
			result = target->SendMessage(&the_message);
		}
	}
	return result;
}

// There can be a with <name>=<type>() [and <name>=<type> ...]
// I treat "and" just the same as "with", it's just to make the script syntax more English-like.
status_t add_with(BMessage *to_message, char *argv[], int32 *argx, int32 argc)
{
	status_t result=B_OK;
	if(*argx < argc - 1 && argv[++(*argx)]!=NULL){
		// printf ("argv[%ld] = %s\n", *argx, argv[*argx]);
		if(strcasecmp(argv[*argx], "with")==0){
			// printf ("\"with\" detected!\n");
			(*argx)++;
			bool done = false;
			do
			{
				result=add_data(to_message, argv, argx);
				if(result!=B_OK){
					if(result==B_FILE_NOT_FOUND){
						if(!silent) fprintf(stderr, "File not found!\n");
					}else{
						if(!silent) fprintf(stderr, "Invalid 'with...' value format!\n");
					}
					return result;
				}
				(*argx)++;
				// printf ("argc = %d, argv[%d] = %s\n", argc, *argx, argv[*argx]);
				if (*argx < argc - 1 && strcasecmp(argv[*argx], "and")==0)
				{
					(*argx)++;
				}
				else
					done = true;
			} while (!done);
		}
	}
	return result;
}

// returns B_OK if successful 
//         B_ERROR if no more specifiers
//         B_BAD_SCRIPT_SYNTAX if syntax error
status_t add_specifier(BMessage *to_message, char *argv[], int32 *argx, int32 argc)
{

	char *property=argv[*argx];
	
	if(property==NULL) return B_ERROR;		// no more specifiers
	
	(*argx)++;
	
	if(strcasecmp(property, "do")==0){	// Part of the "hey App let Specifier do Verb".
		return B_ERROR;	// no more specifiers
	}
	
	if(strcasecmp(property, "to")==0){	// it is the 'to' string!!!
		return B_ERROR;	// no more specifiers
	}
	
	if(strcasecmp(property, "with")==0){	// it is the 'with' string!!!
		*argx -= 2;
		add_with (to_message, argv, argx, argc);
		return B_ERROR;	// no more specifiers
	}
	
	if(strcasecmp(property, "of")==0){		// skip "of", read real property
		property=argv[*argx];
		if(property==NULL) return B_BAD_SCRIPT_SYNTAX;		// bad syntax
		(*argx)++;
	}
	
	if(strcasecmp(property, "the")==0){		// skip "the", read real property  -- pfolk@uni.uiuc.edu 1999-11-03
		property=argv[*argx];
		if(property==NULL) return B_BAD_SCRIPT_SYNTAX;		// bad syntax
		(*argx)++;
	}
	
	// decide the specifier

	char *specifier=NULL;
	if(to_message->what==B_CREATE_PROPERTY) // create is always direct. without this, a "with" would be taken as a specifier
		(*argx)--;
	else
		specifier=argv[*argx];
	if(specifier==NULL){	// direct specifier
		to_message->AddSpecifier(property);
		return B_ERROR;		// no more specifiers
	}

	(*argx)++;

	if(strcasecmp(specifier, "of")==0){	// direct specifier
		to_message->AddSpecifier(property);
		return B_OK;	
	}

	if(strcasecmp(specifier, "to")==0){	// direct specifier
		to_message->AddSpecifier(property);
		return B_ERROR;		// no more specifiers	
	}


	if(specifier[0]=='['){	// index, reverse index or range
		char *end;
		int32 ix1, ix2;
		if(specifier[1]=='-'){	// reverse index
			ix1=strtoul(specifier+2, &end, 10);
			BMessage revspec(B_REVERSE_INDEX_SPECIFIER);
			revspec.AddString("property", property);
			revspec.AddInt32("index", ix1);
			to_message->AddSpecifier(&revspec);
		}else{	// index or range
			ix1=strtoul(specifier+1, &end, 10);
			if(end[0]==']'){	// it was an index
				to_message->AddSpecifier(property, ix1);
				return B_OK;	
			}else{
				specifier=argv[*argx];
				if(specifier==NULL){
					// I was wrong, it was just an index
					to_message->AddSpecifier(property, ix1);
					return B_OK;	
				}		
				(*argx)++;
				if(strcasecmp(specifier, "to")==0){
					specifier=argv[*argx];
					if(specifier==NULL){
						return B_BAD_SCRIPT_SYNTAX;		// wrong syntax
					}
					(*argx)++;
					ix2=strtoul(specifier, &end, 10);
					to_message->AddSpecifier(property, ix1, ix2-ix1>0 ? ix2-ix1 : 1);
					return B_OK;		
				}else{
					return B_BAD_SCRIPT_SYNTAX;		// wrong syntax
				}
			}
		}
	}else{	// name specifier
		// if it contains only digits, it will be an index...
		bool index_spec=true;
		bool reverse = specifier[0]=='-';
		//  accept bare reverse-index-specs  -- pfolk@uni.uiuc.edu 1999-11-03
		size_t speclen = strlen(specifier);
		for (int32 i=(reverse?1:0); i<(int32)speclen; ++i){
			if(specifier[i]<'0' || specifier[i]>'9'){
				index_spec=false;
				break;
			}
		}
		
		if(index_spec){
			if (reverse)
			{
				// Copied from above  -- pfolk@uni.uiuc.edu 1999-11-03
				BMessage revspec(B_REVERSE_INDEX_SPECIFIER);
				revspec.AddString("property", property);
				revspec.AddInt32("index", atol(specifier+1));
				to_message->AddSpecifier(&revspec);
			}
			else to_message->AddSpecifier(property, atol(specifier));
		}else{
			// Allow any name by counting an initial " as a literal-string indicator
			//  -- pfolk@uni.uiuc.edu 1999-11-03
			if(specifier[0]=='\"')
			{
				if (specifier[speclen-1]=='\"')
					specifier[speclen-1]='\0';
				++specifier;
				--speclen;
			}
			to_message->AddSpecifier(property, specifier);
		}
		
	}
	
	return B_OK;
}


status_t add_data(BMessage *to_message, char *argv[], int32 *argx)
{
	char *valuestring=argv[*argx];
	
	if(valuestring==NULL) return B_ERROR;
	
	// try to interpret it as an integer or float
	bool contains_only_digits=true;
	bool is_floating_point=false;
	for(int32 i=0;i<(int32)strlen(valuestring);i++){
		if(i!=0 || valuestring[i]!='-') {
			if(valuestring[i]<'0' || valuestring[i]>'9'){
				if(valuestring[i]=='.'){
					is_floating_point=true;
				}else{
					contains_only_digits=false;
					break;
				}
			}
		}
	}
	//printf("%d %d\n",	contains_only_digits,is_floating_point);
	if(contains_only_digits){
		if(is_floating_point){
			to_message->AddFloat("data", atof(valuestring));
			return B_OK;
		}else{
			to_message->AddInt32("data", atol(valuestring));
			return B_OK;
		}
	}
	
	// if true or false, it is bool
	if(strcasecmp(valuestring, "true")==0){
		to_message->AddBool("data", true);
		return B_OK;
	}else if(strcasecmp(valuestring, "false")==0){
		to_message->AddBool("data", false);
		return B_OK;
	}

	// Add support for "<name>=<type>()" here:
	// The type is then added under the name "name".

	#define MAX_NAME_LENGTH 128
	char curname[MAX_NAME_LENGTH];
	strcpy (curname, "data");	// This is the default.
	
	char *s = valuestring;
	while (*++s && *s != '=')
		// Look for a '=' character...
		;
	if (*s == '=')	// We found a <name>= 
	{
		*s = 0;
		strcpy (curname, valuestring);	// Use the new <name>
		valuestring = s + 1;			// Reposition the valuestring ptr.
	}

	// must begin with a type( value )
	if(strncasecmp(valuestring, "int8", strlen("int8"))==0){
		to_message->AddInt8(curname, atol(valuestring+strlen("int8(")));
		return B_OK;
	}else if(strncasecmp(valuestring, "int16", strlen("int16"))==0){
		to_message->AddInt16(curname, atol(valuestring+strlen("int16(")));
		return B_OK;
	}else if(strncasecmp(valuestring, "int32", strlen("int32"))==0){
		to_message->AddInt32(curname, atol(valuestring+strlen("int32(")));
		return B_OK;
	}else if(strncasecmp(valuestring, "int64", strlen("int64"))==0){
		to_message->AddInt64(curname, atol(valuestring+strlen("int64(")));
		return B_OK;
	}else if(strncasecmp(valuestring, "bool", strlen("bool"))==0){
		if(strncasecmp(valuestring+strlen("bool("), "true", 4)==0){
			to_message->AddBool(curname, true);
		}else if(strncasecmp(valuestring+strlen("bool("), "false", 5)==0){
			to_message->AddBool(curname, false);
		}else{
			to_message->AddBool(curname, atol(valuestring+strlen("bool("))==0 ? false : true);
		}
		return B_OK;
	}else if(strncasecmp(valuestring, "float", strlen("float"))==0){
		to_message->AddFloat(curname, atof(valuestring+strlen("float(")));
		return B_OK;
	}else if(strncasecmp(valuestring, "double", strlen("double"))==0){
		to_message->AddDouble(curname, atof(valuestring+strlen("double(")));
		return B_OK;
	}else if(strncasecmp(valuestring, "BPoint", strlen("BPoint"))==0){
		float x,y;
		x=atof(valuestring+strlen("BPoint("));
		if(strchr(valuestring, ',')){
			y=atof(strchr(valuestring, ',')+1);
		}else if(strchr(valuestring, ' ')){
			y=atof(strchr(valuestring, ' ')+1);
		}else{	// bad syntax
			y=0.0f;
		}
		to_message->AddPoint(curname, BPoint(x,y));
		return B_OK;
	}else if(strncasecmp(valuestring, "BRect", strlen("BRect"))==0){
		float l=0.0f, t=0.0f, r=0.0f, b=0.0f;
		char *ptr;
		l=atof(valuestring+strlen("BRect("));
		ptr=strchr(valuestring, ',');
		if(ptr){
			t=atof(ptr+1);
			ptr=strchr(ptr+1, ',');
			if(ptr){
				r=atof(ptr+1);
				ptr=strchr(ptr+1, ',');
				if(ptr){
					b=atof(ptr+1);
				}
			}
		}
		
		to_message->AddRect(curname, BRect(l,t,r,b));
		return B_OK;
	}else if(strncasecmp(valuestring, "rgb_color", strlen("rgb_color"))==0){
		rgb_color clr;
		char *ptr;
		clr.red=atol(valuestring+strlen("rgb_color("));
		ptr=strchr(valuestring, ',');
		if(ptr){
			clr.green=atol(ptr+1);
			ptr=strchr(ptr+1, ',');
			if(ptr){
				clr.blue=atol(ptr+1);
				ptr=strchr(ptr+1, ',');
				if(ptr){
					clr.alpha=atol(ptr+1);
				}
			}
		}
		
		to_message->AddData(curname, B_RGB_COLOR_TYPE, &clr, sizeof(rgb_color));
		return B_OK;
	}else if(strncasecmp(valuestring, "file", strlen("file"))==0){
		entry_ref file_ref;
		
		// remove the last ] or )
		if(valuestring[strlen(valuestring)-1]==')' || valuestring[strlen(valuestring)-1]==']'){
			valuestring[strlen(valuestring)-1]=0;
		}
		
		if(get_ref_for_path(valuestring+5, &file_ref)!=B_OK){
			return B_FILE_NOT_FOUND;
		}
		
		// check if the ref is valid
		BEntry entry;
		if(entry.SetTo(&file_ref)!=B_OK) return B_FILE_NOT_FOUND;
		//if(!entry.Exists())  return B_FILE_NOT_FOUND;
		
		// add both ways, refsreceived needs it as "refs" while scripting needs "data"
		to_message->AddRef("refs", &file_ref);
		to_message->AddRef(curname, &file_ref);
		return B_OK;
	}else{	// it is string
		// does it begin with a quote?
		if(valuestring[0]=='\"'){
			if(valuestring[strlen(valuestring)-1]=='\"') valuestring[strlen(valuestring)-1]=0;
			to_message->AddString(curname, valuestring+1);
		}else{
			to_message->AddString(curname, valuestring);
		}
		return B_OK;
	}

	return B_OK;
}



void print_message(BMessage *message)
{

	BList textlist;
	add_message_contents(&textlist, message, 0);
	

	printf("BMessage(%s):\n", get_datatype_string(message->what));
	for(int32 i=0;i<textlist.CountItems();i++){
		printf("   %s\n", (char*)textlist.ItemAt(i));
		free(textlist.ItemAt(i));
	}

}



void add_message_contents(BList *textlist, BMessage *msg, int32 level)
{
	int32 count;
	int32 i, sizefound, j;
	ulong typefound;
	char *namefound;
	void *voidptr;
	BMessage a_message;
	char *textline, *datatype, *content;
	
	
	// go though all message data
	count=msg->CountNames(B_ANY_TYPE);
	for(i=0;i<count;i++){
		msg->GetInfo(B_ANY_TYPE, i, &namefound, &typefound);
		j=0;
		
		while(msg->FindData(namefound, typefound, j++, (const void **)&voidptr, &sizefound)==B_OK){
			datatype=get_datatype_string(typefound);
			content=format_data(typefound, (char*)voidptr, sizefound);
			textline=(char*)malloc(20+level*4+strlen(namefound)+strlen(datatype)+strlen(content));
			memset(textline, 32, 20+level*4);
			sprintf(textline+level*4, "\"%s\" (%s) : %s", namefound, datatype, content);
			textlist->AddItem(textline);
			delete [] datatype;
			delete [] content;
			
			if(typefound==B_MESSAGE_TYPE){
				msg->FindMessage(namefound, j-1, &a_message);
				add_message_contents(textlist, &a_message, level+1);
			}else
			if(typefound==B_RAW_TYPE && strcmp(namefound, "_previous_")==0){
				if(a_message.Unflatten((const char *)voidptr)==B_OK){
					add_message_contents(textlist, &a_message, level+1);
				}
			}
		}
		
	}

}



char *get_datatype_string(int32 type)
{
	char *str=new char[128];
	
	switch(type){
		case B_ANY_TYPE:	strcpy(str, "B_ANY_TYPE"); break;
		case B_ASCII_TYPE:	strcpy(str, "B_ASCII_TYPE"); break;
		case B_BOOL_TYPE:	strcpy(str, "B_BOOL_TYPE"); break;
		case B_CHAR_TYPE:	strcpy(str, "B_CHAR_TYPE"); break;
		case B_COLOR_8_BIT_TYPE:	strcpy(str, "B_COLOR_8_BIT_TYPE"); break;
		case B_DOUBLE_TYPE:	strcpy(str, "B_DOUBLE_TYPE"); break;
		case B_FLOAT_TYPE:	strcpy(str, "B_FLOAT_TYPE"); break;
		case B_GRAYSCALE_8_BIT_TYPE:	strcpy(str, "B_GRAYSCALE_8_BIT_TYPE"); break;
		case B_INT64_TYPE:	strcpy(str, "B_INT64_TYPE"); break;
		case B_INT32_TYPE:	strcpy(str, "B_INT32_TYPE"); break;
		case B_INT16_TYPE:	strcpy(str, "B_INT16_TYPE"); break;
		case B_INT8_TYPE:	strcpy(str, "B_INT8_TYPE"); break;
		case B_MESSAGE_TYPE:	strcpy(str, "B_MESSAGE_TYPE"); break;
		case B_MESSENGER_TYPE:	strcpy(str, "B_MESSENGER_TYPE"); break;
		case B_MIME_TYPE:	strcpy(str, "B_MIME_TYPE"); break;
		case B_MONOCHROME_1_BIT_TYPE:	strcpy(str, "B_MONOCHROME_1_BIT_TYPE"); break;
		case B_OBJECT_TYPE:	strcpy(str, "B_OBJECT_TYPE"); break;
		case B_OFF_T_TYPE:	strcpy(str, "B_OFF_T_TYPE"); break;
		case B_PATTERN_TYPE:	strcpy(str, "B_PATTERN_TYPE"); break;
		case B_POINTER_TYPE:	strcpy(str, "B_POINTER_TYPE"); break;
		case B_POINT_TYPE:	strcpy(str, "B_POINT_TYPE"); break;
		case B_RAW_TYPE:	strcpy(str, "B_RAW_TYPE"); break;
		case B_RECT_TYPE:	strcpy(str, "B_RECT_TYPE"); break;
		case B_REF_TYPE:	strcpy(str, "B_REF_TYPE"); break;
		case B_RGB_32_BIT_TYPE:	strcpy(str, "B_RGB_32_BIT_TYPE"); break;
		case B_RGB_COLOR_TYPE:	strcpy(str, "B_RGB_COLOR_TYPE"); break;
		case B_SIZE_T_TYPE:	strcpy(str, "B_SIZE_T_TYPE"); break;
		case B_SSIZE_T_TYPE	:	strcpy(str, "B_SSIZE_T_TYPE"); break;
		case B_STRING_TYPE:	strcpy(str, "B_STRING_TYPE"); break;
		case B_TIME_TYPE :	strcpy(str, "B_TIME_TYPE"); break;
		case B_UINT64_TYPE	:	strcpy(str, "B_UINT64_TYPE"); break;
		case B_UINT32_TYPE:	strcpy(str, "B_UINT32_TYPE"); break;
		case B_UINT16_TYPE :	strcpy(str, "B_UINT16_TYPE"); break;
		case B_UINT8_TYPE :	strcpy(str, "B_UINT8_TYPE"); break;
		case B_PROPERTY_INFO_TYPE: strcpy(str, "B_PROPERTY_INFO_TYPE"); break;
		// message constants:
		case B_ABOUT_REQUESTED :	strcpy(str, "B_ABOUT_REQUESTED"); break;
		case B_WINDOW_ACTIVATED :	strcpy(str, "B_WINDOW_ACTIVATED"); break;
		case B_ARGV_RECEIVED :	strcpy(str, "B_ARGV_RECEIVED"); break;
		case B_QUIT_REQUESTED :	strcpy(str, "B_QUIT_REQUESTED"); break;
		case B_CANCEL :	strcpy(str, "B_CANCEL"); break;
		case B_KEY_DOWN :	strcpy(str, "B_KEY_DOWN"); break;
		case B_KEY_UP :	strcpy(str, "B_KEY_UP"); break;
		case B_MINIMIZE	 :	strcpy(str, "B_MINIMIZE"); break;
		case B_MOUSE_DOWN :	strcpy(str, "B_MOUSE_DOWN"); break;
		case B_MOUSE_MOVED :	strcpy(str, "B_MOUSE_MOVED"); break;
		case B_MOUSE_ENTER_EXIT	 :	strcpy(str, "B_MOUSE_ENTER_EXIT"); break;
		case B_MOUSE_UP  :	strcpy(str, "B_MOUSE_UP"); break;
		case B_PULSE  :	strcpy(str, "B_PULSE"); break;
		case B_READY_TO_RUN :	strcpy(str, "B_READY_TO_RUN"); break;
		case B_REFS_RECEIVED :	strcpy(str, "B_REFS_RECEIVED"); break;
		case B_SCREEN_CHANGED :	strcpy(str, "B_SCREEN_CHANGED"); break;
		case B_VALUE_CHANGED :	strcpy(str, "B_VALUE_CHANGED"); break;
		case B_VIEW_MOVED :	strcpy(str, "B_VIEW_MOVED"); break;
		case B_VIEW_RESIZED :	strcpy(str, "B_VIEW_RESIZED"); break;
		case B_WINDOW_MOVED :	strcpy(str, "B_WINDOW_MOVED"); break;
		case B_WINDOW_RESIZED :	strcpy(str, "B_WINDOW_RESIZED"); break;
		case B_WORKSPACES_CHANGED :	strcpy(str, "B_WORKSPACES_CHANGED"); break;
		case B_WORKSPACE_ACTIVATED :	strcpy(str, "B_WORKSPACE_ACTIVATED"); break;
		case B_ZOOM	 :	strcpy(str, "B_ZOOM"); break;
		case _APP_MENU_ :	strcpy(str, "_APP_MENU_"); break;
		case _BROWSER_MENUS_ :	strcpy(str, "_BROWSER_MENUS_"); break;
		case _MENU_EVENT_  :	strcpy(str, "_MENU_EVENT_"); break;
		case _QUIT_  :	strcpy(str, "_QUIT_"); break;
		case _VOLUME_MOUNTED_  :	strcpy(str, "_VOLUME_MOUNTED_"); break;
		case _VOLUME_UNMOUNTED_	 :	strcpy(str, "_VOLUME_UNMOUNTED_"); break;
		case _MESSAGE_DROPPED_ 	 :	strcpy(str, "_MESSAGE_DROPPED_"); break;
		case _MENUS_DONE_ :	strcpy(str, "_MENUS_DONE_"); break;
		case _SHOW_DRAG_HANDLES_	 :	strcpy(str, "_SHOW_DRAG_HANDLES_"); break;
		case B_SET_PROPERTY		 :	strcpy(str, "B_SET_PROPERTY"); break;
		case B_GET_PROPERTY	 :	strcpy(str, "B_GET_PROPERTY"); break;
		case B_CREATE_PROPERTY	 :	strcpy(str, "B_CREATE_PROPERTY"); break;
		case B_DELETE_PROPERTY	 :	strcpy(str, "B_DELETE_PROPERTY"); break;
		case B_COUNT_PROPERTIES	 :	strcpy(str, "B_COUNT_PROPERTIES"); break;
		case B_EXECUTE_PROPERTY  :      strcpy(str, "B_EXECUTE_PROPERTY"); break;
		case B_GET_SUPPORTED_SUITES	 :	strcpy(str, "B_GET_SUPPORTED_SUITES"); break;
		case B_CUT 	 :	strcpy(str, "B_CUT"); break;
		case B_COPY 	 :	strcpy(str, "B_COPY"); break;
		case B_PASTE 	 :	strcpy(str, "B_PASTE"); break;
		case B_SELECT_ALL	 :	strcpy(str, "B_SELECT_ALL"); break;
		case B_SAVE_REQUESTED 	 :	strcpy(str, "B_SAVE_REQUESTED"); break;
		case B_MESSAGE_NOT_UNDERSTOOD :	strcpy(str, "B_MESSAGE_NOT_UNDERSTOOD"); break;
		case B_NO_REPLY 	 :	strcpy(str, "B_NO_REPLY"); break;
		case B_REPLY  :	strcpy(str, "B_REPLY"); break;
		case B_SIMPLE_DATA	 :	strcpy(str, "B_SIMPLE_DATA"); break;
		//case B_MIME_DATA	 :	strcpy(str, "B_MIME_DATA"); break;
		case B_ARCHIVED_OBJECT	 :	strcpy(str, "B_ARCHIVED_OBJECT"); break;
		case B_UPDATE_STATUS_BAR :	strcpy(str, "B_UPDATE_STATUS_BAR"); break;
		case B_RESET_STATUS_BAR	 :	strcpy(str, "B_RESET_STATUS_BAR"); break;
		case B_NODE_MONITOR		 :	strcpy(str, "B_NODE_MONITOR"); break;
		case B_QUERY_UPDATE	 :	strcpy(str, "B_QUERY_UPDATE"); break;
		case B_BAD_SCRIPT_SYNTAX: strcpy(str, "B_BAD_SCRIPT_SYNTAX"); break;

		// specifiers:
		case B_NO_SPECIFIER	 :	strcpy(str, "B_NO_SPECIFIER"); break;
		case B_DIRECT_SPECIFIER	 :	strcpy(str, "B_DIRECT_SPECIFIER"); break;
		case B_INDEX_SPECIFIER	 :	strcpy(str, "B_INDEX_SPECIFIER"); break;
		case B_REVERSE_INDEX_SPECIFIER	 :	strcpy(str, "B_REVERSE_INDEX_SPECIFIER"); break;
		case B_RANGE_SPECIFIER	 :	strcpy(str, "B_RANGE_SPECIFIER"); break;
		case B_REVERSE_RANGE_SPECIFIER	 :	strcpy(str, "B_REVERSE_RANGE_SPECIFIER"); break;
		case B_NAME_SPECIFIER	 :	strcpy(str, "B_NAME_SPECIFIER"); break;

		case B_ERROR	 :	strcpy(str, "B_ERROR"); break;
				
		default:	// unknown
					id_to_string(type, str);
					break;
	}
	
	return str;

}


char *format_data(int32 type, char *ptr, long size)
{
	char idtext[32];
	char *str;
	float *fptr;
	double *dptr;
//	BRect *brptr;
	long i;
	entry_ref aref;
	BEntry entry;
	BPath path;
	int64 i64;
	int32 i32;
	int16 i16;
	int8 i8;
	uint64 ui64;
	uint32 ui32;
	uint16 ui16;
	uint8 ui8;
	BMessage anothermsg;
	BPropertyInfo propinfo;
	const property_info *pinfo;
	int32 pinfo_index;
	const value_info *vinfo;
	int32 vinfo_index, vinfo_count;
	char *tempstr;
	
	
	if(size<=0L){
		str=new char;
		*str=0;
		return str;
	}

	switch(type){
		case B_MIME_TYPE:
		case B_ASCII_TYPE:
		case B_STRING_TYPE:
					if(size>512) size=512;
					str=new char[size+4];
					*str='\"';
					strncpy(str+1, ptr, size);
					strcat(str, "\"");
					break;
		case B_POINTER_TYPE:
					str=new char[64];
					sprintf(str, "%p", *(void**)ptr);
					break;

		case B_REF_TYPE:
					str=new char[1024];
					anothermsg.AddData("myref", B_REF_TYPE, ptr, size);
					anothermsg.FindRef("myref", &aref);
					if(entry.SetTo(&aref)==B_OK){
						entry.GetPath(&path);
						strcpy(str, path.Path());
					}else{
						strcpy(str, "invalid entry_ref");
					}
					break;
					
		case B_SSIZE_T_TYPE:
		case B_INT64_TYPE:
					str=new char[64];
					i64=*(int64*)ptr;
					sprintf(str, "%Ld (0x%LX)", i64, i64);
					break;
		
		case B_SIZE_T_TYPE:
		case B_INT32_TYPE:
					str=new char[64];
					i32=*(int32*)ptr;
					sprintf(str, "%ld (0x%08lX)", i32, i32);
					break;
		
		case B_INT16_TYPE:
					str=new char[64];
					i16=*(int16*)ptr;
					sprintf(str, "%d (0x%04X)", i16, i16);
					break;
		
		case B_CHAR_TYPE:
		case B_INT8_TYPE:
					str=new char[64];
					i8=*(int8*)ptr;
					sprintf(str, "%d (0x%02X)", i8, i8);
					break;
		
		case B_UINT64_TYPE:
					str=new char[64];
					ui64=*(uint64*)ptr;
					sprintf(str, "%Lu (0x%LX)", ui64, ui64);
					break;
		
		case B_UINT32_TYPE:
					str=new char[64];
					ui32=*(uint32*)ptr;
					sprintf(str, "%lu (0x%08lX)", ui32, ui32);
					break;
		
		case B_UINT16_TYPE:
					str=new char[64];
					ui16=*(uint16*)ptr;
					sprintf(str, "%u (0x%04X)", ui16, ui16);
					break;
		
		case B_UINT8_TYPE:
					str=new char[64];
					ui8=*(uint8*)ptr;
					sprintf(str, "%u (0x%02X)", ui8, ui8);
					break;
		
		case B_BOOL_TYPE:
					str=new char[10];
					if(*ptr){
						strcpy(str, "TRUE");
					}else{
						strcpy(str, "FALSE");
					}
					break;
					
		case B_FLOAT_TYPE:
					str=new char[40];
					fptr=(float*)ptr;
					sprintf(str, "%.3f", *fptr);
					break;
					
		case B_DOUBLE_TYPE:
					str=new char[40];
					dptr=(double*)ptr;
					sprintf(str, "%.3f", *dptr);
					break;
					
		case B_RECT_TYPE:
					str=new char[200];
					fptr=(float*)ptr;
					sprintf(str, "BRect(%.1f, %.1f, %.1f, %.1f)", fptr[0], fptr[1], fptr[2], fptr[3]);
					break;
					
		case B_POINT_TYPE:
					str=new char[200];
					fptr=(float*)ptr;
					sprintf(str, "BPoint(%.1f, %.1f)", fptr[0], fptr[1]);
					break;

		case B_RGB_COLOR_TYPE:	
					str=new char[64];
					sprintf(str, "Red=%u  Green=%u  Blue=%u  Alpha=%u", ((uint8*)ptr)[0], ((uint8*)ptr)[1], ((uint8*)ptr)[2], ((uint8*)ptr)[3] );
					break;
					
		case B_COLOR_8_BIT_TYPE:
					str=new char[size*6+4];
					*str=0;
					for(i=0;i<min_c(256,size);i++){
						sprintf(idtext, "%u ", ((unsigned char*)ptr)[i]);
						strcat(str,idtext);
					}
					*(str+strlen(str)-2)=0;
					break;

		case B_MESSAGE_TYPE:
					str=new char[64];
					if(anothermsg.Unflatten((const char *)ptr)==B_OK){
						sprintf(str, "what=%s", get_datatype_string(anothermsg.what));
					}else{
						strcpy(str, "error when unflattening");
					}
					break;
					
		case B_PROPERTY_INFO_TYPE:
					if(propinfo.Unflatten(B_PROPERTY_INFO_TYPE, (const void *)ptr, size)==B_OK){
						str=new char[size*32];	// an approximation

						//propinfo.PrintToStream();
						//sprintf(str, "see the printout above");

						pinfo=propinfo.Properties();
						pinfo_index=0;

						sprintf(str, "\n        property   commands                            specifiers\n--------------------------------------------------------------------------------\n");
						
						while(pinfo_index<propinfo.CountProperties()){
						
							strcat(str,  "                "+(strlen(pinfo[pinfo_index].name) <16 ? strlen(pinfo[pinfo_index].name) : 16 ));
							strcat(str, pinfo[pinfo_index].name);
							strcat(str, "   ");
							char *start=str+strlen(str);

							for(int32 i=0;i<10 && pinfo[pinfo_index].commands[i];i++){
								//id_to_string(pinfo[pinfo_index].commands[i], str+strlen(str) );
								tempstr=get_datatype_string(pinfo[pinfo_index].commands[i]);
								strcat(str, tempstr);
								strcat(str, " ");
								delete [] tempstr;
							}
							
							// pad the rest with spaces
							if(strlen(start)<36){
								strcat(str,  "                                    "+strlen(start) );
							}else{
								strcat(str,  "  " );
							}

							for(int32 i=0;i<10 && pinfo[pinfo_index].specifiers[i];i++){
								switch(pinfo[pinfo_index].specifiers[i]){
									case B_NO_SPECIFIER: strcat(str, "NONE "); break;
									case B_DIRECT_SPECIFIER: strcat(str, "DIRECT "); break;
									case B_INDEX_SPECIFIER: strcat(str, "INDEX "); break;
									case B_REVERSE_INDEX_SPECIFIER: strcat(str, "REV.INDEX "); break;
									case B_RANGE_SPECIFIER: strcat(str, "RANGE "); break;
									case B_REVERSE_RANGE_SPECIFIER: strcat(str, "REV.RANGE "); break;
									case B_NAME_SPECIFIER: strcat(str, "NAME"); break;
									case B_ID_SPECIFIER: strcat(str, "ID"); break;
									default: strcat(str, "<NONE> "); break;
								}
							}
							strcat(str, "\n");
							
							// is there usage info?
							if(pinfo[pinfo_index].usage){
								strcat(str, "                   Usage: ");
								strcat(str, pinfo[pinfo_index].usage);
								strcat(str, "\n");
							}

						
							pinfo_index++;		// take next propertyinfo
						}
						
						
						// handle value infos....
						vinfo=propinfo.Values();
						vinfo_index=0;
						vinfo_count=propinfo.CountValues();
#if TEST_VALUEINFO>0
						value_info vinfo[10]={	{"Backup", 'back', B_COMMAND_KIND, "This command backs up your hard drive."},
												{"Abort", 'abor', B_COMMAND_KIND, "Stops the current operation..."},
												{"Type Code", 'type', B_TYPE_CODE_KIND, "Type code info..."}	
											};
						vinfo_count=3;
#endif

						if(vinfo && vinfo_count>0){
							sprintf(str+strlen(str), "\n            name   value                               kind\n--------------------------------------------------------------------------------\n");
							
							while(vinfo_index<vinfo_count){
							
								char *start=str+strlen(str);
								strcat(str,  "                "+(strlen(vinfo[vinfo_index].name) <16 ? strlen(vinfo[vinfo_index].name) : 16 ));
								strcat(str, vinfo[vinfo_index].name);
								strcat(str, "   ");
							
								sprintf(str+strlen(str), "0x%8lX (", vinfo[vinfo_index].value);
								id_to_string(vinfo[vinfo_index].value, str+strlen(str));
								strcat(str, ")");

								// pad the rest with spaces
								if(strlen(start)<36+19){
									strcat(str,  "                                                       "+strlen(start) );
								}else{
									strcat(str,  "  " );
								}

								switch(vinfo[vinfo_index].kind){
									case B_COMMAND_KIND: 	strcat(str, "COMMAND         "); break;
									case B_TYPE_CODE_KIND: 	strcat(str, "TYPE CODE       "); break;
									default:				strcat(str, "unknown         "); break;
								}
								
	
								strcat(str, "\n");
								
								// is there usage info?
								if(vinfo[vinfo_index].usage){
									strcat(str, "                   Usage: ");
									strcat(str, vinfo[vinfo_index].usage);
									strcat(str, "\n");
								}
	
								vinfo_index++;		// take next valueinfo
							}
						}
						
					}else{
						str=new char[64];
						strcpy(str, "error when unflattening");
					}
					break;

		default:
					str=new char[min_c(256,size)*20+4];
					*str=0;
					for(i=0;i<min_c(256,size);i++){
						//sprintf(idtext, "0x%02X ('%c'), ", (uint16)ptr[i], ptr[i]<32 ? 32 : ptr[i]);
						sprintf(idtext, "0x%02X, ", (uint16)ptr[i] );
						strcat(str,idtext);
					}
					*(str+strlen(str)-2)=0;
					break;
	}
	
	return str;
	
}



char *id_to_string(long ID, char *here)
{
	uint8 digit0=(ID>>24)&255;
	uint8 digit1=(ID>>16)&255;
	uint8 digit2=(ID>>8)&255;
	uint8 digit3=(ID)&255;
	bool itsvalid=false;
	
	if(digit0==0){
		if(digit1==0){
			if(digit2==0){
				// 1 digits
				if(is_valid_char(digit3) ) itsvalid=TRUE;
				sprintf(here, "'%c'", digit3);
			}else{
				// 2 digits
				if(is_valid_char(digit2) && is_valid_char(digit3) ) itsvalid=TRUE;
				sprintf(here, "'%c%c'", digit2, digit3);
			}
		}else{
			// 3 digits
			if(is_valid_char(digit1) && is_valid_char(digit2) && is_valid_char(digit3) ) itsvalid=TRUE;
			sprintf(here, "'%c%c%c'", digit1, digit2, digit3);
		}
	}else{
		// 4 digits
		if(is_valid_char(digit0) && is_valid_char(digit1) && is_valid_char(digit2) && is_valid_char(digit3) ) itsvalid=TRUE;
		sprintf(here, "'%c%c%c%c'", digit0, digit1, digit2, digit3);
	}
	
	if(!itsvalid){
		sprintf(here, "%ldL", ID);
	}

	return here;
}


bool is_valid_char(uint8 c)
{
	return (c>=32 && c<128);
}

