
/**
 * @file:
 * Converts textfield to a autocomplete deluxe widget.
 */

(function($) {
  Drupal.autocomplete_deluxe = Drupal.autocomplete_deluxe || {};

  Drupal.behaviors.autocomplete_deluxe = {
    attach: function(context) {
      var autocomplete_settings = Drupal.settings.autocomplete_deluxe;

      $('input.autocomplete-deluxe-form').once( function() {
        if (autocomplete_settings[$(this).attr('id')].multiple === true) {
          new Drupal.autocomplete_deluxe.MultipleWidget(this, autocomplete_settings[$(this).attr('id')]);
        } else {
          new Drupal.autocomplete_deluxe.SingleWidget(autocomplete_settings[$(this).attr('id')]);
        }
      });
    }
  };

  /**
   * Autogrow plugin which auto resizes the input of the multiple value.
   *
   * http://stackoverflow.com/questions/931207/is-there-a-jquery-autogrow-plugin-for-text-fields
   *
   */
  $.fn.autoGrowInput = function(o) {

    o = $.extend({
      maxWidth: 1000,
      minWidth: 0,
      comfortZone: 70
    }, o);

    this.filter('input:text').each(function(){

      var minWidth = o.minWidth || $(this).width(),
        val = '',
        input = $(this),
        testSubject = $('<tester/>').css({
          position: 'absolute',
          top: -9999,
          left: -9999,
          width: 'auto',
          fontSize: input.css('fontSize'),
          fontFamily: input.css('fontFamily'),
          fontWeight: input.css('fontWeight'),
          letterSpacing: input.css('letterSpacing'),
          whiteSpace: 'nowrap'
        }),
        check = function() {

          if (val === (val = input.val())) {return;}

          // Enter new content into testSubject
          var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,'&nbsp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
          testSubject.html(escaped);

          // Calculate new width + whether to change
          var testerWidth = testSubject.width(),
            newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
            currentWidth = input.width(),
            isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
              || (newWidth > minWidth && newWidth < o.maxWidth);

          // Animate width
          if (isValidWidthChange) {
            input.width(newWidth);
          }

        };

      testSubject.insertAfter(input);

      $(this).bind('keyup keydown blur update', check);

    });

    return this;
  };


  Drupal.autocomplete_deluxe.empty =  {label: '- ' + Drupal.t('None') + ' -', value: "" };

  /**
   * EscapeRegex function from jquery autocomplete, is not included in drupal.
   */
  Drupal.autocomplete_deluxe.escapeRegex = function(value) {
    return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/gi, "\\$&");
  };

  /**
   * Filter function from jquery autocomplete, is not included in drupal.
   */
  Drupal.autocomplete_deluxe.filter = function(array, term) {
    var matcher = new RegExp(Drupal.autocomplete_deluxe.escapeRegex(term), "i");
    return $.grep(array, function(value) {
      return matcher.test(value.label || value.value || value);
    });
  };

  Drupal.autocomplete_deluxe.Widget = function() {
  };

  Drupal.autocomplete_deluxe.Widget.prototype.uri = null;

  /**
   * Allows widgets to filter terms.
   * @param term
   *   A term that should be accepted or not.
   * @return {Boolean}
   *   True if the term should be accepted.
   */
  Drupal.autocomplete_deluxe.Widget.prototype.acceptTerm = function(term) {
    return true;
  };

  Drupal.autocomplete_deluxe.Widget.prototype.init = function(settings) {
    if ($.browser.msie && $.browser.version === "6.0") {
      return;
    }

    this.id = settings.input_id;
    this.jqObject = $('#' + this.id);

    this.uri = settings.uri;
    this.multiple = settings.multiple;
    this.required = settings.required;
    this.limit = settings.limit;
    this.synonyms = settings.use_synonyms === undefined ? false : settings.use_synonyms;
    this.items = {};

    var self = this;
    var parent = this.jqObject.parent();
    var parents_parent = this.jqObject.parent().parent();

    parents_parent.append(this.jqObject);
    parent.remove();
    parent = parents_parent;

    var generateValues = function(data, term) {
      var result = new Array();
      for (var terms in data) {
        if (self.acceptTerm(terms)) {
          result.push({
            label: data[terms],
            value: terms
          });
        }
      }
      if ($.isEmptyObject(result)) {
        result.push({
          label: Drupal.t("The term '@term' will be added.", {'@term' : term}),
          value: term,
          newTerm: true
        });
      }
      return result;
    };

    var cache = {}
    var lastXhr = null;

    this.source = function(request, response) {
      var term = request.term;
      if (term in cache) {
        response(generateValues(cache[term], term));
        return;
      }

      // Some server collapse two slashes if the term is empty, so insert at
      // least a whitespace. This whitespace will later on be trimmed in the
      // autocomplete callback.
      if (!term) {
        term = " ";
      }
      request.synonyms = self.synonyms;
      var url = settings.uri + '/' + term +'/' +  self.limit;
      lastXhr = $.getJSON(url, request, function(data, status, xhr) {
        cache[term] = data;
        if (xhr === lastXhr) {
          response(generateValues(data, term));
        }
      });
    };

    this.jqObject.autocomplete({
      'source' : this.source,
      'minLength': settings.min_length
    });

    var jqObject = this.jqObject;
    var throbber = $('<div class="autocomplete-deluxe-throbber autocomplete-deluxe-closed">&nbsp;</div>').insertAfter(jqObject);

    this.jqObject.bind("autocompletesearch", function(event, ui) {
      throbber.removeClass('autocomplete-deluxe-closed');
      throbber.addClass('autocomplete-deluxe-open');
    });

    this.jqObject.bind("autocompleteopen", function(event, ui) {
      throbber.addClass('autocomplete-deluxe-closed');
      throbber.removeClass('autocomplete-deluxe-open');
    });

    // Monkey patch the _renderItem function jquery so we can highlight the
    // text, that we already entered.
    $.ui.autocomplete.prototype._renderItem = function( ul, item) {
      var t = item.label;
      if (this.term != "") {
        var escapedValue = Drupal.autocomplete_deluxe.escapeRegex( this.term );
        var re = new RegExp('()*""' + escapedValue + '""|' + escapedValue + '()*', 'gi');
        var t = item.label.replace(re,"<span class='autocomplete-deluxe-highlight-char'>$&</span>");
      }
      return $( "<li></li>" )
        .data( "item.autocomplete", item )
        .append( "<a>" + t + "</a>" )
        .appendTo( ul );
    };
  };

  Drupal.autocomplete_deluxe.Widget.prototype.generateValues = function(data) {
    var result = new Array();
    for (var index in data) {
      result.push(data[index]);
    }
    return result;
  };

  /**
   * Generates a single selecting widget.
   */
  Drupal.autocomplete_deluxe.SingleWidget = function(settings) {
    this.init(settings);
    this.setup();
    this.jqObject.addClass('autocomplete-deluxe-form-single');
  };

  Drupal.autocomplete_deluxe.SingleWidget.prototype = new Drupal.autocomplete_deluxe.Widget();

  Drupal.autocomplete_deluxe.SingleWidget.prototype.setup = function() {
    var jqObject = this.jqObject;
    var parent = jqObject.parent();

    parent.mousedown(function() {
      if (parent.hasClass('autocomplete-deluxe-single-open')) {
        jqObject.autocomplete('close');
      } else {
        jqObject.autocomplete('search', '');
      }
    });
  };

  /**
   * Creates a multiple selecting widget.
   */
  Drupal.autocomplete_deluxe.MultipleWidget = function(input, settings) {
    this.init(settings);
    this.setup();
  };

  Drupal.autocomplete_deluxe.MultipleWidget.prototype = new Drupal.autocomplete_deluxe.Widget();
  Drupal.autocomplete_deluxe.MultipleWidget.prototype.items = new Object();


  Drupal.autocomplete_deluxe.MultipleWidget.prototype.acceptTerm = function(term) {
    // Accept only terms, that are not in our items list.
    return !(term in this.items);
  };

  Drupal.autocomplete_deluxe.MultipleWidget.Item = function (widget, item) {
    if (item.newTerm === true) {
      item.label = item.value;
    }

    this.value = item.value;
    this.element = $('<span class="autocomplete-deluxe-item">' + item.label + '</span>');
    this.widget = widget;
    this.item = item;
    var self = this;

    var close = $('<a class="autocomplete-deluxe-item-delete" href="javascript:void(0)"></a>').appendTo(this.element);
    // Use single quotes because of the double quote encoded stuff.
    var input = $('<input type="hidden" value=\'' + this.value + '\'/>').appendTo(this.element);

    close.mousedown(function() {
      self.remove(item);
    });
  };

  Drupal.autocomplete_deluxe.MultipleWidget.Item.prototype.remove = function() {
    this.element.remove();
    var values = this.widget.valueForm.val();
    var escapedValue = Drupal.autocomplete_deluxe.escapeRegex( this.item.value );
    var regex = new RegExp('()*""' + escapedValue + '""|' + escapedValue + '()*', 'gi');
    this.widget.valueForm.val(values.replace(regex, ''));
    delete this.widget.items[this.value];
  };

  Drupal.autocomplete_deluxe.MultipleWidget.prototype.setup = function() {
    var jqObject = this.jqObject;
    var parent = jqObject.parent();
    var value_container = jqObject.parent().parent().children('.autocomplete-deluxe-value-container');
    var value_input = value_container.children().children();
    var items = this.items;
    var self = this;
    this.valueForm = value_input;

    // Override the resize function, so that the suggestion list doesn't resizes
    // all the time.
    jqObject.data("autocomplete")._resizeMenu = function()  {};

    jqObject.show();
    value_container.hide();

    // Add the default values to the box.
    var default_values = value_input.val();
    default_values = $.trim(default_values);
    default_values = default_values.substr(2, default_values.length-4);
    default_values = default_values.split('"" ""');

    for (var index in default_values) {
      var value = default_values[index];
      if (value != '') {
        // If a terms is encoded in double quotes, then the label should have
        // no double quotes.
        var label = value.match(/["][\w|\s|\D|]*["]/gi) !== null ? value.substr(1, value.length-2) : value;
        var item = {
          label : label,
          value : value
        };
        var item = new Drupal.autocomplete_deluxe.MultipleWidget.Item(self, item);
        item.element.insertBefore(jqObject);
        items[item.value] = item;
      }
    }

    jqObject.addClass('autocomplete-deluxe-multiple');
    parent.addClass('autocomplete-deluxe-multiple');


    // Adds a value to the list.
    this.addValue = function(ui_item) {
      var item = new Drupal.autocomplete_deluxe.MultipleWidget.Item(self, ui_item);
      item.element.insertBefore(jqObject);
      items[ui_item.value] = item;
      var new_value = ' ""' + ui_item.value + '""';
      var values = value_input.val();
      value_input.val(values + new_value);
      jqObject.val('');
    };

    parent.mouseup(function() {
      jqObject.autocomplete('search', '');
      jqObject.focus();
    });

    jqObject.bind("autocompleteselect", function(event, ui) {
      self.addValue(ui.item);
      jqObject.width(25);
      // Return false to prevent setting the last term as value for the jqObject.
      return false;
    });

    jqObject.bind("autocompletechange", function(event, ui) {
      jqObject.val('');
    });

    jqObject.blur(function() {
      var last_element = jqObject.parent().children('.autocomplete-deluxe-item').last();
      last_element.removeClass('autocomplete-deluxe-item-focus');
    });

    var clear = false;

    jqObject.keydown(function (event) {
      var value = jqObject.val();
      // If a comma was entered and there is none or more then one comma,or the
      // enter key was entered, then enter the new term.
      if ((event.which == 188 && (value.split('"').length - 1) != 1) || (event.which == 13 && jqObject.val() != "")) {
        value = value.substr(0, value.length);
        if (self.items[value] === undefined && value != '') {
          var ui_item = {
            label: value,
            value: value
          };
          self.addValue(ui_item);
        }
        clear = true;
        if (event.which == 13) {
          return false;
        }
      }

      // If the Backspace key was hit and the input is empty
      if (event.which == 8 && value == '') {
        var last_element = jqObject.parent().children('.autocomplete-deluxe-item').last();
        // then mark the last item for deletion or deleted it if already marked.
        if (last_element.hasClass('autocomplete-deluxe-item-focus')) {
          var value = last_element.children('input').val();
          self.items[value].remove(self.items[value]);
          jqObject.autocomplete('search', '');
        } else {
          last_element.addClass('autocomplete-deluxe-item-focus');
        }
      } else {
        // Remove the focus class if any other key was hit.
        var last_element = jqObject.parent().children('.autocomplete-deluxe-item').last();
        last_element.removeClass('autocomplete-deluxe-item-focus');
      }
    });

    jqObject.autoGrowInput({
      comfortZone: 50,
      minWidth: 10,
      maxWidth: 460
    });


    jqObject.keyup(function (event) {
      if (clear) {
        // Trigger the search, so it display the values for an empty string.
        jqObject.autocomplete('search', '');
        jqObject.val('');
        clear = false;
        // Return false to prevent entering the last character.
        return false;
      }
    });
  };
})(jQuery);
