/**
 * Toolkit version of a DOM Element to be used for non browser environments
 * @constructor
 * @param {String} name The element tag name
 * @export
 */
var DvtElement = function(name) {
  this.Init(name);
};

dvt.Obj.createSubclass(DvtElement, dvt.Obj);

/**
 * Creates a new element with the given tag name and namespace URI
 * @param {String} name The element tag name
 */
DvtElement.prototype.Init = function(name) {
  this.nodeName = name;
  this.firstChild = null;
  this.parentNode = null;
  this.nextSibling = null;
  this.previousSibling = null;
  this.attributes = {};
  this.childNodes = [];
  this.childElementCount = 0;
  this.nodeValue = null;
};

/**
 * Adds an event listener to this element
 * @param {string} type type of event
 * @param {function} listener the listener function
 * @param {boolean} useCapture true if the listener operates in the capture phase, false otherwise
 * @export
 */
DvtElement.prototype.addEventListener = function(type, listener, useCapture) {
  document.addListenerCount++;
};


/**
 * Removes an event listener from this element
 * @param {string} type type of event
 * @param {function} listener the listener function
 * @param {boolean} useCapture true if the listener operates in the capture phase, false otherwise
 * @export
 */
DvtElement.prototype.removeEventListener = function(type, listener, useCapture) {
  document.removeListenerCount++;
};


/**
 * Insert node as the last child of this element
 * @param {DvtElement} child The node to insert
 * @export
 */
DvtElement.prototype.appendChild = function(child) {
  document.appendChildCount++;
  if (child) {
    // remove child from old parent
    if (child.parentNode)
      child.parentNode.removeChild(child);
    // check if child is a current child
    var idx = dvt.ArrayUtils.getIndex(this.childNodes, child);
    if (idx != -1) {
      this.childNodes.splice(idx, 1);
      // Temporarily decrement element count
      this.childElementCount--;
    }

    child.parentNode = this;
    this.childNodes.push(child);
    // Increment element count
    this.childElementCount++;
    this._setFamilyTree();
  }
};


/**
 * Returns a duplicate of the node on which this mehtod was called
 * @param {boolean} deep True if the children of the node should also be cloned or false to clone only the specified node
 * @return {DvtElement} The cloned node
 * @export
 */
DvtElement.prototype.cloneNode = function(deep) {
  var clone = new DvtElement(this.nodeName);
  for (var name in this.attributes)
    clone.attributes[name] = this.attributes[name];

  if (deep) {
    for (var i = 0; i < this.childNodes.length; i++) {
      var childClone = this.childNodes[i].cloneNode(deep);
      clone.appendChild(childClone);//@HTMLUpdateOK
    }
  }
  return clone;
};


/**
 * Dispatches an event
 * @param {dvt.Obj} event The event to dispatch
 * @export
 */
DvtElement.prototype.dispatchEvent = function(event) {
  //No-Op
};


/**
 * Retrieve the value of the attribute with the specified name and namespace from the current node.  Our implementation
 * returns an object that == null when the attribute is not set.
 * @param {String} namespace The namespace uri
 * @param {String} name The attribute name
 * @return {String} The attribute value
 * @export
 */
DvtElement.prototype.getAttributeNS = function(namespace, name) {
  document.getAttrCount++;
  var value = this.attributes[name];
  // coerce non-null values to strings to better reflect browser DOM behavior
  return value == null ? value : ('' + value);
};

/**
 * Retrieve the value of the attribute with the specified name from the current node.  Our implementation
 * returns an object that == null when the attribute is not set.
 * @param {String} name The attribute name
 * @return {String} The attribute value
 * @export
 */
DvtElement.prototype.getAttribute = function(name) {
  return this.getAttributeNS(null, name);
};

/**
 * Returns the bounding box, in current user space, of the geometry of all contained graphics elements
 * @return {Object} The bounding box object with attributes x, y, width, and height
 * @export
 */
DvtElement.prototype.getBBox = function() {
  document.getBBoxCount++;

  // Keep track of the object types that are being measured.
  var type = this._obj.getTypeName();

  if (type) {
    // Strip any remaining $, which replace . in the closure compiler
    type = type.replace(/[0-9\$]+/g, '');

    // Make the first letter uppercase
    type = type.charAt(0).toUpperCase() + type.slice(1);
  }
  else
    type = 'unknown';

  if (!document.bboxInfo[type])
    document.bboxInfo[type] = 1;
  else
    document.bboxInfo[type]++;

  return DvtJavaDimensionUtils.getDimensions(this._obj);
};


/**
 * Returns true if the attribute with the specified name and namespace has been set on the current element.
 * @param {String} namespace The namespace uri
 * @param {String} name The attribute name
 * @return {boolean}
 * @export
 */
DvtElement.prototype.hasAttributeNS = function(namespace, name) {
  // Reusing the get call since the performance impact of hasAttributeNS is not clear, but it should be faster than get.
  // Technically getAttributeNS would return null or "" if not set, but our implementation guarentees == null.
  return this.getAttributeNS(namespace, name) != null;
};


/**
 * Insert the specified node before a reference element as a child of the current node
 * @param {DvtElement} newChild The element to insert
 * @param {DvtElement} referenceChild The reference element
 * @export
 */
DvtElement.prototype.insertBefore = function(newChild, referenceChild) {
  var index = dvt.ArrayUtils.getIndex(this.childNodes, referenceChild);
  if (index > - 1) {
    // remove child from old parent
    if (newChild.parentNode)
      newChild.parentNode.removeChild(newChild);
    // check if child is a current child
    var childIdx = dvt.ArrayUtils.getIndex(this.childNodes, newChild);
    if (childIdx != -1) {
      this.childNodes.splice(childIdx, 1);
      // Temporarily decrement element count
      this.childElementCount--;
    }

    newChild.parentNode = this;
    // index could have changed now
    this.childNodes.splice(dvt.ArrayUtils.getIndex(this.childNodes, referenceChild), 0, newChild);
    // Increment element count
    this.childElementCount++;
    this._setFamilyTree();
  }
  else {
    // if the reference element doesn't exist in the current list of children, insert new element at the end
    this.appendChild(newChild);//@HTMLUpdateOK
  }
};


/**
 * Removes a child from the current element, which must be a child of the current node
 * @param {DvtElement} child The child node to remove
 * @export
 */
DvtElement.prototype.removeChild = function(child) {
  document.removeChildCount++;
  var index = dvt.ArrayUtils.getIndex(this.childNodes, child);
  if (index != - 1) {
    child.parentNode = null;
    this.childNodes.splice(index, 1);
    // Decrement element count
    this.childElementCount--;
    this._setFamilyTree();
  }
};


/**
 * Removes the attribute with the specified name and namespace from the current node
 * @param {String} namespace The namespace uri
 * @param {String} name The attribute name
 * @export
 */
DvtElement.prototype.removeAttributeNS = function(namespace, name) {
  document.setAttrCount++;
  delete this.attributes[name];
};


/**
 * Replaces one child node in the specified element with another
 * @param {DvtElement} newChild The new node to replace oldChild
 * @param {DvtElement} oldChild The existing child to be replaced
 * @export
 */
DvtElement.prototype.replaceChild = function(newChild, oldChild) {
  var index = dvt.ArrayUtils.getIndex(this.childNodes, oldChild);
  if (index > - 1) {
    // remove child from old parent
    if (newChild.parentNode)
      newChild.parentNode.removeChild(newChild);
    // check if child is a current child
    var childIdx = dvt.ArrayUtils.getIndex(this.childNodes, newChild);
    if (childIdx != -1) {
      this.childNodes.splice(childIdx, 1);
      // Decrement element count
      this.childElementCount--;
    }

    newChild.parentNode = this;
    oldChild.parentNode = null;
    // index could have changed
    this.childNodes.splice(dvt.ArrayUtils.getIndex(this.childNodes, oldChild), 1, newChild);  // This does NOT change childElementCount
    this._setFamilyTree();
  }
};

/**
 * Set the value of the attribute with the specified name and namespace from the current node
 * @param {String} namespace The namespace uri
 * @param {String} name The attribute name
 * @param {String} value The attribute value
 * @export
 */
DvtElement.prototype.setAttributeNS = function(namespace, name, value) {
  document.setAttrCount++;
  DvtDocument.setAttrMap[name] = DvtDocument.setAttrMap[name] ? DvtDocument.setAttrMap[name] + 1 : 1;
  this.attributes[name] = value;
};

/**
 * Set the value of the attribute with the specified name from the current node
 * @param {String} name The attribute name
 * @param {String} value The attribute value
 * @export
 */
DvtElement.prototype.setAttribute = function(name, value) {
  this.setAttributeNS(null, name, value);
};

/**
 * Returns the svg string representation of this object and its attributes
 * @return {String} The svg string
 * @export
 */
DvtElement.prototype.toString = function() {
  var svg = '<' + this.nodeName;
  var sortedAttrs = this._getSortedSvgAttributes();
  for (var i = 0; i < sortedAttrs.length; i++) {
    var element = sortedAttrs[i];
    var value = this.attributes[element];
    if (value != null && typeof value != 'undefined') {
      if (typeof value == 'number')
        value = dvt.ToolkitUtils.roundDecimal(value);
      svg += ' ' + element + '="' + value + '"';
    }
  }
  svg += '>';

  for (var j = 0; j < this.childNodes.length; j++)
    svg += this.childNodes[j].toString();

  svg += '<\/' + this.nodeName + '>';
  return svg;
};


/**
 * Goes thru the list of child nodes and sets the sibling pointers for the child nodes and updates the first and last
 * child pointers on this node
 * @private
 */
DvtElement.prototype._setFamilyTree = function() {
  var numChildren = this.childNodes.length;
  if (numChildren > 0) {
    for (var i = 0; i < numChildren; i++) {
      var child = this.childNodes[i];
      if (i == 0)
        child.previousSibling = null;
      if (i > 0)
        child.previousSibling = this.childNodes[i - 1];
      if (i < numChildren - 1)
        child.nextSibling = this.childNodes[i + 1];
      if (i == numChildren - 1)
        child.nextSibling = null;
    }
    this.firstChild = this.childNodes[0];
    this.lastChild = this.childNodes[numChildren - 1];
  } else {
    this.firstChild = null;
    this.lastChild = null;
  }
};


/**
 * Returns an array of sorted Svg attributes. Necessary to prevent SRG breakages due to different attribute orderings.
 * (Bug 16298373)
 * @return {Array} An array of the Svg attributes, sorted alphabetically
 * @private
 */
DvtElement.prototype._getSortedSvgAttributes = function() {
  var attrs = [];
  // Because we use Java for text measurement and do some internal conversions, we need to remove the font-size
  // ending before writing out the string
  if (this.attributes[dvt.CSSStyle.FONT_SIZE]) {
    var numVal = parseFloat(this.attributes[dvt.CSSStyle.FONT_SIZE]);
    this.attributes[dvt.CSSStyle.FONT_SIZE] = (isNaN(numVal) ? CSSStyleUtils.parseFontSize(this.attributes[dvt.CSSStyle.FONT_SIZE]) : numVal);
  }

  for (var element in this.attributes) {
    if (element == dvt.CSSStyle.FONT_FAMILY)
      this.attributes[dvt.CSSStyle.FONT_FAMILY] = this.attributes[dvt.CSSStyle.FONT_FAMILY].replace(/"/g, "'");

    // do not add attributes that batik does not support
    if (dvt.Agent.isEnvironmentBatik()) {
      if (element == 'dominant-baseline') {
        // Adjust based on font-size, which may be specified on a parent.
        var fontSize;
        var currentNode = this;
        while (fontSize == null && currentNode != null) {
          fontSize = currentNode.attributes[dvt.CSSStyle.FONT_SIZE];
          currentNode = currentNode.parentNode;
        }

        if (fontSize) {
          var dominantBaseline = this.attributes[element];
          var relativeShift;
          switch (dominantBaseline) {
            case 'text-before-edge' :
              relativeShift = -1;
              break;
            case 'middle' :
              relativeShift = -.25;
              break;
            case 'text-after-edge' :
              relativeShift = -.15;
              break;
            default:
              relativeShift = 1;
          }
          if (!this.attributes['baseline-shift'])
            attrs.push('baseline-shift');
          this.attributes['baseline-shift'] = relativeShift * fontSize;
        }
      }
      else if (element.indexOf('aria') == -1 && element != 'vector-effect') {
        attrs.push(element);
      }
    } else {
      attrs.push(element);
    }
  }
  return attrs.sort();
};

var DvtTextNode = function(text) {
  this.nodeName = '#text';
  this.nodeValue = text;
};

dvt.Obj.createSubclass(DvtTextNode, dvt.Obj);

/**
 * Returns the svg string representation of this object and its attributes
 * @return {String} The svg string
 * @export
 */
DvtTextNode.prototype.toString = function() {
  return this.nodeValue;//@HtmlUpdateOK
};
/**
 * Toolkit version of a DOM Document to be used for non browser environments
 * @constructor
 * @export
 */
var DvtDocument = function() {
  this.documentElement = {};
  this.getAttrCount = 0;
  this.setAttrCount = 0;
  this.getBBoxCount = 0;
  this.appendChildCount = 0;
  this.removeChildCount = 0;
  this.addListenerCount = 0;
  this.removeListenerCount = 0;
  this.shapeContainerCount = 0;
  this.bboxInfo = {};
};

dvt.Obj.createSubclass(DvtDocument, dvt.Obj);

/**
 * For internal performance tracking only.
 */
DvtDocument.setAttrMap = {};

/**
 * @export
 */
DvtDocument.prototype.appendChild = function(elem) {
  // noop: This exists for unit test purposes only.
};


/**
 * Creates a new element with the given tag name
 * @param {String} name The element tag name
 * @return {DvtElement} The element with the given tag name
 * @export
 */
DvtDocument.prototype.createElement = function(name) {
  var elem = new DvtElement(name);
  if (name == 'svg') {
    dvt.ToolkitUtils.setAttrNullNS(elem, 'xmlns', dvt.ToolkitUtils.SVG_NS);
    dvt.ToolkitUtils.setAttrNullNS(elem, 'xmlns:xlink', dvt.ToolkitUtils.XLINK_NS);
  }
  return elem;
};


/**
 * Creates a new element with the given tag name and namespace URI
 * @param {String} namespace The element namespace URI
 * @param {String} name The element tag name
 * @return {DvtElement} The new element
 * @export
 */
DvtDocument.prototype.createElementNS = function(namespace, name) {
  return this.createElement(name);
};


/**
 * Creates a new text node
 * @param {String} text The data to be put in the text node
 * @return {String} The new text node
 * @export
 */
DvtDocument.prototype.createTextNode = function(text) {
  return new DvtTextNode(text);
};


/**
 * Returns the element visible at the specified coordinates
 * @param {Number} x The x coordinate
 * @param {Number} y The y coordinate
 * @param {DvtElement} The visible element
 * @export
 */
DvtDocument.prototype.elementFromPoint = function(x, y) {
  // TODO if we want to support interactivity
  return null;
};


/**
 * Returns the element with the specified id
 * @param {String} id The element id
 * @return {DvtElement} The element with the given id
 * @export
 */
DvtDocument.prototype.getElementById = function(id) {
  // TODO if we want to support interactivity
  return null;
};


/**
 * Returns the first element within the document (using depth-first pre-order traversal of the document's nodes) that
 * matches the specified group of selectors.
 * @param {string} selectors A string containing one or more CSS selectors separated by commas.
 * @return {Element}
 * @export
 */
DvtDocument.prototype.querySelector = function(selectors) {
  return null; // stub
};


/**
 * Returns the DOM API call counts in html string form
 * @return {String} The html string for the DOM API counts
 * @export
 */
DvtDocument.prototype.getPerformanceCounts = function() {
  // Print BBox characteristics
  var ret = '';
  var keys = [];
  if (this.getBBoxCount > 0) {
    ret += '<br>getBBox: ' + this.getBBoxCount;
    for (var key in this.bboxInfo) {
      keys.push(key);
    }
    keys.sort();
    for (var i = 0; i < keys.length; i++) {
      ret += '<br>' + keys[i] + ': ' + this.bboxInfo[keys[i]];
    }
  }

  ret += '<br>';
  ret += '<br>get/setAttributeNS: ' + this.getAttrCount + '/' + this.setAttrCount;
  ret += '<br>append/removeChild: ' + this.appendChildCount + '/' + this.removeChildCount;
  ret += '<br>add/removeEventListener: ' + this.addListenerCount + '/' + this.removeListenerCount;

  // Track shape container info until all removed
  if (this.shapeContainerCount > 0)
    ret += '<br>shapeContainerCount: ' + this.shapeContainerCount;

  // Add to the global counts
  DvtDocument.instanceCount = DvtDocument.instanceCount ? DvtDocument.instanceCount + 1 : 1;
  DvtDocument.getBBoxCount = DvtDocument.getBBoxCount ? DvtDocument.getBBoxCount + this.getBBoxCount : this.getBBoxCount;
  DvtDocument.getAttrCount = DvtDocument.getAttrCount ? DvtDocument.getAttrCount + this.getAttrCount : this.getAttrCount;
  DvtDocument.setAttrCount = DvtDocument.setAttrCount ? DvtDocument.setAttrCount + this.setAttrCount : this.setAttrCount;
  DvtDocument.appendChildCount = DvtDocument.appendChildCount ? DvtDocument.appendChildCount + this.appendChildCount : this.appendChildCount;
  DvtDocument.removeChildCount = DvtDocument.removeChildCount ? DvtDocument.removeChildCount + this.removeChildCount : this.removeChildCount;
  DvtDocument.addListenerCount = DvtDocument.addListenerCount ? DvtDocument.addListenerCount + this.addListenerCount : this.addListenerCount;
  DvtDocument.removeListenerCount = DvtDocument.removeListenerCount ? DvtDocument.removeListenerCount + this.removeListenerCount : this.removeListenerCount;
  DvtDocument.shapeContainerCount = DvtDocument.shapeContainerCount ? DvtDocument.shapeContainerCount + this.shapeContainerCount : this.shapeContainerCount;

  for (var key in this.bboxInfo) {
    if (!DvtDocument.bboxInfo)
      DvtDocument.bboxInfo = {};

    if (!DvtDocument.bboxInfo[key])
      DvtDocument.bboxInfo[key] = 0;

    DvtDocument.bboxInfo[key] += this.bboxInfo[key];
  }

  return ret;
};


/**
 * Returns the total DOM API call counts in html string form.  The counts are reset after this function is called.
 * @return {String} The html string for the DOM API counts
 * @export
 */
DvtDocument.prototype.getTotalPerformanceCounts = function() {
  var ret = '<br>Instances Tested: ' + DvtDocument.instanceCount;

  // Print BBox characteristics
  ret += '<br><br>getBBox Total: ' + DvtDocument.getBBoxCount;
  var keys = [];
  for (var key in DvtDocument.bboxInfo) {
    keys.push(key);
  }
  keys.sort();
  for (var i = 0; i < keys.length; i++) {
    ret += '<br>' + keys[i] + ': ' + DvtDocument.bboxInfo[keys[i]];
  }

  // Print get/setAttr characteristics
  ret += '<br><br>get/setAttributeNS: ' + DvtDocument.getAttrCount + '/' + DvtDocument.setAttrCount;
  var keys = [];
  for (var key in DvtDocument.setAttrMap) {
    keys.push(key);
  }
  keys.sort();
  for (var i = 0; i < keys.length; i++) {
    ret += '<br>' + keys[i] + ': ' + DvtDocument.setAttrMap[keys[i]];
  }

  ret += '<br><br>append/removeChild: ' + DvtDocument.appendChildCount + '/' + DvtDocument.removeChildCount;
  ret += '<br>add/removeEventListener: ' + DvtDocument.addListenerCount + '/' + DvtDocument.removeListenerCount;

  // Track shape container info until all removed
  if (DvtDocument.shapeContainerCount > 0)
    ret += '<br>shapeContainerCount: ' + DvtDocument.shapeContainerCount;

  // Reset global counts
  DvtDocument.instanceCount = null;
  DvtDocument.getBBoxCount = null;
  DvtDocument.bboxInfo = null;
  DvtDocument.getAttrCount = null;
  DvtDocument.setAttrCount = null;
  DvtDocument.setAttrMap = {};
  DvtDocument.appendChildCount = null;
  DvtDocument.removeChildCount = null;
  DvtDocument.addListenerCount = null;
  DvtDocument.removeListenerCount = null;
  DvtDocument.shapeContainerCount = null;

  return ret;
};
/**
 * Helper class for use in parsing JSON in the java environment.
 * @export
 */
var DvtJsonParser = {};

/**
 * Parses the input string into a JSON object and returns.
 * @return {Object}
 * @export
 */
DvtJsonParser.parse = function(str) {
  if (!str)
    return null;

  // First trim any whitespace
  str = dvt.StringUtils.trim(str);

  var firstChar = str.charAt(0);
  if (firstChar == '[' || firstChar == '{') {
    // Array or Map
    // Count the number of open/close arrays or objects
    var numOpen = 0;

    // Quote handling: Count the number of open single/double quotes, except when there is an
    // open one already.  This handles nested single quotes in double quotes, and vice versa.
    var numSingleQuotes = 0;
    var numDoubleQuotes = 0;

    // Iterate through and split by pieces
    var prevIndex = 1;
    var parts = [];
    for (var i = 1; i < str.length - 1; i++) {
      var iChar = str.charAt(i);
      if (iChar == '[' || iChar == '{')
        numOpen++;
      else if (iChar == ']' || iChar == '}')
        numOpen--;
      else if (iChar == "'" && numDoubleQuotes % 2 == 0)
        numSingleQuotes++;
      else if (iChar == '"' && numSingleQuotes % 2 == 0)
        numDoubleQuotes++;

      // If split index, store the substring
      if (numOpen == 0 && (numSingleQuotes % 2 == 0 && numDoubleQuotes % 2 == 0) && iChar == ',') {
        parts.push(str.substring(prevIndex, i));
        prevIndex = i + 1;
      }
    }

    // Grab the last part if present
    if (prevIndex < str.length - 1)
      parts.push(str.substring(prevIndex, str.length - 1));

    // Decode the parts into the result
    var ret;
    if (firstChar == '[') {
      ret = [];
      for (var arrayIndex = 0; arrayIndex < parts.length; arrayIndex++)
        ret.push(DvtJsonParser.parse(parts[arrayIndex]));
    }
    else if (firstChar == '{') {
      ret = {
      };
      for (var mapIndex = 0; mapIndex < parts.length; mapIndex++) {
        var mapPart = parts[mapIndex];
        var colonIndex = mapPart.indexOf(':');
        var mapKey = mapPart.substring(1, colonIndex - 1); // 1 to -1 to avoid the quotes
        var mapValue = DvtJsonParser.parse(mapPart.substring(colonIndex + 1, mapPart.length));
        ret[mapKey] = mapValue;
      }
    }
    return ret;
  }
  else if (firstChar == '"')// String
    return str.substring(1, str.length - 1);
  else if (str == 'true')
    return true;
  else if (str == 'false')
    return false;
  else if (str == 'null')
    return null;
  else if (dvt.Agent.isEnvironmentTest() && str.substring(0, 5) == 'eval(') {//@HtmlUpdateOK
    // Supports JsonUtils.Verbatim.  Only used for testing.
    //Only used in testing environment @HtmlUpdateOK
    //BUG 34184877 - MAKE DVT COMPATIBLE WITH CSP STANDARDS
    var func = window;
    var funcSplit = str.substring(5, str.length - 3).split('.');
    for(i = 0;i < funcSplit.length;i++)
    {
      func = func[funcSplit[i]];
    } 
    return func();
  }
  else
    return Number(str);
};
/**
 *   Static implementation level object utility functions
 *   @class DvtJavaDimensionUtils
 */
var DvtJavaDimensionUtils = function() {};

dvt.Obj.createSubclass(DvtJavaDimensionUtils, dvt.Obj);


/**
 *   Returns the rectangular dimensions for the specified displayable.  This function
 *   is currently only available for Canvas and Xml contexts.
 *   @param {dvt.Displayable} obj The implementation displayable object whose dimensions are to be returned.
 *   @return {dvt.Rectangle} returns the rectangular dimensions for the specified displayable implementation.
 */
DvtJavaDimensionUtils.getDimensions = function(obj) {
  var dims = DvtJavaDimensionUtils._getDimensions(obj, false);
  return {'x' : dims.x, 'y' : dims.y, 'width' : dims.w, 'height' : dims.h};
};


//Bug 13842194:
/**
 *   Returns the rectangular dimensions for the specified displayable, including stroke width.  This function
 *   is currently only available for Canvas and Xml contexts.
 *   @param {dvt.Displayable} obj the implementation displayable object whose dimensions are to be returned.
 *   @return {dvt.Rectangle} returns the rectangular dimensions for the specified displayable implementation.
 */
DvtJavaDimensionUtils.getDimensionsWithStroke = function(obj) {
  var dims = DvtJavaDimensionUtils._getDimensions(obj, true);
  return {'x' : dims.x, 'y' : dims.y, 'width' : dims.w, 'height' : dims.h};
};


/**
 * Returns the rectangular dimensions for the specified displayable, optionally including stroke width.
 * @param {dvt.Displayable} obj The displayable whose dimensions are to be returned.
 * @param {boolean} bWithStroke True if dimensions should be calculated taking stroke width into consideration
 * @return {dvt.Rectangle} The rectangular dimensions for the specified displayable implementation.
 * @private
 */
DvtJavaDimensionUtils._getDimensions = function(obj, bWithStroke) {
  var isContainer = false;
  var children = obj.getNumChildren ? obj.getNumChildren() : 0;
  var selfDims;

  if (bWithStroke) {
    selfDims = DvtJavaDimensionUtils._getDimensionsSelfWithStroke(obj);
  }
  else {
    selfDims = DvtJavaDimensionUtils._getDimensionsSelf(obj);
  }
  if (!selfDims) {
    selfDims = new dvt.Rectangle();
    if (children > 0) {
      selfDims.x = Number.MAX_VALUE;
      selfDims.y = Number.MAX_VALUE;
    }
    isContainer = true;
  }

  //  If children, find min/max x/y of the children
  var minX, minY, maxX, maxY;
  var bChild = false;

  for (var i = children - 1; i >= 0; i--) {
    var child = obj.getChildAt(i);
    var dims;

    if (bWithStroke) {
      dims = child.getDimensionsWithStroke();
    }
    else {
      dims = child.getDimensions();
    }

    if (!dims) {
      continue;
    }

    if (dims.w !== 0 && dims.h !== 0) {
      // see footnote at end of function.  svg ignores
      dims = DvtJavaDimensionUtils._applyMatrix(child, dims);// ignores zero size and doesn't attempt to apply
    }
    // the matrix, so translation is ignored.
    else if (i < children - 1) {
      // ignore zero-sized, except for one.
      continue;
    }

    if ((!bChild) || (dims.x < minX)) {
      minX = dims.x;
    }
    if ((!bChild) || (dims.y < minY)) {
      minY = dims.y;
    }
    if ((!bChild) || ((dims.x + dims.w) > maxX)) {
      maxX = dims.x + dims.w;
    }
    if ((!bChild) || ((dims.y + dims.h) > maxY)) {
      maxY = dims.y + dims.h;
    }
    bChild = true;// a child had been measured
  }

  if (bChild) {
    if (!isContainer) {
      // Create union if not a container. Containers have no initial dimension.
      minX = Math.min(minX, selfDims.x);
      minY = Math.min(minY, selfDims.y);
      maxX = Math.max(maxX, selfDims.x + selfDims.w);
      maxY = Math.max(maxY, selfDims.y + selfDims.h);
    }

    selfDims.x = minX;
    selfDims.y = minY;
    selfDims.w = maxX - minX;
    selfDims.h = maxY - minY;
  }

  return selfDims;

  //  Footnote:
  //  Note: as at 9/6/12, there is a discrepancy in the way the browsers handle nested zero size objects.
  //  Scenario is:   DvtStage
  //                    dvt.Container           translateX(10)
  //                       dvt.Rect(0,0,0,0)    translateX(15)
  //                       dvt.Rect(0,0,0,0)    translateX(35)
  //
  //  In SVG, getDimensions() on each object returns:
  //
  //    IE9/Firefox/Safari    stage     (0, 0, 0, 0)
  //                          container (0, 0, 0, 0)
  //                          rect      (0, 0, 0, 0)
  //                          rect      (0, 0, 0, 0)
  //
  //          Chrome          stage     (25, 0, 20, 0)       the x's are arguably ok, but the widths are not
  //                          container (15, 0, 20, 0)        ..  ..  ..    ..    ..   ..  ..   ..   ..  ..
  //                          rect      (0,  0, 0,  0)
  //                          rect      (0,  0, 0,  0)
  //
  //   At this point the IE9/Firefox/Safari approach is followed, and the matrix is not applied if the
  //   object is of zero size.
  //
};


/**
 * Applies an object's transform to a dvt.Rectangle
 * @param {dvt.Displayable} obj The displayable whose transform should be used
 * @param {dvt.Rectangle} selfDims The rectangle to transform
 * @return {dvt.Rectangle} The transformed rectangle
 * @private
 */
DvtJavaDimensionUtils._applyMatrix = function(obj, selfDims) {
  var mat = obj.getMatrix();

  if (mat && (!mat.isIdentity())) {
    var tl, tr, bl, br;

    tl = mat.transformPoint(new dvt.Point(selfDims.x, selfDims.y));
    tr = mat.transformPoint(new dvt.Point(selfDims.x + selfDims.w, selfDims.y));
    bl = mat.transformPoint(new dvt.Point(selfDims.x, selfDims.y + selfDims.h));
    br = mat.transformPoint(new dvt.Point(selfDims.x + selfDims.w, selfDims.y + selfDims.h));

    var xmin = Math.min(Math.min(tl.x, tr.x), Math.min(bl.x, br.x));
    var xmax = Math.max(Math.max(tl.x, tr.x), Math.max(bl.x, br.x));
    var ymin = Math.min(Math.min(tl.y, tr.y), Math.min(bl.y, br.y));
    var ymax = Math.max(Math.max(tl.y, tr.y), Math.max(bl.y, br.y));

    selfDims.x = xmin;
    selfDims.y = ymin;
    selfDims.w = xmax - xmin;
    selfDims.h = ymax - ymin;

  }

  return selfDims;
};


/**
 * Returns the rectangular dimensions of the specified displayable.
 * @param {dvt.Displayable} obj The displayable to calculate dimensions for
 * @return {dvt.Rectangle} The object dimensions
 * @private
 */
DvtJavaDimensionUtils._getDimensionsSelf = function(obj) {
  var selfDims;
  var r;

  if (obj instanceof dvt.Line) {
    var minX = Math.min(obj.getX1(), obj.getX2());
    var minY = Math.min(obj.getY1(), obj.getY2());
    var w = Math.abs(obj.getX1() - obj.getX2());
    var h = Math.abs(obj.getY1() - obj.getY2());

    selfDims = new dvt.Rectangle(minX, minY, w, h);
  }
  else if ((obj instanceof dvt.Rect) || (obj instanceof dvt.Image)) {
    selfDims = new dvt.Rectangle(obj.getX(), obj.getY(), obj.getWidth(), obj.getHeight());
  }
  else if (obj instanceof dvt.Circle) {
    r = obj.getRadius();
    var w = r + r;
    selfDims = new dvt.Rectangle(obj.getCx() - r, obj.getCy() - r, w, w);
  }
  else if (obj instanceof dvt.Oval) {
    r = obj.getRadius();
    var rx = obj.getRx();
    var ry = obj.getRy();

    rx = (rx === null || isNaN(rx)) ? r : rx;
    ry = (ry === null || isNaN(ry)) ? r : ry;

    selfDims = new dvt.Rectangle(obj.getCx() - rx, obj.getCy() - ry, 2 * rx, 2 * ry);
  }
  else if ((obj instanceof dvt.SimpleMarker) || (obj instanceof dvt.ImageMarker)) {
    selfDims = new dvt.Rectangle(obj.getCx() - obj.getWidth() / 2, obj.getCy() - obj.getHeight() / 2, obj.getWidth(), obj.getHeight());
  }
  else if (obj instanceof dvt.OutputText || obj instanceof dvt.BackgroundOutputText) {
    selfDims = DvtJavaDimensionUtils._getTextDimensions(obj, obj.getTextString());
  }
  else if (obj instanceof dvt.Path) {
    selfDims = dvt.PathUtils.getDimensions(obj.getCommands());
  }
  else if ((obj instanceof dvt.Polyline) || (obj instanceof dvt.Polygon)) {
    selfDims = dvt.PolygonUtils.getDimensions(obj.getPoints());
  }
  else if (obj instanceof dvt.Arc) {
    var rx = obj.getRx();
    var ry = obj.getRy();
    selfDims = new dvt.Rectangle(obj.getCx() - rx, obj.getCy() - ry, 2 * rx, 2 * ry);
  }
  else if (obj.getTypeName() == 'dvt.TextFormatted') {
    // A dvt.TextFormatted object is at least a text element with a text node child
    // It can also contain tspan elements which have text node children or additional tspan elements
    var map = DvtJavaDimensionUtils._getCSSStyleMap(obj);
    selfDims = DvtJavaDimensionUtils._getTextFormattedDimensions(obj, obj.getElem(), map);
  }
  else if (obj.getTypeName() == 'dvt.Text') {
    selfDims = DvtJavaDimensionUtils._getTextDimensions(obj, obj.impl_getText());
  }

  return selfDims;
};


/**
 * Returns the dimensions for a dvt.TextFormatted object
 * @param {dvt.TextFormatted} obj The text object to measure
 * @param {DvtElement} elem The element to measure
 * @param {Object} styleMap A map of css style attributes and values to use for calculating text dimensions
 * @return {dvt.Rectangle} The text dimensions
 * @private
 */
DvtJavaDimensionUtils._getTextFormattedDimensions = function(obj, elem, styleMap) {
  var dims = new dvt.Rectangle();
  for (var i = 0; i < elem.childNodes.length; i++) {
    var childElem = elem.childNodes[i];
    if (childElem.nodeName == '#text' && childElem.nodeValue) {
      dims = dims.getUnion(DvtJavaDimensionUtils._getJavaTextDim(obj, childElem.nodeValue, styleMap));
    }
    else if (childElem.nodeName == 'tspan') {
      var tspanElem = childElem;
      var map = new Hashtable(styleMap);
      // update style map
      var val = dvt.ToolkitUtils.getAttrNullNS(tspanElem, 'font-family');
      if (val)
        map.put(TextAttribute.FAMILY, val.replace(/"/g, "'"));

      val = dvt.ToolkitUtils.getAttrNullNS(tspanElem, 'font-style');
      if (val == 'italic' || val == 'oblique')
        map.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);

      val = dvt.ToolkitUtils.getAttrNullNS(tspanElem, 'font-weight');
      if (val == 'regular')
        map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
      else if (val == 'bold')
        map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);

      // check children for text elements with updated style map
      var textDims = DvtJavaDimensionUtils._getTextFormattedDimensions(obj, childElem, map);

      // adjust dimensions for dx/dy values
      // since we add dx/dy for dvt.TextFormatted only and they are given in em, we can roughly guess default
      // font size by using the height from the text dimensions since it represents the height of one line of text
      var dy = dvt.ToolkitUtils.getAttrNullNS(tspanElem, 'dy');
      if (dy)
        textDims.y += (parseFloat(dy.substr(0, dy.indexOf('em'))) * textDims.h);
      var dx = dvt.ToolkitUtils.getAttrNullNS(tspanElem, 'dx');
      if (dx)
        textDims.x += (parseFloat(dx.substr(0, dx.indexOf('em'))) * textDims.h);

      dims = dims.getUnion(textDims);
    }
  }
  return dims;
};

/**
 * Returns the dimensions for a single line of text
 * @param {dvt.OutputText} obj The text object to measure
 * @param {String} textStr The text string to use
 * @return {dvt.Rectangle} The text dimensions
 * @private
 */
DvtJavaDimensionUtils._getTextDimensions = function(obj, textStr) {
  var map = DvtJavaDimensionUtils._getCSSStyleMap(obj);
  return DvtJavaDimensionUtils._getJavaTextDim(obj, textStr, map);
};

/**
 * Returns the text dimensions for a single line of text using java font utilities
 * @param {dvt.OutputText} obj The text object to measure
 * @param {String} textStr The text string to use
 * @param {Object} styleMap A map of css style attributes and values to use for calculating text dimensions
 * @return {dvt.Rectangle} The text dimensions
 * @private
 */
DvtJavaDimensionUtils._getJavaTextDim = function(obj, textStr, styleMap) {
  var font = new Font(styleMap);
  var bi = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
  var g = bi.createGraphics();
  var metrics = g.getFontMetrics(font);
  var rect = metrics.getStringBounds(textStr, g);
  var textDims = new dvt.Rectangle(obj.getX(), obj.getY(), rect.getWidth(), rect.getHeight());

  // adjust dimensions for text horizontal and vertical alignment
  var anchor = obj.getHorizAlignment();
  var baseline = obj.getVertAlignment();
  if (anchor === dvt.OutputText.H_ALIGN_RIGHT)
    textDims.x -= textDims.w;
  else if (anchor === dvt.OutputText.H_ALIGN_CENTER)
    textDims.x -= textDims.w / 2;

  if (baseline === dvt.OutputText.V_ALIGN_AUTO || dvt.Agent.isPlatformIE()) // IE doesn't support baseline, dims are always based on auto alignment
    textDims.y = textDims.y - textDims.h + metrics.getDescent();
  else if (baseline === dvt.OutputText.V_ALIGN_BOTTOM)
    textDims.y -= textDims.h;
  else if (baseline === dvt.OutputText.V_ALIGN_MIDDLE)
    textDims.y -= textDims.h / 2;

  return textDims;
};


/**
 * Returns the map of css style attributes and values for a text object
 * @param {dvt.Shape} obj The text object to extract css style info from
 * @return {Object} A map of css style attributes and values
 * @private
 */
DvtJavaDimensionUtils._getCSSStyleMap = function(obj) {
  var map = new Hashtable();

  if (obj instanceof dvt.OutputText || obj instanceof dvt.BackgroundOutputText) {
    map.put(TextAttribute.FAMILY, obj.getCtx().getDefaultFontFamily());
    map.put(TextAttribute.SIZE, CSSStyleUtils.parseFontSize(obj.getCtx().getDefaultFontSize()));
  }
  else
    map.put(TextAttribute.FAMILY, 'tahoma, sans-serif');

  var angleRad = obj.getRotation();
  if (angleRad)
    map.put(TextAttribute.TRANSFORM, AffineTransform.getRotateInstance(angleRad));

  var style = obj.getCSSStyle();
  if (style) {
    var val = style.getStyle('font-family');
    if (val)
      map.put(TextAttribute.FAMILY, val.replace(/"/g, "'"));

    val = style.getStyle('font-size');
    if (val) {
      var numVal = parseFloat(val);
      map.put(TextAttribute.SIZE, isNaN(numVal) ? CSSStyleUtils.parseFontSize(val) : numVal);
    }

    val = style.getStyle('font-style');
    if (val && val == 'italic' || val == 'oblique')
      map.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);

    val = style.getStyle('font-weight');
    if (val) {
      if (val == 'regular')
        map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
      else if (val == 'bold')
        map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
    }

    val = style.getStyle('text-decoration');
    if (val && val == 'underline')
      map.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
  }
  return map;
};


//Bug 13842194:
/**
 *  Returns the rectangular dimensions of this object, including stroke width.
 *  @param {dvt.Displayable} obj
 *  @return {dvt.Rectangle}
 *  @private
 */
DvtJavaDimensionUtils._getDimensionsSelfWithStroke = function(obj) {
  var selfDims = DvtJavaDimensionUtils._getDimensionsSelf(obj);
  if (selfDims) {
    if (obj && obj.getStroke) {
      var stroke = obj.getStroke();
      if (stroke) {
        var sw = stroke.getWidth();
        if (sw) {
          var halfSw = .5 * sw;
          selfDims.x -= halfSw;
          selfDims.y -= halfSw;
          selfDims.w += sw;
          selfDims.h += sw;
        }
      }
    }
  }
  return selfDims;
};
/**
 * Converter to be used for testing. Converted strings are preprended and appended with a single "*" character.
 * @constructor
 * @export
 */
dvt.JavaConverter = {};

dvt.Obj.createSubclass(dvt.JavaConverter, dvt.Obj);

/**
 * Returns a converter that follows the JET converter API.
 * @return {object}
 * @export
 */
dvt.JavaConverter.jetConverter = function() {
  // JET converters rely on a "format" function.
  return {'format': dvt.JavaConverter._format};
};

/**
 * Returns a converter that follows the MAF converter API.
 * @return {object}
 * @export
 */
dvt.JavaConverter.mafConverter = function() {
  // MAF converters rely on a "getAsString" function.
  return {'getAsString': dvt.JavaConverter._format};
};

/**
 * Returns a converter for level1 time axis labels that follows the JET converter API.
 * @return {object}
 * @export
 */
dvt.JavaConverter.jetTimeLabel1Converter = function() {
  // JET converters rely on a "format" function.
  return {'format': dvt.JavaConverter._formatLevel1Date};
};

/**
 * Returns a converter for level2 time axis labels that follows the JET converter API.
 * @return {object}
 * @export
 */
dvt.JavaConverter.jetTimeLabel2Converter = function() {
  // JET converters rely on a "format" function.
  return {'format': dvt.JavaConverter._formatLevel2Date};
};

/**
 * Returns the input value prepended and appended with a single "*" character.
 * @param {object} value
 * @return {string}
 * @private
 */
dvt.JavaConverter._format = function(value) {
  return '*' + value + '*';
};

/**
 * Returns the month from the date string value, prepended and appended with a single "*" character.
 * @param {object} value
 * @return {string}
 * @private
 */
dvt.JavaConverter._formatLevel1Date = function(value) {
  if (value)
    return '*' + (value.getMonth() + 1).toString() + '*';
  return null;
};


/**
 * Returns the year from the date string value, prepended and appended with a single "*" character.
 * @param {object} value
 * @return {string}
 * @private
 */
dvt.JavaConverter._formatLevel2Date = function(value) {
  if (value)
    return '*' + value.toString().split(' ')[3] + '*';
  return null;
};
/**
 * Wrapper class providing access to XML Nodes.
 * @extends {dvt.XmlNode}
 * @class DvtJavaXmlNode
 * @constructor
 * @param {Node} node the Node object
 */
var DvtJavaXmlNode = function(node) {
  this.Init(node);
};

dvt.Obj.createSubclass(DvtJavaXmlNode, dvt.XmlNode);


/**
 * @param {Node} node the Node object
 */
DvtJavaXmlNode.prototype.Init = function(node) {
  this._node = node;
};


/**
 * Helper function to create an XML node.  Should be overridden by subclasses.
 * @return {DvtJavaXmlNode}
 */
DvtJavaXmlNode.prototype.CreateNode = function(node) {
  return new DvtJavaXmlNode(node);
};


/**
 * @override
 */
DvtJavaXmlNode.prototype.getAttr = function(attr) {
  // Java Element.getAttribute returns empty string for missing attrs, so need to check explicitly
  var hasAttr = this._node.hasAttribute(attr);
  if (!hasAttr)
    return null;

  var nodeAttr = this._node.getAttribute(attr); // Java String
  return new String(nodeAttr).toString(); // JS String
};


/**
 * @override
 */
DvtJavaXmlNode.prototype.getFirstChild = function() {
  var child = this._node.getFirstChild();
  return child ? this.CreateNode(child) : null;
};


/**
 * @override
 */
DvtJavaXmlNode.prototype.getChildNodes = function() {
  var ret = [];

  var child = this._node.getFirstChild();
  while (child != null) {
    if (child.getNodeType() == Node.ELEMENT_NODE)
      ret.push(this.CreateNode(child));

    child = child.getNextSibling();
  }
  return ret;
};


/**
 * @override
 */
DvtJavaXmlNode.prototype.getName = function() {
  return new String(this._node.getNodeName()).toString();
};


/**
 * @override
 */
DvtJavaXmlNode.prototype.getAttributes = function() {
  var array = [];
  var attrList = this._node.getAttributes();
  var numAttrs = attrList.length;
  for (var i = 0; i < numAttrs; i++) {
    var attr = attrList.item(i);
    array.push({
      name: new String(attr.getNodeName()).toString(), value: new String(attr.getNodeValue()).toString()
    });
  }
  return array;
};


/**
 * @override
 */
DvtJavaXmlNode.prototype.getAllChildNodes = function() {
  var ret = [];

  var child = this._node.getFirstChild();
  while (child != null) {
    ret.push(this.CreateNode(child));

    child = child.getNextSibling();
  }
  return ret;
};


/**
 * @override
 */
DvtJavaXmlNode.prototype.getNodeValue = function() {
  return new String(this._node.getNodeValue()).toString();
};


/**
 * @override
 */
DvtJavaXmlNode.prototype.getTextContent = function() {
  return new String(this._node.getTextContent()).toString();
};


/**
 * @override
 */
DvtJavaXmlNode.prototype.getElementsByTagName = function(name) {
  var ret = [];
  var nodes = this._node.getElementsByTagName(name);
  if (nodes) {
    for (var i = 0; i < nodes.length; i++) {
      if (nodes.item)
        ret.push(this.CreateNode(nodes.item(i)));
    }
  }
  return ret;
};
/**
 * Wrapper class providing XML parsing functionality.
 * @class DvtJavaXmlParser
 * @extends {dvt.Obj}
 * @constructor
 */
var DvtJavaXmlParser = function() {
  var dbf = DocumentBuilderFactory.newInstance();
  dbf.setIgnoringElementContentWhitespace(true);
  dbf.setIgnoringComments(true);
  this._parser = dbf.newDocumentBuilder();
};

dvt.Obj.createSubclass(DvtJavaXmlParser, dvt.Obj);


/**
 * Parses the XML string and returns the root node.
 * @param {string} xml The XML string to parse.
 * @return {dvt.XmlNode} The root node of the XML document.
 */
DvtJavaXmlParser.prototype.parse = function(xml) {
  var xmlString = java.lang.String.valueOf(xml);
  var xmlDoc = this._parser.parse(new ByteArrayInputStream(xmlString.getBytes()));
  var root = xmlDoc.getDocumentElement();
  return new DvtJavaXmlNode(root);
};
/**
 * DOM Node implementation for XML parsing.  Only contains the APIs currently in use by our components.  JSDoc for
 * functions purposefully omitted as this class should implement the DOM spec, which is readily available.
 * @param {Node} node the Node object
 * @extends {dvt.XmlNode}
 * @class DvtDOMNode
 * @constructor
 * @export
 */
var DvtDOMNode = function(node) {
  this.Init(node);

  // Expose public properties
  this['childNodes'] = this.getChildNodes();
  this['nodeName'] = this.getName();
  this['textContent'] = this.getTextContent();
};

// TODO: When Flash support is no longer needed, all components should switch to using the DOM parser directly.  We will
//       be able to remove all the XML wrappers from the browser toolkit.
dvt.Obj.createSubclass(DvtDOMNode, DvtJavaXmlNode);


/**
 * @override
 */
DvtDOMNode.prototype.CreateNode = function(node) {
  return new DvtDOMNode(node);
};


/**
 * @export
 */
DvtDOMNode.prototype.getAttribute = function(attr) {
  return this.getAttr(attr);
};


/**
 * @export
 */
DvtDOMNode.prototype.hasChildNodes = function() {
  return this.getChildNodes().length > 0;
};
/**
 * DOM Parser implementation.  This class is a replacement for the XML parser wrapper classes, like DvtJavaXmlParser,
 * which introduce overhead and prevent us from relying on native APIs directly in the browser. Only contains the APIs
 * currently in use by our components.
 * @class DvtDOMParser
 * @constructor
 * @export
 */
var DvtDOMParser = function() {
  var dbf = DocumentBuilderFactory.newInstance();
  dbf.setIgnoringElementContentWhitespace(true);
  dbf.setIgnoringComments(true);
  this._parser = dbf.newDocumentBuilder();
};

dvt.Obj.createSubclass(DvtDOMParser, dvt.Obj);


/**
 * Parses the XML string and returns the root node.
 * @param {string} xml The XML string to parse.
 * @return {dvt.XmlNode} The root node of the XML document.
 * @export
 */
DvtDOMParser.prototype.parseFromString = function(xml) {
  var xmlString = java.lang.String.valueOf(xml);
  var xmlDoc = this._parser.parse(new ByteArrayInputStream(xmlString.getBytes()));
  var root = xmlDoc.getDocumentElement();
  return new DvtDOMNode(root);
};
/**
 * @export
 */
dvt.JavaImageLoader = {};

dvt.Obj.createSubclass(dvt.JavaImageLoader, dvt.Obj);

dvt.JavaImageLoader._cache = {};

dvt.JavaImageLoader.addBubbleEventListener = function(element, type, listener) {
  return false;
};


/**
 * Load an image.
 *
 * @param src URL of the image to load
 * @param onComplete function to call when the image is loaded
 *
 * @return image if image is already loaded and onComplete
 *         function is null, otherwise null
 */
dvt.JavaImageLoader.loadImage = function(src, onComplete) {
  var entry = dvt.JavaImageLoader.getImageInfo(src);

  if (entry) {
    if (onComplete)
      onComplete(entry);
    return entry;
  } else {
    return null;
  }
};


/**
 * @this {dvt.JavaImageLoader}
 * Load a new image if needed and retruns the uri, width, height of the image or null if it could not be loaded.
 *
 * @param src URL of the image to load
 */
dvt.JavaImageLoader.getImageInfo = function(src) {
  if (!dvt.JavaImageLoader._cache[src]) {
    var imageInfo = dvt.JavaImageLoader._loadDataUri(src);
    // base64 encoded uri
    if (imageInfo) {
      dvt.JavaImageLoader._cache[src] = imageInfo;
      return imageInfo;
    }

    var markerUrl;
    // unit tests don't have access to FacesContext
    var fc = FacesContext.getCurrentInstance();
    if (fc == null) {
      markerUrl = DVTRendererUtils.getResourceImageURL(src, dvt.JavaImageLoader._resourceClass);
    }
    else {
      // Make sure there is a leading slash.
      var uri = (src.charAt(0) != '/') ? ('/' + src) : src;
      // Remove context path if it exists
      var contextPath = fc.getExternalContext().getRequestContextPath();
      if (uri.search(contextPath) == 0)
        uri = uri.replace(contextPath, '');
      markerUrl = DVTRendererUtils.getImageURL(uri);
    }

    if (!markerUrl) {
      imageInfo = new Object();
      imageInfo['uri'] = src;
      imageInfo['width'] = 1;
      imageInfo['height'] = 1;
      dvt.JavaImageLoader._cache[src] = imageInfo;
    }
    else {
      var strUrl = markerUrl.toString();
      // read the image to get its dimensions
      var width, height;
      var javaImageInfo = DVTRendererUtils.getImageInfo(markerUrl);
      if (javaImageInfo) {
        strUrl = javaImageInfo.uri;
        width = javaImageInfo.width;
        height = javaImageInfo.height;
      }
      else {
        var dims = DVTSVGLoaderUtils.getSVGDimensions(markerUrl);
        width = dims.getWidth();
        height = dims.getHeight();
      }
      imageInfo = new Object();
      imageInfo['uri'] = strUrl;
      imageInfo['width'] = width;
      imageInfo['height'] = height;
      dvt.JavaImageLoader._cache[src] = imageInfo;
    }
  }
  return dvt.JavaImageLoader._cache[src];
};


/**
 * Attempts to get image info from a base64-encoded data uri
 * @param uri {string} - the URI to try loading
 * @return {object} the imageInfo structure if the uri was a base64-encoded data uri, null otherwise
 *
 */
dvt.JavaImageLoader._loadDataUri = function(uri) {
  var base64Prefix = 'data:image/png;base64,';
  if (uri && uri.indexOf(base64Prefix) == 0) {
    var imageInfo = new Object();
    imageInfo['uri'] = uri;
    var dims = ImageRendererUtils.loadImageFromDataURI(uri);
    if (dims) {
      imageInfo['width'] = dims.getWidth();
      imageInfo['height'] = dims.getHeight();
    }
    return imageInfo;
  }
  return null;
};

/**
 * Sets the name of the class that should be instantiated and used
 * to load any "application" images in the unit tests
 * (Should only be called by unit tests)
 *
 * @param {string} resourceClass the name of the class (i.e. javaObject.getClass().getName())
 * @export
 */
dvt.JavaImageLoader.setResourceClass = function(resourceClass) {
  dvt.JavaImageLoader._resourceClass = resourceClass;
};
