/* jshint node: true */
'use strict';

var zmeta = require('./zmeta'),
    Unpi = require('unpi'),
    Concentrate = Unpi.Concentrate,
    DChunks = Unpi.DChunks,
    ru = DChunks().Rule();

/*************************************************************************************************/
/*** ZpiObject Class                                                                           ***/
/***   1. Provides command framer (SREQ)                                                       ***/
/***   2. Provides parser (SRSP, AREQ)                                                         ***/
/*************************************************************************************************/
function ZpiObject(subsys, cmd, args) { // args is optional, and can be an array or a value-object if given
    var subsystem = zmeta.Subsys.get(subsys),
        command,
        reqParams;

    this.type = undefined;      // string after assgined
    this.subsys = undefined;    // string after assigned
    this.cmd = undefined;       // string after assigned
    this.cmdId = undefined;     // number after assigned
    this.args = undefined;      // array after assigned: [ { name, type, value }, ... ]

    if (!subsystem)
        throw new Error('Unrecognized subsystem');

    this.subsys = subsystem.key;
    command = zmeta[this.subsys].get(cmd);

    if (!command)
        throw new Error('Unrecognized command');

    this.cmd = command.key;
    this.cmdId = command.value;

    this.type = zmeta.getType(this.subsys, this.cmd);

    if (!this.type)
        throw new Error('Unrecognized type');

    // if args is given, this is for REQ transmission
    // otherwise, maybe just for parsing RSP packet
    if (args)
        reqParams = zmeta.getReqParams(this.subsys, this.cmd);    // [ { name, type }, ... ]

    if (reqParams) {
        if (Array.isArray(args)) {
            // arg: { name, type } -> { name, type, value }
            reqParams.forEach(function (arg, idx) {
                arg.value = args[idx];
            });
        } else if (typeof args === 'object') {
            reqParams.forEach(function (arg, idx) {
                if (!args.hasOwnProperty(arg.name))
                    throw new Error('The argument object has incorrect properties');
                else
                    arg.value = args[arg.name];
            });
        }

        this.args = reqParams;              // [ { name, type, value }, ... ]
    }
}

ZpiObject.prototype.parse = function (type, bufLen, zBuf, callback) {
    var chunkRules = [],
        err,
        rspParams,
        parser,
        parseTimeout;

    if (type === 'SRSP' || type === 3)      // SRSP
        rspParams = zmeta.getRspParams(this.subsys, this.cmd);
    else if (type === 'AREQ' || type === 2) // AREQ
        rspParams = zmeta.getReqParams(this.subsys, this.cmd);

    if (rspParams) {                        // [ { name, type }, ... ]
        rspParams.forEach(function (arg) {
            var rule = ru[arg.type];
            if (rule) {
                rule = rule(arg.name, bufLen);
                chunkRules.push(rule);
            } else {
                err = new Error('Parsing rule for ' + arg.type + ' is not found.');
            }
        });
    } else {
        err = new Error('Response parameter definitions not found.');
    }

    if (!err) {
        if (chunkRules.length === 0) {
            callback(null, {});
            return;
        }

        parser = DChunks().join(chunkRules).compile();

        parseTimeout = setTimeout(function () {
            if (parser.listenerCount('parsed'))
                parser.emit('parsed', '__timeout__');

            parseTimeout = null;
        }, 3000);

        parser.once('parsed', function (result) {
            if (parseTimeout) {
                clearTimeout(parseTimeout);
                parseTimeout = null;
            }

            parser = null;

            if (result === '__timeout__')
                callback(new Error('parse timeout'));
            else
                callback(null, result);
        });
    }

    if (!parser)    // error occurs, no parser created
        callback(err);
    else
        parser.end(zBuf);
};

ZpiObject.prototype.frame = function () {
    if (!Array.isArray(this.args))  // no args, cannot build frame
        return null;

    var dataBuf = Concentrate();

    this.args.forEach(function (arg, idx) { // arg: { name, type, value }
        var type = arg.type,
            val = arg.value;

        switch (type) {
            case 'uint8':
            case 'uint16':
            case 'uint32':
                dataBuf = dataBuf[type](val);
                break;
            case 'buffer':
            case 'dynbuffer':
                dataBuf = dataBuf.buffer(new Buffer(val));
                break;
            case 'longaddr':    // string '0x00124b00019c2ee9'
                var msb = parseInt(val.slice(2,10), 16),
                    lsb = parseInt(val.slice(10), 16);

                dataBuf = dataBuf.uint32le(lsb).uint32le(msb);
                break;
            case 'listbuffer':  // [ 0x0001, 0x0002, 0x0003, ... ]
                var tempBuf = new Buffer(val.length * 2),
                    idxbuf;

                for (idxbuf = 0; idxbuf < val.length; idxbuf += 1) {
                    tempBuf.writeUInt16LE(val[idxbuf], idxbuf * 2);
                }
                dataBuf = dataBuf.buffer(tempBuf);
                break;
            default:
                throw new Error('Unknown Data Type');
        }
    });

    return dataBuf.result();
};

/*************************************************************************************************/
/*** Add Parsing Rules to DChunks                                                              ***/
/*************************************************************************************************/
var rules = [ 'buffer8', 'buffer16', 'buffer18', 'buffer32', 'buffer42', 'buffer100',
              '_preLenUint8', '_preLenUint16' ];

rules.forEach(function (ruName) {
    ru.clause(ruName, function (name) {
        var needTap = true,
            bufLen;

        if (ruName === '_preLenUint8') {
            this.uint8(name);
        } else if (ruName === '_preLenUint16') {
            this.uint16(name);
        } else {
            needTap = false;
            bufLen = parseInt(ruName.slice(6));
            this.buffer(name, bufLen);
        }

        if (needTap)
            this.tap(function () {
                this.buffer('preLenData', this.vars[name]);
            });
    });
});

ru.clause('longaddr', function (name) {
    this.buffer(name, 8).tap(function () {
        var addrBuf = this.vars[name];
        this.vars[name] = addrBuf2Str(addrBuf);
    });
});

ru.clause('uint8ZdoInd', function (name, bufLen) {
    if (bufLen === 3)
        this.uint16('nwkaddr').uint8(name);
    else if (bufLen === 1)
        this.uint8(name);
});

ru.clause('devlistbuffer', function (name, bufLen) {
    this.buffer(name, bufLen - 13).tap(function () {
        this.vars[name] = bufToArray(this.vars[name], 'uint16');
    });
});

ru.clause('nwklistbuffer', function (name, bufLen) {
    this.buffer(name, bufLen - 6).tap(function () {
        var buf = this.vars[name],
            list = [],
            listcount,
            getList,
            start = 0,
            end,
            len,
            i;

        if (name === 'networklist') {
            listcount = this.vars.networklistcount;
            end = len = 12;
            getList = networkList;
        } else if (name === 'neighborlqilist') {
            listcount = this.vars.neighborlqilistcount;
            end = len = 22;
            getList = neighborLqiList;
        } else if (name === 'routingtablelist') {
            listcount = this.vars.routingtablelistcount;
            end = len = 5;
            getList = routingTableList;
        } else {
            listcount = this.vars.bindingtablelistcount;
            this.vars[name] = bindTableList(buf, listcount);
            return;
        }

        for (i = 0; i < listcount; i += 1) {
            list.push(getList(buf.slice(start, end)));
            start = start + len;
            end = end + len;
        }

        this.vars[name] = list;
    });
});

ru.clause('zdomsgcb', function (name, bufLen) {
    this.buffer(name, bufLen - 9);
});

ru.clause('preLenList', function (name) {
    this.uint8(name).tap(function () {
        this.buffer('preLenData', 2 * (this.vars[name]));
    });
});

ru.clause('preLenBeaconlist', function (name) {
    this.uint8(name).tap(function () {
        this.buffer('preLenData', 21 * (this.vars[name])).tap(function () {
            var buf = this.vars.preLenData,
                list = [],
                len = 21,
                start = 0,
                end = 21,
                i;

            for (i = 0; i < this.vars[name]; i += 1) {
                list.push(beaconList(buf.slice(start, end)));
                start = start + len;
                end = end + len;
            }

            this.vars.preLenData = list;
        });
    });
});

ru.clause('dynbuffer', function (name) {
    this.tap(function () {
        this.vars[name] = this.vars.preLenData;
        delete this.vars.preLenData;
    });
});

function networkList(buf) {
    var item = {},
        i = 0;

    item.neightborPanId = buf.readUInt16LE(i);
    i += (2+6);
    item.logicalChannel = buf.readUInt8(i);
    i += 1;
    item.stackProfile = buf.readUInt8(i) & 0x0F;
    item.zigbeeVersion = (buf.readUInt8(i) & 0xF0) >> 4;
    i += 1;
    item.beaconOrder = buf.readUInt8(i) & 0x0F;
    item.superFrameOrder = (buf.readUInt8(i) & 0xF0) >> 4;
    i += 1;
    item.permitJoin = buf.readUInt8(i);
    i += 1;

    return item;
}

function neighborLqiList(buf) {
    var item = {},
        i = 0;

    item.extPandId = addrBuf2Str(buf.slice(0, 8));
    i += 8;
    item.extAddr = addrBuf2Str(buf.slice(8, 16));
    i += 8;
    item.nwkAddr = buf.readUInt16LE(i);
    i += 2;
    item.deviceType = buf.readUInt8(i) & 0x03;
    item.rxOnWhenIdle = (buf.readUInt8(i) & 0x0C) >> 2;
    item.relationship = (buf.readUInt8(i) & 0x70) >> 4;
    i += 1;
    item.permitJoin = buf.readUInt8(i) & 0x03;
    i += 1;
    item.depth = buf.readUInt8(i);
    i += 1;
    item.lqi = buf.readUInt8(i);
    i += 1;

    return item;
}

function routingTableList(buf) {
    var item = {},
        i = 0;

    item.destNwkAddr = buf.readUInt16LE(i);
    i += 2;
    item.routeStatus = buf.readUInt8(i) & 0x07;
    i += 1;
    item.nextHopNwkAddr = buf.readUInt16LE(i);
    i += 2;

    return item;
}

function bindTableList(buf, listcount) {
    var itemObj,
        list = [],
        len = 21,
        start = 0,
        end = len,
        i;

    function getList(buf) {
        var itemObj = {
                item: {},
                thisItemLen: 0
            },
            itemLen = 21,
            item = {},
            i = 0;

        item.srcAddr = addrBuf2Str(buf.slice(0, 8));
        i += 8;
        item.srcEp = buf.readUInt8(i);
        i += 1;
        item.clusterId = buf.readUInt16LE(i);
        i += 2;
        item.dstAddrMode = buf.readUInt8(i);
        i += 1;
        item.dstAddr = addrBuf2Str(buf.slice(12, 20));
        i += 8;

        if (item.dstAddrMode === 3) {  // 'Addr64Bit'
            item.dstEp = buf.readUInt8(i);
            i += 1;
        } else {
            itemLen = itemLen - 1;
        }

        itemObj.thisItemLen = itemLen;
        itemObj.item = item;
        return itemObj;
    }

    for (i = 0; i < listcount; i += 1) {
        itemObj = getList(buf.slice(start, end));
        list.push(itemObj.item);

        start = start + itemObj.thisItemLen;
        if (i === listcount - 2) {  // for the last item, we don't know the length of bytes
            end = buf.length;       // so, assign 'end' by the buf length to avoid memory leak.
        } else {
            end = start + len;      // for each item, take 21 bytes from buf to parse
        }
    }

    return list;
}

function beaconList(buf) {
    var item = {},
        i = 0;

    item.srcAddr = buf.readUInt16LE(i);
    i += 2;
    item.padId = buf.readUInt16LE(i);
    i += 2;
    item.logicalChannel = buf.readUInt8(i);
    i += 1;
    item.permitJoin = buf.readUInt8(i);
    i += 1;
    item.routerCapacity = buf.readUInt8(i);
    i += 1;
    item.deviceCapacity = buf.readUInt8(i);
    i += 1;
    item.protocolVersion = buf.readUInt8(i);
    i += 1;
    item.stackProfile = buf.readUInt8(i);
    i += 1;
    item.lqi = buf.readUInt8(i);
    i += 1;
    item.depth = buf.readUInt8(i);
    i += 1;
    item.updateId = buf.readUInt8(i);
    i += 1;
    item.extPandId = addrBuf2Str(buf.slice(13));
    i += 8;

    return item;
}

function addrBuf2Str(buf) {
    var bufLen = buf.length,
        val,
        strChunk = '0x';

    for (var i = 0; i < bufLen; i += 1) {
        val = buf.readUInt8(bufLen - i - 1);
        if (val <= 15) {
            strChunk += '0' + val.toString(16);
        } else {
            strChunk += val.toString(16);
        }
    }

    return strChunk;
}

function bufToArray(buf, nip) {
    var i,
        nipArr = [];
    if (nip === 'uint8') {
        for (i = 0; i < buf.length; i += 1) {
            nipArr.push(buf.readUInt8(i));
        }
    } else if (nip === 'uint16') {
        for (i = 0; i < buf.length; i += 2) {
            nipArr.push(buf.readUInt16LE(i));
        }
    }
    return nipArr;
}

module.exports = ZpiObject;


'use strict';

if (process.addAsyncListener) throw new Error("Don't require polyfill unless needed");

var shimmer      = require('shimmer')
  , semver       = require('semver')
  , wrap         = shimmer.wrap
  , massWrap     = shimmer.massWrap
  , wrapCallback = require('./glue.js')
  , util         = require('util')
  ;

var v6plus = semver.gte(process.version, '6.0.0');
var v7plus = semver.gte(process.version, '7.0.0');
var v8plus = semver.gte(process.version, '8.0.0');
var v11plus = semver.gte(process.version, '11.0.0');

var net = require('net');

// From Node.js v7.0.0, net._normalizeConnectArgs have been renamed net._normalizeArgs
if (v7plus && !net._normalizeArgs) {
  // a polyfill in our polyfill etc so forth -- taken from node master on 2017/03/09
  net._normalizeArgs = function (args) {
    if (args.length === 0) {
      return [{}, null];
    }

    var arg0 = args[0];
    var options = {};
    if (typeof arg0 === 'object' && arg0 !== null) {
      // (options[...][, cb])
      options = arg0;
    } else if (isPipeName(arg0)) {
      // (path[...][, cb])
      options.path = arg0;
    } else {
      // ([port][, host][...][, cb])
      options.port = arg0;
      if (args.length > 1 && typeof args[1] === 'string') {
        options.host = args[1];
      }
    }

    var cb = args[args.length - 1];
    if (typeof cb !== 'function')
      return [options, null];
    else
      return [options, cb];
  }
} else if (!v7plus && !net._normalizeConnectArgs) {
  // a polyfill in our polyfill etc so forth -- taken from node master on 2013/10/30
  net._normalizeConnectArgs = function (args) {
    var options = {};

    function toNumber(x) { return (x = Number(x)) >= 0 ? x : false; }

    if (typeof args[0] === 'object' && args[0] !== null) {
      // connect(options, [cb])
      options = args[0];
    }
    else if (typeof args[0] === 'string' && toNumber(args[0]) === false) {
      // connect(path, [cb]);
      options.path = args[0];
    }
    else {
      // connect(port, [host], [cb])
      options.port = args[0];
      if (typeof args[1] === 'string') {
        options.host = args[1];
      }
    }

    var cb = args[args.length - 1];
    return typeof cb === 'function' ? [options, cb] : [options];
  };
}

// In https://github.com/nodejs/node/pull/11796 `_listen2` was renamed
// `_setUpListenHandle`. It's still aliased as `_listen2`, and currently the
// Node internals still call the alias - but who knows for how long. So better
// make sure we use the new name instead if available.
if ('_setUpListenHandle' in net.Server.prototype) {
  wrap(net.Server.prototype, '_setUpListenHandle', wrapSetUpListenHandle);
} else {
  wrap(net.Server.prototype, '_listen2', wrapSetUpListenHandle);
}

function wrapSetUpListenHandle(original) {
  return function () {
    this.on('connection', function (socket) {
      if (socket._handle) {
        socket._handle.onread = wrapCallback(socket._handle.onread);
      }
    });

    try {
      return original.apply(this, arguments);
    }
    finally {
      // the handle will only not be set in cases where there has been an error
      if (this._handle && this._handle.onconnection) {
        this._handle.onconnection = wrapCallback(this._handle.onconnection);
      }
    }
  };
}

function patchOnRead(ctx) {
  if (ctx && ctx._handle) {
    var handle = ctx._handle;
    if (!handle._originalOnread) {
      handle._originalOnread = handle.onread;
    }
    handle.onread = wrapCallback(handle._originalOnread);
  }
}

wrap(net.Socket.prototype, 'connect', function (original) {
  return function () {
    var args;
    // Node core uses an internal Symbol here to guard against the edge-case
    // where the user accidentally passes in an array. As we don't have access
    // to this Symbol we resort to this hack where we just detect if there is a
    // symbol or not. Checking for the number of Symbols is by no means a fool
    // proof solution, but it catches the most basic cases.
    if (v8plus &&
        Array.isArray(arguments[0]) &&
        Object.getOwnPropertySymbols(arguments[0]).length > 0) {
      // already normalized
      args = arguments[0];
    } else {
      // From Node.js v7.0.0, net._normalizeConnectArgs have been renamed net._normalizeArgs
      args = v7plus
        ? net._normalizeArgs(arguments)
        : net._normalizeConnectArgs(arguments);
    }
    if (args[1]) args[1] = wrapCallback(args[1]);
    var result = original.apply(this, args);
    patchOnRead(this);
    return result;
  };
});

var http = require('http');

// NOTE: A rewrite occurred in 0.11 that changed the addRequest signature
// from (req, host, port, localAddress) to (req, options)
// Here, I use the longer signature to maintain 0.10 support, even though
// the rest of the arguments aren't actually used
wrap(http.Agent.prototype, 'addRequest', function (original) {
  return function (req) {
    var onSocket = req.onSocket;
    req.onSocket = wrapCallback(function (socket) {
      patchOnRead(socket);
      return onSocket.apply(this, arguments);
    });
    return original.apply(this, arguments);
  };
});

var childProcess = require('child_process');

function wrapChildProcess(child) {
  if (Array.isArray(child.stdio)) {
    child.stdio.forEach(function (socket) {
      if (socket && socket._handle) {
        socket._handle.onread = wrapCallback(socket._handle.onread);
        wrap(socket._handle, 'close', activatorFirst);
      }
    });
  }

  if (child._handle) {
    child._handle.onexit = wrapCallback(child._handle.onexit);
  }
}

// iojs v2.0.0+
if (childProcess.ChildProcess) {
  wrap(childProcess.ChildProcess.prototype, 'spawn', function (original) {
    return function () {
      var result = original.apply(this, arguments);
      wrapChildProcess(this);
      return result;
    };
  });
} else {
  massWrap(childProcess, [
    'execFile', // exec is implemented in terms of execFile
    'fork',
    'spawn'
  ], function (original) {
    return function () {
      var result = original.apply(this, arguments);
      wrapChildProcess(result);
      return result;
    };
  });
}

// need unwrapped nextTick for use within < 0.9 async error handling
if (!process._fatalException) {
  process._originalNextTick = process.nextTick;
}

var processors = [];
if (process._nextDomainTick) processors.push('_nextDomainTick');
if (process._tickDomainCallback) processors.push('_tickDomainCallback');

massWrap(
  process,
  processors,
  activator
);
wrap(process, 'nextTick', activatorFirst);

var asynchronizers = [
  'setTimeout',
  'setInterval'
];
if (global.setImmediate) asynchronizers.push('setImmediate');

var timers = require('timers');
var patchGlobalTimers = global.setTimeout === timers.setTimeout;

massWrap(
  timers,
  asynchronizers,
  activatorFirst
);

if (patchGlobalTimers) {
  massWrap(
    global,
    asynchronizers,
    activatorFirst
  );
}

var dns = require('dns');
massWrap(
  dns,
  [
    'lookup',
    'resolve',
    'resolve4',
    'resolve6',
    'resolveCname',
    'resolveMx',
    'resolveNs',
    'resolveTxt',
    'resolveSrv',
    'reverse'
  ],
  activator
);

if (dns.resolveNaptr) wrap(dns, 'resolveNaptr', activator);

var fs = require('fs');
massWrap(
  fs,
  [
    'watch',
    'rename',
    'truncate',
    'chown',
    'fchown',
    'chmod',
    'fchmod',
    'stat',
    'lstat',
    'fstat',
    'link',
    'symlink',
    'readlink',
    'realpath',
    'unlink',
    'rmdir',
    'mkdir',
    'readdir',
    'close',
    'open',
    'utimes',
    'futimes',
    'fsync',
    'write',
    'read',
    'readFile',
    'writeFile',
    'appendFile',
    'watchFile',
    'unwatchFile',
    "exists",
  ],
  activator
);

// only wrap lchown and lchmod on systems that have them.
if (fs.lchown) wrap(fs, 'lchown', activator);
if (fs.lchmod) wrap(fs, 'lchmod', activator);

// only wrap ftruncate in versions of node that have it
if (fs.ftruncate) wrap(fs, 'ftruncate', activator);

// Wrap zlib streams
var zlib;
try { zlib = require('zlib'); } catch (err) { }
if (zlib && zlib.Deflate && zlib.Deflate.prototype) {
  var proto = Object.getPrototypeOf(zlib.Deflate.prototype);
  if (proto._transform) {
    // streams2
    wrap(proto, "_transform", activator);
  }
  else if (proto.write && proto.flush && proto.end) {
    // plain ol' streams
    massWrap(
      proto,
      [
        'write',
        'flush',
        'end'
      ],
      activator
    );
  }
}

// Wrap Crypto
var crypto;
try { crypto = require('crypto'); } catch (err) { }
if (crypto) {

  var toWrap = [
      'pbkdf2',
      'randomBytes',
  ];
  if (!v11plus) {
    toWrap.push('pseudoRandomBytes');
  }

  massWrap(crypto, toWrap, activator);
}

// It is unlikely that any userspace promise implementations have a native
// implementation of both Promise and Promise.toString.
var instrumentPromise = !!global.Promise &&
    Promise.toString() === 'function Promise() { [native code] }' &&
    Promise.toString.toString() === 'function toString() { [native code] }';

// Check that global Promise is native
if (instrumentPromise) {
  // shoult not use any methods that have already been wrapped
  var promiseListener = process.addAsyncListener({
    create: function create() {
      instrumentPromise = false;
    }
  });

  // should not resolve synchronously
  global.Promise.resolve(true).then(function notSync() {
    instrumentPromise = false;
  });

  process.removeAsyncListener(promiseListener);
}

/*
 * Native promises use the microtask queue to make all callbacks run
 * asynchronously to avoid Zalgo issues. Since the microtask queue is not
 * exposed externally, promises need to be modified in a fairly invasive and
 * complex way.
 *
 * The async boundary in promises that must be patched is between the
 * fulfillment of the promise and the execution of any callback that is waiting
 * for that fulfillment to happen. This means that we need to trigger a create
 * when resolve or reject is called and trigger before, after and error handlers
 * around the callback execution. There may be multiple callbacks for each
 * fulfilled promise, so handlers will behave similar to setInterval where
 * there may be multiple before after and error calls for each create call.
 *
 * async-listener monkeypatching has one basic entry point: `wrapCallback`.
 * `wrapCallback` should be called when create should be triggered and be
 * passed a function to wrap, which will execute the body of the async work.
 * The resolve and reject calls can be modified fairly easily to call
 * `wrapCallback`, but at the time of resolve and reject all the work to be done
 * on fulfillment may not be defined, since a call to then, chain or fetch can
 * be made even after the promise has been fulfilled. To get around this, we
 * create a placeholder function which will call a function passed into it,
 * since the call to the main work is being made from within the wrapped
 * function, async-listener will work correctly.
 *
 * There is another complication with monkeypatching Promises. Calls to then,
 * chain and catch each create new Promises that are fulfilled internally in
 * different ways depending on the return value of the callback. When the
 * callback return a Promise, the new Promise is resolved asynchronously after
 * the returned Promise has been also been resolved. When something other than
 * a promise is resolved the resolve call for the new Promise is put in the
 * microtask queue and asynchronously resolved.
 *
 * Then must be wrapped so that its returned promise has a wrapper that can be
 * used to invoke further continuations. This wrapper cannot be created until
 * after the callback has run, since the callback may return either a promise
 * or another value. Fortunately we already have a wrapper function around the
 * callback we can use (the wrapper created by resolve or reject).
 *
 * By adding an additional argument to this wrapper, we can pass in the
 * returned promise so it can have its own wrapper appended. the wrapper
 * function can the call the callback, and take action based on the return
 * value. If a promise is returned, the new Promise can proxy the returned
 * Promise's wrapper (this wrapper may not exist yet, but will by the time the
 * wrapper needs to be invoked). Otherwise, a new wrapper can be create the
 * same way as in resolve and reject. Since this wrapper is created
 * synchronously within another wrapper, it will properly appear as a
 * continuation from within the callback.
 */

if (instrumentPromise) {
  wrapPromise();
}

function wrapPromise() {
  var Promise = global.Promise;

  // Updates to this class should also be applied to the the ES6 version
  // in es6-wrapped-promise.js.
  function wrappedPromise(executor) {
    if (!(this instanceof wrappedPromise)) {
      return Promise(executor);
    }

    if (typeof executor !== 'function') {
      return new Promise(executor);
    }

    var context, args;
    var promise = new Promise(wrappedExecutor);
    promise.__proto__ = wrappedPromise.prototype;

    try {
      executor.apply(context, args);
    } catch (err) {
      args[1](err);
    }

    return promise;

    function wrappedExecutor(resolve, reject) {
      context = this;
      args = [wrappedResolve, wrappedReject];

      // These wrappers create a function that can be passed a function and an argument to
      // call as a continuation from the resolve or reject.
      function wrappedResolve(val) {
        ensureAslWrapper(promise, false);
        return resolve(val);
      }

      function wrappedReject(val) {
        ensureAslWrapper(promise, false);
        return reject(val);
      }
    }
  }

  util.inherits(wrappedPromise, Promise);

  wrap(Promise.prototype, 'then', wrapThen);
  // Node.js <v7 only, alias for .then
  if (Promise.prototype.chain) {
    wrap(Promise.prototype, 'chain', wrapThen);
  }

  if (v6plus) {
    global.Promise = require('./es6-wrapped-promise.js')(Promise, ensureAslWrapper);
  } else {
    var PromiseFunctions = [
      'all',
      'race',
      'reject',
      'resolve',
      'accept',  // Node.js <v7 only
      'defer'    // Node.js <v7 only
    ];

    PromiseFunctions.forEach(function(key) {
      // don't break `in` by creating a key for undefined entries
      if (typeof Promise[key] === 'function') {
        wrappedPromise[key] = Promise[key];
      }
    });
    global.Promise = wrappedPromise
  }

  function ensureAslWrapper(promise, overwrite) {
    if (!promise.__asl_wrapper || overwrite) {
      promise.__asl_wrapper = wrapCallback(propagateAslWrapper);
    }
  }

  function propagateAslWrapper(ctx, fn, result, next) {
    var nextResult;
    try {
      nextResult = fn.call(ctx, result);
      return {returnVal: nextResult, error: false}
    } catch (err) {
      return {errorVal: err, error: true}
    } finally {
      // Wrap any resulting futures as continuations.
      if (nextResult instanceof Promise) {
        next.__asl_wrapper = function proxyWrapper() {
          var aslWrapper = nextResult.__asl_wrapper || propagateAslWrapper;
          return aslWrapper.apply(this, arguments);
        }
      } else {
        ensureAslWrapper(next, true);
      }
    }
  }

  function wrapThen(original) {
    return function wrappedThen() {
      var promise = this;
      var next = original.apply(promise, Array.prototype.map.call(arguments, bind));

      next.__asl_wrapper = function proxyWrapper(ctx, fn, val, last) {
        if (promise.__asl_wrapper) {
          promise.__asl_wrapper(ctx, function () {}, null, next);
          return next.__asl_wrapper(ctx, fn, val, last);
        }
        return propagateAslWrapper(ctx, fn, val, last);
      }

      return next;

      // wrap callbacks (success, error) so that the callbacks will be called as a
      // continuations of the resolve or reject call using the __asl_wrapper created above.
      function bind(fn) {
        if (typeof fn !== 'function') return fn;
        return wrapCallback(function (val) {
          var result = (promise.__asl_wrapper || propagateAslWrapper)(this, fn, val, next);
          if (result.error) {
            throw result.errorVal
          } else {
            return result.returnVal
          }
        });
      }
    }
  }
}

// Shim activator for functions that have callback last
function activator(fn) {
  var fallback = function () {
    var args;
    var cbIdx = arguments.length - 1;
    if (typeof arguments[cbIdx] === "function") {
      args = Array(arguments.length)
      for (var i = 0; i < arguments.length - 1; i++) {
        args[i] = arguments[i];
      }
      args[cbIdx] = wrapCallback(arguments[cbIdx]);
    }
    return fn.apply(this, args || arguments);
  };
  // Preserve function length for small arg count functions.
  switch (fn.length) {
    case 1:
      return function (cb) {
        if (arguments.length !== 1) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, cb);
      };
    case 2:
      return function (a, cb) {
        if (arguments.length !== 2) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, a, cb);
      };
    case 3:
      return function (a, b, cb) {
        if (arguments.length !== 3) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, a, b, cb);
      };
    case 4:
      return function (a, b, c, cb) {
        if (arguments.length !== 4) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, a, b, c, cb);
      };
    case 5:
      return function (a, b, c, d, cb) {
        if (arguments.length !== 5) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, a, b, c, d, cb);
      };
    case 6:
      return function (a, b, c, d, e, cb) {
        if (arguments.length !== 6) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, a, b, c, d, e, cb);
      };
    default:
      return fallback;
  }
}

// Shim activator for functions that have callback first
function activatorFirst(fn) {
  var fallback = function () {
    var args;
    if (typeof arguments[0] === "function") {
      args = Array(arguments.length)
      args[0] = wrapCallback(arguments[0]);
      for (var i = 1; i < arguments.length; i++) {
        args[i] = arguments[i];
      }
    }
    return fn.apply(this, args || arguments);
  };
  // Preserve function length for small arg count functions.
  switch (fn.length) {
    case 1:
      return function (cb) {
        if (arguments.length !== 1) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, cb);
      };
    case 2:
      return function (cb, a) {
        if (arguments.length !== 2) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, cb, a);
      };
    case 3:
      return function (cb, a, b) {
        if (arguments.length !== 3) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, cb, a, b);
      };
    case 4:
      return function (cb, a, b, c) {
        if (arguments.length !== 4) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, cb, a, b, c);
      };
    case 5:
      return function (cb, a, b, c, d) {
        if (arguments.length !== 5) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, cb, a, b, c, d);
      };
    case 6:
      return function (cb, a, b, c, d, e) {
        if (arguments.length !== 6) return fallback.apply(this, arguments);
        if (typeof cb === "function") cb = wrapCallback(cb);
        return fn.call(this, cb, a, b, c, d, e);
      };
    default:
      return fallback;
  }
}

// taken from node master on 2017/03/09
function toNumber(x) {
  return (x = Number(x)) >= 0 ? x : false;
}

// taken from node master on 2017/03/09
function isPipeName(s) {
  return typeof s === 'string' && toNumber(s) === false;
}


var wrap = require('shimmer').wrap;

/*
 *
 * CONSTANTS
 *
 */
var HAS_CREATE_AL = 1 << 0;
var HAS_BEFORE_AL = 1 << 1;
var HAS_AFTER_AL = 1 << 2;
var HAS_ERROR_AL = 1 << 3;

/**
 * There is one list of currently active listeners that is mutated in place by
 * addAsyncListener and removeAsyncListener. This complicates error-handling,
 * for reasons that are discussed below.
 */
var listeners = [];

/**
 * There can be multiple listeners with the same properties, so disambiguate
 * them by assigning them an ID at creation time.
 */
var uid = 0;

/**
 * Ensure that errors coming from within listeners are handed off to domains,
 * process._fatalException, or uncaughtException without being treated like
 * user errors.
 */
var inAsyncTick = false;

/**
 * Because asynchronous contexts can be nested, and errors can come from anywhere
 * in the stack, a little extra work is required to keep track of where in the
 * nesting we are. Because JS arrays are frequently mutated in place
 */
var listenerStack = [];

/**
 * The error handler on a listener can capture errors thrown during synchronous
 * execution immediately after the listener is added. To capture both
 * synchronous and asynchronous errors, the error handler just uses the
 * "global" list of active listeners, and the rest of the code ensures that the
 * listener list is correct by using a stack of listener lists during
 * asynchronous execution.
 */
var asyncCatcher;

/**
 * The guts of the system -- called each time an asynchronous event happens
 * while one or more listeners are active.
 */
var asyncWrap;

/**
 * Simple helper function that's probably faster than using Array
 * filter methods and can be inlined.
 */
function union(dest, added) {
  var destLength = dest.length;
  var addedLength = added.length;
  var returned = [];

  if (destLength === 0 && addedLength === 0) return returned;

  for (var j  = 0; j < destLength; j++) returned[j] = dest[j];

  if (addedLength === 0) return returned;

  for (var i = 0; i < addedLength; i++) {
    var missing = true;
    for (j = 0; j < destLength; j++) {
      if (dest[j].uid === added[i].uid) {
        missing = false;
        break;
      }
    }
    if (missing) returned.push(added[i]);
  }

  return returned;
}

/*
 * For performance, split error-handlers and asyncCatcher up into two separate
 * code paths.
 */

// 0.9+
if (process._fatalException) {
  /**
   * Error handlers on listeners can throw, the catcher needs to be able to
   * discriminate between exceptions thrown by user code, and exceptions coming
   * from within the catcher itself. Use a global to keep track of which state
   * the catcher is currently in.
   */
  var inErrorTick = false;

  /**
   * Throwing always happens synchronously. If the current array of values for
   * the current list of asyncListeners is put in a module-scoped variable right
   * before a call that can throw, it will always be correct when the error
   * handlers are run.
   */
  var errorValues;

  asyncCatcher = function asyncCatcher(er) {
    var length = listeners.length;
    if (inErrorTick || length === 0) return false;

    var handled = false;

    /*
     * error handlers
     */
    inErrorTick = true;
    for (var i = 0; i < length; ++i) {
      var listener = listeners[i];
      if ((listener.flags & HAS_ERROR_AL) === 0) continue;

      var value = errorValues && errorValues[listener.uid];
      handled = listener.error(value, er) || handled;
    }
    inErrorTick = false;

    /* Test whether there are any listener arrays on the stack. In the case of
     * synchronous throws when the listener is active, there may have been
     * none pushed yet.
     */
    if (listenerStack.length > 0) listeners = listenerStack.pop();
    errorValues = undefined;

    return handled && !inAsyncTick;
  };

  asyncWrap = function asyncWrap(original, list, length) {
    var values = [];

    /*
     * listeners
     */
    inAsyncTick = true;
    for (var i = 0; i < length; ++i) {
      var listener = list[i];
      values[listener.uid] = listener.data;

      if ((listener.flags & HAS_CREATE_AL) === 0) continue;

      var value = listener.create(listener.data);
      if (value !== undefined) values[listener.uid] = value;
    }
    inAsyncTick = false;

    /* One of the main differences between this polyfill and the core
     * asyncListener support is that core avoids creating closures by putting a
     * lot of the state managemnt on the C++ side of Node (and of course also it
     * bakes support for async listeners into the Node C++ API through the
     * AsyncWrap class, which means that it doesn't monkeypatch basically every
     * async method like this does).
     */
    return function () {
      // put the current values where the catcher can see them
      errorValues = values;

      /* More than one listener can end up inside these closures, so save the
       * current listeners on a stack.
       */
      listenerStack.push(listeners);

      /* Activate both the listeners that were active when the closure was
       * created and the listeners that were previously active.
       */
      listeners = union(list, listeners);

      /*
       * before handlers
       */
      inAsyncTick = true;
      for (var i = 0; i < length; ++i) {
        if ((list[i].flags & HAS_BEFORE_AL) > 0) {
          list[i].before(this, values[list[i].uid]);
        }
      }
      inAsyncTick = false;

      // save the return value to pass to the after callbacks
      var returned = original.apply(this, arguments);

      /*
       * after handlers (not run if original throws)
       */
      inAsyncTick = true;
      for (i = 0; i < length; ++i) {
        if ((list[i].flags & HAS_AFTER_AL) > 0) {
          list[i].after(this, values[list[i].uid]);
        }
      }
      inAsyncTick = false;

      // back to the previous listener list on the stack
      listeners = listenerStack.pop();
      errorValues = undefined;

      return returned;
    };
  };

  wrap(process, '_fatalException', function (_fatalException) {
    return function _asyncFatalException(er) {
      return asyncCatcher(er) || _fatalException(er);
    };
  });
}
// 0.8 and below
else {
  /**
   * If an error handler in asyncWrap throws, the process must die. Under 0.8
   * and earlier the only way to put a bullet through the head of the process
   * is to rethrow from inside the exception handler, so rethrow and set
   * errorThrew to tell the uncaughtHandler what to do.
   */
  var errorThrew = false;

  /**
   * Under Node 0.8, this handler *only* handles synchronously thrown errors.
   * This simplifies it, which almost but not quite makes up for the hit taken
   * by putting everything in a try-catch.
   */
  asyncCatcher = function uncaughtCatcher(er) {
    // going down hard
    if (errorThrew) throw er;

    var handled = false;

    /*
     * error handlers
     */
    var length = listeners.length;
    for (var i = 0; i < length; ++i) {
      var listener = listeners[i];
      if ((listener.flags & HAS_ERROR_AL) === 0) continue;
      handled = listener.error(null, er) || handled;
    }

    /* Rethrow if one of the before / after handlers fire, which will bring the
     * process down immediately.
     */
    if (!handled && inAsyncTick) throw er;
  };

  asyncWrap = function asyncWrap(original, list, length) {
    var values = [];

    /*
     * listeners
     */
    inAsyncTick = true;
    for (var i = 0; i < length; ++i) {
      var listener = list[i];
      values[listener.uid] = listener.data;

      if ((listener.flags & HAS_CREATE_AL) === 0) continue;

      var value = listener.create(listener.data);
      if (value !== undefined) values[listener.uid] = value;
    }
    inAsyncTick = false;

    /* One of the main differences between this polyfill and the core
     * asyncListener support is that core avoids creating closures by putting a
     * lot of the state managemnt on the C++ side of Node (and of course also it
     * bakes support for async listeners into the Node C++ API through the
     * AsyncWrap class, which means that it doesn't monkeypatch basically every
     * async method like this does).
     */
    return function () {
      /*jshint maxdepth:4*/

      // after() handlers don't run if threw
      var threw = false;

      // ...unless the error is handled
      var handled = false;

      /* More than one listener can end up inside these closures, so save the
       * current listeners on a stack.
       */
      listenerStack.push(listeners);

      /* Activate both the listeners that were active when the closure was
       * created and the listeners that were previously active.
       */
      listeners = union(list, listeners);

      /*
       * before handlers
       */
      inAsyncTick = true;
      for (var i = 0; i < length; ++i) {
        if ((list[i].flags & HAS_BEFORE_AL) > 0) {
          list[i].before(this, values[list[i].uid]);
        }
      }
      inAsyncTick = false;

      // save the return value to pass to the after callbacks
      var returned;
      try {
        returned = original.apply(this, arguments);
      }
      catch (er) {
        threw = true;
        for (var i = 0; i < length; ++i) {
          if ((listeners[i].flags & HAS_ERROR_AL) == 0) continue;
          try {
            handled = listeners[i].error(values[list[i].uid], er) || handled;
          }
          catch (x) {
            errorThrew = true;
            throw x;
          }
        }

        if (!handled) {
          // having an uncaughtException handler here alters crash semantics
          process.removeListener('uncaughtException', asyncCatcher);
          process._originalNextTick(function () {
            process.addListener('uncaughtException', asyncCatcher);
          });

          throw er;
        }
      }
      finally {
        /*
         * after handlers (not run if original throws)
         */
        if (!threw || handled) {
          inAsyncTick = true;
          for (i = 0; i < length; ++i) {
            if ((list[i].flags & HAS_AFTER_AL) > 0) {
              list[i].after(this, values[list[i].uid]);
            }
          }
          inAsyncTick = false;
        }

        // back to the previous listener list on the stack
        listeners = listenerStack.pop();
      }

      return returned;
    };
  };

  // will be the first to fire if async-listener is the first module loaded
  process.addListener('uncaughtException', asyncCatcher);
}

// for performance in the case where there are no handlers, just the listener
function simpleWrap(original, list, length) {
  inAsyncTick = true;
  for (var i = 0; i < length; ++i) {
    var listener = list[i];
    if (listener.create) listener.create(listener.data);
  }
  inAsyncTick = false;

  // still need to make sure nested async calls are made in the context
  // of the listeners active at their creation
  return function () {
    listenerStack.push(listeners);
    listeners = union(list, listeners);

    var returned = original.apply(this, arguments);

    listeners = listenerStack.pop();

    return returned;
  };
}

/**
 * Called each time an asynchronous function that's been monkeypatched in
 * index.js is called. If there are no listeners, return the function
 * unwrapped.  If there are any asyncListeners and any of them have callbacks,
 * pass them off to asyncWrap for later use, otherwise just call the listener.
 */
function wrapCallback(original) {
  var length = listeners.length;

  // no context to capture, so avoid closure creation
  if (length === 0) return original;

  // capture the active listeners as of when the wrapped function was called
  var list = listeners.slice();

  for (var i = 0; i < length; ++i) {
    if (list[i].flags > 0) return asyncWrap(original, list, length);
  }

  return simpleWrap(original, list, length);
}

function AsyncListener(callbacks, data) {
  if (typeof callbacks.create === 'function') {
    this.create = callbacks.create;
    this.flags |= HAS_CREATE_AL;
  }

  if (typeof callbacks.before === 'function') {
    this.before = callbacks.before;
    this.flags |= HAS_BEFORE_AL;
  }

  if (typeof callbacks.after === 'function') {
    this.after = callbacks.after;
    this.flags |= HAS_AFTER_AL;
  }

  if (typeof callbacks.error === 'function') {
    this.error = callbacks.error;
    this.flags |= HAS_ERROR_AL;
  }

  this.uid = ++uid;
  this.data = data === undefined ? null : data;
}
AsyncListener.prototype.create = undefined;
AsyncListener.prototype.before = undefined;
AsyncListener.prototype.after  = undefined;
AsyncListener.prototype.error  = undefined;
AsyncListener.prototype.data   = undefined;
AsyncListener.prototype.uid    = 0;
AsyncListener.prototype.flags  = 0;

function createAsyncListener(callbacks, data) {
  if (typeof callbacks !== 'object' || !callbacks) {
    throw new TypeError('callbacks argument must be an object');
  }

  if (callbacks instanceof AsyncListener) {
    return callbacks;
  }
  else {
    return new AsyncListener(callbacks, data);
  }
}

function addAsyncListener(callbacks, data) {
  var listener;
  if (!(callbacks instanceof AsyncListener)) {
    listener = createAsyncListener(callbacks, data);
  }
  else {
    listener = callbacks;
  }

  // Make sure the listener isn't already in the list.
  var registered = false;
  for (var i = 0; i < listeners.length; i++) {
    if (listener === listeners[i]) {
      registered = true;
      break;
    }
  }

  if (!registered) listeners.push(listener);

  return listener;
}

function removeAsyncListener(listener) {
  for (var i = 0; i < listeners.length; i++) {
    if (listener === listeners[i]) {
      listeners.splice(i, 1);
      break;
    }
  }
}

process.createAsyncListener = createAsyncListener;
process.addAsyncListener    = addAsyncListener;
process.removeAsyncListener = removeAsyncListener;

module.exports = wrapCallback;


var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var path = require('path');
var Buffer = require('buffer').Buffer;
var _ = require('lodash');

/**
  * Finds the largest file in the given directory, optionally performing a recursive search.
  * @param {string} dir - the directory to search.
  * @param {object?} options - optional settings: { recurse?: boolean; preview?: boolean }.
  * @returns {object?} null if no files found, otherwise an object of the form
  *                    { path: string; size: number; preview?: string, searched: number; }
  */
var largest = function (dir, options, internal) {

    // Parse arguments.
    options = options || largest.options;

    // Get all file stats in parallel.
    return fs.readdirAsync(dir)
    .then (function (files) {
        var paths = _.map(files, function (file) { return path.join(dir, file); });
        return Promise.all(_.map(paths, function (path) { return fs.statAsync(path); }))
        .then(function (stats) { return [paths, stats]; });
    })

    // Build up a list of possible candidates, recursing into subfolders if requested.
    .spread(function (paths, stats) {
        return Promise.all(
            _.map(stats, function (stat, i) {
                if (stat.isFile()) return Promise.resolve({ path: paths[i], size: stat.size, searched: 1 });
                return options.recurse ? largest(paths[i], options, true) : Promise.resolve(null);
            })
        );
    })

    // Choose the best candidate.
    .then(function (candidates) {
        return _(candidates)
        .compact()
        .reduce(function (best, cand) {
            if (cand.size > best.size) var temp = cand, cand = best, best = temp;
            best.searched += cand.searched;
            return best;
        });
    })

    // Add a preview if requested (but skip if this is an internal step in a recursive search).
    .then(function (result) {
        if (result && options.preview && !internal) {
            var fd_;
            return fs.openAsync(result.path, 'r')
            .then(function (fd) {
                fd_ = fd;
                var buffer = new Buffer(40);
                return fs.readAsync(fd, buffer, 0, 40, 0);
            })
            .spread(function (bytesRead, buffer) {
                result.preview = buffer.toString('utf-8', 0, bytesRead);
                return fs.closeAsync(fd_);
            })
            .then(function () {
                return result;
            });
        } else {
            return result; // Return without adding preview.
        }
    });
};
largest.options = {};

function nodeified(dir, options, callback) {
    if (arguments.length == 2) callback = options, options = null;
    largest(dir, options).nodeify(callback);
}
module.exports = nodeified;


var Fiber = require('../fibers');
var _ = require('lodash');
var Config = require('./config');
var FiberMgr = require('./fiberManager');
var RunContext = require('./runContext');
var Semaphore = require('./semaphore');
var AsyncIterator = require('./asyncIterator');
var defer = require('./defer');
var await = require('../await/index');
/** Function for creating a specific variant of the async function. */
function makeAsyncFunc(config) {
    // Validate the specified configuration
    config.validate();
    // Create an async function tailored to the given options.
    var result = function async(bodyFunc) {
        // Create a semaphore for limiting top-level concurrency, if specified in options.
        var semaphore = config.maxConcurrency ? new Semaphore(config.maxConcurrency) : Semaphore.unlimited;
        // Choose and run the appropriate function factory based on whether the result should be iterable.
        var makeFunc = config.isIterable ? makeAsyncIterator : makeAsyncNonIterator;
        var result = makeFunc(bodyFunc, config, semaphore);
        // Ensure the suspendable function's arity matches that of the function it wraps.
        var arity = bodyFunc.length;
        if (config.acceptsCallback)
            ++arity;
        result = makeFuncWithArity(result, arity);
        return result;
    };
    // Add the mod() function, and return the result.
    result.mod = makeModFunc(config);
    return result;
}
/** Function for creating iterable suspendable functions. */
function makeAsyncIterator(bodyFunc, config, semaphore) {
    // Return a function that returns an iterator.
    return function iterable() {
        // Capture the initial arguments used to start the iterator, as an array.
        var startupArgs = new Array(arguments.length + 1); // Reserve 0th arg for the yield function.
        for (var i = 0, len = arguments.length; i < len; ++i)
            startupArgs[i + 1] = arguments[i];
        // Create a yield() function tailored for this iterator.
        var yield_ = function (expr) {
            // Ensure this function is executing inside a fiber.
            if (!Fiber.current) {
                throw new Error('await functions, yield functions, and value-returning suspendable ' +
                    'functions may only be called from inside a suspendable function. ');
            }
            // Notify waiters of the next result, then suspend the iterator.
            if (runContext.callback)
                runContext.callback(null, { value: expr, done: false });
            if (runContext.resolver)
                runContext.resolver.resolve({ value: expr, done: false });
            Fiber.yield();
        };
        // Insert the yield function as the first argument when starting the iterator.
        startupArgs[0] = yield_;
        // Create the iterator.
        var runContext = new RunContext(bodyFunc, this, startupArgs);
        var iterator = new AsyncIterator(runContext, semaphore, config.returnValue, config.acceptsCallback);
        // Wrap the given bodyFunc to properly complete the iteration.
        runContext.wrapped = function () {
            var len = arguments.length, args = new Array(len);
            for (var i = 0; i < len; ++i)
                args[i] = arguments[i];
            bodyFunc.apply(this, args);
            iterator.destroy();
            return { done: true };
        };
        // Return the iterator.
        return iterator;
    };
}
/** Function for creating non-iterable suspendable functions. */
function makeAsyncNonIterator(bodyFunc, config, semaphore) {
    // Return a function that executes fn in a fiber and returns a promise of fn's result.
    return function nonIterable() {
        // Get all the arguments passed in, as an array.
        var argsAsArray = new Array(arguments.length);
        for (var i = 0; i < argsAsArray.length; ++i)
            argsAsArray[i] = arguments[i];
        // Remove concurrency restrictions for nested calls, to avoid race conditions.
        if (FiberMgr.isExecutingInFiber())
            this._semaphore = Semaphore.unlimited;
        // Configure the run context.
        var runContext = new RunContext(bodyFunc, this, argsAsArray, function () { return semaphore.leave(); });
        if (config.returnValue !== Config.NONE) {
            var resolver = defer();
            runContext.resolver = resolver;
        }
        if (config.acceptsCallback && argsAsArray.length && _.isFunction(argsAsArray[argsAsArray.length - 1])) {
            var callback = argsAsArray.pop();
            runContext.callback = callback;
        }
        // Execute bodyFunc to completion in a coroutine. For thunks, this is a lazy operation.
        if (config.returnValue === Config.THUNK) {
            var thunk = function (done) {
                if (done)
                    resolver.promise.then(function (val) { return done(null, val); }, function (err) { return done(err); });
                semaphore.enter(function () { return FiberMgr.create().run(runContext); });
            };
        }
        else {
            semaphore.enter(function () { return FiberMgr.create().run(runContext); });
        }
        // Return the appropriate value.
        switch (config.returnValue) {
            case Config.PROMISE: return resolver.promise;
            case Config.THUNK: return thunk;
            case Config.RESULT: return await(resolver.promise);
            case Config.NONE: return;
        }
    };
}
/** Returns a function that directly proxies the given function, whilst reporting the given arity. */
function makeFuncWithArity(fn, arity) {
    // Need to handle each arity individually, but the body never changes.
    switch (arity) {
        case 0: return function f0() { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
            r[i] = arguments[i]; return fn.apply(this, r); };
        case 1: return function f1(a) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
            r[i] = arguments[i]; return fn.apply(this, r); };
        case 2: return function f2(a, b) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
            r[i] = arguments[i]; return fn.apply(this, r); };
        case 3: return function f3(a, b, c) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
            r[i] = arguments[i]; return fn.apply(this, r); };
        case 4: return function f4(a, b, c, d) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
            r[i] = arguments[i]; return fn.apply(this, r); };
        case 5: return function f5(a, b, c, d, e) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
            r[i] = arguments[i]; return fn.apply(this, r); };
        case 6: return function f6(a, b, c, d, e, f) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
            r[i] = arguments[i]; return fn.apply(this, r); };
        case 7: return function f7(a, b, c, d, e, f, g) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
            r[i] = arguments[i]; return fn.apply(this, r); };
        case 8: return function f8(a, b, c, d, e, f, g, h) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
            r[i] = arguments[i]; return fn.apply(this, r); };
        case 9: return function f9(a, b, c, d, e, f, g, h, _i) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
            r[i] = arguments[i]; return fn.apply(this, r); };
        default: return fn; // Bail out if arity is crazy high.
    }
}
function makeModFunc(config) {
    return function (options, maxConcurrency) {
        if (_.isString(options)) {
            // This way of specifying options is useful for TypeScript users, as they get better type information.
            // JavaScript users can use this too, but providing an options hash is more useful in that case.
            var rt, cb, it;
            switch (options) {
                case 'returns: promise, callback: false, iterable: false':
                    rt = 'promise';
                    cb = false;
                    it = false;
                    break;
                case 'returns: thunk, callback: false, iterable: false':
                    rt = 'thunk';
                    cb = false;
                    it = false;
                    break;
                case 'returns: result, callback: false, iterable: false':
                    rt = 'result';
                    cb = false;
                    it = false;
                    break;
                case 'returns: promise, callback: true, iterable: false':
                    rt = 'promise';
                    cb = true;
                    it = false;
                    break;
                case 'returns: thunk, callback: true, iterable: false':
                    rt = 'thunk';
                    cb = true;
                    it = false;
                    break;
                case 'returns: result, callback: true, iterable: false':
                    rt = 'result';
                    cb = true;
                    it = false;
                    break;
                case 'returns: none, callback: true, iterable: false':
                    rt = 'none';
                    cb = true;
                    it = false;
                    break;
                case 'returns: promise, callback: false, iterable: true':
                    rt = 'promise';
                    cb = false;
                    it = true;
                    break;
                case 'returns: thunk, callback: false, iterable: true':
                    rt = 'thunk';
                    cb = false;
                    it = true;
                    break;
                case 'returns: result, callback: false, iterable: true':
                    rt = 'result';
                    cb = false;
                    it = true;
                    break;
                case 'returns: promise, callback: true, iterable: true':
                    rt = 'promise';
                    cb = true;
                    it = true;
                    break;
                case 'returns: thunk, callback: true, iterable: true':
                    rt = 'thunk';
                    cb = true;
                    it = true;
                    break;
                case 'returns: result, callback: true, iterable: true':
                    rt = 'result';
                    cb = true;
                    it = true;
                    break;
                case 'returns: none, callback: true, iterable: true':
                    rt = 'none';
                    cb = true;
                    it = true;
                    break;
            }
            options = { returnValue: rt, acceptsCallback: cb, isIterable: it, maxConcurrency: maxConcurrency };
        }
        var newConfig = new Config(_.defaults({}, options, config));
        return makeAsyncFunc(newConfig);
    };
}
module.exports = makeAsyncFunc;
//# sourceMappingURL=makeAsyncFunc.js.map


var Fiber = require('../fibers');
/** Helpers for Fiber management. */
var FiberManager;
(function (FiberManager) {
    /** Returns true if the current execution context is within a fiber. */
    function isExecutingInFiber() {
        return !!Fiber.current;
    }
    FiberManager.isExecutingInFiber = isExecutingInFiber;
    /** Creates and returns a new fiber in which an arbitrary function may be executed. */
    function create() {
        return Fiber(runInFiber);
    }
    FiberManager.create = create;
})(FiberManager || (FiberManager = {}));
/**
 * The runInFiber() function provides the prolog/epilog wrapper code for running a function inside
 * a fiber. The runInFiber() function accepts a RunContext instance, and calls the wrapped function
 * specified there. The final return/throw value of the wrapped function is used to notify the
 * promise resolver and/or callback specified in the RunContext. This function must take all its
 * information in a single argument because it is called via Fiber#run(), which accepts one argument.
 * NB: Since try/catch/finally prevents V8 optimisations, the function is split into several parts.
 */
function runInFiber(runCtx) {
    try {
        tryBlock(runCtx);
    }
    catch (err) {
        catchBlock(runCtx, err);
    }
    finally {
        finallyBlock(runCtx);
    }
}
function tryBlock(runCtx) {
    // Maintain an accurate count of currently active fibers, for pool management.
    adjustFiberCount(+1);
    // Call the wrapped function. It may be suspended several times (at await and/or yield calls).
    var result = runCtx.wrapped.apply(runCtx.thisArg, runCtx.argsAsArray);
    // The wrapped function returned normally. Notify any waiters.
    if (runCtx.callback)
        runCtx.callback(null, result);
    if (runCtx.resolver)
        runCtx.resolver.resolve(result);
}
function catchBlock(runCtx, err) {
    // The wrapped function threw an exception. Notify any waiters.
    if (runCtx.callback)
        runCtx.callback(err);
    if (runCtx.resolver)
        runCtx.resolver.reject(err);
}
function finallyBlock(runCtx) {
    // Maintain an accurate count of currently active fibers, for pool management.
    adjustFiberCount(-1);
    // Execute the done() callback, if provided.
    if (runCtx.done)
        runCtx.done();
}
/**
 * The following functionality prevents memory leaks in node-fibers by actively managing Fiber.poolSize.
 * For more information, see https://github.com/laverdet/node-fibers/issues/169.
 */
function adjustFiberCount(delta) {
    activeFiberCount += delta;
    if (activeFiberCount >= fiberPoolSize) {
        fiberPoolSize += 100;
        Fiber.poolSize = fiberPoolSize;
    }
}
var fiberPoolSize = Fiber.poolSize;
var activeFiberCount = 0;
module.exports = FiberManager;
//# sourceMappingURL=fiberManager.js.map


var Fiber = require('../fibers');
var Promise = require('bluebird');
var _ = require('lodash');
/** Function for creating a specific variant of the await() function.
 *  @param {string} variant - Recognised values: undefined, 'in', 'top'.
 */
function makeAwaitFunc(variant) {
    // Return an await function tailored to the given options.
    switch (variant) {
        case 'in': return getExtraInfo(traverseInPlace);
        case 'top': return function (n) { return getExtraInfo(traverseInPlace, n); };
        default: return getExtraInfo(traverseClone);
    }
}
/** Helper function for makeAwaitFunc(). */
function getExtraInfo(traverse, topN) {
    return function await() {
        // Ensure this function is executing inside a fiber.
        if (!Fiber.current) {
            throw new Error('await functions, yield functions, and value-returning suspendable ' +
                'functions may only be called from inside a suspendable function. ');
        }
        // Parse argument(s). If not a single argument, treat it like an array was passed in.
        if (arguments.length === 1) {
            var expr = arguments[0];
        }
        else {
            expr = new Array(arguments.length);
            for (var i = 0; i < arguments.length; ++i)
                expr[i] = arguments[i];
            traverse = traverseInPlace;
        }
        // Handle each supported 'awaitable' appropriately...
        var fiber = Fiber.current;
        if (expr && _.isFunction(expr.then)) {
            // A promise: resume the coroutine with the resolved value, or throw the rejection value into it.
            // NB: ensure the handlers return null to avoid bluebird 3.x warning 'a promise was created in a
            //     handler but none were returned from it'. This occurs if the next resumption of the suspendable
            //     function (i.e. in the client's code) creates a bluebird 3.x promise and then awaits it.
            expr.then(function (val) { return (fiber.run(val), fiber = null); }, function (err) { return (fiber.throwInto(err), fiber = null); });
        }
        else if (_.isFunction(expr)) {
            // A thunk: resume the coroutine with the callback value, or throw the errback value into it.
            expr(function (err, val) { if (err)
                fiber.throwInto(err);
            else
                fiber.run(val); fiber = null; });
        }
        else if (_.isArray(expr) || _.isPlainObject(expr)) {
            // An array or plain object: resume the coroutine with a deep clone of the array/object,
            // where all contained promises and thunks have been replaced by their resolved values.
            // NB: ensure handlers return null (see similar comment above).
            var trackedPromises = [];
            expr = traverse(expr, trackAndReplaceWithResolvedValue(trackedPromises));
            if (!topN) {
                Promise.all(trackedPromises).then(function (val) { return (fiber.run(expr), fiber = null); }, function (err) { return (fiber.throwInto(err), fiber = null); });
            }
            else {
                Promise.some(trackedPromises, topN).then(function (val) { return (fiber.run(val), fiber = null); }, function (err) { return (fiber.throwInto(err), fiber = null); });
            }
        }
        else {
            // Anything else: resume the coroutine immediately with the value.
            setImmediate(function () { fiber.run(expr); fiber = null; });
        }
        // Suspend the current fiber until the one of the above handlers resumes it again.
        return Fiber.yield();
    };
}
/** In-place (ie non-cloning) object traversal. */
function traverseInPlace(o, visitor) {
    if (_.isArray(o)) {
        var len = o.length;
        for (var i = 0; i < len; ++i) {
            traverseInPlace(o[i], visitor);
            visitor(o, i);
        }
    }
    else if (_.isPlainObject(o)) {
        for (var key in o) {
            if (!o.hasOwnProperty(key))
                continue;
            traverseInPlace(o[key], visitor);
            visitor(o, key);
        }
    }
    return o;
}
/** Object traversal with cloning. */
function traverseClone(o, visitor) {
    var result;
    if (_.isArray(o)) {
        var len = o.length;
        result = new Array(len);
        for (var i = 0; i < len; ++i) {
            result[i] = traverseClone(o[i], visitor);
            visitor(result, i);
        }
    }
    else if (_.isPlainObject(o)) {
        result = {};
        for (var key in o) {
            if (o.hasOwnProperty(key)) {
                result[key] = traverseClone(o[key], visitor);
                visitor(result, key);
            }
        }
    }
    else {
        result = o;
    }
    return result;
}
/** Visitor function factory for handling thunks and promises in awaited object graphs. */
function trackAndReplaceWithResolvedValue(tracking) {
    // Return a visitor function closed over the specified tracking array.
    return function (obj, key) {
        // Get the value being visited, and return early if it's falsy.
        var val = obj[key];
        if (!val)
            return;
        // If the value is a thunk, convert it to an equivalent promise.
        if (_.isFunction(val))
            val = thunkToPromise(val);
        // If the value is a promise, add it to the tracking array, and replace it with its value when resolved.
        if (_.isFunction(val.then)) {
            tracking.push(val);
            val.then(function (result) { obj[key] = result; }, function (err) { });
        }
    };
}
/** Convert a thunk to a promise. */
function thunkToPromise(thunk) {
    return new Promise(function (resolve, reject) {
        var callback = function (err, val) { return (err ? reject(err) : resolve(val)); };
        thunk(callback);
    });
}
module.exports = makeAwaitFunc;
//# sourceMappingURL=makeAwaitFunc.js.map


var fs = require('fs');
var path = require('path');
var _ = require('lodash');

/** Returns the number of files in the given directory. */
var countFiles = function (dir) {
    var files = fs.readdirSync(dir);

    // Get all file stats in parallel.
    var paths = _.map(files, function (file) { return path.join(dir, file); });
    var stats = _.map(paths, function (path) { return fs.statSync(path); });

    // Count the files.
    return _.filter(stats, function (stat) { return stat.isFile(); }).length;
};

function nodeified(dir, callback) {
    try {
        callback(null, countFiles(dir));
    }
    catch (err) {
        callback(err);
    }
}
module.exports = nodeified;


module.exports = regRequest

// npm: means
// 1. https
// 2. send authorization
// 3. content-type is 'application/json' -- metadata
//
var assert = require('assert')
var url = require('url')
var zlib = require('zlib')
var Stream = require('stream').Stream
var STATUS_CODES = require('http').STATUS_CODES

var request = require('request')
var once = require('once')

function regRequest (uri, params, cb_) {
  assert(typeof uri === 'string', 'must pass uri to request')
  assert(params && typeof params === 'object', 'must pass params to request')
  assert(typeof cb_ === 'function', 'must pass callback to request')

  params.method = params.method || 'GET'
  this.log.verbose('request', 'uri', uri)

  // Since there are multiple places where an error could occur,
  // don't let the cb be called more than once.
  var cb = once(cb_)

  if (uri.match(/^\/?favicon.ico/)) {
    return cb(new Error("favicon.ico isn't a package, it's a picture."))
  }

  var adduserChange = /\/?-\/user\/org\.couchdb\.user:([^/]+)\/-rev/
  var isUserChange = uri.match(adduserChange)
  var adduserNew = /\/?-\/user\/org\.couchdb\.user:([^/?]+)$/
  var isNewUser = uri.match(adduserNew)
  var alwaysAuth = params.auth && params.auth.alwaysAuth
  var isDelete = params.method === 'DELETE'
  var isWrite = params.body || isDelete

  if (isUserChange && !isWrite) {
    return cb(new Error('trying to change user document without writing(?!)'))
  }

  if (params.authed == null) {
    // new users can *not* use auth, because they don't *have* auth yet
    if (isUserChange) {
      this.log.verbose('request', 'updating existing user; sending authorization')
      params.authed = true
    } else if (isNewUser) {
      this.log.verbose('request', "new user, so can't send auth")
      params.authed = false
    } else if (alwaysAuth) {
      this.log.verbose('request', 'always-auth set; sending authorization')
      params.authed = true
    } else if (isWrite) {
      this.log.verbose('request', 'sending authorization for write operation')
      params.authed = true
    } else {
      // most of the time we don't want to auth
      this.log.verbose('request', 'no auth needed')
      params.authed = false
    }
  }

  var self = this
  this.attempt(function (operation) {
    makeRequest.call(self, uri, params, function (er, parsed, raw, response) {
      if (response) {
        self.log.verbose('headers', response.headers)
        if (response.headers['npm-notice']) {
          self.log.warn('notice', response.headers['npm-notice'])
        }
      }

      if (!er || (er.message && er.message.match(/^SSL Error/))) {
        if (er) er.code = 'ESSL'
        return cb(er, parsed, raw, response)
      }

      // Only retry on 408, 5xx or no `response`.
      var statusCode = response && response.statusCode

      var timeout = statusCode === 408
      var serverError = statusCode >= 500
      var statusRetry = !statusCode || timeout || serverError
      if (er && statusRetry && operation.retry(er)) {
        self.log.info('retry', 'will retry, error on last attempt: ' + er)
        return undefined
      }
      cb.apply(null, arguments)
    })
  })
}

function makeRequest (uri, params, cb_) {
  var socket
  var cb = once(function (er, parsed, raw, response) {
    if (socket) {
      // The socket might be returned to a pool for re-use, so don’t keep
      // the 'error' listener from here attached.
      socket.removeListener('error', cb)
    }

    return cb_(er, parsed, raw, response)
  })

  var parsed = url.parse(uri)
  var headers = {}

  // metadata should be compressed
  headers['accept-encoding'] = 'gzip'

  // metadata should be minified, if the registry supports it

  var er = this.authify(params.authed, parsed, headers, params.auth)
  if (er) return cb_(er)

  var useCorgi = params.fullMetadata == null ? false : !params.fullMetadata

  var opts = this.initialize(
    parsed,
    params.method,
    useCorgi ? 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*' : 'application/json',
    headers
  )

  opts.followRedirect = (typeof params.follow === 'boolean' ? params.follow : true)
  opts.encoding = null // tell request let body be Buffer instance

  if (params.etag) {
    this.log.verbose('etag', params.etag)
    headers[params.method === 'GET' ? 'if-none-match' : 'if-match'] = params.etag
  }

  if (params.lastModified && params.method === 'GET') {
    this.log.verbose('lastModified', params.lastModified)
    headers['if-modified-since'] = params.lastModified
  }

  // figure out wth body is
  if (params.body) {
    if (Buffer.isBuffer(params.body)) {
      opts.body = params.body
      headers['content-type'] = 'application/json'
      headers['content-length'] = params.body.length
    } else if (typeof params.body === 'string') {
      opts.body = params.body
      headers['content-type'] = 'application/json'
      headers['content-length'] = Buffer.byteLength(params.body)
    } else if (params.body instanceof Stream) {
      headers['content-type'] = 'application/octet-stream'
      if (params.body.size) headers['content-length'] = params.body.size
    } else {
      delete params.body._etag
      delete params.body._lastModified
      opts.json = params.body
    }
  }

  this.log.http('request', params.method, parsed.href || '/')

  var done = requestDone.call(this, params.method, uri, cb)
  var req = request(opts, params.streaming ? undefined : decodeResponseBody(done))

  req.on('error', cb)

  // This should not be necessary, as the HTTP implementation in Node
  // passes errors occurring on the socket to the request itself. Being overly
  // cautious comes at a low cost, though.
  req.on('socket', function (s) {
    socket = s
    socket.on('error', cb)
  })

  if (params.streaming) {
    req.on('response', function (response) {
      if (response.statusCode >= 400) {
        var parts = []
        response.on('data', function (data) {
          parts.push(data)
        })
        response.on('end', function () {
          decodeResponseBody(done)(null, response, Buffer.concat(parts))
        })
      } else {
        response.on('end', function () {
          // don't ever re-use connections that had server errors.
          // those sockets connect to the Bad Place!
          if (response.socket && response.statusCode > 500) {
            response.socket.destroy()
          }
        })

        return cb(null, response)
      }
    })
  }

  if (params.body && (params.body instanceof Stream)) {
    params.body.pipe(req)
  }
}

function decodeResponseBody (cb) {
  return function (er, response, data) {
    if (er) return cb(er, response, data)

    // don't ever re-use connections that had server errors.
    // those sockets connect to the Bad Place!
    if (response.socket && response.statusCode > 500) {
      response.socket.destroy()
    }

    if (response.headers['content-encoding'] !== 'gzip') {
      return cb(er, response, data)
    }

    zlib.gunzip(data, function (er, buf) {
      if (er) return cb(er, response, data)

      cb(null, response, buf)
    })
  }
}

// cb(er, parsed, raw, response)
function requestDone (method, where, cb) {
  return function (er, response, data) {
    if (er) return cb(er)

    var urlObj = url.parse(where)
    if (urlObj.auth) urlObj.auth = '***'
    this.log.http(response.statusCode, url.format(urlObj))

    if (Buffer.isBuffer(data)) {
      data = data.toString()
    }

    var parsed
    if (data && typeof data === 'string' && response.statusCode !== 304) {
      try {
        parsed = JSON.parse(data)
      } catch (ex) {
        ex.message += '\n' + data
        this.log.verbose('bad json', data)
        this.log.error('registry', 'error parsing json')
        return cb(ex, null, data, response)
      }
    } else if (data) {
      parsed = data
      data = JSON.stringify(parsed)
    }

    // expect data with any error codes
    if (!data && response.statusCode >= 400) {
      var code = response.statusCode
      return cb(
        makeError(code + ' ' + STATUS_CODES[code], null, code),
        null,
        data,
        response
      )
    }

    er = null
    if (parsed && response.headers.etag) {
      parsed._etag = response.headers.etag
    }

    if (parsed && response.headers['last-modified']) {
      parsed._lastModified = response.headers['last-modified']
    }

    // for the search endpoint, the 'error' property can be an object
    if ((parsed && parsed.error && typeof parsed.error !== 'object') ||
        response.statusCode >= 400) {
      var w = url.parse(where).pathname.substr(1)
      var name
      if (!w.match(/^-/)) {
        w = w.split('/')
        var index = w.indexOf('_rewrite')
        if (index === -1) {
          index = w.length - 1
        } else {
          index++
        }
        name = decodeURIComponent(w[index])
      }

      if (!parsed.error) {
        if (response.statusCode === 401 && response.headers['www-authenticate']) {
          const auth = response.headers['www-authenticate'].split(/,\s*/).map(s => s.toLowerCase())
          if (auth.indexOf('ipaddress') !== -1) {
            er = makeError('Login is not allowed from your IP address', name, response.statusCode, 'EAUTHIP')
          } else if (auth.indexOf('otp') !== -1) {
            er = makeError('OTP required for this operation', name, response.statusCode, 'EOTP')
          } else {
            er = makeError('Unable to authenticate, need: ' + response.headers['www-authenticate'], name, response.statusCode, 'EAUTHUNKNOWN')
          }
        } else {
          const msg = parsed.message ? ': ' + parsed.message : ''
          er = makeError(
            'Registry returned ' + response.statusCode +
            ' for ' + method +
            ' on ' + where +
            msg,
            name,
            response.statusCode
          )
        }
      } else if (name && parsed.error === 'not_found') {
        er = makeError('404 Not Found: ' + name, name, response.statusCode)
      } else if (name && parsed.error === 'User not found') {
        er = makeError('User not found. Check `npm whoami` and make sure you have a NPM account.', name, response.statusCode)
      } else {
        er = makeError(
          parsed.error + ' ' + (parsed.reason || '') + ': ' + (name || w),
          name,
          response.statusCode
        )
      }
    }
    return cb(er, parsed, data, response)
  }.bind(this)
}

function makeError (message, name, statusCode, code) {
  var er = new Error(message)
  if (name) er.pkgid = name
  if (statusCode) {
    er.statusCode = statusCode
    er.code = code || 'E' + statusCode
  }
  return er
}


module.exports = get

var assert = require('assert')
var url = require('url')

/*
 * This is meant to be overridden in specific implementations if you
 * want specialized behavior for metadata (i.e. caching).
 */
function get (uri, params, cb) {
  assert(typeof uri === 'string', 'must pass registry URI to get')
  assert(params && typeof params === 'object', 'must pass params to get')
  assert(typeof cb === 'function', 'must pass callback to get')

  var parsed = url.parse(uri)
  assert(
    parsed.protocol === 'http:' || parsed.protocol === 'https:',
    'must have a URL that starts with http: or https:'
  )

  this.request(uri, params, cb)
}


const algoliasearch = require('algoliasearch');
const chunk = require('lodash.chunk');
const report = require('gatsby-cli/lib/reporter');

/**
 * give back the same thing as this was called with.
 *
 * @param {any} obj what to keep the same
 */
const identity = obj => obj;

exports.onPostBuild = async function(
  { graphql },
  { appId, apiKey, queries, indexName: mainIndexName, chunkSize = 1000 }
) {
  const activity = report.activityTimer(`index to Algolia`);
  activity.start();
  const client = algoliasearch(appId, apiKey);

  setStatus(activity, `${queries.length} queries to index`);

  const jobs = queries.map(async function doQuery(
    { indexName = mainIndexName, query, transformer = identity, settings },
    i
  ) {
    if (!query) {
      report.panic(
        `failed to index to Algolia. You did not give "query" to this query`
      );
    }
    const index = client.initIndex(indexName);
    const mainIndexExists = await indexExists(index);
    const tmpIndex = client.initIndex(`${indexName}_tmp`);
    const indexToUse = mainIndexExists ? tmpIndex : index;

    if (mainIndexExists) {
      setStatus(activity, `query ${i}: copying existing index`);
      await scopedCopyIndex(client, index, tmpIndex);
    }

    setStatus(activity, `query ${i}: executing query`);
    const result = await graphql(query);
    if (result.errors) {
      report.panic(`failed to index to Algolia`, result.errors);
    }
    const objects = transformer(result);
    const chunks = chunk(objects, chunkSize);

    setStatus(activity, `query ${i}: splitting in ${chunks.length} jobs`);

    const chunkJobs = chunks.map(async function(chunked) {
      const { taskID } = await indexToUse.addObjects(chunked);
      return indexToUse.waitTask(taskID);
    });

    await Promise.all(chunkJobs);

    if (settings) {
      indexToUse.setSettings(settings);
    }

    if (mainIndexExists) {
      setStatus(activity, `query ${i}: moving copied index to main index`);
      return moveIndex(client, tmpIndex, index);
    }
  });

  try {
    await Promise.all(jobs);
  } catch (err) {
    report.panic(`failed to index to Algolia`, err);
  }
  activity.end();
};

/**
 * Copy the settings, synonyms, and rules of the source index to the target index
 * @param client
 * @param sourceIndex
 * @param targetIndex
 * @return {Promise}
 */
async function scopedCopyIndex(client, sourceIndex, targetIndex) {
  const { taskID } = await client.copyIndex(
    sourceIndex.indexName,
    targetIndex.indexName,
    ['settings', 'synonyms', 'rules']
  );
  return targetIndex.waitTask(taskID);
}

/**
 * moves the source index to the target index
 * @param client
 * @param sourceIndex
 * @param targetIndex
 * @return {Promise}
 */
async function moveIndex(client, sourceIndex, targetIndex) {
  const { taskID } = await client.moveIndex(
    sourceIndex.indexName,
    targetIndex.indexName
  );
  return targetIndex.waitTask(taskID);
}

/**
 * Does an Algolia index exist already
 *
 * @param index
 */
async function indexExists(index) {
  try {
    const { nbHits } = await index.search();
    return nbHits > 0;
  } catch (e) {
    return false;
  }
}

/**
 * Hotfix the Gatsby reporter to allow setting status (not supported everywhere)
 *
 * @param {Object} activity reporter
 * @param {String} status status to report
 */
function setStatus(activity, status) {
  if (activity && activity.setStatus) {
    activity.setStatus(status);
  } else {
    console.log('Algolia:', status);
  }
}


/*!
 * negotiator
 * Copyright(c) 2012 Federico Romero
 * Copyright(c) 2012-2014 Isaac Z. Schlueter
 * Copyright(c) 2015 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict';

/**
 * Cached loaded submodules.
 * @private
 */

var modules = Object.create(null);

/**
 * Module exports.
 * @public
 */

module.exports = Negotiator;
module.exports.Negotiator = Negotiator;

/**
 * Create a Negotiator instance from a request.
 * @param {object} request
 * @public
 */

function Negotiator(request) {
  if (!(this instanceof Negotiator)) {
    return new Negotiator(request);
  }

  this.request = request;
}

Negotiator.prototype.charset = function charset(available) {
  var set = this.charsets(available);
  return set && set[0];
};

Negotiator.prototype.charsets = function charsets(available) {
  var preferredCharsets = loadModule('charset').preferredCharsets;
  return preferredCharsets(this.request.headers['accept-charset'], available);
};

Negotiator.prototype.encoding = function encoding(available) {
  var set = this.encodings(available);
  return set && set[0];
};

Negotiator.prototype.encodings = function encodings(available) {
  var preferredEncodings = loadModule('encoding').preferredEncodings;
  return preferredEncodings(this.request.headers['accept-encoding'], available);
};

Negotiator.prototype.language = function language(available) {
  var set = this.languages(available);
  return set && set[0];
};

Negotiator.prototype.languages = function languages(available) {
  var preferredLanguages = loadModule('language').preferredLanguages;
  return preferredLanguages(this.request.headers['accept-language'], available);
};

Negotiator.prototype.mediaType = function mediaType(available) {
  var set = this.mediaTypes(available);
  return set && set[0];
};

Negotiator.prototype.mediaTypes = function mediaTypes(available) {
  var preferredMediaTypes = loadModule('mediaType').preferredMediaTypes;
  return preferredMediaTypes(this.request.headers.accept, available);
};

// Backwards compatibility
Negotiator.prototype.preferredCharset = Negotiator.prototype.charset;
Negotiator.prototype.preferredCharsets = Negotiator.prototype.charsets;
Negotiator.prototype.preferredEncoding = Negotiator.prototype.encoding;
Negotiator.prototype.preferredEncodings = Negotiator.prototype.encodings;
Negotiator.prototype.preferredLanguage = Negotiator.prototype.language;
Negotiator.prototype.preferredLanguages = Negotiator.prototype.languages;
Negotiator.prototype.preferredMediaType = Negotiator.prototype.mediaType;
Negotiator.prototype.preferredMediaTypes = Negotiator.prototype.mediaTypes;

/**
 * Load the given module.
 * @private
 */

function loadModule(moduleName) {
  var module = modules[moduleName];

  if (module !== undefined) {
    return module;
  }

  // This uses a switch for static require analysis
  switch (moduleName) {
    case 'charset':
      module = require('./lib/charset');
      break;
    case 'encoding':
      module = require('./lib/encoding');
      break;
    case 'language':
      module = require('./lib/language');
      break;
    case 'mediaType':
      module = require('./lib/mediaType');
      break;
    default:
      throw new Error('Cannot find module \'' + moduleName + '\'');
  }

  // Store to prevent invoking require()
  modules[moduleName] = module;

  return module;
}


/**
 * negotiator
 * Copyright(c) 2012 Isaac Z. Schlueter
 * Copyright(c) 2014 Federico Romero
 * Copyright(c) 2014-2015 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict';

/**
 * Module exports.
 * @public
 */

module.exports = preferredLanguages;
module.exports.preferredLanguages = preferredLanguages;

/**
 * Module variables.
 * @private
 */

var simpleLanguageRegExp = /^\s*([^\s\-;]+)(?:-([^\s;]+))?\s*(?:;(.*))?$/;

/**
 * Parse the Accept-Language header.
 * @private
 */

function parseAcceptLanguage(accept) {
  var accepts = accept.split(',');

  for (var i = 0, j = 0; i < accepts.length; i++) {
    var language = parseLanguage(accepts[i].trim(), i);

    if (language) {
      accepts[j++] = language;
    }
  }

  // trim accepts
  accepts.length = j;

  return accepts;
}

/**
 * Parse a language from the Accept-Language header.
 * @private
 */

function parseLanguage(str, i) {
  var match = simpleLanguageRegExp.exec(str);
  if (!match) return null;

  var prefix = match[1],
    suffix = match[2],
    full = prefix;

  if (suffix) full += "-" + suffix;

  var q = 1;
  if (match[3]) {
    var params = match[3].split(';')
    for (var j = 0; j < params.length; j++) {
      var p = params[j].split('=');
      if (p[0] === 'q') q = parseFloat(p[1]);
    }
  }

  return {
    prefix: prefix,
    suffix: suffix,
    q: q,
    i: i,
    full: full
  };
}

/**
 * Get the priority of a language.
 * @private
 */

function getLanguagePriority(language, accepted, index) {
  var priority = {o: -1, q: 0, s: 0};

  for (var i = 0; i < accepted.length; i++) {
    var spec = specify(language, accepted[i], index);

    if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
      priority = spec;
    }
  }

  return priority;
}

/**
 * Get the specificity of the language.
 * @private
 */

function specify(language, spec, index) {
  var p = parseLanguage(language)
  if (!p) return null;
  var s = 0;
  if(spec.full.toLowerCase() === p.full.toLowerCase()){
    s |= 4;
  } else if (spec.prefix.toLowerCase() === p.full.toLowerCase()) {
    s |= 2;
  } else if (spec.full.toLowerCase() === p.prefix.toLowerCase()) {
    s |= 1;
  } else if (spec.full !== '*' ) {
    return null
  }

  return {
    i: index,
    o: spec.i,
    q: spec.q,
    s: s
  }
};

/**
 * Get the preferred languages from an Accept-Language header.
 * @public
 */

function preferredLanguages(accept, provided) {
  // RFC 2616 sec 14.4: no header = *
  var accepts = parseAcceptLanguage(accept === undefined ? '*' : accept || '');

  if (!provided) {
    // sorted list of all languages
    return accepts
      .filter(isQuality)
      .sort(compareSpecs)
      .map(getFullLanguage);
  }

  var priorities = provided.map(function getPriority(type, index) {
    return getLanguagePriority(type, accepts, index);
  });

  // sorted list of accepted languages
  return priorities.filter(isQuality).sort(compareSpecs).map(function getLanguage(priority) {
    return provided[priorities.indexOf(priority)];
  });
}

/**
 * Compare two specs.
 * @private
 */

function compareSpecs(a, b) {
  return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
}

/**
 * Get full language string.
 * @private
 */

function getFullLanguage(spec) {
  return spec.full;
}

/**
 * Check if a spec has any quality.
 * @private
 */

function isQuality(spec) {
  return spec.q > 0;
}


/**
 * negotiator
 * Copyright(c) 2012 Isaac Z. Schlueter
 * Copyright(c) 2014 Federico Romero
 * Copyright(c) 2014-2015 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict';

/**
 * Module exports.
 * @public
 */

module.exports = preferredCharsets;
module.exports.preferredCharsets = preferredCharsets;

/**
 * Module variables.
 * @private
 */

var simpleCharsetRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/;

/**
 * Parse the Accept-Charset header.
 * @private
 */

function parseAcceptCharset(accept) {
  var accepts = accept.split(',');

  for (var i = 0, j = 0; i < accepts.length; i++) {
    var charset = parseCharset(accepts[i].trim(), i);

    if (charset) {
      accepts[j++] = charset;
    }
  }

  // trim accepts
  accepts.length = j;

  return accepts;
}

/**
 * Parse a charset from the Accept-Charset header.
 * @private
 */

function parseCharset(str, i) {
  var match = simpleCharsetRegExp.exec(str);
  if (!match) return null;

  var charset = match[1];
  var q = 1;
  if (match[2]) {
    var params = match[2].split(';')
    for (var j = 0; j < params.length; j++) {
      var p = params[j].trim().split('=');
      if (p[0] === 'q') {
        q = parseFloat(p[1]);
        break;
      }
    }
  }

  return {
    charset: charset,
    q: q,
    i: i
  };
}

/**
 * Get the priority of a charset.
 * @private
 */

function getCharsetPriority(charset, accepted, index) {
  var priority = {o: -1, q: 0, s: 0};

  for (var i = 0; i < accepted.length; i++) {
    var spec = specify(charset, accepted[i], index);

    if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
      priority = spec;
    }
  }

  return priority;
}

/**
 * Get the specificity of the charset.
 * @private
 */

function specify(charset, spec, index) {
  var s = 0;
  if(spec.charset.toLowerCase() === charset.toLowerCase()){
    s |= 1;
  } else if (spec.charset !== '*' ) {
    return null
  }

  return {
    i: index,
    o: spec.i,
    q: spec.q,
    s: s
  }
}

/**
 * Get the preferred charsets from an Accept-Charset header.
 * @public
 */

function preferredCharsets(accept, provided) {
  // RFC 2616 sec 14.2: no header = *
  var accepts = parseAcceptCharset(accept === undefined ? '*' : accept || '');

  if (!provided) {
    // sorted list of all charsets
    return accepts
      .filter(isQuality)
      .sort(compareSpecs)
      .map(getFullCharset);
  }

  var priorities = provided.map(function getPriority(type, index) {
    return getCharsetPriority(type, accepts, index);
  });

  // sorted list of accepted charsets
  return priorities.filter(isQuality).sort(compareSpecs).map(function getCharset(priority) {
    return provided[priorities.indexOf(priority)];
  });
}

/**
 * Compare two specs.
 * @private
 */

function compareSpecs(a, b) {
  return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
}

/**
 * Get full charset string.
 * @private
 */

function getFullCharset(spec) {
  return spec.charset;
}

/**
 * Check if a spec has any quality.
 * @private
 */

function isQuality(spec) {
  return spec.q > 0;
}


/**
 * negotiator
 * Copyright(c) 2012 Isaac Z. Schlueter
 * Copyright(c) 2014 Federico Romero
 * Copyright(c) 2014-2015 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict';

/**
 * Module exports.
 * @public
 */

module.exports = preferredEncodings;
module.exports.preferredEncodings = preferredEncodings;

/**
 * Module variables.
 * @private
 */

var simpleEncodingRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/;

/**
 * Parse the Accept-Encoding header.
 * @private
 */

function parseAcceptEncoding(accept) {
  var accepts = accept.split(',');
  var hasIdentity = false;
  var minQuality = 1;

  for (var i = 0, j = 0; i < accepts.length; i++) {
    var encoding = parseEncoding(accepts[i].trim(), i);

    if (encoding) {
      accepts[j++] = encoding;
      hasIdentity = hasIdentity || specify('identity', encoding);
      minQuality = Math.min(minQuality, encoding.q || 1);
    }
  }

  if (!hasIdentity) {
    /*
     * If identity doesn't explicitly appear in the accept-encoding header,
     * it's added to the list of acceptable encoding with the lowest q
     */
    accepts[j++] = {
      encoding: 'identity',
      q: minQuality,
      i: i
    };
  }

  // trim accepts
  accepts.length = j;

  return accepts;
}

/**
 * Parse an encoding from the Accept-Encoding header.
 * @private
 */

function parseEncoding(str, i) {
  var match = simpleEncodingRegExp.exec(str);
  if (!match) return null;

  var encoding = match[1];
  var q = 1;
  if (match[2]) {
    var params = match[2].split(';');
    for (var j = 0; j < params.length; j++) {
      var p = params[j].trim().split('=');
      if (p[0] === 'q') {
        q = parseFloat(p[1]);
        break;
      }
    }
  }

  return {
    encoding: encoding,
    q: q,
    i: i
  };
}

/**
 * Get the priority of an encoding.
 * @private
 */

function getEncodingPriority(encoding, accepted, index) {
  var priority = {o: -1, q: 0, s: 0};

  for (var i = 0; i < accepted.length; i++) {
    var spec = specify(encoding, accepted[i], index);

    if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
      priority = spec;
    }
  }

  return priority;
}

/**
 * Get the specificity of the encoding.
 * @private
 */

function specify(encoding, spec, index) {
  var s = 0;
  if(spec.encoding.toLowerCase() === encoding.toLowerCase()){
    s |= 1;
  } else if (spec.encoding !== '*' ) {
    return null
  }

  return {
    i: index,
    o: spec.i,
    q: spec.q,
    s: s
  }
};

/**
 * Get the preferred encodings from an Accept-Encoding header.
 * @public
 */

function preferredEncodings(accept, provided) {
  var accepts = parseAcceptEncoding(accept || '');

  if (!provided) {
    // sorted list of all encodings
    return accepts
      .filter(isQuality)
      .sort(compareSpecs)
      .map(getFullEncoding);
  }

  var priorities = provided.map(function getPriority(type, index) {
    return getEncodingPriority(type, accepts, index);
  });

  // sorted list of accepted encodings
  return priorities.filter(isQuality).sort(compareSpecs).map(function getEncoding(priority) {
    return provided[priorities.indexOf(priority)];
  });
}

/**
 * Compare two specs.
 * @private
 */

function compareSpecs(a, b) {
  return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
}

/**
 * Get full encoding string.
 * @private
 */

function getFullEncoding(spec) {
  return spec.encoding;
}

/**
 * Check if a spec has any quality.
 * @private
 */

function isQuality(spec) {
  return spec.q > 0;
}