“use strict”; module.exports = function (Promise, apiRejection, tryConvertToPromise,

createContext) {
var TypeError = require("./errors.js").TypeError;
var inherits = require("./util.js").inherits;
var PromiseInspection = Promise.PromiseInspection;

function inspectionMapper(inspections) {
    var len = inspections.length;
    for (var i = 0; i < len; ++i) {
        var inspection = inspections[i];
        if (inspection.isRejected()) {
            return Promise.reject(inspection.error());
        }
        inspections[i] = inspection._settledValue;
    }
    return inspections;
}

function thrower(e) {
    setTimeout(function(){throw e;}, 0);
}

function castPreservingDisposable(thenable) {
    var maybePromise = tryConvertToPromise(thenable);
    if (maybePromise !== thenable &&
        typeof thenable._isDisposable === "function" &&
        typeof thenable._getDisposer === "function" &&
        thenable._isDisposable()) {
        maybePromise._setDisposable(thenable._getDisposer());
    }
    return maybePromise;
}
function dispose(resources, inspection) {
    var i = 0;
    var len = resources.length;
    var ret = Promise.defer();
    function iterator() {
        if (i >= len) return ret.resolve();
        var maybePromise = castPreservingDisposable(resources[i++]);
        if (maybePromise instanceof Promise &&
            maybePromise._isDisposable()) {
            try {
                maybePromise = tryConvertToPromise(
                    maybePromise._getDisposer().tryDispose(inspection),
                    resources.promise);
            } catch (e) {
                return thrower(e);
            }
            if (maybePromise instanceof Promise) {
                return maybePromise._then(iterator, thrower,
                                          null, null, null);
            }
        }
        iterator();
    }
    iterator();
    return ret.promise;
}

function disposerSuccess(value) {
    var inspection = new PromiseInspection();
    inspection._settledValue = value;
    inspection._bitField = 268435456;
    return dispose(this, inspection).thenReturn(value);
}

function disposerFail(reason) {
    var inspection = new PromiseInspection();
    inspection._settledValue = reason;
    inspection._bitField = 134217728;
    return dispose(this, inspection).thenThrow(reason);
}

function Disposer(data, promise, context) {
    this._data = data;
    this._promise = promise;
    this._context = context;
}

Disposer.prototype.data = function () {
    return this._data;
};

Disposer.prototype.promise = function () {
    return this._promise;
};

Disposer.prototype.resource = function () {
    if (this.promise().isFulfilled()) {
        return this.promise().value();
    }
    return null;
};

Disposer.prototype.tryDispose = function(inspection) {
    var resource = this.resource();
    var context = this._context;
    if (context !== undefined) context._pushContext();
    var ret = resource !== null
        ? this.doDispose(resource, inspection) : null;
    if (context !== undefined) context._popContext();
    this._promise._unsetDisposable();
    this._data = null;
    return ret;
};

Disposer.isDisposer = function (d) {
    return (d != null &&
            typeof d.resource === "function" &&
            typeof d.tryDispose === "function");
};

function FunctionDisposer(fn, promise, context) {
    this.constructor$(fn, promise, context);
}
inherits(FunctionDisposer, Disposer);

FunctionDisposer.prototype.doDispose = function (resource, inspection) {
    var fn = this.data();
    return fn.call(resource, resource, inspection);
};

function maybeUnwrapDisposer(value) {
    if (Disposer.isDisposer(value)) {
        this.resources[this.index]._setDisposable(value);
        return value.promise();
    }
    return value;
}

Promise.using = function () {
    var len = arguments.length;
    if (len < 2) return apiRejection(
                    "you must pass at least 2 arguments to Promise.using");
    var fn = arguments[len - 1];
    if (typeof fn !== "function") return apiRejection("fn must be a function\u000a\u000a    See http://goo.gl/916lJJ\u000a");

    var input;
    var spreadArgs = true;
    if (len === 2 && Array.isArray(arguments[0])) {
        input = arguments[0];
        len = input.length;
        spreadArgs = false;
    } else {
        input = arguments;
        len--;
    }
    var resources = new Array(len);
    for (var i = 0; i < len; ++i) {
        var resource = input[i];
        if (Disposer.isDisposer(resource)) {
            var disposer = resource;
            resource = resource.promise();
            resource._setDisposable(disposer);
        } else {
            var maybePromise = tryConvertToPromise(resource);
            if (maybePromise instanceof Promise) {
                resource =
                    maybePromise._then(maybeUnwrapDisposer, null, null, {
                        resources: resources,
                        index: i
                }, undefined);
            }
        }
        resources[i] = resource;
    }

    var promise = Promise.settle(resources)
        .then(inspectionMapper)
        .then(function(vals) {
            promise._pushContext();
            var ret;
            try {
                ret = spreadArgs
                    ? fn.apply(undefined, vals) : fn.call(undefined,  vals);
            } finally {
                promise._popContext();
            }
            return ret;
        })
        ._then(
            disposerSuccess, disposerFail, undefined, resources, undefined);
    resources.promise = promise;
    return promise;
};

Promise.prototype._setDisposable = function (disposer) {
    this._bitField = this._bitField | 262144;
    this._disposer = disposer;
};

Promise.prototype._isDisposable = function () {
    return (this._bitField & 262144) > 0;
};

Promise.prototype._getDisposer = function () {
    return this._disposer;
};

Promise.prototype._unsetDisposable = function () {
    this._bitField = this._bitField & (~262144);
    this._disposer = undefined;
};

Promise.prototype.disposer = function (fn) {
    if (typeof fn === "function") {
        return new FunctionDisposer(fn, this, createContext());
    }
    throw new TypeError();
};

};