#include <phantasmal/log.h>
#include <phantasmal/lpc_names.h>

#include <type.h>
#include <trace.h>
#include <limits.h>

#define INDENT_LEVEL 4
#define LOG_FILE ("/usr/common/unq_dtd_log.txt")

private mapping dtd;
private int     is_clone;
private mapping builtins;
private string  accum_error;
/* base type of a type -- for inheritance */
private mapping base_type;

/* #define USE_LOG */
/* You can #define USE_LOG to write to a log file.  This doesn't
   use the standard LOGD logging because LOGD requires UNQ DTD
   functionality to successfully start up, so this may all occur
   before you *can* use LOGD.
*/

#ifdef USE_LOG
#define write_log(str) write_file(LOG_FILE, (str) + "\n")
#else 
#define write_log(str)
#endif

static int create(varargs int clone) {
  is_clone = clone;
  if(!find_object(UNQ_PARSER))
    compile_object(UNQ_PARSER);

  dtd = ([ ]);

  base_type = ([ ]);

  if(!is_clone) {
    /* Note: may eventually allow additions to the builtins array
       for user-defined types -- effectively that's what Phantasmal
       does with 'phrase'. */
    builtins = ([ "string" : 1,
		  "int" : 1,
		  "float" : 1,
		  "unq" : 1,
		  "phrase" : 1 ]);
  }
}

/* Functions for creating UNQ text from DTD template data */
        string serialize_to_dtd(mixed* dtd_unq);
private string serialize_to_dtd_type(string label, mixed unq, int indent);
private string serialize_to_string_with_mods(mixed* type, mixed unq,
					     int indent);
private string serialize_to_dtd_struct(string label, mixed unq, int indent);
private string serialize_to_builtin(string type, mixed unq, int indent);

/* Functions for parsing and verifying DTD UNQ input */
        mixed* parse_to_dtd(mixed* unq);
private mixed* parse_to_dtd_type(string label, mixed unq);
private mixed  parse_to_string_with_mods(mixed* type, mixed unq);
private mixed* parse_to_dtd_struct(string label, mixed unq);
private mixed  parse_to_builtin(string type, mixed unq);

/* Functions for loading a DTD */
private void   new_dtd_element(string label, mixed data);
private mixed* dtd_struct(string* array);
private mixed* dtd_string_with_mods(string str);

/* Random helper funcs */
private int     set_up_fields_mapping(mapping fields, string label);



/* This function should be called only as UNQ_DTD->is_builtin(...),
   never locally from a clone.  It won't work if called on a clone.
   That's mainly because there's only one builtins mapping, and it's
   stored in a central location.  To save the trouble of making this
   happen later, I'm doing it now -- if we start adding user-defined
   types to builtins, we'll need to have only a single instance of it.
*/
mixed is_builtin(string label) {
  if(is_clone)
    error("Call is_builtin only on shared issue of unq_dtd!");

  return builtins[label];
}

#define SPACES_MAX "                "
#define SPACES_MAX_LEN 16
private string indent_spaces(int num) {
  int tmp;

  tmp = num > SPACES_MAX_LEN ? SPACES_MAX_LEN : num;
  if(tmp < 1) return "";

  return SPACES_MAX[0..tmp];
}

/*************************** Functions for Serializing ***************/

/* This function Takes UNQ input that could have been the result of
   this DTD and converts it to an UNQ string suitable for writing
   to a file.  Returns its error in accum_error, which may be
   queried with get_parse_error_stack. */
string serialize_to_dtd(mixed* unq) {
  string ret, data;

  ret = "";
  accum_error = "";

  if(sizeof(unq) % 2) {
    accum_error += "Odd-sized UNQ chunk passed to serialize_to_dtd!\n";
    return nil;
  }

  while(sizeof(unq) > 0) {
    if(sizeof(unq) == 1) {
      accum_error += "Internal error (odd chunk-size) in serialize_to_dtd!\n";
      return nil;
    }

    if(!unq[0]) {
      accum_error += "Label (nil) passed to serialize_to_dtd!";
      return nil;
    }

    if(dtd[unq[0]]) {
      data = serialize_to_dtd_type(unq[0], unq[1], 0);
      if(!data) {
	accum_error += "Error on label '" + unq[0] + "'.\n";
	return nil;
      }
      ret += data;
    } else {
      accum_error += "Unrecognized type '" + unq[0] + "'.\n";
      return nil;
    }

    /* Cut off leading UNQ tag and contents */
    unq = unq[2..];
  }

  return ret;
}


/* The label should be passed in as arg1, and the unq (excluding the
   label itself) as arg 2. */
private string serialize_to_dtd_type(string label, mixed unq,
				     int indent) {
  string tmp;

  if(dtd[label][0] == "struct") {
    return serialize_to_dtd_struct(label, unq, indent);
  }

  tmp = serialize_to_string_with_mods(dtd[label], unq, indent);
  if(tmp && !UNQ_DTD->is_builtin(label)) {
    tmp = indent_spaces(indent) + "~" + label + "{" + tmp + "}\n";
  }
  if(!tmp) {
    accum_error += "Couldn't serialize as type " + implode(dtd[label], "")
      + "\n";
    return nil;
  }
  return tmp;
}


/* This serializes the given type, but doesn't wrap it in its appropriate
   label.  The caller will need to do that, if appropriate. */
private string serialize_to_string_with_mods(mixed* type, mixed unq, int indent) {
  string ret;
  int    ctr, is_struct;
  int repmin, repmax;

  if(sizeof(type) != 1 && sizeof(type) != 2)
    error("Illegal type given to serialize_to_string_with_mods!");

  if(sizeof(type) == 1) {
    if(UNQ_DTD->is_builtin(type[0]))
      return serialize_to_builtin(type[0], unq, indent);

    if(dtd[type[0]])
      return serialize_to_dtd_type(type[0], unq, indent);

    accum_error += "Unrecognized type '" + type[0]
      + "' in serialize_to_string_with_mods!\n";
    return nil;
  }

  /* Sizeof(type) == 2, so it's a type/mod combo */
  repmin = 0;
  repmax = INT_MAX;
  if(type[1] == "?") {
    repmax = 1;
  } else if (type[1] == "+") {
    repmin = 1;
  } else if (type[1] == "*") {
    /* do nothing, any value is acceptable */
  } else if (sscanf(type[1], "<%d..%d>", repmin, repmax) == 2 || sscanf(type[1], "<..%d>", repmax) == 1 || sscanf(type[1], "<%d..>", repmin) == 1) {
    /* do nothing, all values already entered */
  } else if (sscanf(type[1], "<%d>", repmin) == 1) {
    repmax = repmin;
  } else {
    accum_error += "Unrecognized type modifier " + type[1] + "!\n";
    return nil;
  }

  if(typeof(unq) == T_STRING) {
    /* TODO:  type checking here */
    return unq;
  }
  if(typeof(unq) != T_ARRAY) {
    accum_error += "Unreasonable type serializing UNQ as "
      + implode(type, ",") + "!\n";
    return nil;
  }

  /* Multiple entries */
  /* First, check to see that the number of entries is reasonable */
  if (sizeof(unq) < repmin || sizeof(unq) > repmax) {
    accum_error += "Number of entries doesn't fit + mod\n";
    return nil;
  }

  /* Set is_struct flag for below... */
  if(UNQ_DTD->is_builtin(type[0])) {
    is_struct = 0;
  } else if(dtd[type[0]][0] == "struct") {
    is_struct = 1;
  } else {
    accum_error += "Type '" + type[0]
      + "' doesn't seem to be struct or builtin!\n";
    return nil;
  }

  ret = "";
  for(ctr = 0; ctr < sizeof(unq); ctr+=2) {
    string tmp;

    if(is_struct)
      tmp = serialize_to_dtd_struct(type[0], unq[ctr + 1], indent);
    else
      tmp = serialize_to_builtin(type[0], unq[ctr + 1], indent);

    if(tmp == nil)
      return nil;

    ret += tmp;
  }

  return ret;
}


/* The label should be passed in as arg1, and the unq (excluding the
   label itself) as arg 2. */
private string serialize_to_dtd_struct(string label, mixed unq,
				       int indent) {
  string  ret;
  mixed*  type;
  mapping fields;
  int     ctr;

  ret = indent_spaces(indent) + "~" + label + "{\n";
  type = dtd[label];
  if(!type || type[0] != "struct") {
    accum_error += "Non-struct passed to serialize_to_dtd_struct!\n";
    return nil;
  }

  /* Rather than an instance tracker, we're just going to serialize
     the fields in the order given.  That puts some constraints on
     the UNQ that gets passed in, but that's fine for now.  Like
     so much of this code, it'll be expanded later if we need the
     new functionality. */

  fields = ([ ]);
  set_up_fields_mapping(fields, label);

  if(typeof(unq) != T_ARRAY) {
    string tmp;

    if(sizeof(type) != 2
       || !UNQ_DTD->is_builtin(type[1][0])) {
      accum_error += "Can't serialize struct '" + label + "' from '"
	+ unq + "' (type " + typeof(unq) + ").\n";
      return nil;
    }

    tmp = serialize_to_string_with_mods(type[1], unq, indent);
    if(tmp) {
      /* serialize_to_string_with_mods will supply its own curly
	 braces. */
      return indent_spaces(indent) + "~" + label + tmp + "\n";
    }
    return nil;
  }

  /* (typeof(unq) == T_ARRAY) from here on */

  for(ctr = 0; ctr < sizeof(unq); ctr++) {
    string tmp;

    if(!fields[unq[ctr][0]]) {
      accum_error += "Unrecognized field '" + STRINGD->mixed_sprint(unq[ctr])
	+ "' at offset " + ctr + ".\n";
      return nil;
    }

    tmp = serialize_to_dtd_type(unq[ctr][0], unq[ctr][1],
				indent + INDENT_LEVEL);
    if(!tmp) {
      accum_error += "Error writing field '" + unq[ctr][0] + "' of struct.\n";
      return nil;
    }
    /* ret += indent_spaces(indent) + "~" + unq[ctr][0] + tmp + "\n"; */
    ret += tmp;
  }

  ret += + indent_spaces(indent) + "}\n";
  return ret;
}


private string serialize_to_builtin(string type, mixed unq, int indent) {
  if(type == "string") {
    if(typeof(unq) != T_STRING) {
      accum_error += "Type " + typeof(unq) + " is not a string!\n";
      return nil;
    }
    return "{" + unq + "}";
  }

  if(type == "int") {
    if(typeof(unq) != T_INT) {
      accum_error += "Type " + typeof(unq) + " is not an int!\n";
      return nil;
    }
    return "{" + unq + "}";
  }

  if(type == "float") {
    if(typeof(unq) != T_FLOAT) {
      accum_error += "Type " + typeof(unq) + " is not a float!\n";
    }
    return "{" + unq + "}";
  }

  if(type == "phrase") {
    if(typeof(unq) == T_STRING) {
      return "{" + unq + "}";
    }
    return "{" + unq->to_unq_text() + "}";
  }

  if(type == "unq") {
    if(typeof(unq) == T_STRING
       || typeof(unq) == T_INT
       || typeof(unq) == T_FLOAT)
      return "{" + unq + "}";

    error("Can't yet serialize arbitrary UNQ, implement now!");
  }

  accum_error += "Don't recognize type " + type + " serializing builtins!\n";
  return nil;
}

/*************************** Functions for Parsing *******************/

/* Takes arbitrary parsed UNQ input and makes it conform to this DTD
   if possible.  Returns errors in accum_error, which may be queried
   with get_parse_error_stack. */
mixed* parse_to_dtd(mixed* unq) {
  int    ctr;
  string label;
  mixed* data, ret;

  accum_error = "";

  ret = ({ });
  unq = UNQ_PARSER->trim_empty_tags(unq);

  write_log("============================================================");
  write_log("Parsing to DTD: '" + STRINGD->mixed_sprint(unq) + "'\n-----");

  for(ctr = 0; ctr < sizeof(unq); ctr+=2) {
    label = STRINGD->trim_whitespace(unq[ctr]);
    if(dtd[label]) {
      data = parse_to_dtd_type(label, unq[ctr + 1]);
      if(data == nil) {
	accum_error += "Mismatch on label '" + label + "'\n";
	return nil;
      }
      write_log("Finished parsing type '" + label + "'.");
      ret += data;
      continue;
    }

    accum_error += "Don't recognize label '" + label + "' in DTD.\n";
    return nil;
  }

  return ret;
}

string get_parse_error_stack(void) {
  return accum_error;
}

/* Label is an entry in the DTD, unq is a chunk of input to parse
   assuming it conforms to that label.

   Returns a labelled array.
*/
private mixed* parse_to_dtd_type(string label, mixed unq) {
  mixed* type;
  mixed  tmp;

  if(typeof(unq) == T_ARRAY) {
    unq = UNQ_PARSER->trim_empty_tags(unq);
  }

  write_log("Parsing to DTD type '" + label + "'");

  type = dtd[label];
  if(typeof(type) != T_ARRAY && typeof(type) != T_STRING)
    error("Invalid type in DTD in parse_to_dtd_type!");

  /* If it's a struct */
  if(type[0] == "struct") {
    return parse_to_dtd_struct(label, unq);
  }

  /* Else, not a struct. */

  tmp = parse_to_string_with_mods(type, unq);

  if(tmp == nil) {
    accum_error += "Couldn't parse " + STRINGD->mixed_sprint(unq)
      + " as type " + implode(type,"") + "\n";
    return nil;
  }
  return ({ label, tmp });
}

/* Returns a typed chunk or (with mods) an array of typed chunks
   fitting the builtin or builtin with modifier.  The item returned
   will not be prefixed with the appropriate label.  If desired,
   that may be done by the caller. */
private mixed parse_to_string_with_mods(mixed* type, mixed unq) {
  int repmin, repmax;

  if(sizeof(type) != 1 && sizeof(type) != 2)
    error("Illegal type given to parse_to_string_with_mods!");

  if(unq == nil)
    return nil;

  if(sizeof(type) == 1) {
    if(UNQ_DTD->is_builtin(type[0]))
      return parse_to_builtin(type[0], unq);

    if(dtd[type[0]])
      return parse_to_dtd_type(type[0], unq);

    accum_error += "Unrecognized type '" + type[0]
      + "' in parse_to_string_with_mods!\n";
    return nil;
  }

  /* Sizeof(type) == 2, so it's a type/mod combo */
  repmin = 0;
  repmax = INT_MAX;
  if(type[1] == "?") {
    repmax = 1;
  } else if (type[1] == "+") {
    repmin = 1;
  } else if (type[1] == "*") {
    /* do nothing, any value is acceptable */
  } else if (sscanf(type[1], "<%d..%d>", repmin, repmax) == 2
	     || sscanf(type[1], "<..%d>", repmax) == 1
	     || sscanf(type[1], "<%d..>", repmin) == 1) {
    /* do nothing, all values already entered */
  } else if (sscanf(type[1], "<%d>", repmin) == 1) {
    repmax = repmin;
  } else {
    accum_error += "Unrecognized type modifier " + type[1] + "!\n";
    return nil;
  }

  if(UNQ_DTD->is_builtin(type[0])) {
    mixed* ret;
    int    ctr;
    mixed  tmp;

    if(typeof(unq) == T_STRING) {
      tmp = parse_to_builtin(type[0], unq);
      if(tmp == nil) return nil;

      /* Only one obj, but since this has modifiers return it as
	 an array-of-one. */
      if(repmin <= 1 && repmax >= 1)
	return ({ tmp });
      else {
	accum_error += "Number of entries doesn't fit " + type[1]
	  + " modifier\n";
	return nil;
      }
    }

    if(typeof(unq) != T_ARRAY) {
      /* Not a string and not an array.  Error! */
      error("Unreasonable type parsing UNQ!");
    }

    /* Okay, multiple entries -- typeof(unq) is T_ARRAY */
    unq = UNQ_PARSER->trim_empty_tags(unq);
    ret = ({ });
    for(ctr = 0; ctr < sizeof(unq); ctr+=2) {
      mixed tmp;

      if(!STRINGD->is_whitespace(unq[ctr])) {
	accum_error += "Labelled data found parsing "
	  + type[0] + type[1] + "!\n";
	return nil;
      }

      tmp = parse_to_builtin(type[0], unq[ctr + 1]);
      if(tmp == nil) return nil;

      ret += ({ tmp });
    }

    if (sizeof(ret) < repmin || sizeof(ret) > repmax) {
      accum_error += "Number of entries doesn't fit " + type[1]
	+ " modifier\n";
      return nil;
    }

    return ret;
  }

  accum_error
    += "Don't yet support modifier characters on non-builtin types!\n";
  return nil;
}

/* Returns a typed chunk of data which is a string, an int, etc as
   appropriate.  The returned chunk will not be prefixed with a
   label. */
private mixed parse_to_builtin(string type, mixed unq) {
  string err;

  if(!UNQ_DTD->is_builtin(type))
    error("Type " + type + " isn't builtin in parse_to_builtin!");

  /* Nil won't parse as anything -- save some checking code below */
  if(unq == nil)
    return nil;

  if(type == "string") {
    if(typeof(unq) != T_STRING) {
      accum_error += "Type " + typeof(unq) + " is not a string\n";
      return nil;
    }
    return STRINGD->trim_whitespace(unq);
  }

  if(type == "int") {
    int val;

    if(typeof(unq) != T_STRING) {
      accum_error += "Type " + typeof(unq)
	+ " is not a string while parsing int\n";
      return nil;
    }
    unq = STRINGD->trim_whitespace(unq);
    if(!sscanf(unq, "%d", val)) {
      accum_error += unq + " is not an integer.\n";
      return nil;
    }

    return val;
  }

  if(type == "float") {
    float val;

    if(typeof(unq) != T_STRING) {
      accum_error += "Type " + typeof(unq)
	+ " is not a string while parsing float\n";
      return nil;
    }

    if(!sscanf(unq, "%f", val)) {
      accum_error += unq + " is not a float.\n";
      return nil;
    }

    return val;
  }

  if(type == "phrase") {
    object tmp;

    if(typeof(unq) == T_STRING)
      return PHRASED->new_simple_english_phrase(unq);

    if(typeof(unq) != T_ARRAY)
      error("Don't recognized parsed UNQ object in parse_to_builtin(phrase)!");

    err = catch(tmp = PHRASED->unq_to_phrase(unq));
    
    if (err != nil) {
      accum_error += err;
      return nil;
    }
     
    return tmp;
  }

  if(type == "unq") {
    if(typeof(unq) == T_STRING) {
      return ({ "", unq }) ;
    }

    if(typeof(unq) != T_ARRAY) {
      accum_error += "Builtin 'unq' object is not an array or string!\n";
      return nil;
    }

    if(sizeof(unq) % 2) {
      accum_error += "Builtin 'unq' array is not a multiple of 2!\n";
      return nil;
    }

    return unq;
  }

  error("Builtins array modified without modifying parse_to_builtin!");
}

/* Parse_to_dtd_struct assumes some input preprocessing -- label is
   whitespace-trimmed and unq's top level is empty-tag-trimmed.  Label
   is also validated to point to an UNQ DTD structure.  UNQ may or may
   not be a valid structure, but has already had the tag corresponding
   to label trimmed from it.

   Returns an array consisting of a label followed by an array
   of labelled fields.
*/
private mixed* parse_to_dtd_struct(string t_label, mixed unq) {
  mixed*  type, *instance_tracker, *ret, *vals;
  mixed   tmp;
  int     ctr;
  mapping fields;
  string  label;
  int     bucket_count;
  int     repmin, repmax;

  write_log("Parsing to dtd struct '" + t_label + "', arg: '"
	    + STRINGD->mixed_sprint(unq) + "'");

  type = dtd[t_label];
  if(type[0] != "struct")
    error("Non-struct passed to parse_to_dtd_struct!");

  if(typeof(unq) == T_STRING
     || (sizeof(type) == 2
	 && UNQ_DTD->is_builtin(type[1][0]))) {
    /* Hrm.  The struct had better consist of a single built-in field
       in this case. */
    if(sizeof(type) > 2) {
      accum_error += "The string '" + unq
	+ "' cannot be parsed as multiple fields for struct '" + t_label
	+ "'.\n";
      return nil;
    }

    if(!UNQ_DTD->is_builtin(type[1][0])) {
      accum_error += "The string '" + unq
	+ "' cannot be parsed as non-builtin field '" + type[1][0]
	+ "' of DTD struct '" + t_label + "'\n";
      return nil;
    }

    tmp = parse_to_string_with_mods(type[1], unq);
    if(tmp == nil) return nil;

    return ({ t_label, tmp });
  }

  /* Set up fields array to know what bucket of instance_tracker
     different labelled fields belong in */ 
  fields = ([ ]);
  bucket_count = set_up_fields_mapping(fields, t_label);

  /* Write fields mapping to log file */
  /* Re-use instance tracker variable for this */
  instance_tracker = map_indices(fields);
  write_log("Fields array for " + t_label + " struct, " + bucket_count
	    + " buckets.");
  for(ctr = 0; ctr < sizeof(instance_tracker); ++ctr) {
    write_log(fields[instance_tracker[ctr]][0] + " - "
	      + instance_tracker[ctr]);
  }
  write_log("-------");

  /* We need to track how many of each tag in the structure are parsed
     so we can check to make sure they match our modifiers or lack
     thereof.  We have one bucket for each type in the struct, and its
     base classes. */
  instance_tracker = allocate(bucket_count);

  for (ctr = 0; ctr < bucket_count; ++ctr) {
    instance_tracker[ctr] = ({ });
  }

  for(ctr = 0; ctr < sizeof(unq); ctr += 2) {
    int   index;
    mixed tmp;
    string match_label;

    match_label = label = STRINGD->trim_whitespace(unq[ctr]);
    if (fields[match_label] == nil) {
      accum_error += "Unrecognized field " + (label ? label : "(nil)")
	+ " in structure\n";
      return nil;
    }

    while (match_label != nil
	   && fields[match_label][0] == nil) {
      match_label = base_type[match_label];
    }

    if (match_label == nil) {
      accum_error += "Unrecognized field " + label + " in structure\n";
      return nil;
    }
    index = fields[match_label][0];

    tmp = parse_to_dtd_type(label, unq[ctr + 1]);
    if(tmp == nil) {
      accum_error += "Error parsing label '" + label
	+ "' of structure '" + t_label + "'.\n";
      return nil;
    }
    instance_tracker[index] += ({ tmp });
    if(instance_tracker[index] == nil)
      return nil;

  }

  vals = map_values(fields);

  /* Verify we match the modifiers */
  for(ctr = 0; ctr < sizeof(vals); ctr++) {
    int num;

    repmin = 0;
    repmax = INT_MAX;
    if (vals[ctr][1] == "?") {
      repmax = 1;
    } else if (vals[ctr][1] == "+") {
      repmin = 1;
    } else if (vals[ctr][1] == "*") {
      /* do nothing, any value is acceptable */
    } else if (sscanf(vals[ctr][1], "<%d..%d>", repmin, repmax) == 2
	       || sscanf(vals[ctr][1], "<..%d>", repmax) == 1
	       || sscanf(vals[ctr][1], "<%d..>", repmin) == 1) {
      /* do nothing, all values already entered */
    } else if (sscanf(vals[ctr][1], "<%d>", repmin) == 1) {
      repmax = repmin;
    } else {
      accum_error += "Unrecognized type modifier " + vals[ctr][1] + "!\n";
      return nil;
    }

    num = sizeof(instance_tracker[vals[ctr][0]]);
    if (num < repmin || num > repmax) {
      int ctr2;
      mixed *types;

      types = map_indices(fields);

      for (ctr2 = 0; ctr2 < sizeof(types); ctr2++) {
	/* Compare instance-tracker bucket numbers */
	if (fields[types[ctr2]][0] == vals[ctr][0]) {
	  accum_error += "Wrong # of fields of type '"
	    + types[ctr2] + "' in struct '" + t_label + "'.  " + num
	    + " given, between " + repmin + " and "
	    + (repmax == INT_MAX ? "an infinite number": repmax)
	    + " required.\n";
	  return nil;
	}
      }

      accum_error += "Wrong # of fields in instance bucket #"
	+ STRINGD->mixed_sprint(vals[ctr][0])
	+ "(unknown) in struct '" + t_label + "'.  "
	+ num + " given, between " + repmin + " and "
	+ (repmax == INT_MAX ? "an infinite number": repmax)
	+ " required.\n";
	  return nil;
    }
  }

  /* Reassemble instances into single array to return */
  ret = ({ });
  for(ctr = 0; ctr < sizeof(instance_tracker); ctr++) {
    ret += instance_tracker[ctr];
  }
  return ({ t_label, ret });
}


/*** To load in the DTD: ***/

void load(string new_dtd) {
  int    ctr, ctr2;
  string str;
  mixed* new_unq;
  string import;

  if(!is_clone)
    error("Can't use non-clone UNQ DTD!  Stop it!");

  new_unq = UNQ_PARSER->basic_unq_parse(new_dtd);

  if(!new_unq)
    error("Can't parse UNQ data passed to unq_dtd:load!");

  new_unq = UNQ_PARSER->trim_empty_tags(new_unq);

  for (ctr2 = 0; ctr2 < sizeof(new_unq); ctr2 += 2) {
    str = STRINGD->trim_whitespace(new_unq[ctr2]);
    if (STRINGD->stricmp(str, "dtd") == 0) {
      /* parse DTD */
      if (typeof(new_unq[ctr2 + 1]) != T_ARRAY) {
	error("This doesn't look complex enough to be a real DTD!");
      }

      new_unq[ctr2+1] = UNQ_PARSER->trim_empty_tags(new_unq[ctr2+1]);
      for(ctr = 0; ctr < sizeof(new_unq[ctr2+1]); ctr+=2) {
	new_dtd_element(new_unq[ctr2+1][ctr], new_unq[ctr2+1][ctr + 1]);
      }
	  
    } else if (STRINGD->stricmp(str, "import") == 0) {
      /* import another file */
      if (typeof(new_unq[ctr2 + 1]) != T_STRING) {
	error ("Filename must be a string in import!");
      }

      import = read_file(new_unq[ctr2 + 1]);

      if (import == nil) {
	error("Could not load file " + new_unq[ctr + 1]);
      }
      
      /* recusively call load */
      load(import);
    } else {
      error("Unknown tag type: " + new_unq[ctr2]);
    }
  }
}

private void new_dtd_element(string label, mixed data) {
  string* tmp_arr;
  string inh;

  label = STRINGD->trim_whitespace(label);
  if (sscanf(label, "%s:%s", label, inh) == 1 || inh == "") {
    inh = nil;
  }

  if(dtd[label] || label == "struct" || UNQ_DTD->is_builtin(label))
    error("Redefining label " + label + " in UNQ DTD!");

  if(typeof(data) == T_STRING) {
    data = STRINGD->trim_whitespace(data);

    tmp_arr = explode(data, ",");
    dtd[label] = dtd_struct(tmp_arr);

    if (inh != nil) {
      if (dtd[inh] != nil) {
	/* Mark down the inheritance relationship */
	base_type[label] = inh;
      } else {
	error("Base type " + inh + " not defined when parsing " + label);
      }
    }

    return;
  } else if (typeof(data) == T_ARRAY) {
    if (sizeof(data) != 0) {
      error("complex subtypes not yet supported!");
    } else {
      if (inh != nil) {
	/* add types allowed for base type of the derived type */
	dtd[label] = dtd[inh];
      } else {
	error("Base type " + inh + " not defined when parsing " + label);
      }
    }
  } else {
    error("Type error -- problem with UNQ parser?");
  }
}

private mixed* dtd_string_with_mods(string str) {
  string mod;

  str = STRINGD->trim_whitespace(str);
  if(str == nil)
    error("Nil passed to dtd_string_with_mods!");

  if(STRINGD->is_ident(str))
    return ({ str });

  if (sscanf(str, "%s<%s>", str, mod) == 2) {
    mod = "<" + mod + ">";
    return ({ str, mod });
  }

  mod = str[strlen(str)-1..strlen(str)-1];
  str = str[..strlen(str)-2];
  if(mod == "?" || mod == "*" || mod == "+")
    return ({ str, mod });

  error("Can't recognize modifiers in modified UNQ type '" + str + "'");
}

private mixed* dtd_struct(string* array) {
  mixed* ret, *tmp;
  int    ctr;

  ret = ({ "struct" });

  for(ctr = 0; ctr < sizeof(array); ctr++) {
    array[ctr] = STRINGD->trim_whitespace(array[ctr]);
    tmp = dtd_string_with_mods(array[ctr]);
    ret += ({ tmp });
  }

  return ret;
}


void clear(void) {
  dtd = ([ ]);
}


/****************** Helper funcs ***************************/

/* Returns the number of buckets used. */
private int set_up_fields_mapping(mapping fields, string label) {
  int ctr;
  int count;
  mixed *type;

  type = dtd[label];

  /* if there's a base class, add the base class' types */
  if (base_type[label]) {
    count = set_up_fields_mapping(fields, base_type[label]);
  } else {
    count = 0;
  }

  /* Set up fields array to know what bucket of instance_tracker
     different labels belong in */
  for(ctr = 1; ctr < sizeof(type); ++ctr) {
    if(typeof(type[ctr]) == T_STRING) {
      if (fields[type[ctr]] == nil) {
	++count;
      }
      /* <1> in second element says there must be exactly one of these */
      fields[type[ctr]] = ({ count - 1, "<1>" });
    } else if(typeof(type[ctr]) == T_ARRAY) {
      if (fields[type[ctr][0]] == nil) {
	++count;
      }
      if (sizeof(type[ctr]) == 1) {
	fields[type[ctr][0]] = ({ count - 1, "<1>" });
      } else {
	fields[type[ctr][0]] = ({ count - 1, type[ctr][1] });
      }
    } else {
      error("Unknown type in DTD struct type!");
    }
  }

  return count;
}
