/*
** This file contains classes used to dynamically generate
** XML documents based on type information.  If no type
** information is available, then this can also be used
** to generate arbitrary XML documents. 
*/

//---------------------------------------------------------------
// Global variables
//---------------------------------------------------------------

/* Holds all the namespace rows */
var docBuilderAttrRows = new Array();

/* Holds all node label prefixes */
var docBuilderPrefixSpans = new Array();

/* Indicates if debugging controls shoudl show up */
var docBuilderDebug = false;

/* Indicates which view is currently selected */
var docBuilderViewDetailed = false;

//---------------------------------------------------------------
// Global functions
//---------------------------------------------------------------

function docBuilderShowNormal() {
  for(var i=0; i < docBuilderAttrRows.length; i++) {
    docBuilderAttrRows[i].style.display = "none";
  }
  for(var i=0; i < docBuilderPrefixSpans.length; i++) {
    docBuilderPrefixSpans[i].style.display = "none";
  }
  docBuilderViewDetailed = false;
}

function docBuilderShowDetailed() {
  for(var i=0; i < docBuilderAttrRows.length; i++) {
    docBuilderAttrRows[i].style.display = "block";
  }
  for(var i=0; i < docBuilderPrefixSpans.length; i++) {
    docBuilderPrefixSpans[i].style.display = "inline";
  }
  docBuilderViewDetailed = true;
}

//---------------------------------------------------------------
// Type Class Hierarchy
//
// Classes used to describe the elements
//---------------------------------------------------------------

/*
 * Base class for all node type information
 */
function NodeType() {
  this.name       = null;
  this.attributes = new Array();
}
NodeType.prototype = new Object();

NodeType.prototype.isSimple = false;
NodeType.prototype.isComplex = false;
NodeType.prototype.getName = function() {
  return this.name;
}

NodeType.prototype.getAttribute = function(attrName) {
  for(var i=0; i<this.attributes.length; i++) {
    if(attrName == this.attributes[i].getName()) {
      return this.attributes[i];
    }
  }
  return null;
}

NodeType.prototype.addAttribute = function(attrType) {
  this.attributes[this.attributes.length] = attrType;
  return attrType;
}

NodeType.prototype.removeAttribute = function(index) {
  this.attributes.splice(index, 1);
}

NodeType.prototype.getAttributes = function() {
  return this.attributes;
}

NodeType.prototype.createAttributesWithValue = function(inst) {
  for(var i=0; i<this.attributes.length; i++) {
    if(this.attributes[i].value) {
      inst.addAttributeFromType(this.attributes[i].getName());
    }
  }
}

/*
 * Simple node type, which means that this node
 * simply has a name and value.  This node can represent
 * an element that contains only text or an attribute.
 * I.e., this is analogous to a XML Schema simple type
 * or complex type with simple content.
 * 
 * name: The name of this node.
 * baseType: The name of the base type
 * attributes: An array of attribute types in order that
 *       this node can support.
 */
function SimpleType(name, baseType, value) {
  this.name       = name;
  this.baseType   = baseType;
  this.attributes = new Array();
  this.value      = value;
}
SimpleType.prototype = new NodeType();

SimpleType.prototype.isSimple = true;

SimpleType.prototype.getBaseType = function() {
  return this.baseType;
}

SimpleType.prototype.isAttribute = false;

SimpleType.prototype.create = function(value) {
  if(!value) value = this.value;
  var inst = new SimpleInstance(this, value);
  this.createAttributesWithValue(inst);
  return inst;
}

/*
 * Custom simple type
 */
var CustomSimpleType = new SimpleType(null, "string");
CustomSimpleType.create = function(customName, value) {
  if(!value) value = this.value;
  var inst = new SimpleInstance(this, value);
  this.createAttributesWithValue(inst);
  inst.customName = customName;
  inst.custom = true;
  return inst;
}

/*
 * An attribute simple type, which just has a name
 * and type.
 */
function AttributeType(name, baseType, value) {
  this.name       = name;
  this.baseType   = baseType;
  this.attributes = new Array();
  this.value      = value;
}
AttributeType.prototype = new SimpleType();

AttributeType.prototype.isAttribute = true;

AttributeType.prototype.create = function(value) {
  if(!value) value = this.value;
  return new AttributeInstance(this, value);
}

/*
 * Custom attribute type
 */
var CustomAttributeType = new AttributeType(null, 'string');
CustomAttributeType.create = function(customName, value) {
  if(!value) value = this.value;
  var inst = new AttributeInstance(this, value);
  inst.customName = customName;
  inst.custom = true;
  return inst;
}

/*
 * NS attribute type
 */
var SchemaTypeAttributeType = new AttributeType("xsi:type", 'string');

/*
 * A complex type which means that this cannot have
 * a single value, but must contain child elements
 */
function ComplexType(name) {
  this.name       = name;
  this.children   = new Array();
  this.attributes = new Array();
}
ComplexType.prototype = new NodeType();

ComplexType.prototype.isComplex = true;

ComplexType.prototype.create = function(dontAutoInstance) {
  var inst = new ComplexInstance(this);
  if(!dontAutoInstance) {
    this.createAttributesWithValue(inst);
    this.createChildren(inst);
  }
  return inst;
}

ComplexType.prototype.addChild = function(child) {
  this.children[this.children.length] = child;
  return child;
}

ComplexType.prototype.removeChild = function(index) {
  this.children.splice(index, 1);
}

ComplexType.prototype.getChild = function(childName) {
  for(var i=0; i<this.children.length; i++) {
    if(childName == this.children[i].getName()) {
      return this.children[i];
    }
  }
  return null;
}

ComplexType.prototype.createChildren = function(inst) {
  for(var i=0; i<this.children.length; i++) {
    if(this.children[i].isSimple) {
      inst.addChildFromType(this.children[i].getName());
    }
  }
}

ComplexType.prototype.getChildren = function() {
  return this.children;
}

/*
 * Custom complex type
 */
var CustomComplexType = new ComplexType(null);
CustomComplexType.create = function(customName) {
  var inst = new ComplexInstance(this);
  this.createAttributesWithValue(inst);
  this.createChildren(inst);
  inst.custom = true;
  inst.customName = customName;
  return inst;
}

/*
 * SOAP Envelope Type
 * 
 * <soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope'>
 *    <soapenv:Body>
 *       <operation xmlns='targetNamespace'>
 *         ...
 *       </operation>
 *    </soapenv:Body>
 * </soapenv:Envelope>
 *
 * bodyTypes: An array of the types that can be created under the soapenv:Body
 *            tag.
 */
function SOAPEnvelopeType(operation,targetNamespace,bodyTypes) {
  this.attributes = new Array();
  this.children = new Array();

  /* 
   * Add standard namespace attribute.
   *
   * WARNING: These must be kept in sync with the standard prefixes in AwpxDocumentBuilderHandler
   */
  this.addAttribute(new AttributeType("xmlns:soap",    "string", "http://schemas.xmlsoap.org/wsdl/soap/"));
  this.addAttribute(new AttributeType("xmlns:soapenv", "string", "http://schemas.xmlsoap.org/soap/envelope/"));
  this.addAttribute(new AttributeType("xmlns:soapenc", "string", "http://schemas.xmlsoap.org/soap/encoding/"));
  this.addAttribute(new AttributeType("xmlns:xsi",     "string", "http://www.w3.org/2001/XMLSchema-instance"));
  this.addAttribute(new AttributeType("xmlns:xsd",     "string", "http://www.w3.org/2001/XMLSchema"));
      
  /* Create body type */
  var soapBodyType = new ComplexType("soapenv:Body");
  this.addChild(soapBodyType);

  /* Create soap action type */
  this.operation = operation;
  var opType = new ComplexType(this.operation);
  if(targetNamespace) {
    opType.addAttribute(new AttributeType("xmlns", "string", targetNamespace));
  }

  if(bodyTypes) {
    for(var i=0; i<bodyTypes.length; i++) {
      opType.addChild(bodyTypes[i]);
    }
  }

  /* Add body child */
  soapBodyType.addChild(opType);
}
SOAPEnvelopeType.prototype = new ComplexType("soapenv:Envelope");

SOAPEnvelopeType.prototype.create = function() {

  /* Create envelope which cannot be deleted */
  var env = new ComplexInstance(this);
  env.isHeader = true;
  this.createAttributesWithValue(env);
  env.canDelete = false;

  /* Create body which cannot be deleted */
  var body = env.addChildFromType("soapenv:Body", null, false, false, true);
  body.canDelete = false;
  body.isHeader = true;

  return env;
}

//---------------------------------------------------------------
// Node Instance Class Hierarchy
//
// Classes used to construct the document instance 
//---------------------------------------------------------------

function NodeInstance() {
  this.parent          = null;
  this.type            = null;
  this.attributes      = new Array();
  this.renderer        = null;
  this.depth           = 0;
  this.changeListeners = new Array();
}
NodeInstance.prototype = new Object();

NodeInstance.prototype.isSimple         = false;
NodeInstance.prototype.isComplex        = false;
NodeInstance.prototype.canDelete        = true;
NodeInstance.prototype.canAddAttributes = true;
NodeInstance.prototype.canAddElements   = true;
NodeInstance.prototype.isHeader         = false;

NodeInstance.prototype.getType = function() {
  return this.type;
}

NodeInstance.prototype.render = function(tbody) {
  this.renderer.render(awpxGetElement(tbody));
}

NodeInstance.prototype.getName = function() {
  if(this.type && !this.custom) {
    return this.type.getName();
  } else if(this.customName) {
    return this.customName;
  }
  return null;
}

NodeInstance.prototype.isFirst = function() {
  if(this.parent) {
    var array = (this.getType().isAttribute ? this.parent.attributes : this.parent.children);
    return(array.indexOf(this) == 0);
  }
  return true;
}

NodeInstance.prototype.isLast = function() {
  if(this.parent) {
    var array = (this.getType().isAttribute ? this.parent.attributes : this.parent.children);
    return(array.indexOf(this) == (array.length - 1));
  }
  return true;
}

/*
 * Moves the node either up or down by one
 */
NodeInstance.prototype.move = function(isDown) {
  if(this.parent) {
    var array = (this.getType().isAttribute ? this.parent.attributes : this.parent.children);
    var nodeIdx = array.indexOf(this);

    var nodeMovingDown = null;
    var nodeMovingUp   = null;
    if(isDown) {
      if(nodeIdx >= 0 && nodeIdx < array.length - 1) {
        nodeMovingDown     = this;
        nodeMovingUp       = array[nodeIdx + 1];
        array[nodeIdx]     = array[nodeIdx + 1];
        array[nodeIdx + 1] = this;
      }
    } else {
      if(nodeIdx > 0 && nodeIdx < array.length) {
        nodeMovingDown     = array[nodeIdx - 1];
        nodeMovingUp       = this;
        array[nodeIdx]     = array[nodeIdx - 1];
        array[nodeIdx - 1] = this;
      }
    }

    /*
     * In order to update the UI, we need to walk through all
     * the rows of the node moving up (and it's children) and
     * move them before the row associated with the node 
     * moving down
     */
    if(nodeMovingDown && nodeMovingUp) {
        var tbody   = nodeMovingUp.getRenderer().getTableBody();
        var curRow  = nodeMovingUp.getRenderer().getRow();

        var rowsToMoveUp = new Array();
        while(curRow) {
            curRow = awpxNextElement(curRow, true);

            if(curRow.node != nodeMovingUp && curRow.node.getDepth() <= nodeMovingUp.getDepth()) {
              break;
            }
            rowsToMoveUp[rowsToMoveUp.length] = curRow;

            curRow = curRow.nextSibling;
        }

        var downRow = nodeMovingDown.getRenderer().getRow();
        for(var i=0; i<rowsToMoveUp.length; i++) {
            tbody.removeChild(rowsToMoveUp[i]);
            tbody.insertBefore(rowsToMoveUp[i], downRow);
        }

        /* Fix up up and down arrows to reflect changes */
        nodeMovingUp.updateControls();
        nodeMovingDown.updateControls();
     }
  }
  this.fireChange();
}

/*
 * Fix up up and down arrows to reflect changes
 */
NodeInstance.prototype.updateControls = function() {
  if(this.upSpan) {
    this.upSpan.style.visibility   = (this.isFirst() ? "hidden" : "visible");
  }
  if(this.downSpan) {
    this.downSpan.style.visibility = (this.isLast() ? "hidden" : "visible");
  }
}

/*
 * This assumes it's the root node and renders the
 * entire table as well as the document and puts the
 * table in the given builderEl element.  The optional
 * valueEL is the input field in which to store the
 * generated XML.
 */
NodeInstance.prototype.renderTable = function(builderEl,valueEl) {
  this.renderer.renderTable(builderEl,valueEl);

  /* 
   * Register a change listener to update the value whenever
   * it has changed
   */
  if(!this.valueEl) {
    this.valueEl = awpxGetElement(valueEl);
    if(this.valueEl) {
      this.addChangeListener(function() {
        this.valueEl.value = this.toXML(); 
      });

      /* Initialize the value */
      this.valueEl.value = this.toXML();
    }
  }

  if(!isNS) {
    docBuilderShowNormal();
  }
}

NodeInstance.prototype.getRenderer = function() {
  return this.renderer;
}

NodeInstance.prototype.getParent = function() {
  return this.parent;
}

NodeInstance.prototype.getDepth = function() {
  return this.depth;
}

NodeInstance.prototype.getIndex = function() {
  var index = 0;
  if(this.parent) {
    index = this.parent.indexOf(this); 
  }
  return index;
}

NodeInstance.prototype.addChangeListener = function(listener) {
  this.changeListeners[this.changeListeners.length] = listener;
}

NodeInstance.prototype.fireChange = function() {
  for(var i=0; i<this.changeListeners.length; i++) {
    this.changer = this.changeListeners[i];
    this.changer();
  }

  if(this.parent) {
    this.parent.fireChange();
  }
}

NodeInstance.prototype.isCustom = function() {
  return this.custom;
}

NodeInstance.prototype.getCustomName = function() {
  return this.customName;
}

NodeInstance.prototype.indexOf = function(node) {

  /* 
   * Since nodes only have attributes, we look for
   * the attribute index
   */
  if(node.getType().isAttribute) {
    for(var i=0; i<this.attributes.length; i++) {
      if(this.attributes[i] == node) {
        return i;
      }
    }
  }
  return -1;

}

NodeInstance.prototype.getNext = function() {
  if(this.parent) {
    return this.parent.getAfter(this);
  }
  return null;
}

NodeInstance.prototype.getAfter = function(node) {
  for(var i=0; i<(this.attributes.length - 1); i++) {
    if(this.attributes[i] == node) {
      return this.attributes[i+1];
    }
  }
  return this.getNext();
}

NodeInstance.prototype.addAttributeFromType = function(attrTypeName, value, useCustom) {
  var attrType = (attrTypeName ? this.getType().getAttribute(attrTypeName) : null);
  if(attrType) {
    return this.addAttribute(attrType.create(value));
  } else if(useCustom) {
    var customName = attrTypeName;
    if(!customName) {
      customName = "attr" + this.customAttrId;
      this.customAttrId++;
    }
    return this.addAttribute(CustomAttributeType.create(customName, value));
  }
  alert("Unknown attribute type: " + attrTypeName + " in " + this.toString());
  return null;
}

NodeInstance.prototype.addAttribute = function(attr) {
  this.attributes[this.attributes.length] = attr;
  attr.parent = this;
  attr.updateDepth();
  this.fireChange();
  return attr;
}

NodeInstance.prototype.getAttribute = function(name) {
  for(var i=0; i<this.attributes.length; i++) {
    if(this.attributes[i].getName() == name) {
       return this.attributes[i];
    }
  }
  return null;
}

NodeInstance.prototype.removeAttribute = function(obj) {
  var index = null;
  if(typeof obj == "object") {
    for(var i=0; i<this.attributes.length; i++) {
      if(this.attributes[i] == obj) {
        index = i;
        break;
      }
    }
  } else {
    index = obj;
  }
  if(index >= 0) {
    this.attributes.splice(index, 1);
  }
  this.fireChange();
}

NodeInstance.prototype.remove = function(dontUpdateParent) {

  /* Need to remove your attributes first */
  for(var i=0; i<this.attributes.length; i++) {
    this.attributes[i].remove(true);
  }
  this.attributes = new Array();

  /* Remove from the renderer */
  this.renderer.remove();

  /* Update parent if necessary (i.e., not called from parent) */
  if(!dontUpdateParent) {
    if(this.parent) {
      this.parent.removeChild(this);
    }
  }

  this.fireChange();
}

NodeInstance.prototype.updateDepth = function() {
  this.depth = this.parent.getDepth() + 1;

  /* Update depths of things added before this child was added */
  for(var i=0; i<this.attributes.length; i++) {
    this.attributes[i].updateDepth();
  }
}

NodeInstance.prototype.getAttributes = function() {
  return this.attributes;
}

NodeInstance.prototype.toString = function() {
  return this.toXML();
}

/*
 * Node instance of a simple type
 *
 * type: Must be a simple type
 * value: The initial value for this object
 */
function SimpleInstance(type, value) {
  this.parent   = null;
  this.type     = type;
  this.value    = value;
  this.renderer = new SimpleInstanceRenderer(this);
  this.depth    = 0;
  this.attributes = new Array();
  this.changeListeners = new Array();
  this.customAttrId    = 1;
  this.customElId      = 1;

  if(!this.value) {
    this.value = "";
  }
}
SimpleInstance.prototype = new NodeInstance();

SimpleInstance.prototype.isSimple       = true;
SimpleInstance.prototype.canAddElements = false;

SimpleInstance.prototype.addAttributeFromType = function(attrTypeName, value, useCustom) {
  var attrType = (attrTypeName ? this.getType().getAttribute(attrTypeName) : null);
  if(attrType) {
    return this.addAttribute(attrType.create(value));
  } else if(useCustom) {
    var customName = attrTypeName;
    if(!customName) {
      customName = "attr" + this.customAttrId;
      this.customAttrId++;
    }
    return this.addAttribute(CustomAttributeType.create(customName, value));
  }
  alert("Unknown attribute type: " + attrTypeName + " in " + this.toString());
  return null;
}

SimpleInstance.prototype.getValue = function() {
  return this.value;
}

SimpleInstance.prototype.setValue = function(value) {
  this.value = value;
  this.fireChange();
}

SimpleInstance.prototype.toXML = function(indent) {
  if(!indent) indent = "";

  var name = (this.isCustom() ? this.getCustomName() : this.getType().getName());
  var xml = indent + "<" + name;
  for(var i=0; i<this.attributes.length; i++) {
    var attr = this.attributes[i];
    xml += " " + attr.toXML();
  }
  xml += ">" + this.getValue() + "</" + name + ">";
  return xml;
}

/*
 * Attribute instance
 */
function AttributeInstance(type, value) {
  this.parent     = null;
  this.type       = type;
  this.value      = value;
  this.renderer   = new SimpleInstanceRenderer(this);
  this.depth      = 0;
  this.attributes = new Array();
  this.changeListeners = new Array();

  if(!this.value) {
    this.value = "";
  }
}
AttributeInstance.prototype = new SimpleInstance();

AttributeInstance.prototype.canAddAttributes = false;

AttributeInstance.prototype.remove = function(dontUpdateParent) {
  this.renderer.remove();
  if(!dontUpdateParent) {
    if(this.parent) {
      this.parent.removeAttribute(this);
    }
  }
  this.fireChange();
}

AttributeInstance.prototype.toXML = function(indent) {
  var name = (this.isCustom() ? this.getCustomName() : this.getType().getName());
  return name + "='" + this.getValue() + "'";
}


/*
 * Node instance of a complex type, which means it
 * just has children and no value. 
 * 
 * type: Must be a complex type
 */
function ComplexInstance(type) {
  this.parent     = null;
  this.depth      = 0;
  this.type       = type;
  this.children   = new Array();
  this.renderer   = new ComplexInstanceRenderer(this);
  this.attributes = new Array();
  this.changeListeners = new Array();
  this.customAttrId    = 1;
  this.customElId      = 1;
}
ComplexInstance.prototype = new NodeInstance();

ComplexInstance.prototype.isComplex = true;

ComplexInstance.prototype.addChildFromType = function(childTypeName, value, useCustom, isSimple, dontAutoInstance) {
  var childType = (childTypeName ? this.getType().getChild(childTypeName) : null);
  if(childType) {
    if(childType.isSimple) {
      return this.addChild(childType.create(value));
    } else {
      return this.addChild(childType.create(dontAutoInstance));
    }
  } else if(useCustom) {
    var customName = childTypeName;
    if(!customName) {
      customName = "node" + this.customElId;
      this.customElId++;
    }
    if(isSimple) {
      return this.addChild(CustomSimpleType.create(customName, value));
    } else {
      return this.addChild(CustomComplexType.create(customName));
    }
  }
  alert("Unknown child type: " + childTypeName + " in " + this.toString());
  return null;
}

ComplexInstance.prototype.addChild = function(child) {
  this.children[this.children.length] = child;
  child.parent = this;
  child.updateDepth();
  this.fireChange();
  return child;
}

ComplexInstance.prototype.updateDepth = function() {
  this.depth = this.parent.getDepth() + 1;

  /* Update depths of things added before this child was added */
  for(var i=0; i<this.attributes.length; i++) {
    this.attributes[i].updateDepth();
  }

  /* Update depths of all children */
  for(var i=0; i<this.children.length; i++) {
    this.children[i].updateDepth();
  }
}

ComplexInstance.prototype.remove = function(dontUpdateParent) {

  /* Need to remove your attributes first */
  for(var i=0; i<this.attributes.length; i++) {
    this.attributes[i].remove(true);
  }
  this.attributes = new Array();

  /* Need to remove your chilren */
  for(var i=0; i<this.children.length; i++) {
    this.children[i].remove(true);
  }
  this.children = new Array();

  /* Remove from the renderer */
  this.renderer.remove();

  /* Update parent if necessary (i.e., not called from parent) */
  if(!dontUpdateParent) {
    if(this.parent) {
      this.parent.removeChild(this);
    }
  }
  this.fireChange();
}

ComplexInstance.prototype.removeChild = function(obj) {
  var index = null;
  if(typeof obj == "object") {
    for(var i=0; i<this.children.length; i++) {
      if(this.children[i] == obj) {
        index = i;
        break;
      }
    }
  } else {
    index = obj;
  }
  if(index >= 0) {
    this.children.splice(index, 1);

    if(this.children.length > 0) {
      this.children[0].updateControls();
      this.children[this.children.length - 1].updateControls();
    }
  }
  this.fireChange();
}

ComplexInstance.prototype.getChild = function(childName) {
  for(var i=0; i<this.children.length; i++) {
    if(childName == this.children[i].getName()) {
      return this.children[i];
    }
  }
  return null;
}

ComplexInstance.prototype.getChildren = function() {
  return this.children;
}

ComplexInstance.prototype.indexOf = function(node) {

  /*
   * For the index of the element, we also have to
   * look for children.  Note that for children, we
   * add in the count of attributes, since they are
   * rendered first.
   */
  if(node.getType().isAttribute) {
    for(var i=0; i<this.attributes.length; i++) {
      if(this.attributes[i] == node) {
        return i;
      }
    }
  } else {
    for(var i=0; i<this.children.length; i++) {
      if(this.children[i] == node) {
        return this.attributes.length + i;
      }
    }
  }

  return -1;
}

ComplexInstance.prototype.getAfter = function(node) {

  if(node.getType().isAttribute) {

    /* If it's an attribute, then we need to return the proper one */
    for(var i=0; i<(this.attributes.length - 1); i++) {
      if(this.attributes[i] == node) {
        return this.attributes[i+1];
      }
    }
    if(this.children.length > 0) {
      return this.children[0];
    }

  } else {

    /* If it's a child, then we need to return the proper one */
    for(var i=0; i<(this.children.length - 1); i++) {
      if(this.children[i] == node) {
        return this.children[i+1];
      }
    }

  }

  return this.getNext();
}

ComplexInstance.prototype.toXML = function(indent) {
  if(!indent) indent = "";

  var name = (this.isCustom() ? this.getCustomName() : this.getType().getName());
  var xml = indent + "<" + name;
  for(var i=0; i<this.attributes.length; i++) {
    var attr = this.attributes[i];
    xml += " " + attr.toXML();
  }

  if(this.children.length > 0) {
    xml += ">\n";
    for(var i=0; i<this.children.length; i++) {
      var child = this.children[i];
      xml += child.toXML(indent + "  ") + "\n";
    }
    xml += indent + "</" + name + ">";
  } else {
    xml += "/>";
  }
  return xml;
}


//---------------------------------------------------------------
// Renderer Class Hierarchy
//
// Classes used to render the nodes
//---------------------------------------------------------------

/*
 * Base class for all node instance renderers
 */
function NodeInstanceRenderer() {
  this.node             = null;
  this.tbody            = null;
  this.row              = null;
}
NodeInstanceRenderer.prototype = new Object();

NodeInstanceRenderer.prototype.renderAttributes = function() {
  var attrs = this.node.getAttributes();
  for(var i = 0; i < attrs.length; i++) {
    attrs[i].render(this.getTableBody());
  }
}

NodeInstanceRenderer.prototype.getNode = function() {
  return this.node;
}

NodeInstanceRenderer.prototype.getTableBody = function() {
  return this.tbody;
}

NodeInstanceRenderer.prototype.getRow = function() {
  return this.row;
}

NodeInstanceRenderer.prototype.render = function(tbody) {
  alert("Base node instance renderer can't render: " + this.node);
}

NodeInstanceRenderer.prototype.remove = function() {
  this.getTableBody().removeChild(this.row);
}

/*
 * builderEl: The DIV tag to use to create the document under
 */
NodeInstanceRenderer.prototype.renderTable = function(builderEl) {
  var table = document.createElement("TABLE");
  table.className="font fg bg document-builder";

  /* Create table body */
  var tbody = document.createElement("TBODY");
  table.appendChild(tbody);

  /* Places the table in the correct location */
  awpxGetElement(builderEl).appendChild(table);

  /* Now we render the document */
  this.render(tbody);
}

NodeInstanceRenderer.prototype.addRow = function() {
  this.row = document.createElement("TR");
  this.row.className = "row";
  this.row.node = this.node;

  /* 
   * In order to add the row properly, we need to find the TR for
   * the next element after this one and insert this row
   * before that one.
   */
  var nextNode = this.node.getNext();
  var nextNodeRow = (nextNode ? nextNode.getRenderer().getRow() : null);
  if(nextNode && nextNodeRow) {
    if(nextNode.isHeader) {
        nextNodeRow = nextNodeRow.previousSibling;
    }
    this.getTableBody().insertBefore(this.row, nextNodeRow);
  } else {
    this.getTableBody().appendChild(this.row);
  }

  /*
   * Need to get the previous entry in the row to figure out
   * if it needs to add a "down" option
   */
  if(!this.node.isFirst()) {
      var array = (this.node.getType().isAttribute ? this.node.parent.attributes : this.node.parent.children);
      var previousNode = array[array.indexOf(this.node) - 1];
      if(previousNode) {
        previousNode.updateControls();        
      }
  }

  return this.row;
}

NodeInstanceRenderer.prototype.createTextInputField = function(value) {
  var input = document.createElement("INPUT");
  input.type      = "TEXT";
  input.className = "choosable";
  if(value) {
    input.value = value;
  }
  input.size = 50;
  return input;  
}

NodeInstanceRenderer.prototype.createCustomLabelCell = function() {
  var customLabelCell = this.createEmptyCell("label");

  /* Add indentation */
  var indent = this.createIndent();
  if(this.node.getType().isAttribute) {
    indent += "@";
  }
  customLabelCell.innerHTML = indent;
    
  /* Add input cell */ 
  var input = this.createTextInputField();
  input.size = 15;
  input.node = this.node;
  if(this.node.customName) {
    input.value = this.node.customName;
  }
  input.onchange = function() {
    this.value = this.value.trim().replace(/\s/g, '_');
    this.node.customName = this.value;
    this.node.fireChange();
  }  
  customLabelCell.appendChild(input);
 
  return customLabelCell;
}

NodeInstanceRenderer.prototype.showPreview = function() {
  showXml(this.node.toXML(), "XML Preview", "Close");
}

NodeInstanceRenderer.prototype.createPreviewLink = function() {
  var link = document.createElement("A");
  link.className = "link helpArea helpId_document_builder_view_xml";
  link.renderer = this;
  link.node = this.node;
  link.href = "#";
  link.onclick = function(e) {
    if(window.awpxHelp_captureKeyEvent) {
      if(!window.awpxHelp_captureKeyEvent(e)) {
        return false;
      }
    }

    this.renderer.showPreview();
    return false;
  }
  link.innerHTML = "View XML";
  link.title = "XML Preview";
  return link;
}

NodeInstanceRenderer.prototype.createPreviewCell = function() {
  var cell = this.createEmptyCell("preview");
  cell.colSpan = 2;
  cell.style.textAlign = "right";
  cell.appendChild(this.createPreviewLink());
  if(docBuilderDebug) {
    cell.appendChild(this.createHtmlLink());
  }
  return cell;
}

NodeInstanceRenderer.prototype.createHtmlLink = function() {
  var link = document.createElement("A");
  link.className = "link";
  link.renderer = this;
  link.node = this.node;
  link.href = "#";
  link.onclick = function(e) {
    if(window.awpxHelp_captureKeyEvent) {
      if(!window.awpxHelp_captureKeyEvent(e)) {
        return false;
      }
    }
    alert(this.renderer.tbody.innerHTML);
    return false;
  }
  link.innerHTML = "View HTML";
  return link;
}

NodeInstanceRenderer.prototype.createIndent = function() {
  var actualDepth = this.node.getDepth() - 1;
  if(this.node.getType().isAttribute) {
    actualDepth--;
  }
  if(actualDepth < 0) actualDepth = 0;

  var indent = "<span class='indent' title='Depth: " + actualDepth + "'>";
  for(var i=0; i<actualDepth; i++) {
    indent += ".";
  }
  indent += "</span>";
  if(this.node.getType().isAttribute) {
    indent += "&nbsp;&nbsp;&nbsp;&nbsp;";
  }
  return indent;
}

function NodeInstanceShowMenu() {
  this.options = new Array();
  this.options[this.options.length] = new MenuOption(this, "Normal View",     "NORMAL_VIEW");
  this.options[this.options.length] = new MenuOption(this, "Detailed View", "DETAILED_VIEW");

  this.trigger = function(menu) {
      var value = menu.getValue();
      if(value == "NORMAL_VIEW") {
          docBuilderShowNormal();
      } else if(value == "DETAILED_VIEW") {
          docBuilderShowDetailed();
      }
  }

  this.triggerArg = this;
}
NodeInstanceShowMenu.prototype = new Menu();

/*
 * For some reason, this doesn't work property in Mozilla/Netscape, so it's not an 
 * option
 */
NodeInstanceRenderer.prototype.createShowMenu = function() {
  var span = document.createElement("SPAN");
  span.className = "document-builder-show-view helpArea helpId_document_builder_show_view";
  if(!isNS) {
    this.showMenu = new NodeInstanceShowMenu();
    this.showMenu.show(span);
  }
  return span;
}

NodeInstanceRenderer.prototype.createLabelCell = function(label) {
  var labelCell = this.createEmptyCell("label");
  var indent = this.createIndent();

  /*
   * For all labels, we wrap any prefix portion in a <span> tag
   * that is stored so that it can be dynamically hidden
   */
  var idx = label.indexOf(":");
  if(idx > 0) {
    var prefix = label.substring(0, idx + 1);
    var name   = label.substring(idx + 1);
    labelCell.innerHTML = indent + "<span>" + prefix + "</span>" + name;
    var prefixSpan = labelCell.getElementsByTagName("SPAN")[1];
    docBuilderPrefixSpans[docBuilderPrefixSpans.length] = prefixSpan;
    if(!isNS && !docBuilderViewDetailed) {
      prefixSpan.style.display = "none";
    }
  } else {
    labelCell.innerHTML = indent + label;
  }

  /*
   * Add tool tip
   */
  if(this.node.getType().isAttribute) {
    labelCell.title = label.substring(1);
  } else { 
    labelCell.title = label;
  }

  /* 
   * If this is the root node, then we include a "Preview" link
   */
  if(!this.node.parent) {
    labelCell.colSpan = 2;
    labelCell.appendChild(this.createShowMenu());
  }
  return labelCell;
}

NodeInstanceRenderer.prototype.createChoosableText = function(text) {
  var span = document.createElement("SPAN");
  span.className = "choosable";
  span.innerHTML = text;
  return span;
}

/*
 * Menu to handle adding attributes to the current node
 */
function NodeInstanceAttributeMenu(node) {
  this.node = node;

  this.options = new Array();
  this.options[this.options.length] = new MenuOption(this, "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;", "MODIFY", null, null, false, true);

  var attrs = node.getType().getAttributes();
  for(var i=0; i<attrs.length; i++) {
     var option = new MenuOption(this, "Add " + attrs[i].getName(), "ATTR-" + i);
     option.isAttribute   = true;
     option.attributeType = attrs[i];
     this.options[this.options.length] = option;
  }
  this.options[this.options.length] = new MenuOption(this, "Add custom attribute", "CUSTOM_ATTRIBUTE");

  this.trigger = function(menu) {
    var value = menu.getValue();
    if(value == "CUSTOM_ATTRIBUTE") {
      this.node.addAttributeFromType(null, null, true).render(this.node.getRenderer().getTableBody());
    } else {
      var option = menu.selectedOption;
      if(option.isAttribute) {
        var newAttr = option.attributeType.create();
        this.node.addAttribute(newAttr);
	newAttr.render(this.node.getRenderer().getTableBody());
      }
    }

    /* After performing the action we reset the menu to the "MODIFY" setting */
    if(value != "MODIFY") {
      menu.setValue("MODIFY");
    }
  }

  this.triggerArg = this;
}
NodeInstanceAttributeMenu.prototype = new Menu();

/*
 * Menu to handle adding attributes/elements or removing the current node.
 */ 
function NodeInstanceElementMenu(node) {
  this.node = node;

  this.options = new Array();
  this.options[this.options.length] = new MenuOption(this, "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;", "MODIFY", null, null, false, true);

  var children = node.getType().getChildren();
  for(var i=0; i<children.length; i++) {
      var option = new MenuOption(this, "Add " + children[i].getName(), "ELEMENT-" + i);
      option.isChild   = true;
      option.childType = children[i];
      this.options[this.options.length] = option;
  }

  this.options[this.options.length] = new MenuOption(this, "Add simple element", "CUSTOM_SIMPLE_NODE");
  this.options[this.options.length] = new MenuOption(this, "Add complex element", "CUSTOM_COMPLEX_NODE");

  this.trigger = function(menu) {
    var value = menu.getValue();
    if(value == "CUSTOM_SIMPLE_NODE") {
      this.node.addChildFromType(null, null, true, true).render(this.node.getRenderer().getTableBody());
    } else if(value == "CUSTOM_COMPLEX_NODE") {
      this.node.addChildFromType(null, null, true, false).render(this.node.getRenderer().getTableBody());
    } else {
      var option = menu.selectedOption;
      if(option.isChild) {
        var newChild = option.childType.create();
        this.node.addChild(newChild);
	newChild.render(this.node.getRenderer().getTableBody());
      }
    }

    /* After performing the action we reset the menu to the "MODIFY" setting */
    if(value != "MODIFY") {
      menu.setValue("MODIFY");
    }
  }

  this.triggerArg = this;
}
NodeInstanceElementMenu.prototype = new Menu();

NodeInstanceRenderer.prototype.createAttributeMenuCell = function() {
  var attrMenuCell = this.createEmptyCell("controls");

  var span = document.createElement("SPAN");
  span.className = "document-builder-attribute helpArea helpId_document_builder_add_attribute";
  attrMenuCell.appendChild(span);
  this.attrMenu = new NodeInstanceAttributeMenu(this.node);
  this.attrMenu.show(span);
  span.title = "Add Attribute";

  return attrMenuCell;  
}

NodeInstanceRenderer.prototype.createElementMenuCell = function() {
  var elMenuCell = this.createEmptyCell("controls");

  var span = document.createElement("SPAN");
  span.className = "document-builder-element helpArea helpId_document_builder_add_element";
  elMenuCell.appendChild(span);
  this.elMenu = new NodeInstanceElementMenu(this.node);
  this.elMenu.show(span);
  span.title = "Add Element";

  return elMenuCell;  
}

NodeInstanceRenderer.prototype.createControlCell = function() {
  var controlCell = this.createEmptyCell("controls");

  var upSpan = document.createElement("SPAN");
  upSpan.className = "moveUp helpArea helpId_document_builder_moveUp";
  upSpan.node = this.node;
  upSpan.onclick = function(e) {
    if(window.awpxHelp_captureKeyEvent) {
      if(!window.awpxHelp_captureKeyEvent(e)) {
        return false;
      }
    }

    this.node.move(false);
  }
  upSpan.title = "Move Up";
  if(isNS) { upSpan.innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" }
  controlCell.appendChild(upSpan);

  var downSpan = document.createElement("SPAN");
  downSpan.className = "moveDown helpArea helpId_document_builder_moveDown";
  downSpan.node = this.node;
  downSpan.onclick = function(e) {
    if(window.awpxHelp_captureKeyEvent) {
      if(!window.awpxHelp_captureKeyEvent(e)) {
        return false;
      }
    }

    this.node.move(true);
  }
  downSpan.title = "Move Down";
  if(isNS) { downSpan.innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" }
  controlCell.appendChild(downSpan);

  var delSpan = document.createElement("SPAN");
  delSpan.className = "delete helpArea helpId_document_builder_delete";
  delSpan.node = this.node;
  delSpan.onclick = function(e) {
    if(window.awpxHelp_captureKeyEvent) {
      if(!window.awpxHelp_captureKeyEvent(e)) {
        return false;
      }
    }

    this.node.remove();
  }
  delSpan.title = "Delete";
  if(isNS) { delSpan.innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"; }
  controlCell.appendChild(delSpan);

  this.node.downSpan = downSpan;
  this.node.upSpan   = upSpan;

  /* 
   * We need to decide if the up and down are rendered now 
   */
  upSpan.style.visibility = (this.node.isFirst() ? "hidden" : "visible");
  downSpan.style.visibility = (this.node.isLast() ? "hidden" : "visible");

  return controlCell;
}

NodeInstanceRenderer.prototype.createEmptyCell = function(styleClass) {
  var cell = document.createElement("TD");
  cell.className = "rowEntry";
  if(styleClass) {
    cell.className += " " + styleClass;
  }
  return cell;
}


/*
 * Renderer for complex node instances, i.e., those that
 * have attributes and children
 */
function ComplexInstanceRenderer(node) {
  this.node             = node;
  this.tbody            = null;
  this.row              = null;
}
ComplexInstanceRenderer.prototype = new NodeInstanceRenderer();

ComplexInstanceRenderer.prototype.render = function(tbody) {
  if(this.getTableBody()) return;

  if(!this.node.isComplex) {
    alert("ComplexInstanceRenderer can only render complex instance nodes.  Got: " + this.node);
    return;
  }

  /* Store the last place it was rendered */
  this.tbody = tbody;

  /* Render the node itself */
  var row = this.addRow();

  if(this.node.isHeader) {
    /* First we add a spacer row */
    row.className += " spacer-row dialogBg";
    var spacerRowCell = this.createEmptyCell("spacer");
    spacerRowCell.colSpan = "6";
    row.appendChild(spacerRowCell);

    row = this.addRow();
    row.className += " header";
  }

  if(this.node.canAddAttributes) {
      row.appendChild(this.createAttributeMenuCell());
  } else {
      row.appendChild(this.createEmptyCell("controls"));
  }

  if(this.node.canAddElements) {
      row.appendChild(this.createElementMenuCell());
  } else {
      row.appendChild(this.createEmptyCell("controls"));
  }

  if(this.node.isCustom()) {
    row.appendChild(this.createCustomLabelCell());
  } else {
    row.appendChild(this.createLabelCell(this.node.getName()));
  }

  if(!this.node.parent) {
    row.appendChild(this.createPreviewCell());
  } else {
    row.appendChild(this.createEmptyCell("type"));
    row.appendChild(this.createEmptyCell("value"));
    if(this.node.canDelete) {
      row.appendChild(this.createControlCell());
    } else {
      row.appendChild(this.createEmptyCell());
    }
  }

  /* Render attributes */
  this.renderAttributes();

  /* Render children */
  var children = this.node.getChildren();
  for(var i = 0; i < children.length; i++) {
    children[i].render(tbody);
  }
}

/*
 * Renderer for simple node instances, i.e., being able to
 * simple render a value
 */
function SimpleInstanceRenderer(node) {
  this.node             = node;
  this.tbody            = null;
  this.row              = null;
}
SimpleInstanceRenderer.prototype = new NodeInstanceRenderer();

SimpleInstanceRenderer.prototype.getLabel = function() {
  var name = this.node.getName();
  if(this.node.getType().isAttribute) {
    name = "@" + name;
  } 
  return name; 
}

SimpleInstanceRenderer.prototype.render = function(tbody) {
  if(this.tbody) return;

  if(!this.node.isSimple) {
    alert("SimpleInstanceRenderer can only render complex instance nodes.  Got: " + node);
    return;
  }

  /* Store the last place it was rendered */
  this.tbody = tbody;

  /* Render the node itself */
  var row = this.addRow();

  /*
   * If these are certain types of attributes, then store them of later access
   */
  if(this.node.getType().isAttribute) {
    if(this.getLabel().indexOf("@xmlns") == 0 || 
       this.getLabel().indexOf("@xsi")   == 0 ||
       this.getLabel().indexOf("@soap")  == 0) {
      docBuilderAttrRows[docBuilderAttrRows.length] = row;
      if(!isNS && !docBuilderShowDetailed) {
        row.style.display = "none";
      }
    }
  }

  if(this.node.canAddAttributes) {
      row.appendChild(this.createAttributeMenuCell());
  } else {
      row.appendChild(this.createEmptyCell("controls"));
  }

  /* We can't add elements, so just put an empty cell here */
  row.appendChild(this.createEmptyCell("controls"));

  if(this.node.isCustom()) {
    row.appendChild(this.createCustomLabelCell());
  } else {
    row.appendChild(this.createLabelCell(this.getLabel(this.node)));
  }
  row.appendChild(this.createTypeCell());
  row.appendChild(this.createValueCell());
  if(this.node.canDelete) {
    row.appendChild(this.createControlCell());
  } else {
    row.appendChild(this.createEmptyCell());
  }

  /* Render attributes, only for non attribute nodes */
  if(!this.node.getType().isAttribute) {
    this.renderAttributes();
  }
}

SimpleInstanceRenderer.prototype.createTypeCell = function() {
  var typeCell = this.createEmptyCell("type");
  var baseType = this.node.getType().getBaseType();
  if(baseType) {
    typeCell.appendChild(document.createTextNode(baseType));
  }
  return typeCell;
}

/*
 * This method contains all the logic to provide different renders for 
 * the different value types
 */
SimpleInstanceRenderer.prototype.createValueCell = function() {
  var valueCell = this.createEmptyCell("value");

  var valueField = this.createTextInputField(this.node.getValue());
  valueField.node = this.node;
  valueField.onchange = function() {
    this.node.setValue(this.value);
  }
  valueCell.appendChild(valueField);

  return valueCell;
}

//---------------------------------------------------------------
// Simple function to render XML data in popup window
//---------------------------------------------------------------

function docDisplayViewXmlHandler(event,xmlData,targetFrame) {
  if (window.awpxHelp_captureKeyEvent) {
     if(!awpxHelp_captureKeyEvent(event)) {
       return false;
     }
  }       

  showXml(xmlData, 'XML', 'Close', targetFrame);
  return false;
}

function showXml(xmlData, title, close, targetFrame, width, height) {
  if(!targetFrame) {
    targetFrame = "_blank";
  }
  if(!width) {
    width="750";
  }
  if(!height) {
    height="500";
  }

  var html = "";
  if(isMSIE) {

     /* Use standard XSL stylesheet to render XML */
     var oXML = new ActiveXObject("Microsoft.XMLDOM");
     var oXSLT = new ActiveXObject("Microsoft.XMLDOM");
     oXML.async = false;

     var canParse = oXML.loadXML(xmlData);

     if(canParse) {    
       oXSLT.async = false;
       oXSLT.load(gBaseUrl + "/awpx/html/xsl/xml-pretty-print.xsl");
       html = oXML.transformNode(oXSLT);
     } else {
       html += "<html>";
       html += " <head>";
       html += "   <title>" + title + "</title>";
       html += " </head>";
       html += " <body style='{font-size: 11px;}'>";
       html += "  <table border='0' width='100%'>";
       html += "    <tr>";
       html += "      <td align='left'>XML Parse Error</td>";
       html += "      <td align='right'><a style='{font-size: 10px;}' href='#' onclick='window.close();'>" + close + "</a></td>";
       html += "    </tr>";
       html += "  </table>";
       html += "  <h4>" + oXML.parseError.reason + "</h4>";
       html += "  <h4>Line=" + oXML.parseError.line + " Position=" + oXML.parseError.linepos + "</h4>";
       html += "  <xmp id='xmldata' style='{font-size: 11px;}'>";
       html +=      oXML.parseError.srcText;
       html += "  </xmp>";
       html += "<hr/>";
       html += "  <xmp id='xmldata' style='{font-size: 11px;}'>";
       html += xmlData;
       html += "  </xmp>";
       html += " </body>";
       html += "</html>";
     }

  } else {

     html += "<html>";
     html += " <head>";
     html += "   <title>" + title + "</title>";
     html += " </head>";
     html += " <body style='{font-size: 11px;}'>";
     html += "  <table border='0' width='100%'>";
     html += "    <tr>";
     html += "      <td align='left'>" + title + "</td>";
     html += "      <td align='right'><a style='{font-size: 10px;}' href='#' onclick='window.close();'>" + close + "</a></td>";
     html += "    </tr>";
     html += "  </table>";
     html += "  <xmp id='xmldata' style='{font-size: 11px;}'>";
     html += xmlData;
     html += "  </xmp>";
     html += " </body>";
     html += "</html>";

  }

  window.awpxXmlToRender = html;
  var previewWindow = window.open("javascript:document.open('text/html', 'replace'); document.write(opener.awpxXmlToRender); document.close(); window.focus();", targetFrame, "scrollbars=yes,height=" + height + ",width=" + width + ",resizable=yes,location=no,menubar=no,status=no,toolbar=no");

}
