// -------------------------------------------------------------------
// class: Dumper
//
// Generate a text or html representation of an arbitrary javascript
// object with the ability to limit recursion depth.
// 
// Example:
//
//    var obj = {
//       foo: "bar",
//       baz: [1, 2, 3]
//    };
//
// Generate indented text version of 'obj' and display in
// an alert box:
//
//    var dumper = new Dumper(obj);
//    alert(dumper.toString(false));
//
// Generate html verion of 'document' but limit depth to 3 levels
//
//    var dumper = new Dumper(document, 3);
//    var html   = dumper.toString(true);
// -------------------------------------------------------------------

function Dumper(theItem, maxDepth)
{
   this.theItem       = theItem;
   this.maxDepth      = maxDepth;
   this.objectHistory = [];

   this.makeIndent = function(doHTML, indentLevel)
   {
      if(doHTML)
         return("");

      // Spaces aren't honored in firefox's alert box but
      // ascii char 160 works as whitespace in both browsers.

      var nbsp   = "\xa0";
      var indent = "";

      for(var i = 0; i < indentLevel; i ++)
         indent += nbsp + nbsp + nbsp;

      return(indent);
   }

   this.toString = function(doHTML)
   {
      return(this._toString(doHTML, 0, "dump", this.theItem));
   }

   this._toString = function(doHTML, indentLevel, theName, theValue)
   {
      var indent  = this.makeIndent(doHTML, indentLevel);
      var newLine = (doHTML ? "<br>" : "") + "\n";
      var s       = indent + theName + ": ";

      if(theValue == null)
      {
         s += 'null';
      }
      else if(this.getType(theValue) == "object")
      {
         // Avoid infinite recursion by first checking
         // to see if we've already encountered this object

         var ref = this.getReference(theValue);

         if(ref != null)
         {
            s += ref;
         }
         else
         {
            this.objectHistory.push(
               { name: theName, value: theValue }
            );

            var isArray = (theValue.constructor == Array);

            s += (isArray ? "[" : "{") + "\n";

            if(doHTML)
               s += "<div style=\"margin-left:20px;\">\n";

            // Will we exceed our max depth by continuing?

            if(indentLevel == this.maxDepth)
            {
               s +=
                  this.makeIndent(doHTML, indentLevel + 1) +
                  "maxDepth exceeded ..." +
                  newLine;
            }
            else
            {
               var theChildren = "";

               for(attribute in theValue)
               {
                  var childString = null;

                  try
                  {
                     var child = theValue[attribute];

                     childString = this._toString(
                        doHTML, indentLevel + 1, attribute, child
                     );
                  }
                  catch(anException)
                  {
                     childString = "<Exception>";
                  }

                  if(theChildren != "")
                     theChildren += "," + newLine;
 
                  theChildren += childString;
               }

               s += theChildren + newLine;
            }

            if(doHTML)
               s += "</div>\n";

            s += indent + (isArray ? "]" : "}");
         }
      }
      else
      {
         s += theValue;
      }

      return(s);
   }

   this.getType = function(anItem)
   {
      var type   = typeof(anItem);
      type       = type.toLowerCase();

      return(type);
   }

   this.getReference = function(someObject)
   {
      for(var i = 0; i < this.objectHistory.length; i ++)
      {
         var entry = this.objectHistory[i];

         if(entry.value == someObject)
         {
            return("[reference => " + entry.name + "]");
         }
      }

      return(null);
   }
}

// -------------------------------------------------------
// class: Util
//
// function: write(s) / write(s, maxDepth)
//
//    If there's not already a write window open, will
//    open one and write 's' to that window.  The argument
//    's' can be any javascript object.
//
// function: show(s) / show(s, maxDepth)
//
//    Pop up an alert dialog box displaying a text
//    representation of 's'.
//
// utility functions, some easier-to-use facades built
// on top of Dumper
//
// function: toString(obj) / toString(obj, maxDepth)
//
//    Creates string representation of 'obj'.  If 'maxDepth'
//    is supplied, recursion depth of complex objects
//    will be limited to the specified value.
//
// function: toHTML(obj) / toHTML(obj,maxDepth)
//
//    Creates html representation of 'obj', otherwise
//    behaves similarly to 'toString'
// -------------------------------------------------------

function Util() {}

Util.getBodyEx = function(theDocument)
{
   var element = theDocument.getElementsByTagName("body").item(0);

   return(element);
}

Util.getBody = function()
{
   var element = Util.getBodyEx(document);

   return(element);
}

Util.openWindow = function(url, windowName, featureList)
{
   var features = "";

   for(var i = 0; i < featureList.length; i ++)
   {
      if(features != "")
         features += ",";

      features += featureList[i].name + "=" + featureList[i].value;
   }

   var theWindow = window.open(url, windowName, features);

   theWindow.focus();

   return(theWindow);
}

Util.write = function(s, maxDepth)
{
   var windowName = "util_WriteWindow";

   var featureList = [
     { name: 'width',      value: '480' },
     { name: 'height',     value: '640' },
     { name: 'scrollbars', value: '1'   },
     { name: 'menubar',    value: '1'   },
     { name: 'toolbar',    value: '0'   },
     { name: 'resizable',  value: '1'   }
   ];

   var writeWindow = Util.openWindow('', windowName, featureList);

   // If this is an existing window, we'll have already added to
   // this document.body a center tag with an id of 'close_window'.
   // We use the presence/absence of that element/id to decide
   // if we need to add the close window link.

   var d    = writeWindow.document;
   var body = Util.getBodyEx(d);

   var closeWindowID = "close_window";

   if(d.getElementById(closeWindowID) == null)
   {
      var link       = d.createElement("a");
      link.href      = "javascript:window.close()";;
      link.innerHTML = "Close This Window";

      var center = d.createElement("center");
      center.id = "close_window";

      center.appendChild(link);
      body.appendChild(center);
   }

   // Now, we'll write 's' ...

   var type = "" + typeof(s);
   type     = type.toLowerCase();

   // Convert to html if we're dealing with an aggregate type

   if(type == "object")
      s = Util.toHTML(s, maxDepth);

   var element       = d.createElement("div");
   element.innerHTML = s;

   body.appendChild(element);

   // Ensure we scroll to the bottom of the screen by scrolling
   // 1,000,000 pixels downward.  Nasty, but it works :-P.

   writeWindow.window.scrollBy(0, 1000000);
}

Util.show = function(s, maxDepth)
{
   var type = "" + typeof(s);
   type     = type.toLowerCase();

   if(type == "object")
      s = Util.toString(s, maxDepth);

   alert(s);
}

Util.toString = function(obj, maxDepth)
{
   return(new Dumper(obj, maxDepth).toString(false));
}

Util.toHTML = function(obj, maxDepth)
{
   return(new Dumper(obj, maxDepth).toString(true));
}
