// MRU cache for callbacks.
var Cache = require('magazine')

// Ever increasing serial value with no maximum value.
var Monotonic = require('monotonic').asString

// Exceptions with context that can be caught by type.
var interrupt = require('interrupt').createInterrupter('bigeasy.cliffhanger')

// Create a Cliffhanger. The optional `cache` argument is a common cache from
// which to create a magazine.

//
function Cliffhanger (cache) {
    cache = cache || new Cache
    this._magazine = cache.createMagazine()
    this._cookie = '0'
}

// Create a cookie that will invoke the given callback.

//
Cliffhanger.prototype.invoke = function (callback) {
    var cookie = this._cookie = Monotonic.increment(this._cookie, 0)
    this._magazine.hold(cookie, { cookie: cookie, callback: callback }).release()
    return cookie
}

// Invoke the callback fro the given cookie with the given arguments. The
// arguments are an array of arguments given to apply.

//
Cliffhanger.prototype.resolve = function (cookie, vargs) {
    var cartridge = this._magazine.hold(cookie, null), outcome = false
    if (cartridge.value != null) {
        cartridge.value.callback.apply(null, vargs)
        outcome = true
    }
    cartridge.remove()
    return outcome
}

// Expire any callbacks that have been in the cache since before the given
// expiration time. The error is the optional error to pass to the callback,
// otherwise an error is created by the Cliffhanger.

//
Cliffhanger.prototype.expire = function (expired, error) {
    var purge = this._magazine.purge()
    while (purge.cartridge && purge.cartridge.when < expired) {
        purge.cartridge.value.callback.call(null, error || interrupt({ name: 'expired' }))
        purge.cartridge.remove()
        purge.next()
    }
    purge.release()
}

// TODO Dubious. Never used it. Maybe rename to `cancelIf` and move it out of
// the way? Shouldn't the caller have a copy of all their cookies?

// Cancel all callbacks that match the given condition.

// The condition is really dubious. If you're going to test based on a cookie,
// then you're going to have to keep information associated with the cookie, so
// why not just create a Magazine for yourself?

// It is outgoing, but I'm going to keep it in case I'm using it somewhere.

//
Cliffhanger.prototype.cancel = function (condition, error) {
    var vargs = Array.prototype.slice.call(arguments)
    if (typeof vargs[0] == 'function') {
        condition = vargs.shift()
    } else {
        condition = function () { return true }
    }
    error = vargs.shift()
    var purge = this._magazine.purge()
    while (purge.cartridge) {
        if (condition(purge.cartridge.value.cookie)) {
            purge.cartridge.value.callback.call(null, error || interrupt({ name: 'canceled' }))
            purge.cartridge.remove()
        } else {
            purge.cartridge.release()
        }
        purge.next()
    }
}

module.exports = Cliffhanger

@@ -728,36 +728,35 @@
  this._magazine.
-hold
+put
 (cookie, %7B cooki
@@ -787,26 +787,16 @@
 lback %7D)
-.release()
 %0A    ret
@@ -1009,25 +1009,26 @@
     var 
-cartridge
+invocation
  = this.
@@ -1041,25 +1041,18 @@
 ine.
-hold
+get
 (cookie
-, null
 ), o
@@ -1074,31 +1074,26 @@
     if (
-cartridge.value
+invocation
  != null
@@ -1104,31 +1104,26 @@
         
-cartridge.value
+invocation
 .callbac
@@ -1155,51 +1155,66 @@
     
-outcome = true%0A    %7D%0A    cartridge.remove()
+this._magazine.remove(cookie)%0A        outcome = true%0A    %7D
 %0A   

Migrate to `get` and `put` Magazine interface.