nakedmudv3.3/
nakedmudv3.3/lib/
nakedmudv3.3/lib/logs/
nakedmudv3.3/lib/misc/
nakedmudv3.3/lib/players/
nakedmudv3.3/lib/txt/
nakedmudv3.3/lib/world/
nakedmudv3.3/lib/world/examples/
nakedmudv3.3/lib/world/examples/mproto/
nakedmudv3.3/lib/world/examples/oproto/
nakedmudv3.3/lib/world/examples/reset/
nakedmudv3.3/lib/world/examples/rproto/
nakedmudv3.3/lib/world/examples/trigger/
nakedmudv3.3/lib/world/limbo/
nakedmudv3.3/lib/world/limbo/room/
nakedmudv3.3/lib/world/limbo/rproto/
nakedmudv3.3/src/alias/
nakedmudv3.3/src/char_vars/
nakedmudv3.3/src/editor/
nakedmudv3.3/src/example_module/
nakedmudv3.3/src/help/
nakedmudv3.3/src/set_val/
nakedmudv3.3/src/socials/
nakedmudv3.3/src/time/
//******************************************************************************
//
// storage.c
//
// It would be nice to have a standards for storing and loading data from files.
// I looked at XML, but the language didn't really appeal to me. YAML
// (http://www.yaml.org) caught my eye, but I did not manage to find a set of
// YAML utilities for C, so I decided to try and design my own conventions for
// storing data. Most of them are based on what I learned from my (brief)
// exposure to YAML.
//
//******************************************************************************

#include "mud.h"
#include "utils.h"    // trim
#include "storage.h"



//*****************************************************************************
//
// local datastructures, functions, and variables
//
//*****************************************************************************
#define SET_MARKER            '-'
#define LIST_MARKER           '='
#define STRING_MARKER         '~'
#define TYPELESS_MARKER       ' '


struct storage_set {
  HASHTABLE *entries;
  int    longest_key;
  int      top_entry;
};

struct storage_set_list {
  LIST            *list;
  LIST_ITERATOR *list_i;
};

typedef struct storage_data {
  char                  *key; // what is the variable name we're known by?
  char              *str_val; // what is the data's string value?
  STORAGE_SET_LIST *list_val; // list value?
  STORAGE_SET       *set_val; // set value?
  int              entry_num; // used by storage_set to keep track of order
} STORAGE_DATA;



/* local functions */
void delete_storage_set (STORAGE_SET *set);
void delete_storage_list(STORAGE_SET_LIST *list);
void delete_storage_data(STORAGE_DATA *data);

bool list_is_empty(STORAGE_SET_LIST *list);
bool set_is_empty (STORAGE_SET *set);

void write_storage_set(STORAGE_SET *set, FILE *fl, int indent);
void write_storage_list(STORAGE_SET_LIST *list, FILE *fl, int indent);
void write_storage_data(STORAGE_DATA *data, FILE *fl, int key_width,int indent);


void delete_storage_set(STORAGE_SET *set) {
  HASH_ITERATOR *hash_i = newHashIterator(set->entries);
  STORAGE_DATA    *data = NULL;
  const char       *key = NULL;
  
  ITERATE_HASH(key, data, hash_i)
    delete_storage_data(data);
  deleteHashIterator(hash_i);

  deleteHashtable(set->entries);
  free(set);
}

void delete_storage_list(STORAGE_SET_LIST *list) {
  if(list->list_i) deleteListIterator(list->list_i);
  deleteListWith(list->list, delete_storage_set);
  free(list);
}

void delete_storage_data(STORAGE_DATA *data) {
  if(data->key)      free(data->key);
  if(data->str_val)  free(data->str_val);
  if(data->list_val) delete_storage_list(data->list_val);
  if(data->set_val)  delete_storage_set(data->set_val);
  free(data);
}

STORAGE_DATA *new_storage_data(const char *key) {
  STORAGE_DATA *data = calloc(1, sizeof(STORAGE_DATA));
  data->key     = strdup(key);
  return data;
}

STORAGE_DATA *new_data_set(STORAGE_SET *val, const char *key) {
  STORAGE_DATA *data = new_storage_data(key);
  data->set_val  = val;
  data->list_val = new_storage_list();
  data->str_val  = strdup("");
  return data;
}

STORAGE_DATA   *new_data_list(STORAGE_SET_LIST *val, const char *key) {
  STORAGE_DATA *data = new_storage_data(key);
  data->list_val = val;
  data->set_val  = new_storage_set();
  data->str_val  = strdup("");
  return data;
}

STORAGE_DATA *new_data_string(const char *val, const char *key) {
  STORAGE_DATA *data = new_storage_data(key);
  data->str_val      = strdup(val);
  data->list_val     = new_storage_list();
  data->set_val      = new_storage_set();
  return data;
}

STORAGE_DATA    *new_data_bool(bool val, const char *key) {
  char str_val[4]; sprintf(str_val, (val ? "yes" : "no"));
  return new_data_string(str_val, key);
}

STORAGE_DATA    *new_data_int(int val, const char *key) {
  char str_val[20]; sprintf(str_val, "%d", val);
  return new_data_string(str_val, key);
}

STORAGE_DATA    *new_data_long(long val, const char *key) {
  char str_val[20]; sprintf(str_val, "%ld", val);
  return new_data_string(str_val, key);
}

STORAGE_DATA *new_data_double(double val, const char *key) {
  char str_val[20]; sprintf(str_val, "%lf", val);
  return new_data_string(str_val, key);
}


void print_indent(FILE *fl, int indent) {
  if(indent > 0) {
    char fmt[20];
    sprintf(fmt, "%%%ds", indent);
    fprintf(fl, fmt, " ");
  }
}


//
// Print a key and the key delimeter to file
//
void print_key(FILE *fl, const char *key, int key_width, int indent) {
  char fmt[30];
  sprintf(fmt, "%%-%ds:", key_width);
  print_indent(fl, indent);
  fprintf(fl, fmt, key);
}


//
// write a string containing newlines to a file
//
void write_string_data(const char *string, FILE *fl, int indent) {
  static char buf[SMALL_BUFFER];
  int i, str_i;

  for(str_i = i = 0; string[str_i] != '\0'; str_i++) {
    buf[i] = string[str_i];
    if(buf[i] == '\n') {
      buf[++i] = '\0';
      print_indent(fl, indent);
      fprintf(fl, "%s", buf);
      i = 0;
    }
    else
      i++;
  }
}


//
// return true if the storage set is empty, and false if it is not
//
bool set_is_empty(STORAGE_SET *set) {
  // if the hashtable has a size of 0, we know for sure it's empty
  if(hashSize(set->entries) > 0) {
    HASH_ITERATOR *hash_i = newHashIterator(set->entries);
    STORAGE_DATA    *data = NULL;
    const char       *key = NULL;

    ITERATE_HASH(key, data, hash_i) {
      if(*data->str_val || 
	 !set_is_empty(data->set_val) || !list_is_empty(data->list_val)) {
	deleteHashIterator(hash_i);
	return FALSE;
      }
    }
    deleteHashIterator(hash_i);
  }

  return TRUE;
}


//
// return true if the storage list is empty, and false if it is not
//
bool list_is_empty(STORAGE_SET_LIST *list) {
  // if the list size is 0, we know for sure it's empty
  if(listSize(list->list) > 0) {
    LIST_ITERATOR *list_i = newListIterator(list->list);
    STORAGE_SET      *set = NULL;
    ITERATE_LIST(set, list_i) {
      if(!set_is_empty(set)) {
	deleteListIterator(list_i);
	return FALSE;
      }
    }
    deleteListIterator(list_i);
  }
  return TRUE;
}


void write_storage_data(STORAGE_DATA *data, FILE *fl, int key_width,int indent){
  // first, we see if we have a string value. If we do, print it
  if(*data->str_val) {
    print_key(fl, data->key, key_width, indent);
    // if we have a newline in our string, we have to write
    // it in a special way so as to preserve the lines
    if(count_letters(data->str_val, '\n', strlen(data->str_val)) > 0) {
      // first, print the string marker and skip down to a newline
      fprintf(fl, "%c\n", STRING_MARKER);
      // now, write the string
      write_string_data(data->str_val, fl, indent+2);
    }
    else
      fprintf(fl, "%c%s\n", TYPELESS_MARKER, data->str_val);
  }

  // If that fails, check if we have a set value. If we do, print it
  else if(!set_is_empty(data->set_val)) {
    print_key(fl, data->key, key_width, indent);
    fprintf(fl, "%c\n", SET_MARKER);
    write_storage_set(data->set_val, fl, indent+2);
  }

  // otherwise, check if we have a list value. If we do, print it
  else if(!list_is_empty(data->list_val)) {
    print_key(fl, data->key, key_width, indent);
    fprintf(fl, "%c\n", LIST_MARKER);
    write_storage_list(data->list_val, fl, indent+2);
  }
}


//
// Compares two variables on their entry_num. This is used by
// write_storage_set to print data out in the same order it was
// entered.
//
int cmp_storage_vars(STORAGE_DATA *data1, STORAGE_DATA *data2) {
  if(data1->entry_num == data2->entry_num)
    return 0;
  else if(data1->entry_num < data2->entry_num)
    return -1;
  return 1;
}


void write_storage_set(STORAGE_SET *set, FILE *fl, int indent) {
  // collect all of the items in our hashtable
  LIST           *elems = newList();
  HASH_ITERATOR *hash_i = newHashIterator(set->entries);
  STORAGE_DATA    *data = NULL;
  const char       *key = NULL;

  ITERATE_HASH(key, data, hash_i)
    listPut(elems, data);
  deleteHashIterator(hash_i);

  // now, sort them all
  listSortWith(elems, cmp_storage_vars);

  // now, for each one, print it
  while( (data = listPop(elems)) != NULL)
    write_storage_data(data, fl, set->longest_key, indent);
  deleteList(elems);

  // print our indent and the end-of-set marker
  print_indent(fl, indent);
  fprintf(fl, "%c\n", SET_MARKER);
}


void write_storage_list(STORAGE_SET_LIST *list, FILE *fl, int indent) {
  LIST_ITERATOR *list_i = newListIterator(list->list);
  STORAGE_SET      *set = NULL;

  ITERATE_LIST(set, list_i)
    write_storage_set(set, fl, indent);
  deleteListIterator(list_i);
}


//*****************************************************************************
//
// All of the functions required for parsing data out of files can be found
// here.
//
//*****************************************************************************

/* local functions */
STORAGE_SET      *parse_storage_set(FILE *fl, int indent);
STORAGE_SET_LIST *parse_storage_list(FILE *fl, int indent);


//
// skip ahead in our indent. If we can skip that far ahead,
// return TRUE. otherwise, return FALSE.
//
bool skip_indent(FILE *fl, int indent) {
  int i;
  char c;
  for(i = 0; i < indent; i++) {
    c = fgetc(fl);
    if(c != ' ')
      break;
  }

  if(i != indent) {
    fseek(fl, -(i+1), SEEK_CUR);
    return FALSE;
  }
  else
    return TRUE;
}


//
// Check to see if we're at the end of a storage entry. If we are,
// return true. otherwise, return false.
//
bool storage_end(FILE *fl) {
  char c = fgetc(fl);
  if(c == SET_MARKER) {
    // also skip the newline that comes after us
    fgetc(fl);
    return TRUE;
  }
  else {
    fseek(fl, -1, SEEK_CUR);
    return FALSE;
  }
}


//
// return the type of the data we're dealing with. It is assumed
// this will be called IMMEDIATELY after parse_key is called
//
char parse_type(FILE *fl) {
  return getc(fl);
}


//
// Parse the name of the key that is immediately in front of us
//
char *parse_key(FILE *fl) {
  static char buf[SMALL_BUFFER];
  char c;
  int  i = 0;
  // parse up to the colon, which is the marker for the end of the key
  while((c = fgetc(fl)) != EOF) {
    if(c == ':') {
      buf[i] = '\0';
      break;
    }
    buf[i++] = c;
  }
  // trim off all of the trailing spaces and return a copy
  trim(buf);
  return strdup(buf);
}


//
// Read until we hit a newline. Return a copy of what we find
//
char *parse_line(FILE *fl) {
  static char buf[SMALL_BUFFER];
  fgetline(fl, buf, SMALL_BUFFER);
  return strdup(buf);
}


//
// read in a string that may possibly have multiple newlines in it
//
char *parse_string(FILE *fl, int indent) {
  static char line[SMALL_BUFFER];
  static char buf [MAX_BUFFER];
  *buf = '\0';
  int i = 0;

  // as long as we can skip up our indent, we can read in data and
  // all is good. Once we can no longer skip up our indent, then
  // we have come to the end of our string
  while(skip_indent(fl, indent)) {
    fgetline(fl, line, SMALL_BUFFER);
    i += snprintf(buf+i, MAX_BUFFER-i-1, "%s\n", line);
  }

  return strdup(buf);
}


//
// Read in a list of storage sets. Return what we find.
//
STORAGE_SET_LIST *parse_storage_list(FILE *fl, int indent) {
  STORAGE_SET_LIST *list = new_storage_list();
  STORAGE_SET       *set = NULL;

  // read in each set in our list
  while( (set = parse_storage_set(fl, indent)) != NULL)
    storage_list_put(list, set);
  return list;
}


//
// Parse one storage set and return it
//
STORAGE_SET *parse_storage_set(FILE *fl, int indent) {
  STORAGE_SET *set = new_storage_set();
  int        loops = 0;

  while(skip_indent(fl, indent)) {
    loops++;
    if(storage_end(fl))
      break;

    char *key = parse_key(fl);
    char type = parse_type(fl);

    switch(type) {
    case TYPELESS_MARKER: {
      char *line = parse_line(fl);
      store_string(set, key, line);
      free(line);
      break;
    }

    case STRING_MARKER: {
      fgetc(fl); // kill the newline
      char *string = parse_string(fl, indent+2);
      store_string(set, key, string);
      free(string);
      break;
    }

    case SET_MARKER:
      fgetc(fl); // kill the newline
      store_set(set, key, parse_storage_set(fl, indent+2));
      break;

    case LIST_MARKER:
      fgetc(fl); // kill the newline
      store_list(set, key, parse_storage_list(fl, indent+2));
      break;
    }
    free(key);
  }

  if(loops == 0) {
    delete_storage_set(set);
    return NULL;
  }
  return set;
}




//*****************************************************************************
//
// implementation of storage.h
// documentation contained in storage.h
//
//*****************************************************************************
void storage_write(STORAGE_SET *set, const char *fname) {
  FILE *fl = NULL;
  // we wanted to open a file, but we couldn't ... abort
  if((fl = fopen(fname, "w+")) == NULL)
    return;
  write_storage_set(set, fl, 0);
  fclose(fl);
}


STORAGE_SET *new_storage_set() {
  STORAGE_SET *set = malloc(sizeof(STORAGE_SET));
  set->entries     = newHashtableSize(20);
  set->longest_key = 0;
  set->top_entry   = 0;
  return set;
}


void storage_close(STORAGE_SET *set) {
  delete_storage_set(set);
}


STORAGE_SET *storage_read(const char *fname) {
  FILE *fl = NULL;
  // we wanted to open a file, but we couldn't ... return an empty set
  if((fl = fopen(fname, "r")) == NULL)
    return NULL;//    return new_storage_set();
  STORAGE_SET *set = parse_storage_set(fl, 0);
  fclose(fl);
  return set;
}

STORAGE_SET_LIST *new_storage_list() {
  STORAGE_SET_LIST *list = malloc(sizeof(STORAGE_SET_LIST));
  list->list = newList();
  list->list_i = NULL;
  return list;
}

STORAGE_SET *storage_list_next(STORAGE_SET_LIST *list) {
  if(list->list_i == NULL)
    list->list_i = newListIterator(list->list);
  else
    listIteratorNext(list->list_i);
  return listIteratorCurrent(list->list_i);
}

void storage_list_put(STORAGE_SET_LIST *list, STORAGE_SET *set) {
  listQueue(list->list, set);
}

void   storage_put(STORAGE_SET *set, STORAGE_DATA *data) {
  // see if it's our longest key
  int key_len      = strlen(data->key);
  set->longest_key = MAX(set->longest_key, key_len);
  // if we already have data by this name, delete it
  STORAGE_DATA *olddata = hashGet(set->entries, data->key);
  if(olddata) delete_storage_data(olddata);
  // add the new data
  hashPut(set->entries, data->key, data);
  data->entry_num = ++set->top_entry;
}


void   store_set(STORAGE_SET *set, const char *key, STORAGE_SET *val) {
  storage_put(set, new_data_set(val, key));
}

void   store_list(STORAGE_SET *set, const char *key, STORAGE_SET_LIST *val) {
  storage_put(set, new_data_list(val, key));
}

void store_string(STORAGE_SET *set, const char *key, const char *val) {
  storage_put(set, new_data_string(val, key));
}

void store_double(STORAGE_SET *set, const char *key, double val) {
  storage_put(set, new_data_double(val, key));
}

void store_bool(STORAGE_SET *set, const char *key, bool val) {
  storage_put(set, new_data_bool(val, key));
}

void store_int(STORAGE_SET *set, const char *key, int val) {
  storage_put(set, new_data_int(val, key));
}

void store_long(STORAGE_SET *set, const char *key, long val) {
  storage_put(set, new_data_long(val, key));
}

STORAGE_SET    *read_set(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) 
    return data->set_val;
  else {
    store_set(set, key, new_storage_set());
    return read_set(set, key);
  }
}

STORAGE_SET_LIST    *read_list(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) 
    return data->list_val;
  else {
    store_list(set, key, new_storage_list());
    return read_list(set, key);
  }
}

const char *read_string(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) return data->str_val;
  else     return "";
}

bool read_bool(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data == NULL) 
    return FALSE;
  else if(!strcasecmp(data->str_val, "Yes"))
    return TRUE;
  else if(atoi(data->str_val) != 0)
    return TRUE;
  else
    return FALSE;
}

double read_double(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) return atof(data->str_val);
  else     return 0;
}

int read_int(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) return atoi(data->str_val);
  else     return 0;
}

long read_long(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) return atol(data->str_val);
  else     return 0;
}

bool storage_contains(STORAGE_SET *set, const char *key) {
  return (hashGet(set->entries, key) != NULL);
}

//
// make a storage list out of a normal MUD list
//
STORAGE_SET_LIST *gen_store_list(LIST *list, void *storer) {
  STORAGE_SET *(* store_func)(void *) = storer;
  STORAGE_SET_LIST *set_list = new_storage_list();

  LIST_ITERATOR *list_i = newListIterator(list);
  void            *elem = NULL;

  ITERATE_LIST(elem, list_i)
    storage_list_put(set_list, store_func(elem));
  deleteListIterator(list_i);
  return set_list;
}


//
// parse a MUD list out of a storage list
//
LIST *gen_read_list(STORAGE_SET_LIST *list, void *reader) {
  void *(* read_func)(STORAGE_SET *) = reader;
  LIST *newlist = newList();

  STORAGE_SET *elem = NULL;
  while( (elem = storage_list_next(list)) != NULL)
    listQueue(newlist, read_func(elem));

  return newlist;
}