
(function(){

  var Dom = YAHOO.util.Dom,
    Event = YAHOO.util.Event,
    Selector = YAHOO.util.Selector,
    applegate = YAHOO.applegate,
    global = YAHOO.applegate.global,
    lang = YAHOO.lang;

  YAHOO.namespace("applegate.autocomplete");

  /**
   * Dwr backed data source for use with AutoComplete widgets.
   * @param method
   * @param args
   * @param cfg
   */
  applegate.autocomplete.DwrAutoCompleteDataSource = function(method, args, cfg){
    applegate.autocomplete.DwrAutoCompleteDataSource.superclass.constructor.call(this, method, args, lang.merge({
      appendRequest:true
    },cfg));
  };

  YAHOO.lang.extend(applegate.autocomplete.DwrAutoCompleteDataSource, applegate.dwr.DwrDataSource);

//  /**
//   * @method doQuery
//   * @param oCallbackFn {HTMLFunction} Callback function implemented by oParent to which to return results.
//   * @param sQuery {String} Query string.
//   * @param oParent {Object} The object instance that has requested data.
//   */
//  applegate.autocomplete.DwrAutoCompleteDataSource.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
//    // copy the args... and add the last parameter as the dwr options block
//    var args = global.copyArray(this._dwrArgs);
//    args.push(decodeURIComponent(sQuery));
//    args.push({
//        callback:global.bind(function(response){
//          global.forEach(response, function(v) {
//            this.updateDisplayValue(v);
//            return this._dwrCfg.transformer(v);
//          }, this, true);
//          this.getResultsEvent.fire(this, oParent, sQuery, response);
//          oCallbackFn(sQuery, response, oParent);
//        }, this)
//    });
//    // run the request
//    this._dwrMethod.apply(this, args);
//  };
//
//  applegate.autocomplete.DwrAutoCompleteDataSource.prototype.updateDisplayValue = function(item){
//    // YUI AutoComplete expects the displayValue to be in position[0]
//    item[0] = this.formatDisplayValue(item) || 'configure displayField';
//  }
//
//  applegate.autocomplete.DwrAutoCompleteDataSource.prototype.formatDisplayValue = function(item){
//    return item[this._dwrCfg['displayField']];
//  }
//
//  applegate.autocomplete.DwrAutoCompleteDataSource.prototype.setDwrArguments = function(args){
//    this._dwrArgs = args || [] ;
//  }

  // AutoComplete (applegate.autocomplete) ====================================


  /**
   * Custom version of the YAHOO.widget.AutoComplete control.
   * Adds some defaults that are sensible for Applegate, including a formatResult implementation
   * that allows a config option of agSuggestProperties:['field1','field2'] (note 'ag' prefix) this will
   * access the properties of the result object and produce a suggestion with mutliple items seperated
   * in there own spans. Note if agSuggestFields isn't specified the default formatResult is used.
   * extra config options:
   * agSelectProperties - Array of property names to search when choosing the content of the input after selection
   *                      will use each matched entry in the order they are presented
   * agSuggestProperties - Array of property names to output in the suggestion, in the order they are presented
   * agTrackingField - object {field:id||reference, property:object property} will update the field
   *                   with the property value of the selected item
   * agAllowEmptyQuery - boolean true and pressing 'down' on an empty will fire a query populate suggestions
   *                     note is useless if minQueryLength is not zero as well.
   * @param elInput
   * @param elContainer
   * @param oDataSource
   * @param oConfigs
   */
  applegate.autocomplete.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
    applegate.autocomplete.AutoComplete.superclass.constructor.call(this, elInput, elContainer, oDataSource,
        lang.merge({useIFrame:true},oConfigs));
    // only attach the customer formatResult if asked for
    if( this.agSuggestProperties){
      this.formatResult = applegate.autocomplete.formatResult;
    }
    // If no select properties are defined, then use the defaults
    if (this.agSelectProperties==null) {
      this.agSelectProperties = ["name", "description", "displayName"];
    }
    if( this.agTrackingField ){
      //noinspection JSUnusedLocalSymbols
      this.itemSelectEvent.subscribe(function(sType, args){
        var data = args[2];
        var field = Dom.get(this.agTrackingField.field);
        field.value = data[this.agTrackingField.property];
      },this, true);
    }
    if( this.agAllowEmptyQuery ) {
      // note the scope isn't overridden
      Event.on(elInput, 'keyup', this.handleKeyUpAllowEmptyQuery, this);
    }

    //noinspection JSUnusedLocalSymbols
    this.itemSelectEvent.subscribe(function(sType, args){
      var data = args[2];
      this._elTextbox.value = this.formatSelectedItem(data);
    },this, true);

    this.animSpeed = 0.1;
    this.containerCollapseEvent.subscribe(this.removeZIndexFromContainer, this, true);
    // 2.6.0 compat, forces the formatResult sig to be old style object literal
    this.resultTypeList = false ;

    var parent = Dom.get(elContainer).parentNode ;
    if( Dom.hasClass(parent, 'yui-ac')){
      Dom.addClass(parent, 'applegate-ac');
    }

  };
  // extends YAHOO's AutoComplete
  lang.extend(applegate.autocomplete.AutoComplete, YAHOO.widget.AutoComplete);

  // note this function is not defined on the AutoComplete prototype
  // this is because it is optional and only used if agSuggestProperties is specified
  //noinspection JSUnusedLocalSymbols
  applegate.autocomplete.formatResult = function(item, query){
    var markup = "";
    global.forEach( this.agSuggestProperties, function(f){
      // Do some formatting (include span to allow for custom CSS layouts)
      if (item[f]===false || item[f]===true) {
        // Booleans require the field name to be shown or they are meaningless
        if (item[f]===true) {
          markup = markup + lang.substitute("<span class='suggestion-{field}-true'>({field}: {value}) </span>", {field:f,value:'Yes'});
        } else {
          markup = markup + lang.substitute("<span class='suggestion-{field}-false'>({field}: {value}) </span>", {field:f,value:'No'});
        }
      } else {
        if (item[f] !=null) {
          markup = markup + lang.substitute("<span class='suggestion-{field}'>{value} </span>", {field:f,value:item[f]});
        } else {
          // To allow for a complete CSS format
          markup = markup + lang.substitute("<span class='suggestion-{field}'></span>", {field:f});
        }
      }
    });
    return markup;
  };

  /**
   * Overridable method to format the text that gets inserted into the text field on selection.
   * Default is to select from choice of 'name', 'description' or 'displayName' with first one
   * encountered being used. If the object does not contain any of these fields then the developer
   * is invited to supply an entry in agSelectProperties
   * @param item model data object.
   */
  applegate.autocomplete.AutoComplete.prototype.formatSelectedItem = function(item){
    var result="";
    for(var i = 0 ; i < this.agSelectProperties.length ; i ++ ) {
      if( !lang.isUndefined(item[this.agSelectProperties[i]]))
        if (item[this.agSelectProperties[i]] !=null) {
          if (result!="") {
            result = result + " ";
          }
          result=result+item[this.agSelectProperties[i]];
        }
    }
    if (result!="") {
      return result;
    }
    return "formatSelectedItem :" + lang.dump(item);
  };

  //noinspection JSUnusedLocalSymbols
  applegate.autocomplete.AutoComplete.prototype.doBeforeExpandContainer = function( elTextbox , elContainer , sQuery , aResults ){
    YAHOO.log("doBeforeExpandContainer","debug", "global-autocomplete.js");
    // IE doesn't like having the z-index changed, so you must rely on the *-ie.css conditional import style sheet.
    if( YAHOO.env.ua.ie === 0)
      Dom.setStyle(Dom.getAncestorByClassName(elContainer, 'yui-ac'),'z-index', 9999);

    return true;
  };

  applegate.autocomplete.AutoComplete.prototype.removeZIndexFromContainer = function(){
    YAHOO.log("removeZIndexFromContainer","debug", "global-autocomplete.js");
    // reverse the effects of the above
    if( YAHOO.env.ua.ie === 0)
      Dom.setStyle(Dom.getAncestorByClassName(this._elContainer, 'yui-ac'),'z-index', "");
  };

  applegate.autocomplete.AutoComplete.prototype.handleKeyUpAllowEmptyQuery = function(e, oSelf){
    // note this is bound to the sender ...ie *this* is the HTMLInputElement
    var sText = lang.trim(this.value) ;
    var key = e.keyCode;
    // key down, with no text and the container is not already open
    if( key === global.key.DOWN
        && sText === ""
        && !oSelf._bContainerOpen){
      oSelf.sendQuery("");
    }
  };

  // MultipleEntryAutoComplete ================================================

  /**
   * Multiple entry autocompleting field, aka the Facebook control.
   *
   * @param elContainer
   * @param sFieldName
   * @param oDataSource
   * @param oConfigs
   * @param oAcConfig
   */
  applegate.autocomplete.MultipleEntryAutoComplete = function(elContainer, sFieldName, oDataSource, oConfigs, oAcConfig){

    this.cfg = lang.merge({
      tabindex:-1,
      allowNewValues:false,
      displayField:0 // defualts to first element
    }, oConfigs || {});

    this.acCfg = lang.merge({
      useIFrame:true
    }, oAcConfig || {});

    // prevent our internal auto complete from having a trackingField
    if(this.acCfg.agTrackingField) delete this.acCfg.agTrackingField ;

    this.container = Dom.get(elContainer);
    this.fieldname = sFieldName;
    this.dataSource = oDataSource;
    this.tabindex = this.cfg.tabindex;
    this.selection = new Array();

    // custom events
    this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelectEvent", this);
    this.beforeItemSelectEvent = new YAHOO.util.CustomEvent("beforeItemSelectEvent", this);

    this.newValueSelectEvent = new YAHOO.util.CustomEvent("newValueSelectEvent", this);
    this.beforeNewValueSelectEvent = new YAHOO.util.CustomEvent("beforeNewValueSelectEvent", this);

    this.itemRemoveEvent = new YAHOO.util.CustomEvent("itemRemoveEvent", this);
    this.beforeItemRemoveEvent = new YAHOO.util.CustomEvent("beforeItemRemoveEvent", this);
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype._element = function(query){
    return Selector.query(query, this.container, true);
  };
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.getInternalContainer = function(){
    return this._element('.applegate-meac-container');
  };
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.getEditField = function(){
    return this._element('input.edit');
  };
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.getFieldWrapper = function(){
    return this._element('.field-wrapper');
  };
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.getSuggest = function(){
    return this._element('.suggest');
  };
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.getTokensContainer = function(){
    return this._element('.tokens');
  };
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.getFieldsContainer = function(){
    return this._element('.fields');
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype.renderField = function(){
    this.container.innerHTML = lang.substitute("<div class='applegate-meac'>" +
                               "<div class='applegate-meac-container applegate-validation-container clearfix'>" +
                               "<span class='stretcher'>^_^</span>" +
//                               "<span class='tokens'></span>"+
                               "<input type='text' class='edit' name='{fieldname}_edit' tabindex='{tabindex}'/>" +
                               "</div>" +
                               "<div class='fields' style='display:none;'></div>" +
                               "<div class='suggest'></div>" +
                               "</div>", this);

    Event.on(this.container, 'click', function(){ this.getEditField().focus(); }, this, true);
    //this.autocompleter.textboxKeyEvent.subscribe(this.resizeEditField, this, true);
    var editField = this.getEditField();
    Event.on(editField,'keyup',this.resizeEditField, this, true);
    Event.on(editField,'paste',this.resizeEditField, this, true);
    Event.on(editField,'keydown',this.handleKeyDownEditField, this, true);
    Event.on(editField,'keyup',this.handleKeyUpEditField, this, true);
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype.renderAutocompleter = function(){
    // build the autocompleter for the input field
    this.autocompleter = new applegate.autocomplete.AutoComplete(this.getEditField(),
        this.getSuggest(),this.dataSource, this.acCfg);

    this.autocompleter.itemSelectEvent.subscribe(this.handleItemSelected, this, true);
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype.render = function(){
    this.renderField();
    this.renderAutocompleter();
  };

  /**
   * Resize the edit field to be proporational to the amount entered.
   */
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.resizeEditField = function(){
    var field = this.getEditField();
    var s = field.value.length;
    YAHOO.log("resizeEditField: textlength:" + s, "debug", "global-autocomplete.js");
    field.style.width = "" + (s * 0.9 + 1) + "em";
  };

  /**
   * Event handler that accepts a AutoComplete user selectiona and creates a token and field
   * and adds in the content.
   * @param sType event type (or name)
   * @param aArgs args array see YAHOO.widget.AutoComplete.handleItemSelected
   */
  //noinspection JSUnusedLocalSymbols
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.handleItemSelected = function(sType, aArgs){
    var selected = aArgs[2];
    // selected an Object
    if( this.beforeItemSelectEvent.fire(this, aArgs[2])){
      this.addToken(selected,true);
      this.itemSelectEvent.fire(this, aArgs[2]);
    }
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype.addToken = function(data, clearEdit){
    var a = this.buildTokenAndField(data);
    var token = a[0];
    var field = a[1];
    this.getInternalContainer().insertBefore(token, this.getEditField());
    this.getFieldsContainer().appendChild(field);
    this.selection[token.getAttribute("id")] = data ;
    if( clearEdit ){
      this.clear();
    }
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype.buildTokenAndField = function(data){
    var newValue = lang.isString(data);
    var pairId = Dom.generateId();
    // do the token (ie the bit you can see)
    var token = document.createElement("span");
//    token.setAttribute("href","#");
    token.setAttribute("id", pairId);
    Dom.addClass(token,"token");
    if( newValue ) Dom.addClass(token, "newvalue");
//    Dom.addClass(token,"clearfix");

    var tokenText = document.createElement("span");
    Dom.addClass(tokenText,"text");
    tokenText.innerHTML = this.formatDisplayValue(data);
    token.appendChild(tokenText);

    var closer = document.createElement("span");
    Dom.addClass(closer,"close");
    Event.on(closer,'click',function(){this.removeToken(pairId);}, this, true);
    token.appendChild(closer);

    var field = document.createElement("input");
    field.setAttribute("type", "hidden");
    field.setAttribute("name", this.fieldname + (newValue ? "_newvalue": ""));
    field.setAttribute("id", pairId + "_field");
    field.setAttribute("value", newValue ? data : (data.id != undefined) ? data.id : data.bindingValue);
    return [token,field];
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype.removeToken = function(id){
    var data = this.selection[id];
    if( lang.isUndefined(data)){
      return false;
    }

    if(this.beforeItemRemoveEvent.fire(this, data)){
      global.removeNode(id);
      global.removeNode(id +"_field");
      delete this.selection[id];
      this.itemRemoveEvent.fire(this, data);
    }
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype.removeTokensByValue = function(value){
    // removeToken for each selection id for that value
    // this is a pural function because the say data can be selected multiple times.
    global.forEach(this.getSelectionIdsByValue(value),function(id){this.removeToken(id);},this);
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype.handleKeyUpEditField = function(e){
    var key = e.keyCode;
    var editField = this.getEditField();
    var sText = lang.trim(editField.value) ;

    if( this.cfg.allowNewValues
        && key === global.key.RETURN
        && sText.length > 0
        && !this.autocompleter._bContainerOpen){

      // insert a token just for a bit of text
      if( this.beforeNewValueSelectEvent.fire(this, sText)){
        this.addToken(sText, true);
        this.newValueSelectEvent.fire(this,sText);
      }
    }
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype.handleKeyDownEditField = function(e){
    var key = e.keyCode;
    var editField = this.getEditField();
    var sText = lang.trim(editField.value) ;

    if( key === global.key.BACKSPACE
        && sText.length === 0){
      var lastToken = Dom.getPreviousSiblingBy(editField, function(elm){return Dom.hasClass(elm, 'token');});
      if( lastToken ){
        this.removeToken(lastToken.getAttribute("id"));
      }
    }
    return true;
  };

  /**
   *
   */
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.getSelection = function(){
    return global.forEach(this.selection, function(v){return v;});
  };

  applegate.autocomplete.MultipleEntryAutoComplete.prototype.getSelectionIdsByValue = function(data){
    // find all that match and then return all there keys
    //noinspection JSUnusedLocalSymbols
    return global.forEach(
        global.selectEach(this.selection, function(v){ return v === data; }),
        function(v,k){return k;});
  };

  /**
   * Overridable method to format the text held in the tokens.
   * @param data string or selection data
   * @return string to be the text contents of the token
   */
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.formatDisplayValue = function(data){
    // default implmentation
    if(lang.isString(data)){
      return data;
    }
    var v = data[this.cfg.displayField];

    if(lang.isFunction(v)) {
      return v.call(data);
    }
    return v;
  };

  /**
   * Clear the editbox of all text.
   */
  applegate.autocomplete.MultipleEntryAutoComplete.prototype.clear = function(){
    this.getEditField().value = '';
  };
})();