if (window.Evri == undefined) window.Evri = {};
if (jQuery == undefined) throw new Error("jQuery is required");


Evri.Find = Evri.Class.create("Evri.Find");
Evri.Find.prototype = {


  /**
   * @constructor
   *
   * Evri.Find provides an easy-to-use search mechanism.
   *
   * We wrap the event handlers during initialization because we want to
   * set them up near where they are defined, and JavaScript doesn't
   * pre-parse the file. There is probably a better way to handle this.
   *
   * Styles:
   *   input.loading styles for the input text box while the component is busy
   *   input.prompt styles for the input text box when populated with the prompt
   *   .results The results list
   *     .empty The results list when there are no results found
   *
   * @param container DOM element (form) containing the user input
   * @param appId The appId to use when making API calls
   */
  initialize: function (container, appId) {

    //-----------------------------------------
    // Mixins
    //-----------------------------------------

    Evri.EventDispatcher.mixin(this);


    //-----------------------------------------
    // Constants - Static
    //-----------------------------------------

    Evri.Find.LOADED = "loaded";
    Evri.Find.VIEWED = "viewed";
    Evri.Find.ENTITY_CLICKED = "entityClicked";
    Evri.Find.ENTITY_MOUSE_OVER = "entityMouseOver";
    Evri.Find.ENTITY_MOUSE_OUT = "entityMouseOut";
    Evri.Find.SUBMIT = "submit";


    //-----------------------------------------
    // Constants - Private
    //-----------------------------------------

    var PROMPT_TEXT = "Find\u200B";
    var NO_MATCH_TEXT = '<strong>No matches for "${term}"</strong><em>Click to see all results.</em>';


    //-----------------------------------------
    // Variables - Instance
    //-----------------------------------------

    var that = this;
    var $container = jQuery(container).removeClass("truncated").append("<ol/>");
    var $query = $container.find("input[type='text']").val(PROMPT_TEXT).addClass("prompt").removeAttr("disabled");
    var $results = $container.find("ol").addClass("results").hide().css("position", "absolute");
    var session = new Evri.API.Session({appId: appId});
    var noMatchText = NO_MATCH_TEXT;
    var promptText = PROMPT_TEXT;
    var blacklist = {};
    var cachedQuery = null;
    var timeoutId = NaN;
    var currentEntity = null;
    var responseCache = {};


    //-----------------------------------------
    // Initialization
    //-----------------------------------------

    $query.bind("blur", function(event) { that._query_blurHandler(event); });
    $query.bind("focus", function(event) { that._query_focusHandler(event); });
    $query.bind("keydown", function(event) { that._query_keyDownHandler(event); });


    //-----------------------------------------
    // Methods - Public
    //-----------------------------------------

    that.getNoMatchText = function() {
      return noMatchText;
    };


    that.setNoMatchText = function(text) {
      noMatchText = text;
    };


    that.getPromptText = function() {
      return promptText.replace("\u200B", "");
    };


    that.setPromptText = function(text) {
      var newPrompt = text + "\u200B";

      if ($query.val() == promptText) {
        $query.val(newPrompt);
      }

      promptText = newPrompt;
    };


    /**
     * Removes certain Entity objects from the search results.
     *
     * @param uris An array of Entity URIs
     */
    that.setBlacklist = function(uris) {
      if (uris.constructor != Array) {
        return;
      }

      blacklist = {};
      for (var i= 0; i < uris.length; i++) {
        blacklist[uris[i]] = true;
      }
    };


    //-----------------------------------------
    // Methods - Private
    //-----------------------------------------

    that._makeRequest = function() {
      var query = $query.val().replace(/^[\s]+/, '');

      if (query.length == 0) {
        // HACK: Not really the place, but hide results if empty query
        $results.fadeOut("slow");
        cachedQuery = null;
        return;
      }

      if (cachedQuery == query) {
        return;
      }
      cachedQuery = query;

      var prefix = query.toLowerCase();
      if (responseCache[prefix]) {
        that._handleResponse(responseCache[prefix]);
      } else {
        $query.addClass("loading");
        session.models.Entity.findByPrefix(prefix, {
          onComplete: function(response) {
            responseCache[prefix] = response;
            that._handleResponse(response);
          },
          onFailure: function(error) {
            alert("Error: " + error);
            $query.removeClass("loading");
          }
        });
      }
    };


    that._handleResponse = function(response) {
      $query.removeClass("loading");
      var entities = [];
      jQuery.each(response.entities, function (index, entity) {
        if (blacklist[entity.href] == undefined) {
          entities.push(entity);
        }
      });

      that._showResults(entities);
    };


    that._showResults = function(entities) {
      $results.empty().removeClass("empty");

      if (entities.length > 0) {
        jQuery.each(entities, function(index, entity) {
          var $item = jQuery("<li><a><strong/><em/></a></li>").appendTo($results);
          $item.data("entity", entity);
          $item.find("strong").text(entity.name).addClass("truncated");
          $item.find("em").text(Evri.Util.toSentence(entity.facets, "name")).addClass("truncated");
          var $a = $item.find("a");
          $a.attr("href", "javascript:void(0);");
          $a.bind("mouseenter", entity, that._entity_mouseEnterHandler);
          $a.bind("mouseleave", entity, that._entity_mouseLeaveHandler);
          $a.bind("click", entity, that._entity_clickHandler);
        });
      } else {
        var term = $query.val();
        var $empty = jQuery("<li/>").html(noMatchText.replace("${term}", Evri.Util.htmlEscape(term)));
        $empty.bind("click", null, that._entity_clickHandler);
        $results.append($empty).addClass("empty");
      }

      // Use callback to ensure we will position the results correctly
      $results.show(10, function() {
        var offset = $query.position();
        $results.css({left: offset.left, top: offset.top + $query.outerHeight()});
        $results.width($query.outerWidth() - 2);
        $results.find("li:first").addClass("first");
        $results.find("li:last").addClass("last");
      });

      // $blocker/*.deactivateTooltip()*/.empty();
    };


    that._focusOnEntity = function(jQuery) {
      if (jQuery) {
        var $target = that._getJQueryListItem(jQuery);
        $target.addClass("selected").siblings().removeClass("selected");
        currentEntity = $target.data("entity");
      } else {
        $results.children().removeClass("selected");
        currentEntity = null;
      }

      // TODO: raise event

      if (currentEntity) {
        $query.val(currentEntity.name);
      } else {
        if (cachedQuery) {
          $query.val(cachedQuery);
        } else {
          $query.val(promptText);
          $query.addClass("prompt");
        }
      }
    };


    that._getJQueryListItem = function(jQuery) {
      if (jQuery[0].nodeName.toLowerCase() == "li") {
        return jQuery;
      } else {
        return jQuery.parents('li:eq(0)');
      }
    };


    //-----------------------------------------
    // Methods - Event Handlers
    //-----------------------------------------

    that._query_focusHandler = function(evt) {
      $query.addClass("active");

      if ($query.val() == promptText) {
        $query.val("");
        $query.removeClass("prompt");
      } else {
        that._makeRequest();
      }
    };


    that._query_blurHandler = function(evt) {
      $query.removeClass("active");
      $results.fadeOut("slow"); // HACK: Keep it around so click event fires

      $query.val(cachedQuery);
      if ($query.val().length == 0) {
        $query.val(promptText);
        $query.addClass("prompt");
      }
    };


    that._query_keyDownHandler = function(evt) {
      var ESCAPE = 27;
      var DOWN_ARROW = 40;
      var UP_ARROW = 38;
      var ENTER = 13;
      var TAB = 9;

      clearTimeout(timeoutId);

      var $selection = $results.find("li.selected");

      switch (evt.keyCode) {
        case ESCAPE:
          $query.val(cachedQuery);
          $query.blur();
          that._focusOnEntity(null);
          break;

        case DOWN_ARROW:
          if ($selection.length > 0) {
            var $next = $selection.next();
            if ($next.length > 0) {
              that._focusOnEntity($next);
            }
          } else {
            that._focusOnEntity($results.find("li:first"));
          }
          break;

        case UP_ARROW:
          if ($selection.length > 0) {
            if ($selection.prev().length == 0) {
              that._focusOnEntity(null);
            } else {
              var $previous = $selection.prev();
              that._focusOnEntity($previous);
            }
          }
          break;

        case ENTER:
          if (currentEntity) {
            $query.blur();
            $selection.find("a").click();
          } else {
            var event = {};
            event.type = Evri.Find.SUBMIT;
            event.query = $query.val();
            event.target = $container[0];

            that.dispatchEvent(event.type,  event);
          }
          evt.preventDefault();
          break;

        case TAB:
          return true;

        default:
          timeoutId = setTimeout(that._makeRequest, 250);
          return true;
      }

      return false;
    };


    that._entity_clickHandler = function (evt) {
      var event = {};
      if (evt.data) {
        var $target = that._getJQueryListItem(jQuery(evt.target));
        event.type = Evri.Find.ENTITY_CLICKED;
        event.target = $target.find("a")[0];
        event.entity = evt.data;
      } else {
        event.type = Evri.Find.SUBMIT;
        event.query = $query.val();
        event.target = $container[0];
      }

      that.dispatchEvent(event.type, event);
    };


    that._entity_mouseEnterHandler = function(evt) {
      var $target = that._getJQueryListItem(jQuery(evt.target));
      that._focusOnEntity($target);

      var event = {};
      event.type = Evri.Find.ENTITY_MOUSE_OVER;
      event.target = $target.find("a")[0];
      event.entity = evt.data;

      that.dispatchEvent(event.type, event);
    };


    that._entity_mouseLeaveHandler = function(evt) {
      var $target = that._getJQueryListItem(jQuery(evt.target));
      $target.removeClass("selected");

      var event = {};
      event.type = Evri.Find.ENTITY_MOUSE_OUT;
      event.target = $target.find("a")[0];
      event.entity = evt.data;

      that.dispatchEvent(event.type, event);
    };
  }
};
