“use strict”; module.exports = function(Promise, INTERNAL) { var THIS = {}; var util = require(“./util”); var nodebackForPromise = require(“./nodeback”); var withAppended = util.withAppended; var maybeWrapAsError = util.maybeWrapAsError; var canEvaluate = util.canEvaluate; var TypeError = require(“./errors”).TypeError; var defaultSuffix = “Async”; var defaultPromisified = {__isPromisified__: true}; var noCopyProps = [

"arity",    "length",
"name",
"arguments",
"caller",
"callee",
"prototype",
"__isPromisified__"

]; var noCopyPropsPattern = new RegExp(“^(?:” + noCopyProps.join(“|”) + “)$”);

var defaultFilter = function(name) {

return util.isIdentifier(name) &&
    name.charAt(0) !== "_" &&
    name !== "constructor";

};

function propsFilter(key) {

return !noCopyPropsPattern.test(key);

}

function isPromisified(fn) {

try {
    return fn.__isPromisified__ === true;
}
catch (e) {
    return false;
}

}

function hasPromisified(obj, key, suffix) {

var val = util.getDataPropertyOrDefault(obj, key + suffix,
                                        defaultPromisified);
return val ? isPromisified(val) : false;

} function checkValid(ret, suffix, suffixRegexp) {

for (var i = 0; i < ret.length; i += 2) {
    var key = ret[i];
    if (suffixRegexp.test(key)) {
        var keyWithoutAsyncSuffix = key.replace(suffixRegexp, "");
        for (var j = 0; j < ret.length; j += 2) {
            if (ret[j] === keyWithoutAsyncSuffix) {
                throw new TypeError("Cannot promisify an API that has normal methods with '%s'-suffix\u000a\u000a    See http://goo.gl/MqrFmX\u000a"
                    .replace("%s", suffix));
            }
        }
    }
}

}

function promisifiableMethods(obj, suffix, suffixRegexp, filter) {

var keys = util.inheritedDataKeys(obj);
var ret = [];
for (var i = 0; i < keys.length; ++i) {
    var key = keys[i];
    var value = obj[key];
    var passesDefaultFilter = filter === defaultFilter
        ? true : defaultFilter(key, value, obj);
    if (typeof value === "function" &&
        !isPromisified(value) &&
        !hasPromisified(obj, key, suffix) &&
        filter(key, value, obj, passesDefaultFilter)) {
        ret.push(key, value);
    }
}
checkValid(ret, suffix, suffixRegexp);
return ret;

}

var escapeIdentRegex = function(str) {

return str.replace(/([$])/, "\\$");

};

var makeNodePromisifiedEval; if (!false) { var switchCaseArgumentOrder = function(likelyArgumentCount) {

var ret = [likelyArgumentCount];
var min = Math.max(0, likelyArgumentCount - 1 - 3);
for(var i = likelyArgumentCount - 1; i >= min; --i) {
    ret.push(i);
}
for(var i = likelyArgumentCount + 1; i <= 3; ++i) {
    ret.push(i);
}
return ret;

};

var argumentSequence = function(argumentCount) {

return util.filledRange(argumentCount, "_arg", "");

};

var parameterDeclaration = function(parameterCount) {

return util.filledRange(
    Math.max(parameterCount, 3), "_arg", "");

};

var parameterCount = function(fn) {

if (typeof fn.length === "number") {
    return Math.max(Math.min(fn.length, 1023 + 1), 0);
}
return 0;

};

makeNodePromisifiedEval = function(callback, receiver, originalName, fn, _, multiArgs) {

var newParameterCount = Math.max(0, parameterCount(fn) - 1);
var argumentOrder = switchCaseArgumentOrder(newParameterCount);
var shouldProxyThis = typeof callback === "string" || receiver === THIS;

function generateCallForArgumentCount(count) {
    var args = argumentSequence(count).join(", ");
    var comma = count > 0 ? ", " : "";
    var ret;
    if (shouldProxyThis) {
        ret = "ret = callback.call(this, {{args}}, nodeback); break;\n";
    } else {
        ret = receiver === undefined
            ? "ret = callback({{args}}, nodeback); break;\n"
            : "ret = callback.call(receiver, {{args}}, nodeback); break;\n";
    }
    return ret.replace("{{args}}", args).replace(", ", comma);
}

function generateArgumentSwitchCase() {
    var ret = "";
    for (var i = 0; i < argumentOrder.length; ++i) {
        ret += "case " + argumentOrder[i] +":" +
            generateCallForArgumentCount(argumentOrder[i]);
    }

    ret += "                                                             \n\
    default:                                                             \n\
        var args = new Array(len + 1);                                   \n\
        var i = 0;                                                       \n\
        for (var i = 0; i < len; ++i) {                                  \n\
           args[i] = arguments[i];                                       \n\
        }                                                                \n\
        args[i] = nodeback;                                              \n\
        [CodeForCall]                                                    \n\
        break;                                                           \n\
    ".replace("[CodeForCall]", (shouldProxyThis
                            ? "ret = callback.apply(this, args);\n"
                            : "ret = callback.apply(receiver, args);\n"));
    return ret;
}

var getFunctionCode = typeof callback === "string"
                            ? ("this != null ? this['"+callback+"'] : fn")
                            : "fn";
var body = "'use strict';                                                \n\
    var ret = function (Parameters) {                                    \n\
        'use strict';                                                    \n\
        var len = arguments.length;                                      \n\
        var promise = new Promise(INTERNAL);                             \n\
        promise._captureStackTrace();                                    \n\
        var nodeback = nodebackForPromise(promise, " + multiArgs + ");   \n\
        var ret;                                                         \n\
        var callback = tryCatch([GetFunctionCode]);                      \n\
        switch(len) {                                                    \n\
            [CodeForSwitchCase]                                          \n\
        }                                                                \n\
        if (ret === errorObj) {                                          \n\
            promise._rejectCallback(maybeWrapAsError(ret.e), true, true);\n\
        }                                                                \n\
        if (!promise._isFateSealed()) promise._setAsyncGuaranteed();     \n\
        return promise;                                                  \n\
    };                                                                   \n\
    notEnumerableProp(ret, '__isPromisified__', true);                   \n\
    return ret;                                                          \n\
".replace("[CodeForSwitchCase]", generateArgumentSwitchCase())
    .replace("[GetFunctionCode]", getFunctionCode);
body = body.replace("Parameters", parameterDeclaration(newParameterCount));
return new Function("Promise",
                    "fn",
                    "receiver",
                    "withAppended",
                    "maybeWrapAsError",
                    "nodebackForPromise",
                    "tryCatch",
                    "errorObj",
                    "notEnumerableProp",
                    "INTERNAL",
                    body)(
                Promise,
                fn,
                receiver,
                withAppended,
                maybeWrapAsError,
                nodebackForPromise,
                util.tryCatch,
                util.errorObj,
                util.notEnumerableProp,
                INTERNAL);

}; }

function makeNodePromisifiedClosure(callback, receiver, _, fn, __, multiArgs) {

var defaultThis = (function() {return this;})();
var method = callback;
if (typeof method === "string") {
    callback = fn;
}
function promisified() {
    var _receiver = receiver;
    if (receiver === THIS) _receiver = this;
    var promise = new Promise(INTERNAL);
    promise._captureStackTrace();
    var cb = typeof method === "string" && this !== defaultThis
        ? this[method] : callback;
    var fn = nodebackForPromise(promise, multiArgs);
    try {
        cb.apply(_receiver, withAppended(arguments, fn));
    } catch(e) {
        promise._rejectCallback(maybeWrapAsError(e), true, true);
    }
    if (!promise._isFateSealed()) promise._setAsyncGuaranteed();
    return promise;
}
util.notEnumerableProp(promisified, "__isPromisified__", true);
return promisified;

}

var makeNodePromisified = canEvaluate

? makeNodePromisifiedEval
: makeNodePromisifiedClosure;

function promisifyAll(obj, suffix, filter, promisifier, multiArgs) {

var suffixRegexp = new RegExp(escapeIdentRegex(suffix) + "$");
var methods =
    promisifiableMethods(obj, suffix, suffixRegexp, filter);

for (var i = 0, len = methods.length; i < len; i+= 2) {
    var key = methods[i];
    var fn = methods[i+1];
    var promisifiedKey = key + suffix;
    if (promisifier === makeNodePromisified) {
        obj[promisifiedKey] =
            makeNodePromisified(key, THIS, key, fn, suffix, multiArgs);
    } else {
        var promisified = promisifier(fn, function() {
            return makeNodePromisified(key, THIS, key,
                                       fn, suffix, multiArgs);
        });
        util.notEnumerableProp(promisified, "__isPromisified__", true);
        obj[promisifiedKey] = promisified;
    }
}
util.toFastProperties(obj);
return obj;

}

function promisify(callback, receiver, multiArgs) {

return makeNodePromisified(callback, receiver, undefined,
                            callback, null, multiArgs);

}

Promise.promisify = function (fn, options) {

if (typeof fn !== "function") {
    throw new TypeError("expecting a function but got " + util.classString(fn));
}
if (isPromisified(fn)) {
    return fn;
}
options = Object(options);
var receiver = options.context === undefined ? THIS : options.context;
var multiArgs = !!options.multiArgs;
var ret = promisify(fn, receiver, multiArgs);
util.copyDescriptors(fn, ret, propsFilter);
return ret;

};

Promise.promisifyAll = function (target, options) {

if (typeof target !== "function" && typeof target !== "object") {
    throw new TypeError("the target of promisifyAll must be an object or a function\u000a\u000a    See http://goo.gl/MqrFmX\u000a");
}
options = Object(options);
var multiArgs = !!options.multiArgs;
var suffix = options.suffix;
if (typeof suffix !== "string") suffix = defaultSuffix;
var filter = options.filter;
if (typeof filter !== "function") filter = defaultFilter;
var promisifier = options.promisifier;
if (typeof promisifier !== "function") promisifier = makeNodePromisified;

if (!util.isIdentifier(suffix)) {
    throw new RangeError("suffix must be a valid identifier\u000a\u000a    See http://goo.gl/MqrFmX\u000a");
}

var keys = util.inheritedDataKeys(target);
for (var i = 0; i < keys.length; ++i) {
    var value = target[keys[i]];
    if (keys[i] !== "constructor" &&
        util.isClass(value)) {
        promisifyAll(value.prototype, suffix, filter, promisifier,
            multiArgs);
        promisifyAll(value, suffix, filter, promisifier, multiArgs);
    }
}

return promisifyAll(target, suffix, filter, promisifier, multiArgs);

}; };