"use strict";

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

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

var _fromMetadata = _interopRequireDefault(require("@polkadot/api-metadata/extrinsics/fromMetadata"));

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

var _create = require("../../codec/create");

var _Struct = _interopRequireDefault(require("../../codec/Struct"));

var _U8aFixed = _interopRequireDefault(require("../../codec/U8aFixed"));

// Copyright 2017-2019 @polkadot/api-metadata authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
const FN_UNKNOWN = {
  method: 'unknown',
  section: 'unknown'
};
const injected = {};
/**
 * @name CallIndex
 * @description
 * A wrapper around the `[sectionIndex, methodIndex]` value that uniquely identifies a method
 */

class CallIndex extends _U8aFixed.default {
  constructor(value) {
    super(value, 16);
  }

}
/**
 * @name Call
 * @description
 * Extrinsic function descriptor, as defined in
 * {@link https://github.com/paritytech/wiki/blob/master/Extrinsic.md#the-extrinsic-format-for-node}.
 */


exports.CallIndex = CallIndex;

class Call extends _Struct.default {
  constructor(value, meta) {
    const decoded = Call.decodeCall(value, meta);
    super({
      callIndex: CallIndex,
      args: _Struct.default.with(decoded.argsDef)
    }, decoded);
    this._meta = void 0;
    this._meta = decoded.meta;
  }
  /**
   * Decode input to pass into constructor.
   *
   * @param value - Value to decode, one of:
   * - hex
   * - Uint8Array
   * - {@see DecodeMethodInput}
   * @param _meta - Metadata to use, so that `injectMethods` lookup is not
   * necessary.
   */


  static decodeCall() {
    let value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Uint8Array();

    let _meta = arguments.length > 1 ? arguments[1] : undefined;

    if ((0, _util.isHex)(value) || (0, _util.isU8a)(value)) {
      return Call.decodeCallViaU8a((0, _util.u8aToU8a)(value), _meta);
    } else if ((0, _util.isObject)(value) && value.callIndex && value.args) {
      return Call.decodeCallViaObject(value, _meta);
    }

    throw new Error("Call: Cannot decode value '".concat(value, "' of type ").concat(typeof value));
  }

  static decodeCallViaObject(value, _meta) {
    // we only pass args/methodsIndex out
    const {
      args,
      callIndex
    } = value; // Get the correct lookupIndex

    const lookupIndex = callIndex instanceof CallIndex ? callIndex.toU8a() : callIndex; // Find metadata with callIndex

    const meta = _meta || Call.findFunction(lookupIndex).meta;

    return {
      args,
      argsDef: Call.getArgsDef(meta),
      meta,
      callIndex
    };
  }

  static decodeCallViaU8a(value, _meta) {
    // The first 2 bytes are the callIndex
    const callIndex = value.subarray(0, 2); // Find metadata with callIndex

    const meta = _meta || Call.findFunction(callIndex).meta;

    return {
      args: value.subarray(2),
      argsDef: Call.getArgsDef(meta),
      callIndex,
      meta
    };
  } // If the extrinsic function has an argument of type `Origin`, we ignore it


  static filterOrigin(meta) {
    // FIXME should be `arg.type !== Origin`, but doesn't work...
    return meta ? meta.args.filter((_ref) => {
      let {
        type
      } = _ref;
      return type.toString() !== 'Origin';
    }) : [];
  } // We could only inject the meta (see injectMethods below) and then do a
  // meta-only lookup via
  //
  //   metadata.modules[callIndex[0]].module.call.functions[callIndex[1]]
  //
  // As a convenience helper though, we return the full constructor function,
  // which includes the meta, name, section & actual interface for calling


  static findFunction(callIndex) {
    (0, _util.assert)(Object.keys(injected).length > 0, 'Calling Call.findFunction before extrinsics have been injected.');
    return injected[callIndex.toString()] || FN_UNKNOWN;
  }
  /**
   * Get a mapping of `argument name -> argument type` for the function, from
   * its metadata.
   *
   * @param meta - The function metadata used to get the definition.
   */


  static getArgsDef(meta) {
    return Call.filterOrigin(meta).reduce((result, _ref2) => {
      let {
        name,
        type
      } = _ref2;
      const Type = (0, _create.getTypeClass)((0, _create.getTypeDef)(type.toString()));
      result[name.toString()] = Type;
      return result;
    }, {});
  }

  static injectMetadata(metadata) {
    const extrinsics = (0, _fromMetadata.default)(metadata);
    Object.values(extrinsics).forEach(methods => Object.values(methods).forEach(method => {
      injected[method.callIndex.toString()] = method;
    }));
  }
  /**
   * @description The arguments for the function call
   */


  get args() {
    // FIXME This should return a Struct instead of an Array
    return [...this.get('args').values()];
  }
  /**
   * @description The argument definitions
   */


  get argsDef() {
    return Call.getArgsDef(this.meta);
  }
  /**
   * @description The encoded `[sectionIndex, methodIndex]` identifier
   */


  get callIndex() {
    return this.get('callIndex').toU8a();
  }
  /**
   * @description The encoded data
   */


  get data() {
    return this.get('args').toU8a();
  }
  /**
   * @description `true` if the `Origin` type is on the method (extrinsic method)
   */


  get hasOrigin() {
    const firstArg = this.meta.args[0];
    return !!firstArg && firstArg.type.toString() === 'Origin';
  }
  /**
   * @description The [[FunctionMetadata]]
   */


  get meta() {
    return this._meta;
  }
  /**
   * @description Returns the name of the method
   */


  get methodName() {
    return Call.findFunction(this.callIndex).method;
  }
  /**
   * @description Returns the module containing the method
   */


  get sectionName() {
    return Call.findFunction(this.callIndex).section;
  }
  /**
   * @description Returns the base runtime type name for this instance
   */


  toRawType() {
    return 'Call';
  }

}

exports.default = Call;