﻿// MetaPress RnD JavaScript Library

/************************************* Useful Generic Functions ***********************************************************/
/*global window, document, navigator, clearTimeout, setTimeout, $, Image */

// local
var windowCoords, PageInStandardsMode, core_sha1, sha1_ft, sha1_kt, core_hmac_sha1, safe_add, rol, str2binb,
binb2str, binb2hex, binb2b64;

// Cross-browser event handling for IE5+, NS6+, and Mozilla/Gecko (MODIFIED)
// By Scott Andrew
function AddEvent(elm, evType, fn, useCapture) {
  var success = false;
  if (elm.addEventListener) {
    if (evType == 'mousewheel') { evType = 'DOMMouseScroll'; }
    elm.addEventListener(evType, fn, useCapture);
    success = true;
  } else if (elm.attachEvent) {
    if (evType == 'mousewheel') {
      window.onmousewheel = document.onmousewheel = fn;
      success = true;
    } else {
      var r = elm.attachEvent('on' + evType, fn);
      success = r;
    }
  } else {
    success = false;
  }
  elm = null;
  return success;
}

// Removes an event handler from an element.
function RemoveEvent(elm, evType, fn, useCapture) {
  if (elm && evType && fn) {
    if (elm.removeEventListener) {
      elm.removeEventListener(evType, fn, useCapture);
      return true;
    } else if (elm.detachEvent) {
      var r = elm.detachEvent('on' + evType, fn);
      return r;
    } else {
      throw new Error("RemoveEvent failed on element: " + elm + ", event type: " + evType +
        ", function: " + fn + ", use capture: " + useCapture + ".");
    }
  }
}

// Stops an event from propagating up to parent elements.
function PopEventBubble(ev) {
  if (!ev) { ev = window.event; }

  // Cancel bubbling  
  ev.cancelBubble = true;
  if (ev.stopPropagation) { ev.stopPropagation(); }

  // Cancel default behavior
  ev.returnValue = false;
  if (ev.preventDefault) { ev.preventDefault(); }
}

// getElementsByClassName Deluxe Edition
// From http://muffinresearch.co.uk/archives/2006/04/29/getelementsbyclassname-deluxe-edition/
function getElementsByClassName(objContElm, strTag, strClass) {
  strTag = strTag || '*';
  objContElm = objContElm || document;
  var objColl = objContElm.getElementsByTagName(strTag);
  if (!objColl.length && strTag == '*' && objContElm.all) { objColl = objContElm.all; }
  var arr = [];
  var delim = strClass.indexOf('|') != -1 ? '|' : ' ';
  var arrClass = strClass.split(delim);
  for (var i = 0, j = objColl.length; i < j; i++) {
    var arrObjClass = objColl[i].className.split(' ');
    if (delim == ' ' && arrClass.length > arrObjClass.length) { continue; }
    var c = 0;
    comparisonLoop:
    for (var k = 0, l = arrObjClass.length; k < l; k++) {
      for (var m = 0, n = arrClass.length; m < n; m++) {
        if (arrClass[m] == arrObjClass[k]) { c++; }
        if ((delim == '|' && c == 1) || (delim == ' ' && c == arrClass.length)) {
          arr.push(objColl[i]);
          break comparisonLoop;
        }
      }
    }
  }
  return arr;
}

// Create the prototype indexOf for IE arrays, because IE ROCKS!
if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function (item) {
    var result = -1;
    if ((this.length < 1) || (arguments.length != 1)) {
      return -1;
    }
    for (var i = 0; i < this.length; i++) {
      if (this[i] == item) {
        result = i;
        break;
      }
    }
    return result;
  };
}

//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
if (!Array.prototype.every) {
  Array.prototype.every = function (fun /*, thisp*/) {
    var len = this.length;
    if (typeof fun != "function") {
      throw new TypeError();
    }
    var thisp = arguments[1];
    for (var i = 0; i < len; i++) {
      if (i in this &&
          !fun.call(thisp, this[i], i, this)) {
        return false;
      }
    }

    return true;
  };
}

// Determines if any value in one array is present in another array
var ArraysIntersect = function (array1, array2) {
  var arraysIntersect = false;
  for (var i = 0; i < array1.length; i++) {
    if (array1[i]) {
      if (array2.indexOf(array1[i]) != -1) {
        arraysIntersect = true;
        break;
      }
    }
  }
  return arraysIntersect;
};



// The Custom Event *************************************************************************************************
// This is a custom event model for throwing and handling events on JavaScript objects (as opposed to DOM elements).
var CustomEvent = function (eventName, scope) {
  var error;
  if (eventName === null) { error = new Error('"eventName" cannot be null in CustomEvent().'); }
  if (scope === null) { error = new Error('"scope" cannot be null in CustomEvent().'); }
  if (error) {
    var errorMessage = 'An error occurred on "' + error.fileName + '" (line #' + error.lineNumber + '). ' + error.message;
    throw new Error(errorMessage);
  }

  this.name = eventName;
  this.owningObject = scope;
  this.callbacks = [];
  this.callbackOwners = [];
};

CustomEvent.prototype.Subscribe = function (handler, handlerOwner) {
  var error;
  if (handler === null) { error = new Error('"handler" cannot be null in CustomEvent.Subscribe().'); }
  if (handlerOwner === null) { error = new Error('"handler" cannot be null in CustomEvent.Subscribe().'); }
  if (error) {

    var errorMessage = 'An error occurred on "' + error.fileName + '" (line #' + error.lineNumber + '). ' + error.message;
    throw new Error(errorMessage);
  }

  if (!this.HandlerExists(handler, handlerOwner)) {
    this.callbacks.push(handler);
    this.callbackOwners.push(handlerOwner);
  }
};

CustomEvent.prototype.HandlerExists = function (handler, handlerOwner) {
  var callbackIndex = this.callbacks.indexOf(handler);
  var callbackOwner = this.callbackOwners[callbackIndex];
  if ((callbackIndex == -1) && (callbackOwner != handlerOwner)) {
    return false;
  } else {
    return true;
  }
};

CustomEvent.prototype.Unsubscribe = function (handler, handlerOwner) {
  var error;
  if (handler === null) { error = new Error('"handler" cannot be null in CustomEvent.Unsubscribe().'); }
  if (handlerOwner === null) { error = new Error('"handler" cannot be null in CustomEvent.Unsubscribe().'); }
  if (error) {
    var errorMessage = 'An error occurred on "' + error.fileName + '" (line #' + error.lineNumber + '). ' + error.message;
    throw new Error(errorMessage);
  }
  if (this.HandlerExists(handler, handlerOwner)) {
    var callbackIndex = this.callbacks.indexOf(handler);
    this.callbacks.splice(callbackIndex, 1);
    this.callbackOwners.splice(callbackIndex, 1);
    return true;
  } else {
    return false;
  }
};

CustomEvent.prototype.UnsubscribeAll = function () {
  this.callbacks = [];
  this.callbackOwners = [];
};

CustomEvent.prototype.Fire = function (params) {
  // The callbacks could be unsubscribed during this process.
  // For that reason copies of the CustomEvent arrays are made.
  var listeners = this.callbackOwners.slice();
  var handlers = this.callbacks.slice();
  for (var i = 0; i < handlers.length; i++) {
    var handlerParams = { name: this.name, scope: this.owningObject, paramObject: params };
    handlers[i].call(listeners[i], handlerParams);
  }
};

//******* End CustomEvent **************************************************************************************/

// Disables an element from being highlighted (selected).
function StopElementSelection(element) {
  // IE mouse selection canceling
  element.onselectstart = function (ev) { if (ev.target.tagName != 'INPUT') { return false; } };

  // Firefox mouse selection canceling
  //element.style.MozUserSelect = "none";
}

// Get the scrollbar width
// From http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
// By Matthew Eernisse
function getScrollerWidth() {
  var scr = null;
  var inn = null;
  var wNoScroll = 0;
  var wScroll = 0;

  // Outer scrolling div
  scr = document.createElement('div');
  scr.style.position = 'absolute';
  scr.style.top = '-1000px';
  scr.style.left = '-1000px';
  scr.style.width = '100px';
  scr.style.height = '50px';
  // Start with no scrollbar
  scr.style.overflow = 'hidden';

  // Inner content div
  inn = document.createElement('div');
  inn.style.width = '100%';
  inn.style.height = '200px';

  // Put the inner div in the scrolling div
  scr.appendChild(inn);
  // Append the scrolling div to the doc
  document.body.appendChild(scr);

  // Width of the inner div sans scrollbar
  wNoScroll = inn.offsetWidth;
  // Add the scrollbar
  scr.style.overflow = 'auto';
  // Width of the inner div width scrollbar
  wScroll = inn.offsetWidth;

  // Remove the scrolling div from the doc
  document.body.removeChild(
        document.body.lastChild);

  // Pixel width of the scroller
  return (wNoScroll - wScroll);
}

// Modified queue object from http://www.safalra.com/web-design/javascript/queues/
// SampleList is a fixed length queue used for error prone sampling.
var SampleList = function (maxSize) {
  this.list = [];
  this.listSpace = 0;
  this.maxSize = maxSize;
};
SampleList.prototype.Add = function (number) {
  this.list.reverse();
  this.list.push(number);
  this.list.reverse();
  if (this.list.length > this.maxSize) {
    this.list.pop();
  }
};
SampleList.prototype.GetSample = function (index) {
  return this.list[index];
};
SampleList.prototype.GetAvg = function () {
  var total = 0;
  var avg = 0;
  for (var i = 0; i < this.list.length; i++) { total += this.list[i]; }
  if (total !== 0) { avg = total / this.list.length; }
  return avg;
};

SampleList.prototype.Fill = function (newAmount) {
  for (var i = 0; i < this.list.length; i++) { this.list[i] = newAmount; }
};


// This is a custom queue object that holds one of each value.
// The queue is given a lenth limit and older items can be purged.
var LimitedQueue = function (limit) {
  this.cache = [];
  this.limit = limit;
};

LimitedQueue.prototype.Add = function (object) {
  var indexInCache = this.cache.indexOf(object);
  if (indexInCache != -1) { this.cache.splice(indexInCache, 1); }
  this.cache.push(object);
};

LimitedQueue.prototype.AddRange = function (objectArray) {
  for (var i = 0; i < objectArray.length; i++) {
    var indexInCache = this.cache.indexOf(objectArray[i]);
    if (indexInCache != -1) { this.cache.splice(indexInCache, 1); }
    this.cache.push(objectArray[i]);
  }
};

LimitedQueue.prototype.Remove = function () {
  return this.cache.shift();
};

LimitedQueue.prototype.RemoveExcess = function () {
  var extraElements = [];
  while (this.cache.length > this.limit) { extraElements.push(this.cache.shift()); }
  return extraElements;
};

LimitedQueue.prototype.RemoveAll = function () {
  this.cache = [];
};

LimitedQueue.prototype.Contains = function (object) {
  var indexInCache = this.cache.indexOf(object);
  var objectIsInCache = false;
  if (indexInCache != -1) { objectIsInCache = true; }
  return objectIsInCache;
};

LimitedQueue.prototype.GetSize = function () {
  return this.cache.length;
};

LimitedQueue.prototype.GetCacheObjects = function () {
  return this.cache;
};

LimitedQueue.prototype.SizeExceedsLimit = function () {
  var limitExceeded = false;
  if (this.cache.length > this.limit) { limitExceeded = true; }
  return limitExceeded;
};

LimitedQueue.prototype.GetLimit = function () {
  return this.limit;
};

LimitedQueue.prototype.SetLimit = function (newLimit) {
  this.limit = newLimit;
};

// Modified from http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
function CloneObject(obj) {
  if (obj === null || typeof (obj) != 'object') { return obj; }
  var temp = {};
  for (var key in obj) {
    if (key) {
      temp[key] = obj[key];
    }
  }
  return temp;
}

// Returns mouse coordinates
function mouseCoords(ev) {
  var windowPos = windowCoords();
  if (ev.pageX || ev.pageY) {
    return { x: ev.pageX, y: ev.pageY };
  }
  return {
    x: ev.clientX + windowPos.scrollX - document.body.clientLeft,
    y: ev.clientY + windowPos.scrollY - document.body.clientTop
  };
}

// Function for determining the dimensions of the browser window and position of scrollbars.
function windowCoords() {
  var windowWidth = window.innerWidth || document.documentElement.clientWidth;
  if (!windowWidth) { windowWidth = document.body.clientWidth; }
  var windowHeight = window.innerHeight || document.documentElement.clientHeight;
  if (!windowHeight) { windowHeight = document.body.clientHeight; }
  var windowScrollX = window.scrollX || document.body.scrollLeft || document.documentElement.scrollLeft;
  var windowScrollY = window.scrollY || document.body.scrollTop || document.documentElement.scrollTop;
  return {
    width: windowWidth,
    height: windowHeight,
    scrollX: windowScrollX,
    scrollY: windowScrollY
  };
}

// Based on code from http://www.quirksmode.org/js/findpos.html
function FindElementDocumentPosition(element) {
  var left = 0;
  var top = 0;
  var lastElement = element;
  if (element.offsetParent) {
    left += element.offsetLeft;
    top += element.offsetTop;
    element = element.offsetParent;
    while (element) {
      left += element.offsetLeft;
      top += element.offsetTop;
      left -= element.scrollLeft;
      top -= element.scrollTop;
      lastElement = element;
      element = element.offsetParent;
    }
  }
  // Adjust for document scroll position
  var agent = navigator.userAgent.toLowerCase();
  if (!PageInStandardsMode() || (agent.indexOf('firefox') == -1)) {
    var leftAdjust = document.body.scrollLeft || document.documentElement.scrollLeft;
    var topAdjust = document.body.scrollTop || document.documentElement.scrollTop;
    left += leftAdjust;
    top += topAdjust;
  }
  return {
    x: left,
    y: top
  };
}

// Determines if the web page is in standards mode.
function PageInStandardsMode() {
  var standardsMode = false;
  if (document.compatMode == 'CSS1Compat') {
    standardsMode = true;
  }
  // Check IE8 mode
  if (document.documentMode == 8) {
    standardsMode = true;
  }
  return standardsMode;
}


// Produces an array of numbers from a string comprised of integers separated by commas and dashes.
// Commas delimit numbers that should be included in the output as-is.  Dashes delimit ranges of numbers.
// For example, "1,5,6,7-10" would produce [1,5,6,7,8,9,10].  "10-5,5,5" would produce [10,9,8,7,6,5,5,5].
// Consecutive commas are permitted, but unterminated ranges are not.
function parseSegmentNotation(segmentStr) {
  var trim = function (str) { return str.replace(/^\s+|\s+$/g, ''); };

  if (segmentStr === null) { throw new Error("argument cannot be null"); }
  if (segmentStr.constructor != String) { throw new Error("argument must be a string"); }

  var pageArray = [];

  // Split string by ranges
  var rangeStrings = segmentStr.split(",");

  // Parse all ranges' numbers
  for (var i = 0; i < rangeStrings.length; i++) {
    var pages = trim(rangeStrings[i]).split("-");
    if (pages.length == 1) {
      var pageNum = parseInt(pages[0], 10);
      if (isNaN(pageNum)) { pageArray.push(pages[0]); }
      else { pageArray.push(pageNum); }
    } else if (pages.length == 2) {
      if (pages[0] === '' || pages[1] === '') { throw new Error("error in segment notation: " + rangeStrings[i]); }
      var begin = parseInt(trim(pages[0]), 10);
      if (isNaN(begin)) { throw new Error("could not parse " + pages[0]); }
      var end = parseInt(trim(pages[1]), 10);
      if (isNaN(end)) { throw new Error("could not parse " + pages[1]); }
      for (var j = begin; j != end; j = j + (begin < end ? 1 : -1)) {
        pageArray.push(j);
      }
      pageArray.push(end);
    } else { throw new Error("error in segment notation: " + rangeStrings[i]); }
  }

  return pageArray;
}

// This state machine implementation performs significantly 
// faster than the string manipulation method above, thought it
// is significantly harder to read.
// (JAS measured a 91% performance increase on the string "2,5,17,24-30,24-30,24-30,24-30,24-30,24-30,24-30,24-30,24-30,24-30,5000-3500")
function parseSegmentNotationOptimized(segmentStr) {
  if (segmentStr === null) { throw new Error("argument cannot be null"); }
  if (segmentStr.constructor !== String) { throw new Error("argument must be a string"); }

  var seekBeginPageStart = 0;
  var seekBeginPageEnd = 1;
  var seekEndPageStart = 2;
  var seekEndPageEnd = 3;
  var parseSinglePage = 4;
  var parsePageRange = 5;
  var parsingError = 10;
  var complete = 20;
  var state = seekBeginPageStart;

  var pageArray = [];
  var beginPageStr = '';
  var endPageStr = '';

  var i = 0;
  while (state != complete) {
    switch (state) {
      case parseSinglePage:
        pageArray.push(parseInt(beginPageStr, 10));
        beginPageStr = '';
        state = (i < segmentStr.length) ? seekBeginPageStart : complete;
        break;

      case parsePageRange:
        var beginPageNum = parseInt(beginPageStr, 10);
        var endPageNum = parseInt(endPageStr, 10);
        for (var j = parseInt(beginPageStr, 10); j != parseInt(endPageStr, 10); j = j + (beginPageNum < endPageNum ? 1 : -1)) {
          pageArray.push(j);
        }
        pageArray.push(endPageNum);
        beginPageStr = endPageStr = '';
        state = (i < segmentStr.length) ? seekBeginPageStart : complete;
        break;

      case parsingError:
        throw new Error("Error parsing string at: " + segmentStr.slice(0, i + 1));

      default:
        if (i >= segmentStr.length) {
          switch (state) {
            case seekBeginPageEnd: state = parseSinglePage; break;
            case seekEndPageEnd: state = parsePageRange; break;
            default: state = parsingError;
          }
        } else {
          var charCode = segmentStr.charCodeAt(i);
          switch (charCode) {
            case 48:
            case 49:
            case 50:
            case 51:
            case 52:
            case 53:
            case 54:
            case 55:
            case 56:
            case 57:
              switch (state) {
                case seekBeginPageStart: state = seekBeginPageEnd; break;
                case seekBeginPageEnd: beginPageStr = beginPageStr + segmentStr[i++]; break;
                case seekEndPageStart: state = seekEndPageEnd; break;
                case seekEndPageEnd: endPageStr = endPageStr + segmentStr[i++]; break;
                default: state = parsingError;
              }
              break;
            case 44:  // ,
              switch (state) {
                case seekBeginPageStart: break;
                case seekBeginPageEnd: state = parseSinglePage; break;
                case seekEndPageEnd: state = parsePageRange; break;
                default: state = parsingError;
              }
              i++;
              break;
            case 45:  // -
              if (state == seekBeginPageEnd) {
                state = seekEndPageStart;
              } else {
                state = parsingError;
              }
              i++;
              break;
            case 32:  // whitespace (space)
            case 9:   // whitespace (tab)
              i++;
              break;
            default: state = parsingError;
          }
        }
    }
  }

  return pageArray;
}

function CreateCslFromArray(array) {
  var csl = (array.length > 0) ? array[0] : '';
  for (var i = 1; i < array.length; i++) {
    csl += ',' + array[i];
  }
  return csl;
}

/************************************************** Custom Tooltip ****************************************************************/
var CustomTooltip = function (element, innerHtml, delay_ms) {
  this.id = 'CustomTooltipElement';
  this.hostElement = element;
  this.innerHtml = innerHtml;
  this.delay = (delay_ms) ? delay_ms : 0;
  this.tooltipElement = this.GetTooltipElement();
  this.AddEvents();
  this.summonTimeout = null;
  this.visible = false;
  this.enabled = true;
  this.onElement = false;
};

CustomTooltip.prototype.GetTooltipElement = function () {
  var tooltipElement = document.getElementById('CustomTooltipElement');
  if (!tooltipElement) {
    tooltipElement = this.CreateTooltipElement();
  }
  return tooltipElement;
};

CustomTooltip.prototype.CreateTooltipElement = function () {
  var tooltip = document.createElement('div');
  tooltip.className = 'Tooltip';
  tooltip.id = this.id;
  tooltip.style.position = 'absolute';
  tooltip.style.top = '0px';
  tooltip.style.left = '0px';

  tooltip.style.overflow = 'hidden';
  tooltip.style.display = 'none';
  tooltip.style.zIndex = 90;
  document.body.appendChild(tooltip);

  return tooltip;
};

CustomTooltip.prototype.AddEvents = function () {
  var _self = this;
  AddEvent(_self.hostElement, 'mouseover', function (ev) { _self.OnMouseOverHost(ev); }, false);
  AddEvent(_self.hostElement, 'mousemove', function (ev) { _self.OnMouseMoveHost(ev); }, false);
  AddEvent(_self.hostElement, 'mouseout', function (ev) { _self.OnMouseOutHost(ev); }, false);
  AddEvent(_self.hostElement, 'click', function (ev) { _self.OnMouseClickHost(ev); }, false);
};

CustomTooltip.prototype.OnMouseOverHost = function (ev) {
  this.onElement = true;
};

CustomTooltip.prototype.OnMouseMoveHost = function (ev) {
  if (!this.onElement || !this.enabled) { this.Dismiss(); }
  clearTimeout(this.summonTimeout);

  var mousePos = mouseCoords(ev);

  if (this.enabled && this.onElement && !this.visible) {
    var _self = this;
    this.summonTimeout = setTimeout(function () { _self.Summon(mousePos); }, this.delay);
  }
};

CustomTooltip.prototype.OnMouseOutHost = function (ev) {
  if (this.summonTimeout !== null) {
    clearTimeout(this.summonTimeout);
    this.summonTimeout = null;
  }
  this.onElement = false;
  this.Dismiss();
};

CustomTooltip.prototype.OnMouseClickHost = function (ev) {
  if (this.summonTimeout !== null) {
    clearTimeout(this.summonTimeout);
    this.summonTimeout = null;
  }
  this.onElement = true;
  this.Dismiss();
};

CustomTooltip.prototype.Summon = function (mousePos) {
  this.LoadContents();
  if (mousePos) { this.PositionOnMouse(mousePos); }
  this.Show();
  this.visible = true;
};

CustomTooltip.prototype.Dismiss = function () {
  this.Hide();
  this.PositionOnOrigin();
  this.ClearContents();
  this.visible = false;
};

CustomTooltip.prototype.LoadContents = function (contents) {
  if (contents) { this.innerHtml = contents; }
  this.tooltipElement.innerHTML = this.innerHtml;
};

CustomTooltip.prototype.ClearContents = function () {
  this.tooltipElement.innerHTML = '';
};

CustomTooltip.prototype.PositionOnMouse = function (mousePos) {
  var windowPos = windowCoords();
  var windowCenterX = windowPos.scrollX + windowPos.width / 2;
  var windowCenterY = windowPos.scrollY + windowPos.height / 2;

  var tooltipLeft = this.tooltipElement.offsetLeft;
  var tooltipTop = this.tooltipElement.offsetTop;

  var visibility = this.tooltipElement.style.visibility;
  var display = this.tooltipElement.style.display;
  this.tooltipElement.style.visibility = 'hidden';
  this.tooltipElement.style.display = 'block';
  this.tooltipElement.style.left = '0px';
  this.tooltipElement.style.top = '0px';

  var tooltipWidth = this.tooltipElement.offsetWidth;
  var tooltipHeight = this.tooltipElement.offsetHeight;

  if (mousePos.x > windowCenterX) {
    tooltipLeft = mousePos.x - 5 - tooltipWidth;
  } else {
    tooltipLeft = mousePos.x + 15;
  }

  if (mousePos.y > windowCenterY) {
    tooltipTop = mousePos.y - 5 - tooltipHeight;
  } else {
    tooltipTop = mousePos.y + 15;
  }

  this.tooltipElement.style.display = display;
  this.tooltipElement.style.visibility = visibility;

  this.tooltipElement.style.left = tooltipLeft + 'px';
  this.tooltipElement.style.top = tooltipTop + 'px';
};

CustomTooltip.prototype.PositionOnOrigin = function () {
  this.tooltipElement.style.left = '0px';
  this.tooltipElement.style.top = '0px';
};

CustomTooltip.prototype.Show = function () {
  $('#' + this.id).fadeIn('slow');
  //this.tooltipElement.style.display = 'block';
};

CustomTooltip.prototype.Hide = function () {
  $('#' + this.id).hide();
  //this.tooltipElement.style.display = 'none';
};

CustomTooltip.prototype.Disable = function () {
  this.enabled = false;
};

CustomTooltip.prototype.Enable = function () {
  this.enabled = true;
};

/******************************************** End Custom Tooltip ******************************************************/

/******************************************** CustomBorder ************************************************************/

function CustomBorder(baseID, baseClassName, targetElement, isOverlay, beamThickness) {
  var error;
  if (baseID === null) { error = new Error('"baseID" may not be null in CustomBorder().'); }
  if (baseClassName === null) { error = new Error('"baseClassName" may not be null in CustomBorder().'); }
  if (targetElement === null) { error = new Error('"targetElement" may not be null in CustomBorder().'); }
  if (isOverlay === null) { error = new Error('"isOverlay" may not be null in CustomBorder().'); }
  if (beamThickness === null) { error = new Error('"beamThickness" may not be null in CustomBorder().'); }
  if (error) {
    var errorMessage = 'An error occurred on "' + error.fileName + '" (line #' + error.lineNumber + '). ' + error.message;
    throw new Error(errorMessage);
  }

  this.id = baseID;
  this.baseClassName = baseClassName;
  this.targetElement = targetElement;
  this.isOverlay = isOverlay;

  this.parentElement = (isOverlay) ? targetElement.parentNode : targetElement;
  this.beamThickness = beamThickness;

  this.leftElement = null;
  this.topElement = null;
  this.rightElement = null;
  this.bottomElement = null;

  this.Build();
  this.AttachToParent();
  this.FitToParent();
}

CustomBorder.prototype.Build = function () {
  this.leftElement = this.BuildLeft();
  this.topElement = this.BuildTop();
  this.rightElement = this.BuildRight();
  this.bottomElement = this.BuildBottom();
};

CustomBorder.prototype.Destroy = function () {
  this.RemoveFromParent();
  this.leftElement = null;
  this.topElement = null;
  this.rightElement = null;
  this.bottomElement = null;
};

CustomBorder.prototype.AttachToParent = function () {
  this.parentElement.appendChild(this.leftElement);
  this.parentElement.appendChild(this.topElement);
  this.parentElement.appendChild(this.rightElement);
  this.parentElement.appendChild(this.bottomElement);
};

CustomBorder.prototype.RemoveFromParent = function () {
  this.parentElement.removeChild(this.leftElement);
  this.parentElement.removeChild(this.topElement);
  this.parentElement.removeChild(this.rightElement);
  this.parentElement.removeChild(this.bottomElement);
};

CustomBorder.prototype.CreateBeam = function (type) {
  var beam = document.createElement('div');
  beam.className = this.baseClassName + ' ' + this.baseClassName + type;

  beam.id = this.id + type;
  beam.style.position = 'absolute';

  return beam;
};

CustomBorder.prototype.BuildLeft = function () {
  return this.CreateBeam('Left');
};

CustomBorder.prototype.BuildTop = function () {
  return this.CreateBeam('Top');
};

CustomBorder.prototype.BuildRight = function () {
  return this.CreateBeam('Right');
};

CustomBorder.prototype.BuildBottom = function () {
  return this.CreateBeam('Bottom');
};

CustomBorder.prototype.FitToParent = function () {
  this.FitToParentLeft();
  this.FitToParentTop();
  this.FitToParentRight();
  this.FitToParentBottom();
};

CustomBorder.prototype.GetTargetLocation = function () {
  var left = 0;
  var top = 0;
  if (this.isOverlay) {
    left = this.targetElement.offsetLeft;
    top = this.targetElement.offsetTop;
  }
  return {
    left: left,
    top: top
  };
};

CustomBorder.prototype.FitToParentLeft = function () {
  var targetLoc = this.GetTargetLocation();
  this.leftElement.style.width = this.beamThickness + 'px';
  this.leftElement.style.height = this.targetElement.offsetHeight + 'px';
  this.leftElement.style.left = (targetLoc.left - this.beamThickness) + 'px';
  this.leftElement.style.top = targetLoc.top + 'px';
};

CustomBorder.prototype.FitToParentTop = function () {
  var targetLoc = this.GetTargetLocation();
  this.topElement.style.width = this.targetElement.offsetWidth + 'px';
  this.topElement.style.height = this.beamThickness + 'px';
  this.topElement.style.left = targetLoc.left + 'px';
  this.topElement.style.top = (targetLoc.top - this.beamThickness) + 'px';
};

CustomBorder.prototype.FitToParentRight = function () {
  var targetLoc = this.GetTargetLocation();
  this.rightElement.style.width = this.beamThickness + 'px';
  this.rightElement.style.height = this.targetElement.offsetHeight + 'px';
  this.rightElement.style.left =
    (targetLoc.left + this.targetElement.offsetWidth) + 'px';
  this.rightElement.style.top = targetLoc.top + 'px';
};

CustomBorder.prototype.FitToParentBottom = function () {
  var targetLoc = this.GetTargetLocation();
  this.bottomElement.style.width = this.targetElement.offsetWidth + 'px';
  this.bottomElement.style.height = this.beamThickness + 'px';
  this.bottomElement.style.left = targetLoc.left + 'px';
  this.bottomElement.style.top =
    (targetLoc.top + this.targetElement.offsetHeight) + 'px';
};

/******************************************** End CustomBorder *******************************************************/

// Converts each integer in an array into a boolean value at the index value. false is the default value.
// ConvertIndexArrayToBooleanArray([1,2,3,6], 8); returns [false,true,true,true,false,false,true,false]
//                                                                  1,   2,   3,           ,   6
function ConvertIndexArrayToBooleanArray(indexArray, booleanArrayLength) {
  var booleanArray = [](booleanArrayLength);
  for (var i = 0; i < indexArray.length; i++) {
    booleanArray[indexArray[i]] = true;
  }
  return booleanArray;
}

// Adds an ordinal extension to any number (e.g. 21 to 21st, 11 to 11th).
function AddOrdinalExtension(number) {
  var numberWithOE = number + 'th';
  var firstThreeMod = number % 10;
  var exceptionMod = number % 100;
  if ((exceptionMod != 11) && (exceptionMod != 12) && (exceptionMod != 13)) {
    if (firstThreeMod == 1) { numberWithOE = number + 'st'; }
    if (firstThreeMod == 2) { numberWithOE = number + 'nd'; }
    if (firstThreeMod == 3) { numberWithOE = number + 'rd'; }
  }
  return numberWithOE;
}

// Determines if an image is cached by the browser.
function IsImageCached(url) {
  var image = new Image(url);
  image.scr = url;
  return image.complete;
}


/************************************************ Start HMAC SHA1 **********************************************************/

/*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS PUB 180-1
* Version 2.1a Copyright Paul Johnston 2000 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for details.
*/

/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_sha1(s) { return binb2hex(core_sha1(str2binb(s), s.length * chrsz)); }
function b64_sha1(s) { return binb2b64(core_sha1(str2binb(s), s.length * chrsz)); }
function str_sha1(s) { return binb2str(core_sha1(str2binb(s), s.length * chrsz)); }
function hex_hmac_sha1(key, data) { return binb2hex(core_hmac_sha1(key, data)); }
function b64_hmac_sha1(key, data) { return binb2b64(core_hmac_sha1(key, data)); }
function str_hmac_sha1(key, data) { return binb2str(core_hmac_sha1(key, data)); }

/*
* Perform a simple self-test to see if the VM is working
*/
function sha1_vm_test() {
  return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
}

/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
function core_sha1(x, len) {
  /* append padding */
  x[len >> 5] |= 0x80 << (24 - len % 32);
  x[((len + 64 >> 9) << 4) + 15] = len;

  var w = Array(80);
  var a = 1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d = 271733878;
  var e = -1009589776;

  for (var i = 0; i < x.length; i += 16) {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;
    var olde = e;

    for (var j = 0; j < 80; j++) {
      if (j < 16) { w[j] = x[i + j]; }
      else { w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1); }
      var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
                       safe_add(safe_add(e, w[j]), sha1_kt(j)));
      e = d;
      d = c;
      c = rol(b, 30);
      b = a;
      a = t;
    }

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
    e = safe_add(e, olde);
  }
  return Array(a, b, c, d, e);

}

/*
* Perform the appropriate triplet combination function for the current
* iteration
*/
function sha1_ft(t, b, c, d) {
  if (t < 20) { return (b & c) | ((~b) & d); }
  if (t < 40) { return b ^ c ^ d; }
  if (t < 60) { return (b & c) | (b & d) | (c & d); }
  return b ^ c ^ d;
}

/*
* Determine the appropriate additive constant for the current iteration
*/
function sha1_kt(t) {
  return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
         (t < 60) ? -1894007588 : -899497514;
}

/*
* Calculate the HMAC-SHA1 of a key and some data
*/
function core_hmac_sha1(key, data) {
  var bkey = str2binb(key);
  if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * chrsz); }

  var ipad = Array(16), opad = Array(16);
  for (var i = 0; i < 16; i++) {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
  return core_sha1(opad.concat(hash), 512 + 160);
}

/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y) {
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
* Bitwise rotate a 32-bit number to the left.
*/
function rol(num, cnt) {
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
* Convert an 8-bit or 16-bit string to an array of big-endian words
* In 8-bit function, characters >255 have their hi-byte silently ignored.
*/
function str2binb(str) {
  var bin = Array();
  var mask = (1 << chrsz) - 1;
  for (var i = 0; i < str.length * chrsz; i += chrsz) {
    bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i % 32);
  }
  return bin;
}

/*
* Convert an array of big-endian words to a string
*/
function binb2str(bin) {
  var str = "";
  var mask = (1 << chrsz) - 1;
  for (var i = 0; i < bin.length * 32; i += chrsz) {
    str += String.fromCharCode((bin[i >> 5] >>> (32 - chrsz - i % 32)) & mask);
  }
  return str;
}

/*
* Convert an array of big-endian words to a hex string.
*/
function binb2hex(binarray) {
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for (var i = 0; i < binarray.length * 4; i++) {
    str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) +
           hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF);
  }
  return str;
}

/*
* Convert an array of big-endian words to a base-64 string
*/
function binb2b64(binarray) {
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for (var i = 0; i < binarray.length * 4; i += 3) {
    var triplet = (((binarray[i >> 2] >> 8 * (3 - i % 4)) & 0xFF) << 16)
                | (((binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4)) & 0xFF) << 8)
                | ((binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4)) & 0xFF);
    for (var j = 0; j < 4; j++) {
      if (i * 8 + j * 6 > binarray.length * 32) { str += b64pad; }
      else { str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F); }
    }
  }
  return str;
}

/************************************************ End of HMAC SHA1 **********************************************************/

// Adds a query string to a url using an appropriate separator.
function AddQueryStringToUrl(url, queryString) {
  var urlSeparator = '';
  if (queryString !== '') {
    queryString = queryString.replace('?', '', 'g');
    if (url.indexOf('?') == -1) {
      urlSeparator = '?';
    } else {
      urlSeparator = '&';
    }
  }
  return url + urlSeparator + queryString;
}

function AddKeyValuePairsToUrlQueryString(url, keyArray, valueArray) {
  var keyValuePair = keyArray[0] + '=' + valueArray[0];
  url = AddQueryStringToUrl(url, keyValuePair);
  if ((keyArray.length > 1) && (valueArray.length > 1)) {
    url = AddKeyValuePairsToUrlQueryString(url, keyArray.slice(1), valueArray.slice(1));
  }
  return url;
}

// From: http://frogsbrain.wordpress.com/2007/04/28/javascript-stringformat-method/ 
String.format = function (text) {
  //check if there are two arguments in the arguments list
  if (arguments.length <= 1) {
    //if there are not 2 or more arguments there’s nothing to replace
    //just return the original text
    return text;
  }
  //decrement to move to the second argument in the array
  var tokenCount = arguments.length - 2;
  for (var token = 0; token <= tokenCount; token++) {
    //iterate through the tokens and replace their placeholders from the original text in order
    text = text.replace(new RegExp('\\{' + token + '\\}', 'gi'), arguments[token + 1]);
  }
  return text;
};

// Finds each replacementObject property name in the string parameter and replaces it with the property value.
// For example:
// var text = 'I will {verb} the {fruit}.';
// var replacementObject = { verb: 'smite', fruit: 'watermelon' }
// String.extendedReplace(text, replacementObject) will return 'I will smite the watermelon.'
String.extendedReplace = function (text, replacementObject) {
  for (var property in replacementObject) {
    if (property) {
      text = text.replace('{' + property + '}', replacementObject[property]);
    }
  }
  return text;
};

var MonthArray = ["January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December"];

// month is one-based
function CreateJSDateFromString(dateString) {
  var dateArray = dateString.split(',');
  var date = new Date();
  date.setFullYear(dateArray[0], dateArray[1] - 1, dateArray[2]);
  return date;
}

Object.checkProperties = function (objectToCheck, objectVariableName, propertyNullExceptionList, illegalValues) {
  if (!illegalValues) {
    illegalValues = [undefined, null, Infinity];
  }
  if (!propertyNullExceptionList) {
    propertyNullExceptionList = [];
  }
  for (var property in objectToCheck) {
    var propertyValue = objectToCheck[property];
    if ((typeof propertyValue == 'object')) {
      Object.checkProperties(propertyValue, objectVariableName + '.' + property, propertyNullExceptionList, illegalValues);
    } else {
      for (var i = 0; i < illegalValues.length; i++) {
        var illegalValue = illegalValues[i];
        if ((illegalValue === null) && (propertyNullExceptionList.indexOf(property) != -1)) {
          // Skip check because the property is in the propertyNullExceptionList.
        } else {
          // Invalid property value
          if (propertyValue === illegalValue) {
            throw new Error('The "' + property + '" property in "' + objectVariableName + '" is "' + illegalValue + '".');
          }
        }
      }
    }
  }
};

Object.removeFunctions = function (object) {
  var newObject = {};
  for (var property in object) {
    if (property) {
      var type = typeof (object[property]);
      if (type != 'function') {
        newObject[property] = object[property];
      }
    }
  }
  return newObject;
};

Object.getPropertiesFromValue = function (object, id) {
  var properties = [];
  for (var property in object) {
    if (object[property] == id) {
      properties.push(property);
    }
  }
  return properties;
};

var GetTopLevelDomains = function () {
  var hostName = window.location.host || window.location.hostname;
  var hostHeaderParts = hostName.split('.');
  var topDomains = '.' + hostHeaderParts[hostHeaderParts.length - 2] + '.' +
    hostHeaderParts[hostHeaderParts.length - 1];
  return topDomains;
}

String.prototype.trim = function () {
  return this.replace(/^\s*/, "").replace(/\s*$/, "");
}

var RGB2Hex = function (rgb) {
  if (rgb) {
    rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
    if (rgb == null) {
      return rgb;
    }
    function hex(x) {
      return ("0" + parseInt(x).toString(16)).slice(-2);
    };
    return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
  } else {
    return null;
  }
};

var ExclusiveOR = function (a, b) {
  return (a || b) && !(a && b);
};

// From https://gist.github.com/738720
var browserSupports3DTransforms = function () {
  // borrowed from modernizr
  var div = document.createElement('div');
  var ret = false;
  properties = ['perspectiveProperty', 'WebkitPerspective'];
  for (var i = properties.length - 1; i >= 0; i--) {
    ret = ret ? ret : div.style[properties[i]] != undefined;
  };

  // webkit has 3d transforms disabled for chrome, though
  // it works fine in safari on leopard and snow leopard
  // as a result, it 'recognizes' the syntax and throws a false positive
  // thus we must do a more thorough check:
  if (ret) {
    var st = document.createElement('style');
    // webkit allows this media query to succeed only if the feature is enabled.
    // "@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-ms-transform-3d),(-webkit-transform-3d),(modernizr){#modernizr{height:3px}}"
    st.textContent = '@media (-webkit-transform-3d){#test3d{height:3px}}';
    document.getElementsByTagName('head')[0].appendChild(st);
    div.id = 'test3d';
    document.body.appendChild(div);

    ret = div.offsetHeight === 3;

    st.parentNode.removeChild(st);
    div.parentNode.removeChild(div);
  }
  return ret;
};

var objectToString = function (anyOleObject) {
  var touchProperties = '';
  for (var property in anyOleObject) {
    touchProperties += property + ': ' + anyOleObject[property] + ', ';
  }
  return touchProperties;
};

Math.roundToLength = function (value, length) {
  if (length > 0) {
    var falseValue = Math.round(value * 10 * length);
    return falseValue / (10 * length);
  } else {
    return Math.round(value);
  }
};
