// Control-flow utilities.
var cadence = require('cadence')
var abend = require('abend')

// Common utilities.
var coalesce = require('extant')

// Create bound user callback.
var Operation = require('operation/variadic')

// Create a turnstile that will invoke the given operation with each entry
// pushed into the work queue.

//
function Turnstile () {
    var vargs = Array.prototype.slice.call(arguments)
    var operation = Operation(vargs)
    var options = vargs.shift() || {}
    this._head = {}
    this._head.next = this._head.previous = this._head
    this.health = {
        occupied: 0,
        waiting: 0,
        rejecting: 0,
        turnstiles: coalesce(options.turnstiles, 1)
    }
    this.health.occupied = 0
    this.health.waiting = 0
    this.health.rejecting = 0
    this.timeout = coalesce(options.timeout, Infinity)
    this._operation = operation
    this._Date = coalesce(options.Date, Date)
    this._setImmediate = coalesce(options.setImmediate, true)
}

Turnstile.prototype.reconfigure = function (options) {
    options.turnstiles == null || (this.health.turnstiles = options.turnstiles)
    options.timeout == null || (this.timeout = options.timeout)
}

// Push an entry into the work queue along with a callback. The callback is the
// difference between a work queue and a message queue.

//
Turnstile.prototype._enqueue = function (work, callback) {
    var task = {
        when: this._Date.now(),
        body: work,
        callback: callback,
        previous: this._head.previous,
        next: this._head
    }
    task.next.previous = task
    task.previous.next = task
    this.health.waiting++
}

Turnstile.prototype.enqueue = function (work, callback) {
    this._enqueue(work, callback)
    this._nudge(abend)
}

Turnstile.prototype.push = function (work) {
    this.enqueue(work, abend)
}

Turnstile.prototype._stopWorker = function () {
    return this.health.waiting == 0
}

Turnstile.prototype._stopRejector = function () {
    return this.health.waiting == 0
        || this._Date.now() - this._head.next.when <= this.timeout
}

// We use Cadence because of its superior try/catch abilities.
Turnstile.prototype._work = cadence(function (async, counter, stopper) {
    var severed = ! this._setImmediate
    async([function () {
        this.health[counter]--
    }], function () {
        this.health[counter]++
    }, function () {
        var loop = async(function () {
            if (this[stopper]()) {
                return [ loop.break ]
            }
            var task = this._head.next
            this._head.next = task.next
            this._head.next.previous = this._head
            this.health.waiting--
            async(function () {
                if (!severed) {
                    severed = true
                    setImmediate(async()) // <- price, we only pay it to start work.
                }
            }, [function () {
                this._operation.call(null, {
                    module: 'turnstile',
                    method: 'enter',
                    when: task.when,
                    timedout: this._Date.now() - task.when >= this.timeout,
                    body: task.body
                }, async())
            }, function (error) {
                (task.callback)(error)
                return [ loop.continue ]
            }], [], function (vargs) {
                task.callback.apply(null, [ null ].concat(vargs))
            })
        })()
    })
})

Turnstile.prototype._nudge = function (callback) {
    if (this.health.waiting && this.health.occupied < this.health.turnstiles) {
        this._work('occupied', '_stopWorker', callback)
    } else if (this.health.waiting && !this.health.rejecting && this._Date.now() - this._head.next.when >= this.timeout) {
        this._work('rejecting', '_stopRejector', callback)
    } else {
        callback()
    }
}

module.exports = Turnstile

@@ -1377,24 +1377,77 @@
 callback) %7B%0A
+%7D%0A%0ATurnstile.prototype.enter = function (envelope) %7B%0A
     var task
@@ -1501,40 +1501,129 @@
 dy: 
-work,%0A        callback
+envelope.body,%0A        started: coalesce(envelope.started, abend),%0A        completed
 : c
+o
 al
-lback
+esce(envelope.completed, abend)
 ,%0A  
@@ -1850,31 +1850,69 @@
 his.
-_
 en
-queue(work, callback
+ter(%7B%0A        completed: callback,%0A        body: work%0A    %7D
 )%0A  
@@ -1995,20 +1995,44 @@
 s.en
-queue(work, 
+ter(%7B body: work %7D)%0A    this._nudge(
 aben
@@ -2874,16 +2874,52 @@
 iting--%0A
+            task.started.call(null)%0A
         
@@ -3496,24 +3496,34 @@
     
-(
 task.c
-allback)(
+ompleted.call(null, 
 erro
@@ -3627,23 +3627,24 @@
   task.c
-allback
+ompleted
 .apply(n

Add notification when work is pulled from queue.