var clock, original, debounced, originalPromiseResolvesWith, callback;

var nothingFired = function(additionalMessage) {
  ok(!original.called, "original function is not called " + additionalMessage);
  ok(!callback.called, "debounced promise is not resolved " + additionalMessage);
};

var originalAndCallbackFiredOnce = function(additionalMessage) {
  ok(original.calledOnce, "original function is called once " + additionalMessage);
  ok(callback.calledOnce, "debounced promise is resolved once " + additionalMessage);
};

module("Discourse.debouncePromise", {
  setup: function() {
    clock = sinon.useFakeTimers();

    originalPromiseResolvesWith = null;
    original = sinon.spy(function() {
      var promise = Ember.Deferred.create();
      promise.resolve(originalPromiseResolvesWith);
      return promise;
    });

    debounced = Discourse.debouncePromise(original, 100);
    callback = sinon.spy();
  },

  teardown: function() {
    clock.restore();
  }
});

test("delays execution till the end of the timeout", function() {
  debounced().then(callback);
  nothingFired("immediately after calling debounced function");

  clock.tick(99);
  nothingFired("just before the end of the timeout");

  clock.tick(1);
  originalAndCallbackFiredOnce("exactly at the end of the timeout");
});

test("executes only once, no matter how many times debounced function is called during the timeout", function() {
  debounced().then(callback);
  debounced().then(callback);

  clock.tick(100);
  originalAndCallbackFiredOnce("(second call was supressed)");
});

test("prolongs the timeout when the debounced function is called for the second time during the timeout", function() {
  debounced().then(callback);

  clock.tick(50);
  debounced().then(callback);

  clock.tick(50);
  nothingFired("at the end of the original timeout");

  clock.tick(50);
  originalAndCallbackFiredOnce("exactly at the end of the prolonged timeout");
});

test("preserves last call's context and params when executing delayed function", function() {
  var firstObj = {};
  var secondObj = {};

  debounced.call(firstObj, "first");
  debounced.call(secondObj, "second");

  clock.tick(100);
  ok(original.calledOn(secondObj), "the context of the second of two subsequent calls is preserved");
  ok(original.calledWithExactly("second"), "param passed during the second of two subsequent calls is preserved");
});

test("can be called again after timeout passes", function() {
  debounced().then(callback);

  clock.tick(100);
  debounced().then(callback);

  clock.tick(100);
  ok(original.calledTwice, "original function is called for the second time");
  ok(callback.calledTwice, "debounced promise is resolved for the second time");
});

test("passes resolved value from the original promise as a param to the debounced promise's callback", function() {
  originalPromiseResolvesWith = "original promise return value";
  debounced().then(callback);

  clock.tick(100);
  ok(callback.calledWith("original promise return value"));
});