"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _bn = _interopRequireDefault(require("bn.js"));

var _rxjs = require("rxjs");

var _operators = require("rxjs/operators");

var _apiDerive = _interopRequireDefault(require("@polkadot/api-derive"));

var _rpcCore = _interopRequireDefault(require("@polkadot/rpc-core"));

var _rpcProvider = require("@polkadot/rpc-provider");

var _types = require("@polkadot/types");

var _Linkage = require("@polkadot/types/codec/Linkage");

var _constants = require("@polkadot/types/primitive/Extrinsic/constants");

var _util = require("@polkadot/util");

var _submittable = require("../submittable");

var _decorate = require("../util/decorate");

var _Events = _interopRequireDefault(require("./Events"));

// Copyright 2017-2019 @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.
class Decorate extends _Events.default {
  // HACK Use BN import so decorateDerive works... yes, wtf.

  /**
   * This is the one and only method concrete children classes need to implement.
   * It's a higher-order function, which takes one argument
   * `method: Method extends (...args: any[]) => Observable<any>`
   * (and one optional `options`), and should return the user facing method.
   * For example:
   * - For ApiRx, `decorateMethod` should just be identity, because the input
   * function is already an Observable
   * - For ApiPromise, `decorateMethod` should return a function that takes all
   * the parameters from `method`, adds an optional `callback` argument, and
   * returns a Promise.
   *
   * We could easily imagine other user-facing interfaces, which are simply
   * implemented by transforming the Observable to Stream/Iterator/Kefir/Bacon
   * via `deocrateMethod`.
   */

  /**
   * @description Create an instance of the class
   *
   * @param options Options object to create API instance or a Provider instance
   *
   * @example
   * <BR>
   *
   * ```javascript
   * import Api from '@polkadot/api/promise';
   *
   * const api = new Api().isReady();
   *
   * api.rpc.subscribeNewHeads((header) => {
   *   console.log(`new block #${header.number.toNumber()}`);
   * });
   * ```
   */
  constructor(options, type, decorateMethod) {
    super();
    this.__phantom = new _bn.default(0);
    this._consts = void 0;
    this._derive = void 0;
    this._extrinsics = void 0;
    this._extrinsicType = _constants.DEFAULT_VERSION;
    this._genesisHash = void 0;
    this._isConnected = void 0;
    this._isReady = false;
    this._options = void 0;
    this._query = void 0;
    this._queryMulti = void 0;
    this._rpc = void 0;
    this._rpcCore = void 0;
    this._runtimeMetadata = void 0;
    this._runtimeVersion = void 0;
    this._rx = {};
    this._type = void 0;
    this.decorateMethod = void 0;

    this.rxDecorateMethod = method => {
      return method;
    };

    const thisProvider = options.source ? options.source._rpcCore.provider.clone() : options.provider || new _rpcProvider.WsProvider();
    this.decorateMethod = decorateMethod;
    this._options = options;
    this._type = type;
    this._rpcCore = new _rpcCore.default(thisProvider, this._options.rpc);
    this._isConnected = new _rxjs.BehaviorSubject(this._rpcCore.provider.isConnected());
    this._rx.hasSubscriptions = this._rpcCore.provider.hasSubscriptions;
  }
  /**
   * @returns `true` if the API operates with subscriptions
   */


  get hasSubscriptions() {
    return this._rpcCore.provider.hasSubscriptions;
  }

  decorateFunctionMeta(input, output) {
    output.meta = input.meta;
    output.method = input.method;
    output.section = input.section;
    output.toJSON = input.toJSON;

    if (input.callIndex) {
      output.callIndex = input.callIndex;
    }

    return output;
  } // Filter all RPC methods based on the results of the rpc_methods call. We do this in the following
  // manner to cater for both old and new:
  //   - when the number of entries are 0, only remove the ones with isOptional (account & contracts)
  //   - when non-zero, remove anything that is not in the array (we don't do this)


  async filterRpc() {
    let methods;

    try {
      // we ignore the version (adjust as versions change, for now only "1")
      methods = (await this._rpcCore.rpc.methods().toPromise()).methods.map(t => t.toString());
    } catch (error) {
      // the method is not there, we adjust accordingly
      methods = [];
    }

    this.filterRpcMethods(methods);
  }

  filterRpcMethods(methods) {
    // this is true when the RPC has entries
    const hasResults = methods.length !== 0; // loop through all entries we have (populated in decorate) and filter as required

    [...this._rpcCore.mapping.entries()].filter((_ref) => {
      let [key, {
        isOptional
      }] = _ref;
      return (// only remove when we have results and method missing, or with no results if optional
        hasResults ? !methods.includes(key) && key !== 'rpc_methods' // rpc_methods doesn't appear, v1
        : isOptional || key === 'rpc_methods'
      );
    } // we didn't find this one, remove
    ) // eslint-disable-next-line @typescript-eslint/no-unused-vars
    .forEach((_ref2) => {
      let [_, {
        method,
        section
      }] = _ref2;
      delete this._rpc[section][method];
      delete this._rx.rpc[section][method];
    });
  }

  decorateRpc(rpc, decorateMethod) {
    return rpc.sections.reduce((out, _sectionName) => {
      const sectionName = _sectionName; // out and section here are horrors to get right from a typing perspective :(

      out[sectionName] = Object.entries(rpc[sectionName]).reduce((section, _ref3) => {
        let [methodName, method] = _ref3;

        //  skip subscriptions where we have a non-subscribe interface
        if (this.hasSubscriptions || !(methodName.startsWith('subscribe') || methodName.startsWith('unsubscribe'))) {
          section[methodName] = decorateMethod(method, {
            methodName
          });
        }

        return section;
      }, {});
      return out;
    }, {});
  }

  decorateMulti(decorateMethod) {
    return decorateMethod(calls => this._rpcCore.state.subscribeStorage(calls.map(arg => // the input is a QueryableStorageEntry, convert to StorageEntry
    Array.isArray(arg) ? [arg[0].creator, ...arg.slice(1)] : [arg.creator])));
  }

  decorateExtrinsics(extrinsics, decorateMethod) {
    const creator = (0, _submittable.createSubmittable)(this._type, this._rx, decorateMethod);
    return Object.entries(extrinsics).reduce((out, _ref4) => {
      let [name, section] = _ref4;
      out[name] = Object.entries(section).reduce((out, _ref5) => {
        let [name, method] = _ref5;
        out[name] = this.decorateExtrinsicEntry(method, creator);
        return out;
      }, {});
      return out;
    }, creator);
  }

  decorateExtrinsicEntry(method, creator) {
    const decorated = function decorated() {
      return creator(method(...arguments));
    };

    return this.decorateFunctionMeta(method, decorated);
  }

  decorateStorage(storage, decorateMethod) {
    return Object.entries(storage).reduce((out, _ref6) => {
      let [name, section] = _ref6;
      out[name] = Object.entries(section).reduce((out, _ref7) => {
        let [name, method] = _ref7;
        out[name] = this.decorateStorageEntry(method, decorateMethod);
        return out;
      }, {});
      return out;
    }, {});
  }

  decorateStorageEntry(creator, decorateMethod) {
    var _this = this;

    // get the storage arguments, with DoubleMap as an array entry, otherwise spread
    const getArgs = function getArgs() {
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }

      return creator.meta.type.isDoubleMap ? [creator, args] : [creator, ...args];
    }; // FIXME We probably want to be able to query the full list with non-subs as well


    const decorated = this.hasSubscriptions && creator.headKey ? this.decorateStorageEntryLinked(creator, decorateMethod) : decorateMethod(function () {
      return _this.hasSubscriptions ? _this._rpcCore.state // Unfortunately for one-shot calls we also use .subscribeStorage here
      .subscribeStorage([getArgs(...arguments)]) // state_storage returns an array of values, since we have just subscribed to
      // a single entry, we pull that from the array and return it as-is
      .pipe((0, _operators.map)((_ref8) => {
        let [data] = _ref8;
        return data;
      })) : _this._rpcCore.state.getStorage(getArgs(...arguments));
    }, {
      methodName: creator.method
    });
    decorated.creator = creator;
    decorated.at = decorateMethod((hash, arg1, arg2) => this._rpcCore.state.getStorage(getArgs(arg1, arg2), hash));
    decorated.hash = decorateMethod((arg1, arg2) => this._rpcCore.state.getStorageHash(getArgs(arg1, arg2)));

    decorated.key = (arg1, arg2) => (0, _util.u8aToHex)((0, _util.compactStripLength)(creator(creator.meta.type.isDoubleMap ? [arg1, arg2] : arg1))[1]);

    if (this.hasSubscriptions) {
      // When using double map storage function, user need to pass double map key as an array
      decorated.multi = decorateMethod(args => this._rpcCore.state.subscribeStorage(args.map(arg => [creator, arg])));
    }

    decorated.size = decorateMethod((arg1, arg2) => this._rpcCore.state.getStorageSize(getArgs(arg1, arg2)));
    return this.decorateFunctionMeta(creator, decorated);
  }

  decorateStorageEntryLinked(creator, decorateMethod) {
    var _this2 = this;

    const result = new Map();
    let subject;
    let head = null; // retrieve a value based on the key, iterating if it has a next entry. Since
    // entries can be re-linked in the middle of a list, we subscribe here to make
    // sure we catch any updates, no matter the list position

    const getNext = key => this._rpcCore.state.subscribeStorage([[creator, key]]).pipe((0, _operators.switchMap)((_ref9) => {
      let [data] = _ref9;
      result.set(key, data); // iterate from this key to the linkages, constructing entries for all
      // those found and available

      if (data[1].next.isSome) {
        return getNext(data[1].next.unwrap());
      }

      const [keys, vals] = [[], []];
      let nextKey = head; // loop through the results collected, starting at the head an re-creating
      // the list. Our map may have old entries, based on the linking these will
      // not be returned in the final result

      while (nextKey) {
        const entry = result.get(nextKey);

        if (!entry) {
          break;
        }

        const [item, linkage] = entry;
        keys.push(nextKey);
        vals.push(item);
        nextKey = linkage.next && linkage.next.unwrapOr(null);
      }

      const nextResult = vals.length ? new _Linkage.LinkageResult([keys[0].constructor, keys], [vals[0].constructor, vals]) : new _Linkage.LinkageResult([_types.Null, []], [_types.Null, []]); // we set our result into a subject so we have a single observable for
      // which the value changes over time. Initially create, follow-up next

      if (subject) {
        subject.next(nextResult);
      } else {
        subject = new _rxjs.BehaviorSubject(nextResult);
      }

      return subject;
    })); // this handles the case where the head changes effectively, i.e. a new entry
    // appears at the top of the list, the new getNext gets kicked off


    return decorateMethod(function () {
      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
        args[_key2] = arguments[_key2];
      }

      return args.length ? _this2._rpcCore.state.subscribeStorage([[creator, ...args]]).pipe((0, _operators.map)((_ref10) => {
        let [data] = _ref10;
        return data;
      })) : _this2._rpcCore.state.subscribeStorage([creator.headKey]).pipe((0, _operators.switchMap)((_ref11) => {
        let [key] = _ref11;
        return getNext(head = key);
      }));
    });
  }

  decorateDerive(apiRx, decorateMethod) {
    // Pull in derive from api-derive
    const derive = (0, _apiDerive.default)(apiRx, this._options.derives);
    return (0, _decorate.decorateSections)(derive, decorateMethod);
  }
  /**
   * Put the `this.onCall` function of ApiRx here, because it is needed by
   * `api._rx`.
   */


}

exports.default = Decorate;