"use strict";
// Copyright 2017-2020 @polkadot/api authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const util_1 = require("@polkadot/util");
const base_1 = __importDefault(require("../base"));
const Combinator_1 = __importDefault(require("./Combinator"));
// extract the arguments and callback params from a value array possibly containing a callback
function extractArgs(args, needsCallback) {
    let callback;
    const actualArgs = args.slice();
    // If the last arg is a function, we pop it, put it into callback.
    // actualArgs will then hold the actual arguments to be passed to `method`
    if (args.length && util_1.isFunction(args[args.length - 1])) {
        callback = actualArgs.pop();
    }
    // When we need a subscription, ensure that a valid callback is actually passed
    util_1.assert(!needsCallback || util_1.isFunction(callback), 'Expected a callback to be passed with subscriptions');
    return [actualArgs, callback];
}
// a Promise completion tracker, wrapping an isComplete variable that ensures the promise only resolves once
function promiseTracker(resolve, reject) {
    let isCompleted = false;
    const complete = (fn, value) => {
        if (!isCompleted) {
            isCompleted = true;
            fn(value);
        }
    };
    return {
        reject: (error) => {
            complete(reject, error);
            return rxjs_1.EMPTY;
        },
        resolve: (value) => {
            complete(resolve, value);
        }
    };
}
/**
 * @description Decorate method for ApiPromise, where the results are converted to the Promise equivalent
 */
function decorateMethod(method, options) {
    const needsCallback = options && options.methodName && options.methodName.includes('subscribe');
    return function (...args) {
        const [actualArgs, callback] = extractArgs(args, !!needsCallback);
        if (!callback) {
            return method(...actualArgs).pipe(operators_1.first()).toPromise();
        }
        return new Promise((resolve, reject) => {
            const tracker = promiseTracker(resolve, reject);
            const subscription = method(...actualArgs)
                .pipe(
            // if we find an error (invalid params, etc), reject the promise
            operators_1.catchError((error) => tracker.reject(error)), 
            // upon the first result, resolve the with the unsub function
            operators_1.tap(() => tracker.resolve(() => subscription.unsubscribe())))
                .subscribe(callback);
        }); // ???
    };
}
exports.decorateMethod = decorateMethod;
/**
 * # @polkadot/api/promise
 *
 * ## Overview
 *
 * @name ApiPromise
 * @description
 * ApiPromise is a standard JavaScript wrapper around the RPC and interfaces on the Polkadot network. As a full Promise-based, all interface calls return Promises, including the static `.create(...)`. Subscription calls utilise `(value) => {}` callbacks to pass through the latest values.
 *
 * The API is well suited to real-time applications where either the single-shot state is needed or use is to be made of the subscription-based features of Polkadot (and Substrate) clients.
 *
 * @see [[ApiRx]]
 *
 * ## Usage
 *
 * Making rpc calls -
 * <BR>
 *
 * ```javascript
 * import ApiPromise from '@polkadot/api/promise';
 *
 * // initialise via static create
 * const api = await ApiPromise.create();
 *
 * // make a subscription to the network head
 * api.rpc.chain.subscribeNewHeads((header) => {
 *   console.log(`Chain is at #${header.number}`);
 * });
 * ```
 * <BR>
 *
 * Subscribing to chain state -
 * <BR>
 *
 * ```javascript
 * import { ApiPromise, WsProvider } from '@polkadot/api';
 *
 * // initialise a provider with a specific endpoint
 * const provider = new WsProvider('wss://example.com:9944')
 *
 * // initialise via isReady & new with specific provider
 * const api = await new ApiPromise({ provider }).isReady;
 *
 * // retrieve the block target time
 * const blockPeriod = await api.query.timestamp.blockPeriod().toNumber();
 * let last = 0;
 *
 * // subscribe to the current block timestamp, updates automatically (callback provided)
 * api.query.timestamp.now((timestamp) => {
 *   const elapsed = last
 *     ? `, ${timestamp.toNumber() - last}s since last`
 *     : '';
 *
 *   last = timestamp.toNumber();
 *   console.log(`timestamp ${timestamp}${elapsed} (${blockPeriod}s target)`);
 * });
 * ```
 * <BR>
 *
 * Submitting a transaction -
 * <BR>
 *
 * ```javascript
 * import ApiPromise from '@polkadot/api/promise';
 *
 * ApiPromise.create().then((api) => {
 *   const nonce = await api.query.system.accountNonce(keyring.alice.address);
 *
 *   api.tx.balances
 *     // create transfer
 *     transfer(keyring.bob.address, 12345)
 *     // sign the transcation
 *     .sign(keyring.alice, { nonce })
 *     // send the transaction (optional status callback)
 *     .send((status) => {
 *       console.log(`current status ${status.type}`);
 *     })
 *     // retrieve the submitted extrinsic hash
 *     .then((hash) => {
 *       console.log(`submitted with hash ${hash}`);
 *     });
 * });
 * ```
 */
class ApiPromise extends base_1.default {
    /**
     * @description Creates an instance of the ApiPromise class
     * @param options Options to create an instance. This can be either [[ApiOptions]] or
     * an [[WsProvider]].
     * @example
     * <BR>
     *
     * ```javascript
     * import Api from '@polkadot/api/promise';
     *
     * new Api().isReady.then((api) => {
     *   api.rpc.subscribeNewHeads((header) => {
     *     console.log(`new block #${header.number.toNumber()}`);
     *   });
     * });
     * ```
     */
    constructor(options) {
        super(options, 'promise', decorateMethod);
        this._isReadyPromise = new Promise((resolve, reject) => {
            super.once('ready', () => {
                resolve(this);
            });
            super.once('error', (e) => {
                reject(e);
            });
        });
    }
    /**
     * @description Creates an ApiPromise instance using the supplied provider. Returns an Promise containing the actual Api instance.
     * @param options options that is passed to the class contructor. Can be either [[ApiOptions]] or a
     * provider (see the constructor arguments)
     * @example
     * <BR>
     *
     * ```javascript
     * import Api from '@polkadot/api/promise';
     *
     * Api.create().then(async (api) => {
     *   const timestamp = await api.query.timestamp.now();
     *
     *   console.log(`lastest block timestamp ${timestamp}`);
     * });
     * ```
     */
    static create(options) {
        return new ApiPromise(options).isReady;
    }
    /**
     * @description Promise that returns the first time we are connected and loaded
     */
    get isReady() {
        return this._isReadyPromise;
    }
    /**
     * @description Returns a clone of this ApiPromise instance (new underlying provider connection)
     */
    clone() {
        return new ApiPromise(Object.assign(Object.assign({}, this._options), { source: this }));
    }
    /**
     * @description Creates a combinator that can be used to combine the latest results from multiple subscriptions
     * @param fns An array of function to combine, each in the form of `(cb: (value: void)) => void`
     * @param callback A callback that will return an Array of all the values this combinator has been applied to
     * @example
     * <BR>
     *
     * ```javascript
     * const address = '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFacT7';
     *
     * // combines values from balance & nonce as it updates
     * api.combineLatest([
     *   api.rpc.chain.subscribeNewHeads,
     *   [api.query.balances.freeBalance, address],
     *   (cb) => api.query.system.accountNonce(address, cb)
     * ], ([head, balance, nonce]) => {
     *   console.log(`#${head.number}: You have ${balance} units, with ${nonce} transactions sent`);
     * });
     * ```
     */
    // eslint-disable-next-line @typescript-eslint/require-await
    combineLatest(fns, callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const combinator = new Combinator_1.default(fns, callback);
            return () => {
                combinator.unsubscribe();
            };
        });
    }
}
exports.default = ApiPromise;
