/**
 * Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

gBrowser.selectedTab = gBrowser.addTab();

function finishAndCleanUp()
{
  gBrowser.removeCurrentTab();
  waitForClearHistory(finish);
}

/**
 * One-time observer callback.
 */
function waitForObserve(name, callback)
{
  var observerService = Cc["@mozilla.org/observer-service;1"]
                        .getService(Ci.nsIObserverService);
  var observer = {
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
    observe: function(subject, topic, data) {
      observerService.removeObserver(observer, name);
      observer = null;
      callback(subject, topic, data);
    }
  };

  observerService.addObserver(observer, name, false);
}

/**
 * One-time DOMContentLoaded callback.
 */
function waitForLoad(callback)
{
  gBrowser.addEventListener("DOMContentLoaded", function() {
    gBrowser.removeEventListener("DOMContentLoaded", arguments.callee, true);
    callback();
  }, true);
}

var conn = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;

/**
 * Gets a single column value from either the places or historyvisits table.
 */
function getColumn(table, column, fromColumnName, fromColumnValue)
{
  let sql = "SELECT " + column + " " +
            "FROM " + table + " " +
            "WHERE " + fromColumnName + " = :val " +
            "LIMIT 1";
  let stmt = conn.createStatement(sql);
  try {
    stmt.params.val = fromColumnValue;
    ok(stmt.executeStep(), "Expect to get a row");
    return stmt.row[column];
  }
  finally {
    stmt.reset();
  }
}

/**
 * Clears history invoking callback when done.
 */
function waitForClearHistory(aCallback) {
  const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
  let observer = {
    observe: function(aSubject, aTopic, aData) {
      Services.obs.removeObserver(this, TOPIC_EXPIRATION_FINISHED);
      aCallback();
    }
  };
  Services.obs.addObserver(observer, TOPIC_EXPIRATION_FINISHED, false);

  let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
           getService(Ci.nsINavHistoryService);
  hs.QueryInterface(Ci.nsIBrowserHistory).removeAllPages();
}

function test()
{
  // Make sure places visit chains are saved correctly with a redirect
  // transitions.

  waitForExplicitFinish();

  // Part 1: observe history events that fire when a visit occurs.
  // Make sure visits appear in order, and that the visit chain is correct.
  var expectedUrls = [
    "http://example.com/tests/toolkit/components/places/tests/browser/begin.html",
    "http://example.com/tests/toolkit/components/places/tests/browser/redirect_twice.sjs",
    "http://example.com/tests/toolkit/components/places/tests/browser/redirect_once.sjs",
    "http://example.com/tests/toolkit/components/places/tests/browser/final.html"
  ];
  var currentIndex = 0;

  waitForObserve("uri-visit-saved", function(subject, topic, data) {
    var uri = subject.QueryInterface(Ci.nsIURI);
    var expected = expectedUrls[currentIndex];
    is(uri.spec, expected, "Saved URL visit " + uri.spec);

    var placeId = getColumn("moz_places", "id", "url", uri.spec);
    var fromVisitId = getColumn("moz_historyvisits", "from_visit", "place_id", placeId);

    if (currentIndex == 0) {
      is(fromVisitId, 0, "First visit has no from visit");
    }
    else {
      var lastVisitId = getColumn("moz_historyvisits", "place_id", "id", fromVisitId);
      var fromVisitUrl = getColumn("moz_places", "url", "id", lastVisitId);
      is(fromVisitUrl, expectedUrls[currentIndex - 1],
         "From visit was " + expectedUrls[currentIndex - 1]);
    }

    currentIndex++;
    if (currentIndex < expectedUrls.length) {
      waitForObserve("uri-visit-saved", arguments.callee);
    }
    else {
      finishAndCleanUp();
    }
  });

  // Load begin page, click link on page to record visits.
  content.location.href = "http://example.com/tests/toolkit/components/places/tests/browser/begin.html";
  waitForLoad(function() {
    EventUtils.sendMouseEvent({type: 'click'}, 'clickme', content.window);
    waitForLoad(function() {
      content.location.href = "about:blank";
    });
  });
}
