var HOWF = window.HOWF || {};

/** Since some people are used to using this. */
function $(id) {
  return document.getElememtById(id);
}

/** Give us the ability to extend objects. */
if (typeof Object.create !== 'function') {
  Object.create = function(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
  };
}

/** Define Node interface for browsers that fail. */
if (!window["Node"]) {
  window.Node = new Object();
  Node.ELEMENT_NODE = 1;
  Node.ATTRIBUTE_NODE = 2;
  Node.TEXT_NODE = 3;
  Node.CDATA_SECTION_NODE = 4;
  Node.ENTITY_REFERENCE_NODE = 5;
  Node.ENTITY_NODE = 6;
  Node.PROCESSING_INSTRUCTION_NODE = 7;
  Node.COMMENT_NODE = 8;
  Node.DOCUMENT_NODE = 9;
  Node.DOCUMENT_TYPE_NODE = 10;
  Node.DOCUMENT_FRAGMENT_NODE = 11;
  Node.NOTATION_NODE = 12;
}

/** Toggles (a.k.a. pseudo-constants); will be modified at load time. */
HOWF.Toggles = {
  "DEBUG_ENABLED" : false
};

/** Logging singleton. */
HOWF.Debug = (function() {

  var mode = "default";
  var log = "";
  var logRef = null;

  return {

    /**
     * Add a message to the debug log.
     */
    log : function(message,prefix) {

      if (!HOWF.Toggles.DEBUG_ENABLED) {
        return;
      }

      if (typeof prefix == "undefined") {
        prefix = "";
      }

      log = log + "\n" + prefix + message;
      this.logToClient(message,prefix);

    },

    /**
     * Clear the client log area.
     */
    clearClientLog : function() {

      if (logRef && logRef.hasChildNodes()) {

        for (var i = (logRef.childNodes.length-1); i >= 0; i--) {
          logRef.removeChild(logRef.childNodes[i]);
        }

      }

      this.log("Reset client log.");

      return false;

    },

    /**
     * Display and flush the debug log.
     */
    displayAndFlushLog : function() {

      if (HOWF.Toggles.DEBUG_ENABLED) {

        alert(log);
        log = "";

      }

    },

    /**
     * Attach a log message to the debug window in the client.
     */
    logToClient : function(message,prefix) {

      if (logRef === null) {
        logRef = document.getElementById("debug_script_inner_log");
      }

      if (!logRef) {
        alert("Error: Unable to write to debug log.");
        return false;
      }

      if (typeof prefix == "undefined") {
        prefix = "";
      }

      // split by newlines
      var strings = message.split("\n");

      for (var i = 0; i < strings.length; i++) {

        var newContainer = document.createElement("div");
        newContainer.appendChild(document.createTextNode(prefix + strings[i]));
        logRef.appendChild(newContainer);

      }

    }

  };

})();

/** Browser detection, ganked from Yahoo; no point in reinventing the wheel. */
HOWF.Browsers = (function() {

  // this is what we'll return
  var o = {

    ie : 0,
    opera : 0,
    gecko : 0,
    webkit : 0 ,
    mobile : 0,
    air : 0

  };

  var ua = navigator.userAgent;
  var m;

  // modern KHTML browsers should qualify as Safari X-Grade
  if ((/KHTML/).test(ua)) {
    o.webkit = 1;
  }

  // modern WebKit browsers are at least X-Grade
  m = ua.match(/AppleWebKit\/([^\s]*)/);

  if (m && m[1]) {

    o.webkit = parseFloat(m[1]);

    // Mobile browser check
    if (/ Mobile\//.test(ua)) {
      o.mobile = "Apple"; // iPhone or iPod Touch
    } else {
      m=ua.match(/NokiaN[^\/]*/);

      if (m) {
        o.mobile = m[0]; // Nokia N-series, ex: NokiaN95
      }
    }

    m = ua.match(/AdobeAIR\/([^\s]*)/);

    if (m) {
      o.air = m[0]; // Adobe AIR 1.0 or better
    }

  }

  if (!o.webkit) { // not webkit

    m = ua.match(/Opera[\s\/]([^\s]*)/);

    if (m&&m[1]) {

      o.opera=parseFloat(m[1]);
      m = ua.match(/Opera Mini[^;]*/);

      if (m) {
        o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316
      }

    } else { // not opera or webkit

      m = ua.match(/MSIE\s([^;]*)/);

      if (m && m[1]) {

        o.ie = parseFloat(m[1]);

      } else { // not opera, webkit, or ie
        m = ua.match(/Gecko\/([^\s]*)/);

        if (m) {

          o.gecko=1; // Gecko detected, look for revision

          m = ua.match(/rv:([^\s\)]*)/);

          if (m && m[1]) {
            o.gecko=parseFloat(m[1]);
          }

        }
      }
    }
  }

  return o;

})();

/** This will be our global data store. */
HOWF.StoredData = {};

/** A singleton with our utility methods. */
HOWF.Utils = (function() {

  // browser checks
  var isOpera = HOWF.Browsers.opera;
  var isSafari = HOWF.Browsers.webkit;
  var isGecko = HOWF.Browsers.gecko;
  var isIE = HOWF.Browsers.ie;

  return {

    /**
     * Centers a particular window.
     */
    centerWindow : function(win) {

      var left = window.XMLHttpRequest == null ? document.documentElement.scrollLeft : 0;
      var top = window.XMLHttpRequest == null ? document.documentElement.scrollTop : 0;

      win.style.left = Math.max((left + (this.getWindowWidth() - win.offsetWidth) / 2), 0) + 'px';
      win.style.top = Math.max((top + (this.getWindowHeight() - win.offsetHeight) / 2), 0) + 'px';

    },

    /**
     * Opens a centered window.
     */
    centeredWindow : function(url,name,width,height,scrollbars,resizeable) {

      var xPos = (screen.availWidth / 2) - (width / 2);
      var yPos = (screen.availHeight / 2) - (height / 2);

      var options = "width=" + width + ",height=" + height + ",resizeable=" + resizeable +",scrollbars=" + scrollbars + ",left=" + xPos + ",top=" + yPos;

      window.open(url,name,options);

    },

    /**
     * Clear a field if its current value matches the default; if it's a
     * password input, remove the background and store its value.
     */
    clearFieldDefault : function(element,def,password) {

      if (element.value == def || (password && element.value == "")) {

        element.value = "";

        if (password) {
          HOWF.StoredData.passwordBackgroundImage = element.style.backgroundImage;
          element.style.backgroundImage = '';
        }

      }

    },

    /**
     * Dump an object to a string (probably a better way of doing this).
     */
    dumpObject : function(obj,showFunctions,showChildren,level) {

      var data = new Array;
      var prefix = "";

      if (typeof level != "number") {
        level = 0;
      }

      for (var i = 0; i < level; i++) {
        prefix = prefix + "__";
      }

      for (var x in obj) {

        if (typeof obj[x] == 'function' && showFunctions !== true) {
          data.push(prefix + x + ": [function]");
          continue;
        }

        if (typeof obj[x] == 'object' && showChildren === true) {
          data.push(prefix + x + ": [object]");
          data.push(this.dumpObject(obj[x],showFunctions,showChildren,(level+1)));
          continue;
        }

        data.push(prefix + x + ": " + obj[x]);

      }

      return this.implode("\n",data);

    },

    /**
     * Enable the HOWF Javascript debug logging functions.
     */
    enableDebugLog : function() {

      HOWF.Toggles.DEBUG_ENABLED = true;

      // add keyboard shortcut for debug log
      document.onkeydown = function(e) {

        e = e || window.event;

        var code;

        // select code based on browser model
        if (window.event) {
          code = e.keyCode;
        } else if (e) {
          code = e.which;
        } else {
          return true;
        }

        if (String.fromCharCode(code).toLowerCase() == "d" && e.ctrlKey && e.shiftKey) {

          HOWF.Utils.toggleDebugLog();
          HOWF.Utils.toggleDebugScript();

          return false;

        } else {

          return true;

        }

      }

      HOWF.Debug.log("Initialized HOWF Javascript debug log engine.");

    },

    /**
     * Extend the current onload event(s).
     */
    extendOnload : function(newfunc) {

      var old = window.onload;

      if (typeof old != 'function') {

        window.onload = newfunc;

      } else {

        window.onload = (function() {

          old();
          newfunc();

        });

      }

    },

    /**
     * Check to see if an element is a descendant of an element whose
     * id begins with some string, and if so, return that element;
     * otherwise, return false;
     */
    findParentElementWithId : function(element,id) {

      while (element.parentNode) {

        element = element.parentNode;

        if (element.id && element.id.indexOf(id) != -1) {
          return element;
        }

      }

    },

    /**
     * Get an X,Y coordinate pair of an element's position.
     */
    getCoords : function(element) {

      // for internet explorer, use precalculated
      if (document.documentElement.getBoundingClientRect) {

        var box = element.getBoundingClientRect();
        var rootNode = element.ownerDocument;

        return [Math.round(box.left + this.getDocumentScrollLeft(rootNode)), Math.round(box.top + this.getDocumentScrollTop(rootNode))];

      } else {

        var pos = [element.offsetLeft, element.offsetTop];
        var parentNode = element.offsetParent;

        // safari: subtract body offsets if el is abs (or any offsetParent), unless body is offsetParent
        var accountForBody = (isSafari && element.style.position == 'absolute' && element.offsetParent == element.ownerDocument.body);

        if (parentNode != element) {

          while (parentNode) {

            pos[0] += parentNode.offsetLeft;
            pos[1] += parentNode.offsetTop;

            if (!accountForBody && isSafari && parentNode.style.position == 'absolute') {
              accountForBody = true;
            }

            parentNode = parentNode.offsetParent;

          }

        }

        if (accountForBody) {
          pos[0] -= element.ownerDocument.body.offsetLeft;
          pos[1] -= element.ownerDocument.body.offsetTop;
        }

        parentNode = element.parentNode;

        // account for any scrolled ancestors
        while (parentNode.tagName && !/^body|html$/i.test(parentNode.tagName)) {

          if (parentNode.scrollTop || parentNode.scrollLeft) {
            pos[0] -= parentNode.scrollLeft;
            pos[1] -= parentNode.scrollTop;
          }

          parentNode = parentNode.parentNode;

        }

        return pos;
     }

    },

    /**
     * Get the value of any horizon scroll of the document.
     */
    getDocumentScrollLeft : function(d) {

      d = d || document;
      return Math.max(d.documentElement.scrollLeft, d.body.scrollLeft);

    },

    /**
     * Get the value of any vertical scroll of the document.
     */
    getDocumentScrollTop : function(d) {

      d = d || document;
      return Math.max(d.documentElement.scrollTop, d.body.scrollTop);

    },

    /**
     * Get the concatenation of all of the text inside an element;
     * this is replaced by element.textContent in DOM Level 3.
     */
    getElementText : function(element) {

      if (element.nodeType && element.nodeType == Node.TEXT_NODE) {
        return element.nodeValue;
      }

      var children = element.childNodes;
      var textContent = "";

      for (var i = 0; i < children.length; i++) {

        var moreText = HOWF.Utils.getElementText(children[i]);

        if (moreText) {

          if (textContent != "") {
            textContent = textContent + " ";
          }

          textContent = textContent + moreText;

        }

      }

      return textContent;

    },

    /**
     * Get the element which is the target of an event.
     */
    getEventTarget : function(e) {

      var t;

      // get the target element
      if (e.target) {
        t = e.target;
      } else if (e.srcElement) {
        t = e.srcElement;
      }

      // if it's a text node, use the parent
      if (t.nodeType == 3) {
        t = t.parentNode;
      }

      return t;

    },

    /**
     * Get the first child of a node that is a full DOM element.
     */
    getFirstDomChild : function(element) {

      if (element.hasChildNodes()) {

        var children = element.childNodes;

        for (var i = 0; i < children.length; i++) {

          if (children[i].nodeType == Node.ELEMENT_NODE) {
            return children[i];
          }

        }

      }

      return false;

    },

    /**
     * Get the client window height.
     */
    getWindowHeight : function() {

      var height = document.documentElement && document.documentElement.clientHeight ||
                   document.body && document.body.clientHeight ||
                   document.body && document.body.parentNode && document.body.parentNode.clientHeight ||
                   0;

      return height;

    },

    /**
     * Get the client window width.
     */
    getWindowWidth : function() {

      var width = document.documentElement && document.documentElement.clientWidth ||
                  document.body && document.body.clientWidth ||
                  document.body && document.body.parentNode && document.body.parentNode.clientWidth ||
                  0;

      return width;

    },

    /**
     * Join an array of strings together using some "glue" string.
     */
    implode : function(glue,data) {

      var final = "";

      for (var i = 0; i < data.length; i++) {

        if (final != "") {
          final = final + glue;
        }

        final = final + data[i];

      }

      return final;

    },

    /**
     * See if an element is contained in an array.
     */
    inArray : function(element,collection) {

      if (!(collection instanceof Array)) {
        return false;
      }

      for (var i = 0; i < collection.length; i++) {
        if (element == collection[i]) {
          return true;
        }
      }

      return false;

    },

    /**
     * Open the Terms of Use.
     */
    openTermsWindow : function() {

      this.centeredWindow("legal.php?action=terms","legalWindow",600,400,1,1);

      return false;

    },
    
    /**
     * Remove all of an element's children.
     */
    removeChildren : function(element) {

      for (var i = (element.childNodes.length-1); i >= 0; i--) {
        element.removeChild(element.childNodes[i]);
      }
    
    },

    /**
     * Replace all of the children of a node with some text.
     */
    replaceChildrenWithText : function(element,text) {

      // remove existing children
      for (var i = (element.childNodes.length-1); i >= 0; i--) {
        element.removeChild(element.childNodes[i]);
      }

      // add the text
      element.appendChild(this.replaceEntitiesWithCharacters(document.createTextNode(text)));

    },

    /**
     * Replace any entity references within a string with their character codes.
     */
    replaceEntitiesWithCharacters : function(textNode) {

      var testTm = /&trade;/;
      var testReg = /&reg;/;
      var testCopy = /&copy;/;

      textNode.nodeValue = textNode.nodeValue.replace(testCopy,String.fromCharCode(169));
      textNode.nodeValue = textNode.nodeValue.replace(testReg,String.fromCharCode(174));

      if (testTm.test(textNode.nodeValue)) {

        textNode.nodeValue = textNode.nodeValue.replace(testTm,"");

        var newTm = document.createElement("sup");
        newTm.style.fontSize = "8px";
        newTm.style.verticalAlign = "top";
        newTm.appendChild(document.createTextNode("TM"));

        var newContainer = document.createElement("span");
        newContainer.appendChild(textNode);
        newContainer.appendChild(newTm);

        return newContainer;

      }

      return textNode;

    },

    /**
     * Restore a field to its default value if it's empty; if it's a
     * password input, restore the stored background image.
     */
    restoreFieldDefault : function(element,def,password) {

      if (element.value == "") {

        if (password) {
          element.style.backgroundImage = HOWF.StoredData.passwordBackgroundImage;
        } else {
          element.value = def;
        }

      }

    },

    /**
     * Convert spaces in a string to underscores.
     */
    spacesToUnderscores : function(str) {

      return str.replace(" ","_");

    },

    /**
     * Stop event bubbling in cross-browser fashion.
     */
    stopBubble : function(e) {

      // for IE
      e.cancelBubble = true;

      // for W3C
      if (e.stopPropagation) {
        e.stopPropagation();
      }

    },

    /**
     * Open or close a web service call in the debug log.
     */
    toggleDebugCall : function(id) {

      var js_debug_call = document.getElementById("debug_call_" + id);
      var js_debug_call_link = document.getElementById("debug_call_link_" + id);

      js_debug_call.style.display = (js_debug_call.style.display == "none" ? "block" : "none");

      var link_text = (js_debug_call.style.display == "none" ? "Show Call" : "Hide Call");

      js_debug_call_link.replaceChild(document.createTextNode(link_text),js_debug_call_link.firstChild);

      return false;

    },

    /**
     * Open or close the debug log box.
     */
    toggleDebugLog : function() {

      var js_debug_log = document.getElementById("debug_log");
      var js_debug_log_link = document.getElementById("debug_log_link");

      js_debug_log.style.display = (js_debug_log.style.display == "none" ? "block" : "none");

      var link_text = (js_debug_log.style.display == "none" ? "Show Debug Log" : "Hide Debug Log");

      js_debug_log_link.replaceChild(document.createTextNode(link_text),js_debug_log_link.firstChild);

      return false;

    },

    /**
     * Open or close a query in the debug log.
     */
    toggleDebugQuery : function(id) {

      var js_debug_query = document.getElementById("debug_query_" + id);
      var js_debug_query_link = document.getElementById("debug_query_link_" + id);

      js_debug_query.style.display = (js_debug_query.style.display == "none" ? "block" : "none");

      var link_text = (js_debug_query.style.display == "none" ? "Show Query" : "Hide Query");

      js_debug_query_link.replaceChild(document.createTextNode(link_text),js_debug_query_link.firstChild);

      return false;

    },

    /**
     * Open or close the client script debug log.
     */
    toggleDebugScript : function() {

      var js_debug_log = document.getElementById("debug_script_log");
      var js_debug_log_link = document.getElementById("debug_script_log_link");

      js_debug_log.style.display = (js_debug_log.style.display == "none" ? "block" : "none");

      var link_text = (js_debug_log.style.display == "none" ? "Show Log" : "Hide Log");

      js_debug_log_link.replaceChild(document.createTextNode(link_text),js_debug_log_link.firstChild);

      return false;

    },

    /**
     * Validate that a variable is in fact an object, and implements
     * each of the required fields (which may be functions, objects,
     * primitives, etc).
     */
    validateObject : function(obj,elements) {

      if (typeof elements != "object") {
        alert("Failed to validate object; second parameter must be an object.");
        return false;
      }

      if (typeof obj != "object") {
        return false;
      }

      if (elements instanceof Array) {

        for (var i = 0; i < elements.length; i++) {

          if (!obj[elements[i]]) {
            return false;
          }

        }

      } else {

        for (var x in elements) {

          if (typeof obj[x] == 'undefined') {
            return false;
          }

        }

      }

      return true;

    },

    trim : function(str, chars) {
      return this.ltrim(this.rtrim(str, chars), chars);
    },

    ltrim : function(str, chars) {
      chars = chars || "\\s";
      return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
    },

    rtrim : function(str, chars) {
      chars = chars || "\\s";
      return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
    }

  };

})();
