var async = require('./async.js');

// API module.exports = {

iterator: wrapIterator,
callback: wrapCallback

};

/**

* Wraps iterators with long signature
*
* @this    ReadableAsyncKit#
* @param   {function} iterator - function to wrap
* @returns {function} - wrapped function
*/

function wrapIterator(iterator) {

var stream = this;

return function(item, key, cb)
{
  var aborter
    , wrappedCb = async(wrapIteratorCallback.call(stream, cb, key))
    ;

  stream.jobs[key] = wrappedCb;

  // it's either shortcut (item, cb)
  if (iterator.length == 2)
  {
    aborter = iterator(item, wrappedCb);
  }
  // or long format (item, key, cb)
  else
  {
    aborter = iterator(item, key, wrappedCb);
  }

  return aborter;
};

}

/**

* Wraps provided callback function
* allowing to execute snitch function before
* real callback
*
* @this    ReadableAsyncKit#
* @param   {function} callback - function to wrap
* @returns {function} - wrapped function
*/

function wrapCallback(callback) {

var stream = this;

var wrapped = function(error, result)
{
  return finisher.call(stream, error, result, callback);
};

return wrapped;

}

/**

* Wraps provided iterator callback function
* makes sure snitch only called once,
* but passes secondary calls to the original callback
*
* @this    ReadableAsyncKit#
* @param   {function} callback - callback to wrap
* @param   {number|string} key - iteration key
* @returns {function} wrapped callback
*/

function wrapIteratorCallback(callback, key) {

var stream = this;

return function(error, output)
{
  // don't repeat yourself
  if (!(key in stream.jobs))
  {
    callback(error, output);
    return;
  }

  // clean up jobs
  delete stream.jobs[key];

  return streamer.call(stream, error, {key: key, value: output}, callback);
};

}

/**

* Stream wrapper for iterator callback
*
* @this  ReadableAsyncKit#
* @param {mixed} error - error response
* @param {mixed} output - iterator output
* @param {function} callback - callback that expects iterator results
*/

function streamer(error, output, callback) {

if (error && !this.error)
{
  this.error = error;
  this.pause();
  this.emit('error', error);
  // send back value only, as expected
  callback(error, output && output.value);
  return;
}

// stream stuff
this.push(output);

// back to original track
// send back value only, as expected
callback(error, output && output.value);

}

/**

* Stream wrapper for finishing callback
*
* @this  ReadableAsyncKit#
* @param {mixed} error - error response
* @param {mixed} output - iterator output
* @param {function} callback - callback that expects final results
*/

function finisher(error, output, callback) {

// signal end of the stream
// only for successfully finished streams
if (!error)
{
  this.push(null);
}

// back to original track
callback(error, output);

}