var Q = require('../kew')
var originalQ = require('q')

// create a promise from a literal
exports.testQResolve = function (test) {
  var val = "ok"

  Q.resolve(val)
    .then(function (data) {
      test.equal(data, val, "Promise successfully returned")
      test.done()
    })
}

// create a failed promise from an error literal
exports.testQReject = function (test) {
  var err = new Error("hello")

  Q.reject(err)
    .fail(function (e) {
      test.equal(e, err, "Promise successfully failed")
      test.done()
    })
}

// Test Q.stats
exports.testQStatistics = function (test) {
  var err = new Error("hello")

  var errorsEmitted = Q.stats.errorsEmitted
  var errorsHandled = Q.stats.errorsHandled

  var rejected = Q.reject(err)
  test.equal(errorsEmitted + 1, Q.stats.errorsEmitted, "One additional error emitted")
  test.equal(errorsHandled, Q.stats.errorsHandled, "Error hasn't been handled yet")

  rejected.fail(function (e) {
    test.equal(e, err, "Promise successfully failed")
    test.equal(errorsEmitted + 1, Q.stats.errorsEmitted, "One additional error emitted")
    test.equal(errorsHandled + 1, Q.stats.errorsHandled, "One additional error handled")
  })

  rejected.fail(function (e) {
    test.equal(e, err, "Promise successfully failed")
    test.equal(errorsEmitted + 1, Q.stats.errorsEmitted, "One additional error emitted")
    test.equal(errorsHandled + 1, Q.stats.errorsHandled, "Only count error handling once")
  })
  test.done()
}

exports.testQDeferredStatistics = function (test) {
  var err = new Error("hello")

  var errorsEmitted = Q.stats.errorsEmitted
  var errorsHandled = Q.stats.errorsHandled

  var deferred = Q.defer()

  deferred.fail(function (e) {
    test.equal(e, err, "Promise successfully failed")
    test.equal(errorsEmitted + 1, Q.stats.errorsEmitted, "One additional error emitted")
    test.equal(errorsHandled + 1, Q.stats.errorsHandled, "One additional error handled")
    test.done()
  })

  var rejected = deferred.reject(err)

}

// test Q.all with an empty array
exports.testQEmptySuccess = function (test) {
  var promises = []

  // make sure all results come back
  Q.all(promises)
    .then(function (data) {
      test.equal(data.length, 0, "No records should be returned")
      test.done()
    })
}

// test Q.all with only literals
exports.testQAllLiteralsSuccess = function (test) {
  var vals = [3, 2, 1]
  var promises = []

  promises.push(vals[0])
  promises.push(vals[1])
  promises.push(vals[2])

  // make sure all results come back
  Q.all(promises)
    .then(function (data) {
      test.equal(data[0], vals[0], "First val should be returned")
      test.equal(data[1], vals[1], "Second val should be returned")
      test.equal(data[2], vals[2], "Third val should be returned")
      test.done()
    })
}

// test Q.all with only promises
exports.testQAllPromisesSuccess = function (test) {
  var vals = [3, 2, 1]
  var promises = []

  promises.push(Q.resolve(vals[0]))
  promises.push(Q.resolve(vals[1]))
  promises.push(Q.resolve(vals[2]))

  // make sure all results come back
  Q.all(promises)
    .then(function (data) {
      test.equal(data[0], vals[0], "First val should be returned")
      test.equal(data[1], vals[1], "Second val should be returned")
      test.equal(data[2], vals[2], "Third val should be returned")
      test.done()
    })
}

// create a promise which waits for other promises
exports.testQAllAssortedSuccess = function (test) {
  var vals = [3, 2, 1]
  var promises = []

  // a promise that returns the value immediately
  promises.push(Q.resolve(vals[0]))

  // the value itself
  promises.push(vals[1])

  // a promise which returns in 10ms
  var defer = Q.defer()
  promises.push(defer.promise)
  setTimeout(function () {
    defer.resolve(vals[2])
  }, 10)

  // make sure all results come back
  Q.all(promises)
    .then(function (data) {
      test.equal(data[0], vals[0], "First val should be returned")
      test.equal(data[1], vals[1], "Second val should be returned")
      test.equal(data[2], vals[2], "Third val should be returned")
      test.done()
    })
}

// test Q.all with a failing promise
exports.testQAllError = function (test) {
  var vals = [3, 2, 1]
  var err = new Error("hello")
  var promises = []

  promises.push(vals[0])
  promises.push(vals[1])

  var defer = Q.defer()
  promises.push(defer.promise)
  defer.reject(err)

  // make sure all results come back
  Q.all(promises)
    .fail(function (e) {
      test.equal(e, err)
      test.done()
    })
}

// test all var_args
exports.testAllVarArgs = function (test) {
  var promises = ['a', 'b']

  Q.all.apply(Q, promises)
    .then(function (results) {
      test.equal(promises[0], results[0], "First element should be returned")
      test.equal(promises[1], results[1], "Second element should be returned")
      test.done()
    })
}

// test all array
exports.testAllArray = function (test) {
  var promises = ['a', 'b']

  Q.all(promises)
    .then(function (results) {
      test.equal(promises[0], results[0], "First element should be returned")
      test.equal(promises[1], results[1], "Second element should be returned")
      test.done()
    })
}

exports.testAllIsPromiseLike = function(test) {
  var promises = ['a', originalQ('b')]

  Q.all(promises)
    .then(function (results) {
      test.equal(promises[0], 'a', "First element should be returned")
      test.equal(promises[1], 'b', "Second element should be returned")
      test.done()
    })
}

// test delay
exports.testDelay = function (test) {
  var val = "Hello, there"
  var startTime = Date.now()

  Q.resolve(val)
    .then(function (v) {
      return Q.delay(v, 1000)
    })
    .then(function (returnVal) {
      test.equal(returnVal, val, "Val should be passed through")

      var diff = Date.now() - startTime

      // clock granularity may be off by 15
      test.equal(diff >= 1000 - 15, true, "Should have waited a second. Actually waited " + diff)
      test.done()
    })
}

// test fcall
exports.testFcall = function (test) {
  var calledYet = false
  var adder = function (a, b) {
    calledYet = true
    return a + b
  }

  Q.fcall(adder, 2, 3)
    .then(function (val) {
      test.equal(val, 5, "Val should be 2 + 3")
      test.done()
    })
  test.ok(!calledYet, "fcall() should delay function invocation until next tick")
}

// test fcall
exports.testFcallError = function (test) {
  var error = function () {
    throw new Error('my error')
  }

  Q.fcall(error)
    .then(function (val) {
      test.fail('fcall should throw exception')
    }, function (err) {
      test.equal('my error', err.message)
    })
    .then(function () {
      test.done()
    })
}

// test fcall works when fn returns a promise
exports.testFcallGivenPromise = function (test) {
  var calledYet = false
  var eventualAdd = function (a, b) {
    calledYet = true
    return Q.resolve(a + b)
  }

  Q.fcall(eventualAdd, 2, 3)
    .then(function (val) {
      test.equal(val, 5, "Val should be 2 + 3")
      test.done()
    })
  test.ok(!calledYet, "fcall() should delay function invocation until next tick")
}

// test nfcall, successful case
exports.testNfcall = function (test) {
  var nodeStyleEventualAdder = function (a, b, callback) {
    setTimeout(function () {
      callback(undefined, a + b)
    }, 2)
  }

  Q.nfcall(nodeStyleEventualAdder, 2, 3)
    .then(function (val) {
      test.equal(val, 5, "Val should be 2 + 3")
      test.done()
    })
}

// test nfcall, error case
exports.testNfcallErrors = function (test) {
  var err = new Error('fail')

  var nodeStyleFailer = function (a, b, callback) {
    setTimeout(function() {
      callback(err)
    }, 2)
  }

  Q.nfcall(nodeStyleFailer, 2, 3)
    .fail(function (e) {
      test.equal(e, err, "Promise successfully failed")
      test.done()
    })
}

// test fcall
exports.testNFcallErrorSync = function (test) {
  var error = function () {
    throw new Error('my error')
  }

  Q.nfcall(error)
    .then(function (val) {
      test.fail('nfcall should throw exception')
    }, function (err) {
      test.equal('my error', err.message)
    })
    .then(function () {
      test.done()
    })
}

exports.testNcall = function (test) {
  function TwoAdder() {
    this.a = 2
  }
  TwoAdder.prototype.add = function (num, callback) {
    setTimeout(function () {
      callback(null, this.a + num)
    }.bind(this), 10)
  }
  var adder = new TwoAdder()
  Q.ncall(adder.add, adder, 3)
    .then(function (val) {
      test.equal(val, 5, "Val should be 2 + 3")
      test.done()
    })
}

// test binding a callback function with a promise
exports.testBindPromise = function (test) {
  var adder = function (a, b, callback) {
    callback(null, a + b)
  }

  var boundAdder = Q.bindPromise(adder, null, 2)
  boundAdder(3)
    .then(function (val) {
      test.equal(val, 5, "Val should be 2 + 3")
      test.done()
    })
}

// test checking whether something is a promise
exports.testIsPromise = function (test) {
  var kewPromise = Q.defer()
  var qPromise = originalQ(10)
  var kewLikeObject = {
    promise: function () {
      return 'not a promise sucka!'
    },
    then: function (fn) {
      fn('like a promise, brah!')
    }
  }
  test.equal(Q.isPromise(kewPromise), true, 'A Kew promise is a promise')
  test.equal(Q.isPromise(qPromise), false, 'A Q promise is not a promise')
  test.equal(Q.isPromise(kewLikeObject), false, 'A pretend promise is not a promise')
  test.done()
}

// test checking whether something is a promise-like object
exports.testIsPromiseLike = function (test) {
  var kewPromise = Q.defer()
  var qPromise = originalQ(10)
  var kewLikeObject = {
    promise: function () {
      return 'not a promise sucka!'
    },
    then: function (fn) {
      fn('like a promise, brah!')
    }
  }
  var kewLikeFunction = function() {}
  kewLikeFunction.then = function(fn) {
    fn('like a promise, brah!')
  }
  test.equal(Q.isPromiseLike(kewPromise), true, 'A Kew promise is promise-like')
  test.equal(Q.isPromiseLike(qPromise), true, 'A Q promise is promise-like')
  test.equal(Q.isPromiseLike(kewLikeObject), true, 'A pretend promise is a promise-like')
  test.equal(Q.isPromiseLike(kewLikeFunction), true, 'A pretend function promise is a promise-like')

  test.done()
}
