/*
 This file combines two distinct methods for outputting debug messages with a bit of OO to
 create a comprehensive Debug class which can be used to output as much information as required
 in order to diagnose problems with JS code.
 
 I am grateful to the following:
 - David F. Miller for his elegant logger functionality
 - ?? for providing the original showDebug, hideDebug and debug functions
 
 I have wantonly cannibalised from both of the above in order to create this functionality
 
 This file defines a Debug class which is a singleton class (only has one instance)
 and which manages the output of debug information as appropriate.
 
 Debug can be turned off completely, or set to a certain level which limits the amount of
 information which comes out; higher level implies more debug.
 
 Output can be to one of five places:
 - A separate debug window which is the easiest to use but isn't always convenient.
 - The current window (i.e. the current page).  This can cause issues if the JS carries out DOM parsing.
 - Alert boxes which can be disruptive if there is a lot of information coming out.
 - Messages written to the JavaScript console if there is one.
 - Messages written to a logfile on the browser host.
 
 One or more of these output options can be selected.
*/

// The Debug class definition follows

function Debug(debug_mode) // No parameters yet as only one mode is coded for
{
// Properties used for tracking functions

  this.DBG_functionStack = new Array(); // Name of last function called (set inside the function or this won't work!)
  this.DBG_currentArgs = ""; // Arguments passed into the current function
  this.DBG_indentLevel = 0; // Used to output structured (indented) debug output

// Properties used for managing output options
  this.DBG_window = debug_mode; // Values are 0 (none), 1 (current), 2 (separate)
  this.DBG_alert = false;
  this.DBG_console = false;
  this.DBG_logfile = false;

// Various constants (ideally these would be private properties)
  
  this.FVL_WINDOW_NONE = 0;
  this.FVL_WINDOW_CURRENT = 1;
  this.FVL_WINDOW_SEPARATE = 2;
  
  this.FVLOGGER_VERSION = '1.0';
  this.FVL_LOG_ON = true;  
  this.FVL_DEFAULT_LOG_LEVEL = this.FVL_DEBUG;
  this.FVL_LOG_ID = 'fvlogger';
  this.FVL_LOG_ELEMENT = 'p';

// constants for logging levels
  this.FVL_DEBUG = 0;
  this.FVL_INFO = 1;
  this.FVL_WARN = 2;
  this.FVL_ERROR = 3;
  this.FVL_FATAL = 4;

// the css classes that will be applied to the logging elements
  this.FVL_LOG_CLASSES = new Array("debug","info","warn","error","fatal");
  
// Now the class methods

// retrieves the element whose id is equal to FVL_LOG_ID
  this.getLogger = function(id)
  {
    if (this.DBG_window == this.FVL_WINDOW_CURRENT || this.DBG_window == this.FVL_WINDOW_SEPARATE)
    {
      if (arguments.length == 0) { id = this.FVL_LOG_ID; }
      return document.getElementById(id);
    }
    else
    {
      return false;
    }
  }

  this.showDebug = function() { this.FVL_showMessages(this.FVL_DEBUG); }
  this.showInfo = function() { this.FVL_showMessages(this.FVL_INFO); }
  this.showWarn = function() { this.FVL_showMessages(this.FVL_WARN); }
  this.showError = function() { this.FVL_showMessages(this.FVL_ERROR); }
  this.showFatal = function() { this.FVL_showMessages(this.FVL_FATAL); }
  this.showAll = function() { this.FVL_showMessages(); }

// removes all logging information from the logging element
  this.eraseLog = function(ask)
  {
    var debug = this.getLogger();
    if (! debug) { return false; }

    if (ask && ! confirm("Are you sure you wish to erase the log?"))
    {
      return false;
    }

    var ps = debug.getElementsByTagName(this.FVL_LOG_ELEMENT);
    var length = ps.length;
    for (var i = 0; i < length; i++) { debug.removeChild(ps[length - i - 1]); }
    return true;
  }

  this.debug = function(message) { this.FVL_log("" + message, this.FVL_DEBUG); }
  this.warn = function(message) { this.FVL_log("" + message, this.FVL_WARN); }
  this.info = function(message) { this.FVL_log("" + message, this.FVL_INFO); }
  this.error = function(message) { this.FVL_log("" + message, this.FVL_ERROR);}
  //function fatal(message) { FVL_log("" + message, FVL_FATAL);} Not sure why this was commented out!
  
  this.FVL_showMessages = function(level, hideOthers)
  {
  //	alert('showing ' + level);
  
    var showAll = false;
    
    // if no level has been specified, use the default
    if (arguments.length == 0) { level = this.FVL_DEFAULT_LOG_LEVEL; showAll = true; }
    if (arguments.length < 2) { hideOthers = true; }
  
    // retrieve the element and current statements
    var debug = this.getLogger();
    if (! debug) { return false; }
  
    var ps = debug.getElementsByTagName("p");
    if (ps.length == 0) { return true; }
  
    // get the number of nodes in the list
    var l = ps.length; 
  
    // get the class name for the specified level
    var lookup = this.FVL_LOG_CLASSES[level]; 
  
    // loop through all logging statements/<p> elements...
    for (var i = l - 1; i >= 0; i--)
    {
  
            // hide all elements by default, if specified
            if (this.hideOthers) { this.hide(ps[i]); } 
  
            // get the class name for this <p>
            var c = getNodeClass(ps[i]); // Custom function assumes only one class!
  //		alert(c);
  //		alert("Node #" + i + "'s class is:" + c);
            if (c && c.indexOf(lookup) > -1 || showAll) { show(ps[i]); } 
    }
    return true; // Added to get rid of warning from Komodo (TBG)
  }

  // appends a statement to the logging element if the threshold level is exceeded
  this.FVL_log = function(message, level)
  {
    var i;
    var indentString="";
    var indentIncrement = "..";
    // space = function () { return document.createTextNode(' '); }
    var outputMessage;
    
    // alert("FVL_log: " + message + ", " + level);
  
    // check to make sure logging is turned on
    if (! this.FVL_LOG_ON)
    {
      alert("Returning false 1");
      return false;
    } 
  
    // retrieve the infrastructure
    if (arguments.length == 1)
    {
      level = this.FVL_INFO;
    }
    if (level < this.FVL_DEFAULT_LOG_LEVEL)
    {
      alert("Returning false 2");
      return false;
    }
    
    // Message is to be output so output using enabled mechanism
    
    // First prepend appropriate number of spaces depending upon call level
    
    // if (this.DBG_indentLevel >-1) alert("indentLevel = " + this.DBG_indentLevel);
    
    for (i=0; i<this.DBG_functionStack.length; i++) { indentString += indentIncrement; }
    outputMessage = indentString + message;
    
    // if (this.DBG_indentLevel >-1) alert("outputMessage = " + outputMessage);
    
    if (this.DBG_window == this.FVL_WINDOW_CURRENT)
    {
      alert("Current Window");
      // var div = this.getLogger();
      // if (! div) { return false; }
    }
  
    // append the statement
    var p = document.createElement(this.FVL_LOG_ELEMENT);
  
    // this is a hack work around a bug in ie
    if (p.getAttributeNode("class"))
    {
      for (var i = 0; i < p.attributes.length; i++)
      {
        if (p.attributes[i].name.toUpperCase() == 'CLASS')
        {
          p.attributes[i].value = this.FVL_LOG_CLASSES[level];
        }
      }
    }
    else
    {
      p.setAttribute("class", this.FVL_LOG_CLASSES[level]);
    }
    var text = document.createTextNode(outputMessage);
    p.appendChild(text);
    
    // p.innerHTML = outputMessage;

    
    if (this.FVL_WINDOW_CURRENT)
    {
      // div.appendChild(p);
    }
    
    if (this.FVL_WINDOW_SEPARATE)
    {
      // alert("Separate Window");
    
      // This is a frig which hopefully I will rework! (TBG)

      // alert("p.innerHTML = " + p.innerHTML);
      this.dbgWriteWindow(p);
    }
    
    return true;
  }

  // show a node
  this.show = function(target)
  {
    target.style.display = "";
    return true;
  }
  
  // hide a node
  this.hide = function(target)
  {
    target.style.display = "none";
    return true;
  }
  
  // returns the class attribute of a node
  this.getNodeClass = function(obj)
  {
    var result = false;
  
    if (obj.getAttributeNode("class"))
    {
      result = obj.attributes.getNamedItem("class").value;
    }
    return result;
  }

// Show the debug window
  this.dbgShowWindow = function()
  {
    var status, data;

    status = window.open("",
                "Debug",
                "left=0,top=0,width=600,height=800,scrollbars=yes,"
                +"status=yes,resizable=yes");
    // alert("Window Open Status : " + status);
    this.debugWindow = status;
    this.debugWindow.opener = self;
    // open the document for writing
    this.debugWindow.document.open();

    // var html = this.debugWindow.document.getElementsByTagName("html").item(0);
    
    data =  "<html>" +
              "<head>" +
                "<link rel=\"stylesheet\" type=\"text/css\" href=\"./architecture/css/logger.css\" />" +
                "<title>Logging Window</title>" +
              "</head>" +
              "<body id=\"" + this.FVL_LOG_ID +"\"" +
                "<h1>Logging Session</h1>" +
              "</body>" +
            "</html>";
    // alert("data = " + data);
    // html.innerHTML = data;
    this.debugWindow.document.write(data);
    // alert("data written");
    this.debugWindow.document.close();
  }

  // If the debug window exists, then write to it
  this.dbgWriteWindow = function(element)
  {
    // alert("dbgWriteWindow: " + element);
    if (this.debugWindow && ! this.debugWindow.closed)
    {
      // alert("Writing to window: " + element.firstChild.nodeValue);
      var body = this.debugWindow.document.getElementsByTagName("body").item(0);
      if (!body)
        { /* alert("body = " + body); */ }
      else
        { body.appendChild(element); }
    }
  }
  
  // If the debug window exists, then close it
    this.dbgHideWindow = function() {
    if (this.debugWindow && ! this.debugWindow.closed) {
      this.debugWindow.close();
      this.debugWindow = null;
    }
  }
  
  // A number of functions to help output structured debug

/*  
  this.debugStartFunction = function(FunctionName)
  {
    // alert("debugStartFunction " + FunctionName);
    var Output = "Starting " + arguments[0] + "(";
    for (var i=1; i<arguments.length; i++)
    {
      Output += arguments[i];
      if (i < arguments.length-1)
      {
        Output += ", ";
      }
    }
    Output += ")";
    this.info(Output);
  }
*/
  
  this.debugStartFunction = function(pFunctionName, pArguments)
  {
    // alert("debugStartFunction " + FunctionName);
    
    var length;
    
    this.DBG_functionStack.push(pFunctionName);
    if (pArguments) { length = pArguments.length } else { length = 0; }
    
    var Output = "Starting " + this.DBG_functionStack[this.DBG_functionStack.length-1] + "(";
    for (var i=0; i<length; i++)
    {
      Output += (typeof pArguments[i] == 'object') ? pArguments[i] : pArguments[i]; // Did use dump for objects but this caused bizarre results.  Investigate later
      if (i < length-1)
      {
        Output += ", ";
      }
    }
    Output += ")";
    this.info(Output);
  }
  
  this.debugEndFunction = function(FunctionName)
  {
    this.info("Ending " + this.DBG_functionStack[this.DBG_functionStack.length-1]);
    this.DBG_functionStack.pop();
  }
  
  // CODE EXECUTED DURING OBJECT CREATION
  
  if (this.DBG_window == 2) this.dbgShowWindow();
  // only override the window's error handler if logging is turned on

}

// Wrapper functions to ease call to Debug object

function debug(text)
{
  // alert("debug " + text);
  D.debug(text);
}
