/*
 * mudos_saverestore.c
 *
 * Emulates the MudOS save_variable and restore_variable efunctions.
 * 
 * 8 January 1997	Begun		Greg Lewis
 *	# Wrote and tested initial version.  restore_variable needs to do
 *	  error checking though (i.e., it works if the string it gets is 
 *	  right, but crashes and burns otherwise ;).
 * 
 * 10 January 1997	Version 1.0	Greg Lewis
 *	# Added error checking to restore_variable, it returns RESTORE_ERROR
 *	  upon errors in the restore process.
 *	# Everything seems to test ok, not that I've been all that thorough.
 *
 * TBD
 *	# Prolly needs some more commenting (doesn't most code?)
 */

/* Includes */

#include	<type.h>

#ifndef RESTORE_ERROR
#define RESTORE_ERROR   -0x80000000     /* Largest -ve #, must be hex or oct */
#endif


/* Prototypes */

private static string grab_value_string(string saved_ma, varargs string separator);
private static int find_string_end(string string_bit);
private static int find_arraymap_end(string array_bit, int type);


/*
 * NAME: 	save_variable
 * DESCRIPTION: Given any value it produces a string which represents the
 *		value in the MudOS save/restore format.
 */

string
save_variable(mixed var) {
   string 	result;
   int		i, s;
   mixed	*keys, *values;

   if (!var) {
      return "0";
   }
   switch (typeof(var)) {
      case T_INT : case T_FLOAT :
         result = ""+var;
         break;
      case T_STRING :
         result = implode(explode(var, "\\"), "\\\\");
         result = implode(explode(result, "\""), "\\\"");
         result = "\""+result+"\"";
         break;
      case T_OBJECT :
         result = object_name(var);
         break;
      case T_ARRAY :
         result = "({";
         for (i = 0, s = sizeof(var); i < s; i++) {
            result += save_variable(var[i]) + ",";
         }
         result += "})";
         break;
      case T_MAPPING :
         keys = map_indices(var);
         values = map_values(var);
         result = "([";
         for (i = 0, s = map_sizeof(var); i < s; i++) {
            result += save_variable(keys[i]) + ":" + save_variable(values[i]) 
                      + ","; 
         }
         result += "])";
         break;
      default :
         result = nil;
         break;
   }
   return result;
}

/*
 * NAME: 	restore_variable
 * DESCRIPTION: Given a string in the format returned by save_variable,
 *              restore_variable returns a copy of what was originally saved,
 *	        or RESTORE_ERROR in case of an error.
 */

mixed
restore_variable(string saved_var) {
   int		s;
   mixed	*a, *tmp;
   mapping	m;
   string	elt, elt2;
   object	ob;
   int		number;
   float	real;

   if (!saved_var || !(s = strlen(saved_var))) {
      return RESTORE_ERROR; 
   }
   switch (saved_var[0]) {
      case '"' :
         if (s < 2 || find_string_end(saved_var[1 .. s-1]) != s-2) {
            return RESTORE_ERROR;
         }
         elt = implode(explode(saved_var[1 .. s-2], "\\\\"), "\\");
         elt = implode(explode(elt, "\\\""), "\"");
         return elt;
         break;
      case '/' :
         ob = find_object(saved_var);
         return (ob) ? ob : RESTORE_ERROR;
         break;
      case '(' :
         if (s < 4) {  
            return RESTORE_ERROR;
         }
         if (saved_var[1] == '{') {
            if (saved_var == "({})") {
               return ({ });
            }
/*
            if (find_arraymap_end(saved_var[2 .. s-1], T_ARRAY) != s-3) {
               return RESTORE_ERROR;
            }
*/
            number = 2;
            a = ({ });
            while (number < s-2) {
               elt = grab_value_string(saved_var[number .. s-1]);
               if (!elt) {
                  return RESTORE_ERROR;
               }
               number += strlen(elt)+1;
               a += ({ restore_variable(elt) });
            }
            return a;      
         }
         else if (saved_var[1] == '[') { 
/*
            if (find_arraymap_end(saved_var[2 .. s-1], T_MAPPING) != s-3) {
               return RESTORE_ERROR;
            }
*/
            if (saved_var == "([])") {
               return ([ ]);
            }
            number = 2;
            m = ([ ]);
            while (number < s-2) {
               elt = grab_value_string(saved_var[number .. s-1], ":");
               if (!elt) {
                  return RESTORE_ERROR;
               }
               number += strlen(elt)+1;
               elt2 = grab_value_string(saved_var[number .. s-1]);
               if (!elt2) {
                  return RESTORE_ERROR;
               }
               number += strlen(elt2)+1;
               m[restore_variable(elt)] = restore_variable(elt2); 
            }
            return m;
         } 
         return RESTORE_ERROR;
         break;
      default :
         /* array of one byte strings */
         a = explode(saved_var, ""); 
         /* Check valid chars for numbers */
         tmp = a - ({ "0","1","2","3","4","5","6","7","8","9","-","." });
         if (sizeof(tmp)) {
            /* Should also check if there is a - its at the start 
             * and there is something after it */
            return RESTORE_ERROR;
         }
         if (!sscanf(saved_var, "%*s.%*s")) { 
            if (!sscanf(saved_var, "%d", number)) {
               return RESTORE_ERROR;
            }
            return number;
         }
         else {
            if (!sscanf(saved_var, "%f", real)) {
               return RESTORE_ERROR;
            }
            return real;
         }
         break;
   }
}

/*
 * NAME: 	grab_string_value
 * DESCRIPTION: Grabs the next element in an array or the next key/value in
 *	        a mapping and returns the string representing it in
 *		the save_variable format.  The array or mapping is input in
 *	   	in this same format.  0 is returned on error and separator
 *	    	defaults to ",".  See restore_variable for an example of using
 *		this.
 */

private
static
string
grab_value_string(string saved_ma, varargs string separator) {
   int 		pos;

   if (!saved_ma) {
      return nil;
   }
   if (!separator) {
      separator = ",";
   }
   switch (saved_ma[0]) {
      case '"' :
         pos = find_string_end(saved_ma[1 .. ]);
         if (pos == -1) {
            return nil;
         }
         return saved_ma[0 .. pos+1];
         break; 
      case '(' :
         if (saved_ma[1] == '{') { 
            pos = find_arraymap_end(saved_ma[2 .. ], T_ARRAY);
            if (pos == -1) {
               return nil;
            }
            return saved_ma[0 .. pos+2];
         }
         if (saved_ma[1] == '[') {
            pos = find_arraymap_end(saved_ma[2 .. ], T_MAPPING);
            if (pos == -1) {
               return nil;
            }
            return saved_ma[0 .. pos+2];
         }
         return nil;
      default : /* Should cover objects, ints and floats */
         return explode(saved_ma, separator)[0];
   }
}

/* 
 * NAME: 	find_string_end
 * DESCRIPTION: Given a string in the save_variable format (except for the 
 *		leading ") returns the position at which the matching " occurs.
 *		Returns -1 on error.
 */

private
static
int
find_string_end(string string_bit) {
   int last_char;
   int pos;

   if (string_bit[0] == '"') {
      return 0;
   }
   last_char = string_bit[0];
   pos = 1;
   while (pos < strlen(string_bit)) {
      if (string_bit[pos] == '"' && last_char != '\\') {
         return pos;
      }
      last_char = string_bit[pos++];
   }
   return -1; /* Error */
}

/* 
 * NAME: 	find_array_map_end
 * DESCRIPTION: Given an array or mapping in the save_variable format (except 
 *		for the leading ({ or ([) returns the position at which the
 *		end character of the matching }) or ]) occurs. 
 *		Returns -1 on error. 
 */

private
static
int
find_arraymap_end(string array_bit, int type) {
   int pos;
   int last_char;
   int in_array, in_mapping, in_string;
 
   /* We need two chars at least for an array or map ending */
   if (!array_bit || strlen(array_bit) < 2) {
      return -1;
   }
   in_array = (type == T_ARRAY) ? 1 : 0;
   in_mapping = (type == T_MAPPING) ? 1 : 0; 
   if (!in_array && !in_mapping) {
      return -1;
   }
   in_string = 0;
   last_char = (type == T_ARRAY) ? '{' : '[';
   pos = 0;
   while (pos < strlen(array_bit)) {
      if (!in_string) {
         if (array_bit[pos] == '"') {
            in_string = 1;
            continue;
         }
         if (array_bit[pos] == ')' && last_char == '}') {
            in_array--;
         } 
         else if (array_bit[pos] == ')' && last_char == ']') {
            in_mapping--;
         } 
         else if (array_bit[pos] == '{' && last_char == '(') {
            in_array++;
         } 
         else if (array_bit[pos] == '[' && last_char == '(') {
            in_mapping++;
         } 
      }
      else {
         if (array_bit[pos] == '"' && last_char != '\\') {
            in_string = 0;
         }
      }
      if (!in_array && !in_mapping && !in_string) {
         return pos;
      }
      last_char = array_bit[pos++];
   }
   return -1; /* Error */
}
 
