"use strict";
/*!
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
/*!
 * @module common/paginator
 */
const arrify = require("arrify");
const extend = require("extend");
const resource_stream_1 = require("./resource-stream");
exports.ResourceStream = resource_stream_1.ResourceStream;
/*! Developer Documentation
 *
 * paginator is used to auto-paginate `nextQuery` methods as well as
 * streamifying them.
 *
 * Before:
 *
 *   search.query('done=true', function(err, results, nextQuery) {
 *     search.query(nextQuery, function(err, results, nextQuery) {});
 *   });
 *
 * After:
 *
 *   search.query('done=true', function(err, results) {});
 *
 * Methods to extend should be written to accept callbacks and return a
 * `nextQuery`.
 */
class Paginator {
    /**
     * Cache the original method, then overwrite it on the Class's prototype.
     *
     * @param {function} Class - The parent class of the methods to extend.
     * @param {string|string[]} methodNames - Name(s) of the methods to extend.
     */
    // tslint:disable-next-line:variable-name
    extend(Class, methodNames) {
        methodNames = arrify(methodNames);
        methodNames.forEach(methodName => {
            const originalMethod = Class.prototype[methodName];
            // map the original method to a private member
            Class.prototype[methodName + '_'] = originalMethod;
            // overwrite the original to auto-paginate
            // tslint:disable-next-line:no-any
            Class.prototype[methodName] = function (...args) {
                const parsedArguments = paginator.parseArguments_(args);
                return paginator.run_(parsedArguments, originalMethod.bind(this));
            };
        });
    }
    /**
     * Wraps paginated API calls in a readable object stream.
     *
     * This method simply calls the nextQuery recursively, emitting results to a
     * stream. The stream ends when `nextQuery` is null.
     *
     * `maxResults` will act as a cap for how many results are fetched and emitted
     * to the stream.
     *
     * @param {string} methodName - Name of the method to streamify.
     * @return {function} - Wrapped function.
     */
    // tslint:disable-next-line:no-any
    streamify(methodName) {
        return function (
        // tslint:disable-next-line:no-any
        ...args) {
            const parsedArguments = paginator.parseArguments_(args);
            const originalMethod = this[methodName + '_'] || this[methodName];
            return paginator.runAsStream_(parsedArguments, originalMethod.bind(this));
        };
    }
    /**
     * Parse a pseudo-array `arguments` for a query and callback.
     *
     * @param {array} args - The original `arguments` pseduo-array that the original
     *     method received.
     */
    // tslint:disable-next-line:no-any
    parseArguments_(args) {
        let query;
        let autoPaginate = true;
        let maxApiCalls = -1;
        let maxResults = -1;
        let callback;
        const firstArgument = args[0];
        const lastArgument = args[args.length - 1];
        if (typeof firstArgument === 'function') {
            callback = firstArgument;
        }
        else {
            query = firstArgument;
        }
        if (typeof lastArgument === 'function') {
            callback = lastArgument;
        }
        if (typeof query === 'object') {
            query = extend(true, {}, query);
            // Check if the user only asked for a certain amount of results.
            if (query.maxResults && typeof query.maxResults === 'number') {
                // `maxResults` is used API-wide.
                maxResults = query.maxResults;
            }
            else if (typeof query.pageSize === 'number') {
                // `pageSize` is Pub/Sub's `maxResults`.
                maxResults = query.pageSize;
            }
            if (query.maxApiCalls && typeof query.maxApiCalls === 'number') {
                maxApiCalls = query.maxApiCalls;
                delete query.maxApiCalls;
            }
            // maxResults is the user specified limit.
            if (maxResults !== -1 || query.autoPaginate === false) {
                autoPaginate = false;
            }
        }
        const parsedArguments = {
            query: query || {},
            autoPaginate,
            maxApiCalls,
            maxResults,
            callback,
        };
        parsedArguments.streamOptions = extend(true, {}, parsedArguments.query);
        delete parsedArguments.streamOptions.autoPaginate;
        delete parsedArguments.streamOptions.maxResults;
        delete parsedArguments.streamOptions.pageSize;
        return parsedArguments;
    }
    /**
     * This simply checks to see if `autoPaginate` is set or not, if it's true
     * then we buffer all results, otherwise simply call the original method.
     *
     * @param {array} parsedArguments - Parsed arguments from the original method
     *     call.
     * @param {object=|string=} parsedArguments.query - Query object. This is most
     *     commonly an object, but to make the API more simple, it can also be a
     *     string in some places.
     * @param {function=} parsedArguments.callback - Callback function.
     * @param {boolean} parsedArguments.autoPaginate - Auto-pagination enabled.
     * @param {boolean} parsedArguments.maxApiCalls - Maximum API calls to make.
     * @param {number} parsedArguments.maxResults - Maximum results to return.
     * @param {function} originalMethod - The cached method that accepts a callback
     *     and returns `nextQuery` to receive more results.
     */
    run_(parsedArguments, originalMethod) {
        const query = parsedArguments.query;
        const callback = parsedArguments.callback;
        if (!parsedArguments.autoPaginate) {
            return originalMethod(query, callback);
        }
        const results = new Array();
        const promise = new Promise((resolve, reject) => {
            paginator
                .runAsStream_(parsedArguments, originalMethod)
                .on('error', reject)
                .on('data', (data) => results.push(data))
                .on('end', () => resolve(results));
        });
        if (!callback) {
            return promise.then(results => [results]);
        }
        promise.then(results => callback(null, results), (err) => callback(err));
    }
    /**
     * This method simply calls the nextQuery recursively, emitting results to a
     * stream. The stream ends when `nextQuery` is null.
     *
     * `maxResults` will act as a cap for how many results are fetched and emitted
     * to the stream.
     *
     * @param {object=|string=} parsedArguments.query - Query object. This is most
     *     commonly an object, but to make the API more simple, it can also be a
     *     string in some places.
     * @param {function=} parsedArguments.callback - Callback function.
     * @param {boolean} parsedArguments.autoPaginate - Auto-pagination enabled.
     * @param {boolean} parsedArguments.maxApiCalls - Maximum API calls to make.
     * @param {number} parsedArguments.maxResults - Maximum results to return.
     * @param {function} originalMethod - The cached method that accepts a callback
     *     and returns `nextQuery` to receive more results.
     * @return {stream} - Readable object stream.
     */
    // tslint:disable-next-line:no-any
    runAsStream_(parsedArguments, originalMethod) {
        return new resource_stream_1.ResourceStream(parsedArguments, originalMethod);
    }
}
exports.Paginator = Paginator;
const paginator = new Paginator();
exports.paginator = paginator;
//# sourceMappingURL=index.js.map