/**
 * Javascript class: DOMNode.  
 * Primary designed to optimize the updating of text and 
 * style attributes within web pages.
 * Follows MMTO GUI style guidelines for background status 
 * colors, e.g., "green" is OK, "red" is error.
 * Note that all functions return a reference to this instances, so that
 * commands can be concatenated, e.g., 
 *               domnode.text("Hello, world!").highlight().center();
 * Version: 1.1, May 7 2008
 */
/*
 * "element" can be either the element id string or the element itself. 
 *
 * "text" will default to "NA", i.e., "not available", if no second parameter
 * is given in the constructor.
 */
function DOMNode(element, text) {
    this.ok_color = "limegreen";
    this.warn_color = "yellow";
    this.error_color = "red";
    this.stale_color = "red";
    this.white_color = "white";
    this.off_color = "skyblue";
    this.grayout_color = "gainsboro"; /* A light gray */
    this.transparent_color = "transparent";
    this.highlight_color = "#54FF9F"; /*A light green */
    this.highlight2_color = "yellow";
    this.spreadsheet1_color = "lightsteelblue";
    this.spreadsheet2_color = "white";
    this.text_content = (text === null) ?  "NA" : text;
    if (typeof element == 'string') {
        this.e2lc = element.toLowerCase();
        if ( this.e2lc == "td" ||
            this.e2lc == "th" ||
            this.e2lc == "div" ||
            this.e2lc == "span" ) {
            this.element = document.createElement(this.e2lc);
        } else {
            this.element = document.getElementById(element);
            this.id = element;
        }
    } else {
        this.element = element;
    }
    /* "this.element" must be a valid HTML Element instance and support appendChild() */
    if ( this.isset() && this.element.appendChild ) {
        /* "no_textnode" can be used as a flag to create an instance of a limited DOMNode
        * in which no text is involved, e.g., DOMNode functions can be
        * used to modify the background color of the element.
        * The contents of the element are unchanged and no text node
        * is appended to the element.
        */
        if ( this.text_content == "no_textnode") {
            this.textNode = null;
        } else {
            this.clear();
            this.textNode = document.createTextNode( this.text_content );
            this.element.appendChild( this.textNode );
            this.nodeValue = this.text_content;
        }
    } else {
         this.element = null;
    }
}
/* Appends the associated element of this DOMNode instance to a parent. 
 * The parent can be either the element id string or the element itself.
 */
DOMNode.prototype.appendTo = function(parent) {
    if (typeof parent == 'string') {
        this.parent = document.getElementById(parent);
    } else {
        this.parent = parent;
    }
    if ( this.isset() &&  this.parent.appendChild ) {
        this.parent.appendChild( this.element);
    }
    return this;
};
/* Removes all children */
DOMNode.prototype.clear = function( ) {
    if ( this.isset() ) {
        this.element.innerHTML = "";
    }
    return this;
};
/* Updates contents, i.e., nodeValue attribute, of the child text node.
 * Use a "" parameter value to show an empty string.
 */
DOMNode.prototype.text = function( text )  {
    if ( (text !== null) &&  (this.textNode !== null) && (this.nodeValue !== text) && this.isset()) {
        this.nodeValue = this.textNode.nodeValue = text;
    }
    return this;
};
/* Shouldn't be used in most cases for this class!!!  
 * Use "text()" instead to update the contents of 
 * the text node child.
 *
 * Updates the innerHTML of the instance. 
 */
DOMNode.prototype.html = function(text)  {
    if ((text !== null) && (this.innerHTML !== text) && this.isset()) {
        this.innerHTML = this.element.innerHTML = text;
    }
    return this;
};
/* It's probably better to set individual style attributes
 * so that they can be cached individually, e.g., bg() function.
 *
 * Sets several style attributes at once,
 * e.g, style= "position:absolute;top:265px;left:108px;" 
 */
DOMNode.prototype.styleValue = function ( style )  {
    if ( this.styleValue !== style && this.isset() ) {
        this.styleValue = this.element.style = style;
    }
    return this;
};
/* Sets the background color.  Caches values. */
DOMNode.prototype.bg = function( color) {
    if (this.background !== color && this.isset()) {
        this.background = this.element.style.background = color;
    }
    return this;
};
/* Displays "OK" background status. nominally green. */
DOMNode.prototype.ok = function(){
    return this.bg( this.ok_color );
};
/* Displays "warning" background status, nominally yellow. */
DOMNode.prototype.warn = function(){
    return this.bg( this.warn_color );
};
/* Displays "error" background status, nominally red. */
DOMNode.prototype.error = function(){
    return this.bg( this.error_color );
};
/* Displays "stale" background status, nominally red. */
DOMNode.prototype.stale = function(){
    return this.bg( this.stale_color );
};
/* Displays "off" background status, nominally skyblue. */
DOMNode.prototype.off = function(){
    return this.bg( this.off_color );
};
/* Displays "grayout" background status, indicating field is inactive. */
DOMNode.prototype.grayout = function(){
    return this.bg( this.grayout_color );
};
/* Displays "transparent" background. */
DOMNode.prototype.transparent = function(){
    return this.bg( this.transparent_color );
};
/* Displays "white" background. */
DOMNode.prototype.white = function(){
    return this.bg( this.white_color );
};
/* Highlights element's background, nominally a light green. */
DOMNode.prototype.highlight = function(){
    return this.bg( this.highlight_color );
};
/* Highlights element's background, nominally yellow. */
DOMNode.prototype.highlight2 = function(){
    return this.bg( this.highlight2_color );
};
/* Alternating spreadsheet-like colors, nominally lightsteelblue. */
DOMNode.prototype.spreadsheet1 = function(){
    return this.bg( this.spreadsheet1_color );
};
/* Alternating spreadsheet-like colors, nominally white. */
DOMNode.prototype.spreadsheet2 = function(){
    return this.bg( this.spreadsheet2_color );
};
/* Sets text color, i.e., foreground color. Caches values. */
DOMNode.prototype.fg = function ( color )  {
    if ( this.color !== color && this.isset()) {
        this.color = this.element.style.color = color;
    }
    return this;
};
/* Displays black text */
DOMNode.prototype.blackText = function (str ) {
    return this.text( str ).fg( "black");
};
/* Display blue text. */
DOMNode.prototype.blueText = function ( str ) {
    return this.text( str ).fg( "blue");
};
/* Display green text. */
DOMNode.prototype.greenText = function ( str ) {
    return this.text( str ).fg( "forestgreen" );
};
/* Display ("Gold3") orange text. */
DOMNode.prototype.orangeText = function ( str ) {
    return this.text( str ).fg( "C7A317" );
};
/* Displays ("Firebrick3") red text. */
DOMNode.prototype.redText = function ( str ) {
    return this.text( str ).fg("#C11B17");
};
/* Display yellow text. */
DOMNode.prototype.yellowText = function ( str ) {
    return this.text( str ).fg( "yellow" );
};
/* Aligns text left.  Caches value. */
DOMNode.prototype.leftAlign = function ( )  {
    if ( this.textAlign !== "left" && this.isset()) {
        this.textAlign = this.element.style.textAlign = "left";
    }
    return this;
};
/* Aligns text right.  Caches value. */
DOMNode.prototype.rightAlign = function ( )  {
    if ( this.textAlign !== "right" && this.isset()) {
        this.textAlign = this.element.style.textAlign = "right";
    }
    return this;
};
/* Aligns text center.  Caches value. */
DOMNode.prototype.centerAlign = function ( )  {
    if ( this.textAlign !== "center" && this.isset()) {
        this.textAlign = this.element.style.textAlign = "center";
    }
    return this;
};
/* Left boundary of element */
DOMNode.prototype.left = function ( value )  {
    if ( this.leftValue !== value && this.isset()) {
        this.leftValue = this.element.style.left = value;
    }
    return this;
};
/* Right boundary of element */
DOMNode.prototype.right = function ( value )  {
    if ( this.rightValue !== value && this.isset()) {
        this.rightValue = this.element.style.right = value;
    }
    return this;
};
/* Top boundary of element. Note:  Need to append "px" to value */
DOMNode.prototype.top = function ( value )  {
    if ( this.topValue !== value && this.isset()) {
        this.topValue = this.element.style.top = value;
    }
    return this;
};
/* Bottom boundary of element */
DOMNode.prototype.bottom = function ( value )  {
    if ( this.bottomValue !== value && this.isset()) {
        this.bottomValue = this.element.style.bottom = value;
    }
    return this;
};
/* Position type of element: absolute, relative */
DOMNode.prototype.position = function ( value )  {
    if ( this.positionValue !== value && this.isset()) {
        this.positionValue = this.element.style.position = value;
    }
    return this;
};
/* Height of element */
DOMNode.prototype.height = function ( value )  {
    if ( this.heightValue !== value && this.isset()) {
        this.heightValue = this.element.style.height = value;
    }
    return this;
};
/* Width of element */
DOMNode.prototype.width = function ( value )  {
    if ( this.widthValue !== value && this.isset()) {
        this.widthValue = this.element.style.width = value;
    }
    return this;
};
/* Layering of elements with z-index */
DOMNode.prototype.zIndex = function ( value )  {
    if ( this.zIndexValue !== value && this.isset()) {
        this.zIndexValue = this.element.style.zIndex = value;
    }
    return this;
};
/* Visibility of element: visible or hidden */
DOMNode.prototype.visibility = function ( value )  {
    if ( this.visibilityValue !== value && this.isset()) {
        this.visibilityValue = this.element.style.visibility = value;
    }
    return this;
};
/* Hide element */
DOMNode.prototype.hide = function ()  {
    return this.visibility("hidden");
};
/* Show element */
DOMNode.prototype.show = function ()  {
    return this.visibility("visible");
};
/* Assigns a tooltip.  Caches value. */
DOMNode.prototype.title = function ( tooltip )  {
    if ( this.tooltip !== tooltip && this.isset()) {
        this.tooltip = this.element.title = tooltip;
    }
    return this;
};
/* Creates a horizontal bargraph of a value using "|" symbols
 *  with optional scaling and a maximum allowed value.
 */
DOMNode.prototype.bargraph = function ( value, scale, max  ) {
    this.scale = ( scale === null ) ?  1.0 : scale;	
    this.max = ( max === null ) ?  500 : max;	
    this.str = "";
    this.count = 1;
    this.scaled_value = Math.abs( Math.round( value * this.scale ) );
    while ( ( this.count <= this.scaled_value) && ( this.count++ <= this.max ) ) {
        this.str += "|";
    }
    if ( this.str.length === 0 ) {
        this.str = "|";
    }
    if ( value < 0 ) {
        this.blueText( this.str ).rightAlign();
    } else {
        this.greenText( this.str ).leftAlign();
    }
    if ( this.scaled_value >= this.max ) {
        this.warn();
    } else {
        this.transparent();
    }
    return this;
};
DOMNode.prototype.calcDateTime = function( unix_timestamp ) {
   this.d2 = new Date();
   this.d2.setTime(unix_timestamp * 1000);
   this.year = this.d2.getFullYear();
   this.month = this.d2.getMonth() + 1;
   this.day = this.d2.getDate();
   this.hours = this.d2.getHours();
   this.minutes = this.d2.getMinutes();
   this.seconds = this.d2.getSeconds();
   this.datetime = this.year + "-";
   if (this.month < 10) {
      this.datetime += "0";
   }
   this.datetime += this.month + "-";
   if (this.day < 10) {
      this.datetime += "0";
   }
   this.datetime += this.day + " ";
   if (this.hours < 10) {
      this.datetime += "0";
   }
   this.datetime += this.hours + ":";
   if (this.minutes < 10) {
      this.datetime += "0";
   }
   this.datetime += this.minutes + ":";
   if (this.seconds < 10) {
      this.datetime += "0";
   }
   this.datetime += this.seconds;
   return this;
};

// Changes a UNIX timestamp to MySQL DateTime format, e.g.,  2008-05-15 06:15:42
DOMNode.prototype.toDateTime = function( unix_timestamp, prefix, suffix ) {
   this.prefix = ( prefix === null || typeof prefix == 'undefined' ) ?  "" : prefix;
   this.suffix = ( suffix === null || typeof suffix == 'undefined' ) ?  "" : suffix;
   this.calcDateTime(unix_timestamp);
   this.text( this.prefix + this.datetime + this.suffix );
   return this;
};
// Changes a UNIX timestamp to MySQL DateTime format, e.g.,  2008-05-15 06:15:42
// Same as previous function, but allows HTML be used, as in multi-line headers.
DOMNode.prototype.toDateTimeHTML = function( unix_timestamp, prefix, suffix ) {
 this.prefix = ( prefix === null || typeof prefix == 'undefined' ) ?  "" : prefix;
   this.suffix = ( suffix === null || typeof suffix == 'undefined' ) ?  "" : suffix;
   this.calcDateTime(unix_timestamp);
   this.html( this.prefix + this.datetime + this.suffix );
   return this;
  };

// Changes a MySQL DateTime format to a Unix Timestamp, e.g.,  2008-05-15 06:15:42
DOMNode.prototype.DateTimetoTimestamp = function( datetime ) {
    this.regex = /(\d\d\d\d)-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)/;
    this.re = this.regex.exec(datetime);
    this.str = "NA";
    if (this.re) {
        this.d2 = new Date();
        this.d2.setYear(re[1]);
        this.d2.setMonth(re[2] - 1);
        this.d2.setDate(re[3]);
        this.d2.setHours(re[4]);
        this.d2.setMinutes(re[5]); 
        this.d2.setSeconds(re[6]);
        this.str = Math.round(this.d2.getTime()/1000);
    }
    this.text( this.str );
    return this;
};
/* Check whether a MySQL datetime value is current.
 * Displays the datetime, with an optional prefix.
 * Changes the background color: OK is green, stale is red.
 * datetime is in MySQL format:  2008-08-08 14:04:14
 * time is a Unix Timestamp (in seconds)
 * prefix is text is prepended to the output text, with a space after.
 * suffix is text is appended to the output text, with a space before.
 * interval is the length of time (in seconds) before the time is considered stale.
 */
DOMNode.prototype.headerDateTime = function( datetime, time2, prefix, suffix, interval ) {
    this.prefix = ( prefix === null ) ?  "" : prefix;
    this.suffix = ( suffix === null ) ?  "" : suffix;
    this.interval = ( interval === null ) ?  60 : interval;
    this.regex = /(\d\d\d\d)-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)/;
    this.re = this.regex.exec(datetime);
    if (this.re) {
        this.d2 = new Date();
        this.d2.setFullYear(this.re[1]);
        this.month = this.re[2].replace(/^0/,'');
        this.d2.setMonth(this.month - 1);
        this.day = this.re[3].replace(/^0/,'');
        this.d2.setDate(this.day);
        this.hour = this.re[4].replace(/^0/,'');
        this.d2.setHours(this.hour);
        this.min = this.re[5].replace(/^0/,'');       
        this.d2.setMinutes(this.min);
        this.sec = this.re[6].replace(/^0/,''); 
        this.d2.setSeconds(this.sec);
        this.x = Math.round(this.d2.getTime()/1000);
        this.diff=  Math.abs(this.x - time2);
        this.str = "";
        if (this.prefix.length > 0) {
            this.str += this.prefix + " ";
        }
        this.str += datetime;
        if (this.suffix.length > 0) {
            this.str += " " + this.suffix;
        }
        if ( this.diff < this.interval) {
            this.text(this.str).ok();
        } else {
            this.text(this.str).error();
        }
    } else {
        this.text("error: " + datetime).error();
    }
    return this;
};

// Changes seconds to MM:SS format, e.g.,  15:42
DOMNode.prototype.toMinSec = function( secs ) {  
    this.min = Math.floor(secs / 60);
    this.sec = Math.floor(secs % 60);
    if (this.min < 10) {
        this.min = "0" + this.min;
    }
    if (this.sec < 10) {
        this.sec = "0" + this.sec;
    }
    this.text( this.min + ":"+ this.sec );
    return this;  
};

// Changes seconds to H:MM:SS format, e.g.,  3:15:42
DOMNode.prototype.toHrMinSec = function( secs ) {  
    this.my_secs = secs;
    this.hr = Math.floor(this.my_secs / 3600);
    this.my_secs -= this.hr * 3600;
    this.min = Math.floor(this.my_secs / 60);
    this.sec = Math.floor(this.my_secs % 60);
    if (this.hr < 10) {
        this.hr = "0" + this.hr;
    }
    if (this.min < 10) {
        this.min = "0" + this.min;
    }
    if (this.sec < 10) {
        this.sec = "0" + this.sec;
    }
    this.text( this.hr + ":" + this.min + ":"+ this.sec );
    return this;  
};

// x and y are in units.  Other terms are in either pixels or units.
// y, // Y position on point in units
// x_min_units, // X position of left margin of plot area in units
// x_min_pixels, // X position of left margin of plot area in pixels
// x_max_units,  // X position of right margin of plot area in units
// x_max_pixels,  // X position of right margin of plot area in pixels
// y_min_units, // Y position of bottom margin of plot area in units
// y_min_pixels, // Y position of bottom margin of plot area in pixels
// y_max_units, // Y position of top margin of plot area in units
// y_max_pixels // Y position of top margin of plot area in pixels
DOMNode.prototype.plot_xy = function( x,
                    y, 
                    x_min_units,
                    x_min_pixels,
                    x_max_units,
                    x_max_pixels,
                    y_min_units,
                    y_min_pixels,
                    y_max_units,
                    y_max_pixels ) {
    this.pt_x = (x_min_pixels + ((x_max_pixels-x_min_pixels)*(x-x_min_units)/(x_max_units-x_min_units))).toFixed(0);
    this.pt_y = (y_max_pixels - ((y_max_pixels-y_min_pixels)*(y-y_min_units)/(y_max_units-y_min_units))).toFixed(0);
    // Do we need "px" after each of these???
    this.left(this.pt_x +  "px").top(this.pt_y + "px");
    return this;
};

/* Helper function to see if element is defined */
/* Note: Returns true or false, not the "this" object. */
DOMNode.prototype.isset = function ()  {
    return ( this.element !== null && 
                typeof this.element  != 'undefined'); 
}; 
/* Helper function to see if element is null */
/* Note: Returns true or false, not the "this" object. */
DOMNode.prototype.isnull = function ()  {
    return ( this.element === null );
};

/* Possible parameter values:  none, overline, underline, line-through, blink */
DOMNode.prototype.textDecoration = function ( value )  {
   if ( this.textDecor !== value )  {
      this.element.style.textDecoration = value;
      this.textDecor = value;
   }
};

/*
 *  * Turns text blinking on.  Internet Explorer doesn't support this.
 *  
 */
DOMNode.prototype.blink = function ()  {
   this.textDecoration( "blink" );
};

/*
 *  * Turns text blinking off.  Internet Explorer doesn't support this.
 *  
 */
DOMNode.prototype.nonblink = function ()  {
   this.textDecoration( "none" );
};
