/***********************************************************
 * Abstract Chooser Class
 **********************************************************/

function Chooser() {
}
Chooser.prototype.display = "inline";
Chooser.prototype.show    = function(parent, readOnly) {
   if(this.element) {
       awpxGetElement(this.element).style.display = this.display;
   } else {
       if(!this.changeListeners) {
           this.changeListeners = new Array();
       }
       this.element = this.create(parent, readOnly);

       /* Always keep a reference from the HTML element to the controlling chooser */
       this.element.chooser = this;

       /* Remember if this was read-only or not */
       this.readOnly = readOnly;
   }
}
Chooser.prototype.isShown = function() {
   if(this.element) {
       return (awpxGetElement(this.element).style.display != "none");
   }
   return false;
}
Chooser.prototype.hide    = function() {
   if(this.element) {
       awpxGetElement(this.element).style.display = "none";
   }
}
Chooser.prototype.remove  = function() {
   if(this.element) {
       awpxRemoveElement(this.element);
       this.element = null;
       if(this.onremove) {
           this.onremove();
       }
       this.fireChange();
   }
}
Chooser.prototype.createUniqueId = function() {
    var now = new Date();
    return (now.getTime() % 10000) * 100 + Math.round(100.0*Math.random());
}

/*
 * Register a change listener function
 */
Chooser.prototype.addChangeListener = function(listener) {
  this.changeListeners[this.changeListeners.length] = listener;
}

/*
 * Method that is fired whenever the chooser is updated
 */
Chooser.prototype.fireChange = function() {

  /* 
   * The logic is setup this way because the "this" variable
   * in  a function is defined by the caller of the function
   * which must be this.  If it's done through
   * an array (e.g., this.changeListeners[i]()), then it
   * doesn't work
   */
  if(!this.blockChange) {
    for(var i=0; i<this.changeListeners.length; i++) {
      this.changer = this.changeListeners[i];
      this.changer();
    }
  }
}

/* 
 * Method that must be overridden to create the proper element
 * and return the newly created element.  The new element MUST
 * contain an id.
 *
 * parent: The parent node in which the child can be created.
 *         It's assumed that the child will be appended to the
 *         end of the parent and will not alter the current
 *         children of the parent.
 * readOnly: Boolean which indicates if the chooser should
 *           be rendered as read only or editable.  Defaults
 *           to false, i.e., editable.
 */
Chooser.prototype.create  = function(parent, readOnly) {
   alert(this.toString() + ".create(): NOT IMPLEMENTED YET");
   return null;
}

/*
 * Method that must be overridden to return the current
 * value that will be returned to indicate the current
 * state.  A value of null means that the chooser has
 * no representative value, which is the default behavior.
 */
Chooser.prototype.getValue = function() {
   return null;
}

/*
 * Method that must be overridden to set the current 
 * value to reconstruct it's own state.  The value is 
 * a string and may contain more than what is necessary for
 * just this chooser.  This chooser must extract it's
 * value, which is at the beginning of the value and
 * return the unused portion of the value.
 */
Chooser.prototype.setValue = function(value) {
   alert(this.toString() + ".setValue(): NOT IMPLEMENTED YET");
   return null;
}

/***********************************************************
 ***********************************************************
 * Chooser Subclasses
 ***********************************************************
 **********************************************************/

/***********************************************************
 * Menu Helper Classes
 **********************************************************/

function MenuChooser_Popup(e, opener) {
    if (window.awpxHelp_captureKeyEvent) {
        if(!awpxHelp_captureKeyEvent(e)) {
            return false;
        }
    }

    if(!e) { e = event; }
    if(isNS) {
      var x = e.pageX;
      var y = e.pageY;
    } else {
      var x = e.clientX + document.body.scrollLeft;
      var y = e.clientY + document.body.scrollTop;
    }

    awpxPopupToggleAbsolute(opener.chooser.menu.popupId, x + 4, y + 4);

    /* Prevent any further event bubbling */
    e.cancelBubble = true;

    return false;
}

function MenuChooser(menu, label, value) {
    this.menu = menu;
    this.label = label;
    this.value = value;
}
MenuChooser.prototype = new Chooser();

MenuChooser.prototype.create = function(parent, readOnly) {
    var el = awpxCreateElement(parent, "DIV", "inline");
    if(!readOnly) {
        var openerId = this.menu.openerId;
        var popupId  = this.menu.popupId;

        /*
         * <span class="choosable" id="openerId" onClick="awpxPopupToggle(...)">label</span>
         */
        var html = "";
        html += "<span class=\"choosable MenuChooser\" id=\"" + openerId + "\" ";
        html += "onClick=\"return MenuChooser_Popup(event,this);\">";
        html += this.label;
        html += "</span>";
        el.innerHTML = html;

        var opener = el.getElementsByTagName("SPAN")[0];
        opener.chooser = this;
    } else {
        el.innerHTML = "<span class=\"notchoosable\">" + this.label + "</span>";
    }
    
    return el;
}

MenuChooser.prototype.toString = function() {
    return "MenuChooser";
}

MenuChooser.prototype.getValue = function() {
    return this.value;
}

MenuChooser.prototype.setValue = function(value) {
    if(value.indexOf(this.value) == 0) {
	return value.substring(this.value.length).trim();
    }
    alert(this.toString() + " could not set with value '" + value + "'");
    return null;
}

/*
 * menu: The menu which contains this option
 * label: Label for menu option.  
 * value: The chooser to display if this option has been selected.
 *         - If the value is a string, then it's assumed to be the 
 *           value and a MenuChooser will be created with the same label
 *           and specified value.
 *         - If the value is an array, then a group will automatically
 *           be created.  The first element must be a MenuChooser
 *         - If the value is a group, then the first element must be
 *           a MenuChooser
 * trigger: Optional, function called when menu option is selected.
 * triggerArg: Optional, argument called when the trigger is selected.
 * notSelectable: Optional, indicates if this menu options is user selectable or not
 * notRendered: Optional, indicates if this menu options is shown in the list
 */
function MenuOption(menu, label, value, trigger, triggerArg, notSelectable, notRendered) {
  this.menu       = menu;
  this.label      = label;
  this.trigger    = trigger;
  this.triggerArg = triggerArg;
  this.notSelectable = notSelectable;
  this.notRendered   = notRendered;
  this.element       = null;

  if(typeof value == "string") {
      this.value = new Group([ new MenuChooser(menu, label, value) ]);
  } else if(value.length) {
      this.value = new Group(value);
  } else {
      /* Assumed to be a group */
      this.value = value;
  }

  this.value.parentChooser = this.menu;
}
MenuOption.prototype = new Object();

MenuOption.prototype.show = function() {
  if(this.element) {
    this.element.style.display = "block";
  }
}

MenuOption.prototype.hide = function() {
  if(this.element) {
    this.element.style.display = "none";
  }
}


/* 
 * Menu option separator
 */
function MenuSeparator() {
  this.separator = true;
}

/***********************************************************
 * Menu Object
 **********************************************************/

function Menu(options, selectedIndex, trigger, triggerArg) {
  this.options        = options;
  this.selectedIndex  = (selectedIndex ? selectedIndex : 0);
  this.trigger        = trigger;
  this.triggerArg     = triggerArg;
}

Menu.prototype = new Chooser();

Menu.prototype.toString = function() {
   return "Menu";
}

Menu.prototype.create = function(parent, readOnly) {

    var uniqueId = this.createUniqueId();
    var openerId = "opener-" + uniqueId;
    var popupId = "popup-" + uniqueId;

    this.openerId = openerId;
    this.popupId  = popupId;

    /* Create menu holder */
    var clsName = (this.className ? "Menu " + this.className : "Menu");
    var el = awpxCreateElement(parent, "DIV", "inline", clsName);

    this.menuEl = awpxCreateElement(el, "DIV", "inline", this.menuClassName);
    this.popupEl = awpxCreateElement(el, "DIV", "inline", this.popupClassName);

    /*
     * Render the label.
     */
    this.options[this.selectedIndex].value.show(this.menuEl, readOnly);

    /* Populate menu */
    if(!readOnly)
    {  
        /*
         * <div id="popupId" class="popup">
         *  <table border="0">
         *  <tr onMouseOver="awpxHighlight(AWPX_TRUE, this)"
         *       onMouseOut="awpxHighlight(AWPX_FALSE, this)"
         *          onClick="...">
         *    <td>label</td>
         *  </tr>
         *  ...
         *  </table>
         * </div>
         */
        var html = "<div id=\"" + this.popupId + "\" class=\"popup\">\n";
        html += "<table class=\"choosable-menu\" border=\"0\">\n";        
        for(var i=0; i < this.options.length; i++)
        {
	    if(!this.options[i].separator) {
                if(this.options[i].notSelectable) {
                    html += "<tr class=\"notchoosable\">\n";
                } else {
                    html += "<tr onMouseOver=\"awpxHighlight(AWPX_TRUE, this)\"\n";
                    html += "     onMouseOut=\"awpxHighlight(AWPX_FALSE, this)\"\n";
                    html += "        onClick=\"awpxGetElement('" + this.popupId + "').menu.performSelect(" + i + ");\">\n";
                }
                html += "  <td>" + this.options[i].label + "</td>\n";
                html += "</tr>\n";
	    } else {
                html += "<tr><td class=\"separator\"></td></tr>\n";
	    }
        }
        html += "</table>\n";
        html += "</div>";

        this.popupEl.innerHTML = html;

        var trs = this.popupEl.getElementsByTagName("TR");
        for(var i=0; i < this.options.length; i++)
        {
            this.options[i].element = trs[i];
            if(this.options[i].notRendered) this.options[i].element.style.display = "none";
        }

        /* Hook up selection method onto the DIV popup */
        var popup = this.popupEl.getElementsByTagName("DIV")[0];
        popup.menu = this;
    }

    return el;
}

Menu.prototype.performSelect = function(index,dontFireChange) {
        
   /* 
    * If this is readonly, then just hide the currently selected value
    * and render the selected value 
    */
   if(this.readOnly) {
       this.options[this.selectedIndex].value.hide();
       this.selectedIndex = index;
       this.options[this.selectedIndex].value.show(this.menuEl, this.readOnly);
       return;
   }

   /* Remove previously selected value */
   this.options[this.selectedIndex].value.hide();

   /* Update selected index */
   this.selectedIndex = index;
   this.selectedOption = this.options[this.selectedIndex];

   /* Close popup */
   awpxPopupClose(this.popupId);

   /* Change selected value */
   this.options[this.selectedIndex].value.show(this.menuEl, this.readOnly);

   /* Unselect everything in popup */
   var popup = awpxGetElement(this.popupId);
   awpxHighlight(AWPX_FALSE, awpxGetElementsByClass(popup, AWPX_CLASS_HIGHLIGHT, false));

   /* Fire any triggers */
   var menuOption = this.options[this.selectedIndex];
   if(menuOption.trigger) {
       if(menuOption.triggerArg) {
            menuOption.trigger(menuOption.triggerArg);
       } else {
            menuOption.trigger();
       }
   }

   if(this.trigger) {
       if(this.triggerArg) {
           this.trigger(this.triggerArg);
       } else {
           this.trigger();
       }
   }

   /* Fire change listener */
   if(!dontFireChange) this.fireChange();
}

/* Value of a menu is the menu value plus any follow on (next) values */
Menu.prototype.getValue = function() {
   return this.options[this.selectedIndex].value.getValue();
}

/*
 * Value of the menu is the one which matches the selected value.  Since
 * the values could be substrings of each other, we look for all matches
 * first, then we pick the most specific (i.e., the value with the 
 * longest length).
 */
Menu.prototype.setValue = function(value,dontFireChange) {
   var indexMatches = new Array();

   for(var i=0; i<this.options.length; i++) {
       /* Find the Group then MenuChooser value which matches */
       if(!this.options[i].separator) {
           var group = this.options[i].value;

           var starting = group.getStartValue();
           if(value.indexOf(starting) == 0) {
               indexMatches[indexMatches.length] = i;
           }
       }
   }

   /* If there are multiple matches, we look for the one with the longest value */
   var  matchIndex = -1;
   var matchLength = -1;
   for(var i=0; i<indexMatches.length; i++) {
       var index = indexMatches[i];
       var group = this.options[index].value;
       var groupValueLength = group.getStartValue().length;
       if(groupValueLength > matchLength) {
           matchIndex = index;
           matchLength = groupValueLength;
       }
   }

   if(matchIndex >= 0) {
       this.performSelect(matchIndex,dontFireChange);
       var group = this.options[matchIndex].value;
       return group.setValue(value,dontFireChange).trim();
   }
 
   alert(this.toString() + ".setValue(): No menu value match for '" + value + "'");
   return null;
}

/***********************************************************
 * Time Chooser Object
 **********************************************************/

function TimeChooser(initialValue) {
    this.value = initialValue;
    if(!this.value) {
        this.value = new Date();
    }
}

TimeChooser.prototype = new Chooser();

TimeChooser.prototype.getHours = function() {
    var hours = this.value.getHours();
    if(hours > 12) {
        hours = hours - 12;
    } else if(hours == 0) {
        hours = 12;
    }
    if(hours < 10) {
        hours = "0" + hours;
    }
    return hours;
}

TimeChooser.prototype.setHours = function(hours,dontFireChange) {
    var nHours = parseInt(hours, 10);

    if(isNaN(nHours)) {
        this.hoursInput.value = this.getHours();
        return false;
    }

    while(nHours > 23) {
        nHours -= 24;
    }
    while(nHours < 0) {
	nHours += 24;
    }

    if(nHours > 12) {
	nHours -= 12;
        this.setAmPm("PM");
    }

    if(this.isPm()) {
        if(nHours < 12) {
            this.value.setHours(nHours + 12);
        } else {
            this.value.setHours(nHours);
        }
    } else {
        if(nHours < 12) {
            this.value.setHours(nHours);
        } else {
            this.value.setHours(0);
        }
    }
    if(this.hoursInput) {
        this.hoursInput.value = this.getHours();
    }

    if(!dontFireChange) {
        this.fireChange();
    }
}

TimeChooser.prototype.setMinutes = function(minutes,dontFireChange) {
    var mins = parseInt(minutes, 10);

    if(isNaN(mins)) {
        this.minsInput.value = this.getMinutes();
        return false;
    }

    while(mins > 59) {
	mins -= 60;
    }
    while(mins < 0) {
	mins += 60;
    }
    this.value.setMinutes(mins);
    if(this.minsInput) {
        this.minsInput.value = this.getMinutes();
    }

    if(!dontFireChange) {
        this.fireChange();
    }
}

TimeChooser.prototype.setSeconds = function(seconds,dontFireChange) {
    var secs = parseInt(seconds, 10);

    if(isNaN(secs)) {
        this.secsInput.value = this.getSeconds();
        return false;
    }

    while(secs > 59) {
	secs -= 60;
    }
    while(secs < 0) {
	secs += 60;
    }
    this.value.setSeconds(secs);
    if(this.secsInput) {
        this.secsInput.value = this.getSeconds();
    }

    if(!dontFireChange) {
        this.fireChange();
    }
}

TimeChooser.prototype.toggleAmPm = function() {
    this.setAmPm((this.isPm() ? "AM" : "PM"));
}

TimeChooser.prototype.setAmPm = function(ampm,dontFireChange) {
    if(ampm == "AM" || ampm == "am") {
        if(this.isPm()) {
            this.value.setHours(this.value.getHours() - 12);
        }
        if(this.ampmSelect) {
            this.ampmSelect.selectedIndex = 0;
        }
    } else {
        if(!this.isPm()) {
            this.value.setHours(this.value.getHours() + 12);
        }
        if(this.ampmSelect) {
            this.ampmSelect.selectedIndex = 1;
        }
    }

    if(!dontFireChange) {
        this.fireChange();
    }
}

TimeChooser.prototype.getMinutes = function() {
    var mins = this.value.getMinutes();
    if(mins < 10) {
        mins = "0" + mins;
    }
    return mins;
}

TimeChooser.prototype.getSeconds = function() {
    var secs = this.value.getSeconds();
    if(secs < 10) {
        secs = "0" + secs;
    }
    return secs;
}

TimeChooser.prototype.isPm = function() {
    return (this.value.getHours() > 11);
}

TimeChooser.prototype.getFormattedValue = function() {
    var hours = this.getHours();
    var mins  = this.getMinutes();
    var secs  = this.getSeconds();
    var ampm  = (this.isPm() ? "PM" : "AM");
    return hours + ":" + mins + ":" + secs + " " + ampm;
}

TimeChooser.prototype.create = function(parent, readOnly) {
    var clsName = (this.className ? "TimeChooser " + this.className : "TimeChooser");
    var el = awpxCreateElement(parent, "DIV", "inline", clsName);

    if(readOnly) {
        el.innerHTML = "<span class=\"notchoosable time\">" + this.getFormattedValue() + "</span>";
    } else {
        var html = "<nobr>";
        html += "<input type=\"text\" class=\"choosable time\" value=\"" + this.getHours() + "\"" +
                       " onChange=\"this.chooser.setHours(this.value);\"/>";
        html += "<strong>:</strong>";
        html += "<input type=\"text\" class=\"choosable time\" value=\"" + this.getMinutes() + "\"" +
                       " onChange=\"this.chooser.setMinutes(this.value);\"/>";
        html += "<strong>:</strong>";
        html += "<input type=\"text\" class=\"choosable time\" value=\"" + this.getSeconds() + "\"" +
                       " onChange=\"this.chooser.setSeconds(this.value);\"/>";
        html += "<select class=\"choosable time\"" +
                       " onChange=\"this.chooser.setAmPm(this.options[this.selectedIndex].value);\">";
        if(this.isPm()) {
          html += "  <option value=\"AM\">AM</option>\n";
          html += "  <option value=\"PM\" selected=\"selected\">PM</option>\n";
        } else {
          html += "  <option value=\"AM\" selected=\"selected\">AM</option>\n";
          html += "  <option value=\"PM\">PM</option>\n";
        }
        html += "</select>\n";
        html += "</nobr>\n";
        el.innerHTML = html;

        /* Ensure that any updates are propagated to the value */
        var inputs = el.getElementsByTagName("INPUT");
	this.hoursInput = inputs[0];      
        this.minsInput = inputs[1];
        this.secsInput = inputs[2];
        this.ampmSelect = el.getElementsByTagName("SELECT")[0];

        this.hoursInput.chooser = this;
        this.minsInput.chooser = this;
        this.secsInput.chooser = this;
        this.ampmSelect.chooser = this;
    }
    return el;
}
TimeChooser.prototype.toString = function() {
   return "TimeChooser[" + this.value + "]";
}

TimeChooser.prototype.getValue = function() {
   return "'" + this.getFormattedValue() + "'";
}

/* Value is in the form of a string "'HH:MM:SS {AM|PM}'" */
TimeChooser.prototype.setValue = function(value,dontFireChange) {
   this.blockChange = dontFireChange;
   if(typeof value == "string") {
     var re = /'(..?):(..?):(..?) ([aApP][mM])'/;
     value.search(re);
     this.setHours(RegExp.$1,true);
     this.setMinutes(RegExp.$2,true);
     this.setSeconds(RegExp.$3,true);
     this.setAmPm(RegExp.$4,true);
   } else {
     this.value = value;
     this.setAmPm((this.isPm() ? "PM" : "AM"), true);
     this.setHours(this.getHours(),true);
     this.setMinutes(this.getMinutes(),true);
     this.setSeconds(this.getSeconds(),true);
   }
   this.blockChange = false;
   if(!dontFireChange) this.fireChange();
   return RegExp.rightContext.trim();
}

/***********************************************************
 * Simple static text
 **********************************************************/

function StaticText(text, value) {
    this.text = text;
    this.value = value;
    if(!this.value) {
        this.value = this.text;
    }
}
StaticText.prototype = new Chooser();

StaticText.prototype.create = function(parent, readOnly) {
    var el = awpxCreateElement(parent, "DIV", "inline", this.className);
    el.innerHTML = "<span class=\"notchoosable StaticText\">" + this.text + "</span>";
    return el;
}
StaticText.prototype.toString = function() {
    return "StaticText[" + this.text + "]";
}
StaticText.prototype.getValue = function() {
    return this.value;
}
StaticText.prototype.setValue = function(value,dontFireChange) {
    if(value.indexOf(this.value) == 0) {

        /* Single change event */
        if(!dontFireChange) this.fireChange();

	return value.substring(this.value.length).trim();
    }
    alert(this.toString() + " could not set with value '" + value + "'");
    return null;
}

/***********************************************************
 * Grouping
 **********************************************************/

function Group(elements) {
    this.separator = " ";
    this.elements = elements;
}
Group.prototype = new Chooser();

Group.prototype.create = function(parent, readOnly) {

    /* Create menu holder */
    var clsName = (this.className ? "Group " + this.className : "Group");
    var el = awpxCreateElement(parent, "DIV", "inline", clsName);

    for(var i=0; i<this.elements.length; i++) {
        this.elements[i].show(el, readOnly);

        /* For each, we add a change listener to indicate to us if it changed */
        this.elements[i].group = this;
        this.elements[i].addChangeListener(function() {
          this.group.fireChange();
        });
    }

    /* If we have a parent chooser, then we ensure to fire it's change when we're changed */
    if(this.parentChooser) {
        this.addChangeListener(function() {
            this.parentChooser.fireChange();
        });
    }

    return el;
}

Group.prototype.onremove = function() {
    for(var i=0; i<this.elements.length; i++) {
        this.elements[i].remove();
    }
}

Group.prototype.toString = function() {
    return "Group";
}

Group.prototype.getRawValue = function() {
   var value = "";
   for(var i=0; i<this.elements.length; i++) {
       if(i != 0) {
          value += this.separator;
       }
       value += this.elements[i].getValue();
   }
   return value;
}

Group.prototype.getStartValue = function() {
   return this.elements[0].getValue();
}

/* Group value is ordered list of element values */
Group.prototype.getValue = function() {
   if(!this.elements) {
       return null;
   }

   return this.getRawValue();
}

Group.prototype.setValue = function(value,dontFireChange) {
   var curValue = value.trim();

   for(var i=0; i<this.elements.length; i++) {
       curValue = this.elements[i].setValue(curValue,dontFireChange).trim();
       if(this.separator != " " && i < this.elements.length - 1) {
           curValue = curValue.substring(this.separator.length);
       }
   }

   /* Fire change event */
   if(!dontFireChange) this.fireChange();

   return curValue.trim();
}

/***********************************************************
 * Groups that represent a function value
 **********************************************************/

function FunctionGroup(funcName) {
   this.funcName = funcName;
}
FunctionGroup.prototype = new Group();

FunctionGroup.prototype.getStartValue = function() {
   var rawValue = this.elements[0].getValue();
   return this.funcName + "(" + rawValue;
}

FunctionGroup.prototype.getValue = function() {
   var value = this.funcName + "(";
   for(var i=0; i<this.elements.length; i++) {
       if(i != 0) {
           value += ",";
       }
       value += this.elements[i].getValue();
   }
   value += ")";
   return value;
}

FunctionGroup.prototype.setValue = function(value,dontFireChange) {
   var curValue = value.trim();
   var re = /([^,]+)\s*([,\)])/;

   if(curValue.indexOf(this.funcName + "(") == 0) {
        curValue = curValue.substring(this.funcName.length + 1).trim();
        
        for(var i=0; i<this.elements.length; i++) {
            curValue = this.elements[i].setValue(curValue,dontFireChange).trim();

            // Strip trailing "," or ")"
            curValue = curValue.substring(1).trim();
        }

        /* Fire change event */
        if(!dontFireChange) this.fireChange();

        return curValue;
    }
    alert(this.toString() + " could not be set with value '" + value + "'");
    return null;
}

FunctionGroup.prototype.toString  = function() {
    return "FunctionGroup";
}

/***********************************************************
 * Link Chooser
 **********************************************************/

function LinkChooser_action(e,link) {
    if (window.awpxHelp_captureKeyEvent) {
        if(!awpxHelp_captureKeyEvent(e)) {
            return false;
        }
    }

    link.chooser.performAction(e);
    return false;
}

/*
 * Displays a link that performs a certain JavaScript action 
 * when clicked.  In read only mode, nothing is rendered.
 * 
 * text; Text to be rendered by the link
 * action: JavaScript function to call when clicked
 * actionArg: Optional argument to the action function.
 */
function LinkChooser(text, action, actionArg) {
    this.text      = text;
    this.action    = action;
    this.actionArg = actionArg;
}

LinkChooser.prototype = new Chooser();

LinkChooser.prototype.create = function(parent, readOnly) {
    var el = awpxCreateElement(parent, "DIV", "inline", this.className);
    if(!readOnly) {
        var id = "link-" + this.createUniqueId();
        el.innerHTML = "<a class=\"choosable LinkChooser\" href=\"javascript:void(0);\" id=\"" + id + 
	        "\" onClick=\"return LinkChooser_action(event,this);\">" + this.text + "</a>";

        var anchor = el.getElementsByTagName("A")[0];
        anchor.chooser = this;
    }
    return el;
}

LinkChooser.prototype.performAction = function(e) {
    if(this.actionArg) {
        this.action(this.actionArg);
    } else {
        this.action();
    }        
}

LinkChooser.prototype.toString = function() {
    return "LinkChooser[" + this.text + "]";
}

/***********************************************************
 * Dynamic Adder
 **********************************************************/

function DynamicAdderEntry_removeClause(entry) {
    entry.adder.remove(entry);
}

/*
 * This is a single entry to add to a list of dynamic entries 
 *
 * adder: Parent DynamicAdder class
 * opLabel: The label of the operation to show, e.g., and
 * opValue: The value of the operation, e.g., "AND"
 * removeNextLabel: The label for the "remove next" menu entry
 * clause: The clause that has been added
 * lineBreakBetween: If true, then add linebreak before this entry
 */
function DynamicAdderEntry(adder, opLabel, opValue, removeNextLabel, clause, lineBreakBetween) {
    this.adder            = adder;
    this.opLabel          = opLabel;
    this.opValue          = opValue;
    this.removeNextLabel  = removeNextLabel;
    this.clause           = clause;
    this.lineBreakBetween = lineBreakBetween;
}

DynamicAdderEntry.prototype = new Chooser();

DynamicAdderEntry.prototype.create = function(parent, readOnly) {
    var el = awpxCreateElement(parent, "DIV", "inline", this.className);
    
    /* Add line break after, if necessary */ 
    if(this.lineBreakBetween) {
        var spacer = awpxCreateElement(el, "DIV", "block");
        spacer.className = "choosable-spacer";
    }

    if(!readOnly) {

        /* Create "and" type menu */
        this.addMenu = new Menu();
        this.addMenu.options = [ new MenuOption(this.addMenu, this.opLabel, this.opValue),
                                 new MenuOption(this.addMenu, this.removeNextLabel, "REMOVE_NEXT", 
                                                DynamicAdderEntry_removeClause, this ) ];
        this.addMenu.show(el, readOnly);
        this.addMenu.menuEl.className = "DynamicAdderEntryMenu";

        /* Add clause */
        this.clause.show(el, readOnly);

        /* Add change listener so that this is notified whenever a change occurs */
        this.clause.dynamicAdderEntry = this;
        this.clause.addChangeListener(function() {
           this.dynamicAdderEntry.fireChange();
        });

    } else {
        var readOnlyVersion = new Group([ new StaticText(this.opLabel), this.clause ]);
        readOnlyVersion.show(el, readOnly);
    }

    return el;
}

DynamicAdderEntry.prototype.toString = function() {
    return "DynamicAdderEntry[" + this.opLabel + "]";
}

/* The value is the operation value plus the value of clause, only if shown */
DynamicAdderEntry.prototype.getValue = function() {
    if(this.isShown()) {
        return this.opValue + " " + this.clause.getValue();
    }
    return null;
}

DynamicAdderEntry.prototype.setValue = function(value,dontFireChange) {
    var curValue = value;
    if(curValue.indexOf(this.opValue) == 0) {
	curValue = curValue.substring(this.opValue.length).trim();

        /* Fire change event */
        if(!dontFireChange) this.fireChange();

        return this.clause.setValue(curValue,dontFireChange).trim();
    }
    alert(this.toString() + " could not set with value '" + value + "'");
    return null;
}

function DynamicAdder_add(adder) {
    adder.add();
}

/*
 * This is the list of dynamic adder entries.  The arguments are
 * used to construct the dynamic adder entries.
 *
 * addText: Text to show that, when clicked, will cause an "and" clause
 *          to appear
 * opLabel: The label of the operation to show, e.g., and
 * opValue: The value of the oepration, e.g., "AND"
 * removeNextLabel: The label for the "remove next" menu entry
 * nextName: The type of the chooser to use to add when the addText 
 *       has been clicked.  This is done dynamically, so this 
 *       must be a simple string name, e.g., "CriterionChooser".
 * lineBreakBetween: Add line breaks between clauses
 */
function DynamicAdder(addText, opLabel, opValue, removeNextLabel, nextName, lineBreakBetween) {
    this.opLabel          = opLabel;
    this.opValue          = opValue;
    this.removeNextLabel  = removeNextLabel;
    this.nextName         = nextName;
    this.elements         = [];
    this.addLink          = new LinkChooser(addText, DynamicAdder_add, this);
    this.lineBreakBetween = lineBreakBetween;
}  

DynamicAdder.prototype = new Group();

DynamicAdder.prototype.create = function(parent, readOnly) {

    /* Create menu holder and make it choosable */
    var clsName = (this.className ? "choosable DynamicAdder " + this.className : "choosable DynamicAdder");
    var el = awpxCreateElement(parent, "DIV", "inline", clsName);

    this.clauseEl = awpxCreateElement(el, "DIV", "inline");
    for(var i=0; i<this.elements.length; i++) {
        this.elements[i].show(this.clauseEl, readOnly);
    }

    this.addLink.show(el, readOnly);

    return el;
}

/* Adds an entry to the end of the list */
DynamicAdder.prototype.add = function(dontFireChange) { 
    var entry = new DynamicAdderEntry(this, this.opLabel, this.opValue, 
                                      this.removeNextLabel, new LazyChooser(this.nextName),
                                      this.lineBreakBetween);
    this.elements.push(entry);
    entry.show(this.clauseEl, this.readOnly);

    /* Ensure dynamic adder knows when it's elements have been updated */
    entry.dynamicAdder = this;
    entry.addChangeListener(function() {
      this.dynamicAdder.fireChange();
    });

    /* Fire change event */
    if(!dontFireChange) this.fireChange();

    return entry;
}

/* Removes all the entries from the list */
DynamicAdder.prototype.removeAll = function(dontFireChange) {
    for(var i=0; i<this.elements.length; i++) {
        this.elements[i].remove();
    }
    this.elements = new Array();
    if(!dontFireChange) this.fireChange();
}

/* Remove a given entry from the list. */
DynamicAdder.prototype.remove = function(entry,dontFireChange) {
    var indexToRemove = -1;
    for(var i=0; i<this.elements.length; i++) {
        if(this.elements[i] == entry) {
            indexToRemove = i;
        }
    }

    if(indexToRemove != -1) {
        entry.remove();
        if(indexToRemove == 0) {
            if(this.elements.length > 1) {
                this.elements = this.elements.slice(1);
            } else {
                this.elements.length = 0;
            }
        } else if(indexToRemove == this.elements.length - 1) {
            this.elements = this.elements.slice(0, -1);
        } else {
            var before = this.elements.slice(0, indexToRemove);
            var after  = this.elements.slice(indexToRemove + 1);
            this.elements = before.concat(after);
        }
    }

    /* Fire change event */
    if(!dontFireChange) this.fireChange();
}

DynamicAdder.prototype.toString = function() {
    return "DynamicAdder";
}

DynamicAdder.prototype.setValue = function(value,dontFireChange) {

    /* If it starts with the "adder" value, then add it */
    var curValue = value;

    while(curValue.indexOf(this.opValue) == 0) {

        /* Add the dynamic part */
        var newEntry = this.add(dontFireChange);

        /* Extract all values */
        curValue = newEntry.setValue(curValue,dontFireChange).trim();
    }

    /* Fire change event */
    if(!dontFireChange) this.fireChange();

    return curValue;
}

/***********************************************************
 * Lazily Loaded Chooser
 **********************************************************/
function LazyChooser(type) {
    this.type = type;
}

LazyChooser.prototype = new Chooser();

LazyChooser.prototype.create = function(parent, readOnly) {
    this.chooser = eval("new " + this.type + "();");
    this.chooser.show(parent, readOnly);
    this.chooser.lazyParent = this;

    /* Add an onchange listener that simply fires the onchange of this chooser */
    this.chooser.addChangeListener(function() {
        this.lazyParent.fireChange();
    });

    /* We return the element that was created by the chooser */
    return this.chooser.element;
}

LazyChooser.prototype.toString = function() {
    return "LazyChooser[" + this.type + "]";
} 

/* Lazy value is the value of the embedded type */
LazyChooser.prototype.getValue = function() {
    if(this.chooser) {
        return this.chooser.getValue();
    }
    return null;
}

LazyChooser.prototype.setValue = function(value,dontFireChange) {
    if(value != null && !this.chooser) {
        // FIXIT: Does this case happen?
        return null;
    } else {
        return this.chooser.setValue(value,dontFireChange);
    }
}

/***********************************************************
 * Text Box Entries
 **********************************************************/
function TextEntry(value, size, quote, isNumeric) {
    this.value = value;
    this.size  = size;
    this.quote = quote;
    this.isNumeric = isNumeric;
}
TextEntry.prototype = new Chooser();

TextEntry.prototype.create = function(parent, readOnly) {
    var el = awpxCreateElement(parent, "DIV", "inline", this.className);
    if(!readOnly) {
        this.input = document.createElement("INPUT");
        this.input.type      = "TEXT";
	this.input.size      = this.size;
        this.input.className = "choosable TextEntry";
	if(this.value) {
	    this.input.value = this.value;
        }
        this.input.chooser = this;
        this.input.onchange = function() {
            if(this.chooser.validator) {
              if(!this.chooser.validator(this.value)) {
                  this.value = this.chooser.value;
                  return false;
              }
            }
            this.chooser.value = this.value;
            this.chooser.fireChange();
	}
        el.appendChild(this.input);
    } else {
        el.innerHTML = "<span class=\"notchoosable\">" + this.value + "</span>";
    }
    return el;
}

TextEntry.prototype.toString = function() {
    return "TextEntry[" + this.value + "]";
}

TextEntry.prototype.getValue = function() {
    if(this.quote) {
        return "'" + this.value + "'";
    } else {
        return this.value;
    }
}
TextEntry.prototype.setValue = function(value,dontFireChange) {
   var  newValue = null;
   var remainder = null;

   if(this.quote) {
       var re = /'([^']*)'/;
       var startIndex = value.search(re);
       newValue = RegExp.$1;
       remainder = value.substring(startIndex + newValue.length + 2).trim();
   } else {
       var re = /(\S+)/;
       var startIndex = value.search(re);
       newValue = RegExp.$1;
       remainder = value.substring(startIndex + newValue.length).trim();
   }
   this.value = newValue;
   if(this.input) {
       this.blockChange = dontFireChange;
       this.input.value = this.value;
       this.blockChange = false;
   } else {
       if(this.readOnly) {
           this.element.innerHTML = "<span class=\"notchoosable\">" + newValue + "</span>";
       }
   }

   /* Indicate a change */
   if(!dontFireChange) this.fireChange();

   return remainder;            
}

TextEntry.prototype.setValidator = function(validator) {
  this.validator = validator;
}

function NumericEntry(value, size) {
   if(value) {
       this.value = value;
   }
   if(size) {
       this.size = size;
   }

   this.setValidator(function(value) {
       return !isNaN(value);
   });
}
NumericEntry.prototype = new TextEntry("0", "8", false, true);

function StringEntry(value, size) {
   if(value) { 
       this.value = value;
   }
   if(size) {
       this.size = size;
   }
}
StringEntry.prototype = new TextEntry("", "10", true, false);
