/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var Ci = Components.interfaces;
var Cc = Components.classes;
var Cr = Components.results;
var Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");

// Import common head.
{
  let commonFile = do_get_file("../head_common.js", false);
  let uri = Services.io.newFileURI(commonFile);
  Services.scriptloader.loadSubScript(uri.spec, this);
}

// Put any other stuff relative to this test folder below.

XPCOMUtils.defineLazyServiceGetter(this, "gHistory",
                                   "@mozilla.org/browser/history;1",
                                   "mozIAsyncHistory");

/**
 * @param aSearches
 *        Array of AutoCompleteSearch names.
 */
function AutoCompleteInput(aSearches) {
  this.searches = aSearches;
}
AutoCompleteInput.prototype = {
  searches: null,
  minResultsForPopup: 0,
  timeout: 10,
  searchParam: "",
  textValue: "",
  disableAutoComplete: false,

  completeDefaultIndex: true,
  defaultIndex: 0,

  // Text selection range
  _selStart: 0,
  _selEnd: 0,
  get selectionStart() {
    return this._selStart;
  },
  get selectionEnd() {
    return this._selEnd;
  },
  selectTextRange: function(aStart, aEnd) {
    this._selStart = aStart;
    this._selEnd = aEnd;
  },

  onTextEntered: () => false,
  onTextReverted: () => false,

  get searchCount() {
    return this.searches.length;
  },
  getSearchAt: function(aIndex) {
    return this.searches[aIndex];
  },

  onSearchBegin: function () {},
  onSearchComplete: function () {},

  popupOpen: false,

  popup: {
    selectedIndex: -1,
    invalidate: function () {},

    QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup])
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteInput])
}

/**
 * @param aSearchString
 *        String to search.
 * @param aExpectedValue
 *        Expected value returned by autoFill.
 *        May be a string, or an object like
 *        {
 *          autoFilled: the value suggested by autofill,
 *          completed: the value completed on user's confirmation
 *        }
 *        In the latter case this will also check that on user's confirmation
 *        the result's casing is correctly applied.
 */
function ensure_results(aSearchString, aExpectedValue) {
  let autoFilledValue, completedValue;
  if (typeof(aExpectedValue) == "string") {
    autoFilledValue = aExpectedValue;
  }
  else {
    autoFilledValue = aExpectedValue.autoFilled;
    completedValue = aExpectedValue.completed;
  }

  // Make an AutoCompleteInput that uses our searches and confirms results.
  let input = new AutoCompleteInput(["urlinline"]);
  input.textValue = aSearchString;

  // Caret must be at the end for autoFill to happen.
  let strLen = aSearchString.length;
  input.selectTextRange(strLen, strLen);
  do_check_eq(input.selectionStart, strLen);
  do_check_eq(input.selectionEnd, strLen);

  let controller = Cc["@mozilla.org/autocomplete/controller;1"].
                   getService(Ci.nsIAutoCompleteController);
  controller.input = input;

  let numSearchesStarted = 0;
  input.onSearchBegin = function() {
    numSearchesStarted++;
    do_check_eq(numSearchesStarted, 1);
  };


  let promise = new Promise(resolve => {
    input.onSearchComplete = function() {
      // We should be running only one query.
      do_check_eq(numSearchesStarted, 1);

      // Check the autoFilled result.
      do_check_eq(input.textValue, autoFilledValue);

      if (completedValue) {
        // Now force completion and check correct casing of the result.
        // This ensures the controller is able to do its magic case-preserving
        // stuff and correct replacement of the user's casing with result's one.
        controller.handleEnter(false);
        do_check_eq(input.textValue, completedValue);
      }

      // Cleanup.
      Promise.all([
        PlacesUtils.bookmarks.eraseEverything(),
        PlacesTestUtils.clearHistory()
      ]).then(resolve);
    };
  });

  do_print("Searching for: '" + aSearchString + "'");
  controller.startSearch(aSearchString);

  return promise;
}

function run_test() {
  do_register_cleanup(function () {
    Services.prefs.clearUserPref("browser.urlbar.autocomplete.enabled");
    Services.prefs.clearUserPref("browser.urlbar.autoFill");
    Services.prefs.clearUserPref("browser.urlbar.autoFill.typed");
  });

  gAutoCompleteTests.forEach(function (testData) {
    let [description, searchString, expectedValue, setupFunc] = testData;
    add_task(function* () {
      do_print(description);
      Services.prefs.setBoolPref("browser.urlbar.autocomplete.enabled", true);
      Services.prefs.setBoolPref("browser.urlbar.autoFill", true);
      Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false);

      if (setupFunc) {
        yield setupFunc();
      }

      // At this point frecency could still be updating due to latest pages
      // updates.
      // This is not a problem in real life, but autocomplete tests should
      // return reliable resultsets, thus we have to wait.
      yield PlacesTestUtils.promiseAsyncUpdates();
      yield ensure_results(searchString, expectedValue);
    })
  }, this);

  run_next_test();
}

var gAutoCompleteTests = [];
function add_autocomplete_test(aTestData) {
  gAutoCompleteTests.push(aTestData);
}

function* addBookmark(aBookmarkObj) {
  do_check_true(!!aBookmarkObj.url);
  yield PlacesUtils.bookmarks
                   .insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
                             url: aBookmarkObj.url });
  if (aBookmarkObj.keyword) {
    yield PlacesUtils.keywords.insert({ keyword: aBookmarkObj.keyword,
                                        url: aBookmarkObj.url });
  }
}
