// taken from Atlas compat.js
function registerNamespaces()
{
  for (var i=0;i<arguments.length;i++)
  {
    var astrParts = arguments[i].split(".");
    var root = window;
    for (var j=0; j < astrParts.length; j++)
    {
      if (!root[astrParts[j]]) 
      {
        root[astrParts[j]] = new Object(); 
      }
      root = root[astrParts[j]];
    }
  }
}

// -------------------------------------------------------------------------------------------------
// Utility functions

registerNamespaces("Plaxo.Util");

/**
 * Simple utility for timing how long code takes. The point of having an ID for each timer is that
 * the start and elapsed calls can be in different places and they use the same global registry.

 * Example usage:
 * Plaxo.Util.Timer.startTimer('mytimer');
 * // do some codde
 * alert("seconds to run: " + Plaxo.Util.Timer.getElapsedTime('mytimer'));
 */
Plaxo.Util.Timer = {
  timers: {},

  startTimer: function(id) {
    if (!this.enabled) return;
    this.timers.id = new Date().getTime();
  },

  getElapsedTime: function(id) {
    if (!this.enabled) return;
    var start = this.timers.id;
    if (!start) {
      alert('Unknown timer: ' + id);
    }
    return new Date().getTime() - start;
  },

  alertElapsedTime: function(id) {
    if (!this.enabled) return;
    alert('Elapsed time for "' + id + '": ' + this.getElapsedTime(id));
  },

  enabled: true,
  setTimersEnabled: function(state) {
    this.enabled = state;
  }
};

Plaxo.Util.Looper = {
  doLoop: function(list, func, doneFunc, loopSize, start) {
    if (!loopSize) loopSize = 500;
    if (!start) start = 0;

    var max = start + loopSize;
    var lastLoop = false;
    if (max > list.length) {
      max = list.length;
      lastLoop = true;
    }

    for (var i = start; i < max; i++) {
      func(list, i);
    }

    if (lastLoop) {
      if (doneFunc) doneFunc();
    } else {
      setTimeout(function() {
        Plaxo.Util.Looper.doLoop(list, func, doneFunc, loopSize, max);
      }, 0);
    }
  }
};

// -------------------------------------------------------------------------------------------------
// Form-manipulating functions

registerNamespaces("Plaxo.Form");

Plaxo.Form = {
  /**
   * Returns the selected value from the given <select> object.
   * Why this isn't a built in property/method is beyond me!
   */
  getSelectValue: function(sel) {
    if (!sel) return null;
    if (!sel.options) return sel.value; // fall back for hidden/text fields
    return sel.options[sel.selectedIndex].value;
  },

  /**
   * Focues the first visible element in the given form. 
   * Useful to call onLoad.
   */
  focusFirstVisibleFormElem: function(f, findFirstBlankField) {
    for (var i = 0; i < f.length; i++) {
      var el = f.elements[i];
      if (el.type != 'hidden' && el.style.display != 'none' && (!findFirstBlankField || el.value.length == 0) && el.focus) {
        el.focus();
        break;
      }
    }
  }
};

// -------------------------------------------------------------------------------------------------
// String functions

registerNamespaces("Plaxo.String");

Plaxo.String = {
  contains: function(whole, part) {
    return whole.indexOf(part) != -1;
  },

  alnumChars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_",
  isalnum: function(ch) { 
    return this.contains(this.alnumChars, ch);
  },

  otherSafeEmailChars: ".-+=",

  /** 
    * Returns the index of the char furthest in the given direction from start 
    * that's an alphanum or dot/dash char. Useful for extracting words from special chars.
    *
    * Example:
    * s = "<first-second word>";
    * findBoundary(s, 3, false) -> 2 ("f")
    * findBoundary(s, 3, true) -> 6 ("d")
    */
  findBoundary: function(s, start, forward) {
    if (forward) {
      for (var i = start; i < s.length; i++) {
        var ch = s.charAt(i);
        if (!Plaxo.String.isalnum(ch) && !this.contains(this.otherSafeEmailChars, ch)) {
          return i - 1;
        }
      }
      return s.length - 1;
    } else {
      for (var i = start - 1; i >= 0; i--) {
        var ch = s.charAt(i);
        if (!Plaxo.String.isalnum(ch) && !this.contains(this.otherSafeEmailChars, ch)) {
          return i + 1;
        }
      }
      return 0;
    }
  },

  /**
   * Returns a copy of the given list of strings with duplicates removed.
   */
  removeDups: function(strs, ignoreCase) {
    var uniqueStrs = [];
    var oldStrs = {};
    for (var i = 0; i < strs.length; i++) {
      var s = strs[i];
      if (ignoreCase) s = s.toLowerCase();
      if (!oldStrs[s]) {
        uniqueStrs.push(strs[i]);
        oldStrs[s] = 1;
      }
    }
    return uniqueStrs;
  }
};

// -------------------------------------------------------------------------------------------------
// Debug functions

registerNamespaces("Plaxo.Debug");

/**
 * Basic logging/debugging facilities.
 * TODO:
 * - log to logfile
 * - send to server
 * - better info in assertion msg (line num?)
 * - set log level to silence debug info
 */
Plaxo.Debug = {

  /**
   * Report all logging messages above this log level.
   * Values:
   *   0: no logging
   *   1: just errors
   *   2: errors & warnings
   *   3: errors, warnings, & trace messages
   */
  logLevel: 0,

  assert: function(expr, msg) {
    if (!expr) {
      if (!msg) msg = 'unk';
      throw new Error('Assertion failed: ' + msg);
    }
  },
 
  error: function(msg) {
    if (this.logLevel < 1) return;
    alert(msg); // alert errors regardless
    msg = this.format(msg, 1);
    if (this.dumpEnabled) dump(msg);
  },

  warning: function(msg) {
    if (this.logLevel < 2) return;
    msg = this.format(msg, 2);
    if (this.dumpEnabled) dump(msg);
    else alert(msg);
  },

  trace: function(msg) {
    if (this.logLevel < 3) return;
    msg = this.format(msg, 3);
    if (this.dumpEnabled) dump(msg);
    else window.status = msg;
  },

  format: function(msg, level) {
    var d = new Date();
    return level + '|' + d.toLocaleTimeString() + '.' + d.getMilliseconds() + '|' + msg + '\n';
  },

  dumpEnabled: false,

  /**
   * Tries to turn on console-dumping in mozilla.
   * TODO: doesn't seem to work in moz (prem denied)
   */
  initialize: function() {
    // turning on dump() in moz
    var PREFS_CID      = "@mozilla.org/preferences;1";
    var PREFS_I_PREF   = "nsIPref";
    var PREF_STRING    = "browser.dom.window.dump.enabled";
    try {
      var Pref        = new Components.Constructor(PREFS_CID, PREFS_I_PREF);
      var pref        = new Pref();
      pref.SetBoolPref(PREF_STRING, true);
      this.dumpEnabled = true;
    } catch(e) { 
      if (typeof(dump) != 'undefined') {
        // turned on manually be developer
        this.dumpEnabled = true;
      }
    }
  }
};

Plaxo.Debug.initialize();

// -------------------------------------------------------------------------------------------------
// Adding missing built-in functions

// Emulation of array push/pop for those who lack it (IE 5.0/4.0)
function Array_push() {
  var A_p = 0;
  for (A_p = 0; A_p < arguments.length; A_p++) {
   this[this.length] = arguments[A_p];
  }
  return this.length
}

function Array_pop() {
  var response = this[this.length - 1];
  this.length--;
  return response;
}

function Array_unshift() {
  this.reverse();
  for(var i=arguments.length-1;i>=0;i--) this[this.length]=arguments[i];
  this.reverse();
  return this.length;
}

if (typeof(Array.prototype.unshift) == "undefined") {
  Array.prototype.unshift = Array_unshift;
}

if (typeof(Array.prototype.pop) == "undefined") {
  Array.prototype.pop = Array_pop;
}

if (typeof Array.prototype.push == "undefined") {
  Array.prototype.push = Array_push;
}

function isWhitespace(c) { 
  return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

// emulation of string.trim()
function String_trim(trimset) {
  if (this.length == 0) return this; // nothing to do
  if (!trimset) trimset = " \t\r\n"; // default: trim on whitespace
    
  var start=0;
  while(start<this.length && trimset.indexOf(this.charAt(start)) != -1)
    start++;

  var end=this.length-1;
  while(end>start && trimset.indexOf(this.charAt(end)) != -1)
    end--;

  if(start>0 || end<this.length-1) {
    return this.substring(start, end + 1);
  } else return this; // already trimmed
}

if (typeof String.prototype.trim == "undefined") {
  String.prototype.trim = String_trim;
}

function String_endsWith(s) {
  if (!s) return true;
  if (s.length > this.length) return false;
  var start = this.length - s.length;
  for (var i = 0; i < s.length; i++) {
    if (s.charAt(i) != this.charAt(start + i)) {
      return false;
    }
  }
  return true;
}

if (typeof String.prototype.endsWith == "undefined") {
  String.prototype.endsWith = String_endsWith;
}

