/**
 * module VOptionCtrt provides functionalities for V Option contract.
 * @module contract/VOptionCtrt
 */

'use strict';

import * as ctrt from './ctrt.js';
import * as acnt from '../account.js';
import * as md from '../model.js';
import * as tx from '../tx_req.js';
import * as de from '../data_entry.js';
import * as tcf from './tok_ctrt_factory.js';
import * as msacnt from '../multisign_account.js';

/** FuncIdx is the class for function indexes */
export class FuncIdx extends ctrt.FuncIdx {
  static elems = {
    SUPERSEDE: 0,
    ACTIVATE: 1,
    MINT: 2,
    UNLOCK: 3,
    EXECUTE: 4,
    COLLECT: 5,
  };
  static _ = this.createElems();
}

/** StateVar is the class for state variables */
export class StateVar extends ctrt.StateVar {
  static elems = {
    MAKER: 0,
    BASE_TOKEN_ID: 1,
    TARGET_TOKEN_ID: 2,
    OPTION_TOKEN_ID: 3,
    PROOF_TOKEN_ID: 4,
    EXECUTE_TIME: 5,
    EXECUTE_DEADLINE: 6,
    OPTION_STATUS: 7,
    MAX_ISSUE_NUM: 8,
    RESERVED_OPTION: 9,
    RESERVED_PROOF: 10,
    PRICE: 11,
    PRICE_UNIT: 12,
    TOKEN_LOCKED: 13,
    TOKEN_COLLECTED: 14,
  };
  static _ = this.createElems();
}

/** StateMapIdx is the class for state map index */
export class StateMapIdx extends ctrt.StateMapIdx {
  static elems = {
    BASE_TOKEN_BALANCE: 0,
    TARGET_TOKEN_BALANCE: 1,
    OPTION_TOKEN_BALANCE: 2,
    PROOF_TOKEN_BALANCE: 3,
  };
  static _ = this.createElems();
}

/** DBKey is the class for DB key */
export class DBKey extends ctrt.DBKey {
  /**
   * forMaker returns the DBKey object for querying the maker.
   * @returns {DBKey} The DBKey object for querying the maker.
   */
  static forMaker() {
    return new this(StateVar.MAKER.serialize());
  }

  /**
   * forBaseTokId returns the DBKey object for querying the base token id.
   * @returns {DBKey} The DBKey object for querying the base token id.
   */
  static forBaseTokId() {
    return new this(StateVar.BASE_TOKEN_ID.serialize());
  }

  /**
   * forTargetTokId returns the DBKey object for querying the target token id.
   * @returns {DBKey} The DBKey object for querying the target token id.
   */
  static forTargetTokId() {
    return new this(StateVar.TARGET_TOKEN_ID.serialize());
  }

  /**
   * forOptionTokId returns the DBKey object for querying the option token id.
   * @returns {DBKey} The DBKey object for querying the option token id.
   */
  static forOptionTokId() {
    return new this(StateVar.OPTION_TOKEN_ID.serialize());
  }

  /**
   * forProofTokId returns the DBKey object for querying the proof token id.
   * @returns {DBKey} The DBKey object for querying the proof token id.
   */
  static forProofTokId() {
    return new this(StateVar.PROOF_TOKEN_ID.serialize());
  }

  /**
   * forExecuteTime returns the DBKey object for querying the execute time.
   * @returns {DBKey} The DBKey object for querying the execute time.
   */
  static forExecuteTime() {
    return new this(StateVar.EXECUTE_TIME.serialize());
  }

  /**
   * forExecuteDeadline returns the DBKey object for querying the execute deadline.
   * @returns {DBKey} The DBKey object for querying the execute deadline.
   */
  static forExecuteDeadline() {
    return new this(StateVar.EXECUTE_DEADLINE.serialize());
  }

  /**
   * forOptionStatus returns the DBKey object for querying the execute deadline.
   * @returns {DBKey} The DBKey object for querying the execute deadline.
   */
  static forOptionStatus() {
    return new this(StateVar.EXECUTE_DEADLINE.serialize());
  }

  /**
   * forMaxIssueNum returns the DBKey object for querying the maximum issue number.
   * @returns {DBKey} The DBKey object for querying the maximum issue number.
   */
  static forMaxIssueNum() {
    return new this(StateVar.MAX_ISSUE_NUM.serialize());
  }

  /**
   * forReversedOption returns the DBKey object for querying the reserved option tokens remaining in the pool.
   * @returns {DBKey} The DBKey object for querying the reserved option tokens remaining in the pool.
   */
  static forReversedOption() {
    return new this(StateVar.RESERVED_OPTION.serialize());
  }

  /**
   * forReversedProof returns the DBKey object for querying the reserved proof tokens remaining in the pool.
   * @returns {DBKey} The DBKey object for querying the reserved proof tokens remaining in the pool.
   */
  static forReversedProof() {
    return new this(StateVar.RESERVED_PROOF.serialize());
  }

  /**
   * forPrice returns the DBKey object for querying the price of the contract creator.
   * @returns {DBKey} The DBKey object for querying the price of the contract creator.
   */
  static forPrice() {
    return new this(StateVar.PRICE.serialize());
  }

  /**
   * forPriceUnit returns the DBKey object for querying the price unit of the contract creator.
   * @returns {DBKey} The DBKey object for querying the price unit of the contract creator.
   */
  static forPriceUnit() {
    return new this(StateVar.PRICE_UNIT.serialize());
  }

  /**
   * forTokenLocked returns the DBKey object for querying the address of the contract creator.
   * @returns {DBKey} The DBKey object for querying the address of the contract creator.
   */
  static forTokenLocked() {
    return new this(StateVar.TOKEN_LOCKED.serialize());
  }

  /**
   * forTokenCollect returns the DBKey object for querying the amount of base tokens in the pool.
   * @returns {DBKey} The DBKey object for querying the amount of base tokens in the pool.
   */
  static forTokenCollect() {
    return new this(StateVar.TOKEN_COLLECTED.serialize());
  }

  /**
   * forBaseTokBal returns the DBKey object for querying the total amount of base token balance.
   * @returns {DBKey} The DBKey object for querying the total amount of base token balance.
   */
  static forBaseTokBal(addr) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.BASE_TOKEN_BALANCE,
      de.Addr.fromStr(addr)
    );
    return new this(stmp.serialize());
  }

  /**
   * forTargetTokBal returns the DBKey object for querying the total amount of target token balance.
   * @returns {DBKey} The DBKey object for querying the total amount of target token balance.
   */
  static forTargetTokBal(addr) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.TARGET_TOKEN_BALANCE,
      de.Addr.fromStr(addr)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOptionTokBal returns the DBKey object for querying the total amount of option token balance.
   * @returns {DBKey} The DBKey object for querying the total amount of option token balance.
   */
  static forOptionTokBal(addr) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.OPTION_TOKEN_BALANCE,
      de.Addr.fromStr(addr)
    );
    return new this(stmp.serialize());
  }

  /**
   * forProofTokBal returns the DBKey object for querying the total amount of proof token balance.
   * @returns {DBKey} The DBKey object for querying the total amount of proof token balance.
   */
  static forProofTokBal(addr) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.PROOF_TOKEN_BALANCE,
      de.Addr.fromStr(addr)
    );
    return new this(stmp.serialize());
  }
}

export class VOptionCtrt extends ctrt.Ctrt {
  static CTRT_META = md.CtrtMeta.fromB58Str(
    ' '
  );

  constructor(ctrtId, chain) {
    super(ctrtId, chain);
    this._baseTokId = undefined;
    this._targetTokId = undefined;
    this._optionTokId = undefined;
    this._proofTokId = undefined;

    this._baseTokCtrt = undefined;
    this._targetCtrt = undefined;
    this._proofCtrt = undefined;
    this._optionCtrt = undefined;

    this._executeTime = undefined;
    this._executeDeadline = undefined;
    this._maxIssueNumber = undefined;
    this._price = undefined;
    this._priceUnit = undefined;
  }

  /**
   * getMaker queries & returns the maker of the contract.
   * @returns {md.Addr} The maker of the contract.
   */
  async getMaker() {
    const rawVal = await this.queryDbKey(DBKey.forMaker());
    return new md.Addr(rawVal);
  }

  /**
   * getBaseTokId queries & returns the base token id of the contract.
   * @returns {md.TokenID} The base token id of the contract.
   */
  async getBaseTokId() {
    if (!this._baseTokId) {
      const rawVal = await this.queryDbKey(DBKey.forBaseTokId());
      this._baseTokId = new md.TokenID(rawVal);
    }
    return this._baseTokId;
  }

  /**
   * getTargetTokId queries & returns the target token id of the contract.
   * @returns {md.TokenID} The target token id of the contract.
   */
  async getTargetTokId() {
    if (!this._targetTokId) {
      const rawVal = await this.queryDbKey(DBKey.forTargetTokId());
      this._targetTokId = new md.TokenID(rawVal);
    }
    return this._targetTokId;
  }

  /**
   * getOptionTokId queries & returns the option token id of the contract.
   * @returns {md.TokenID} The option token id of the contract.
   */
  async getOptionTokId() {
    if (!this._optionTokId) {
      const rawVal = await this.queryDbKey(DBKey.forOptionTokId());
      this._optionTokId = new md.TokenID(rawVal);
    }
    return this._optionTokId;
  }

  /**
   * getProofTokId queries & returns the proof token id of the contract.
   * @returns {md.TokenID} The proof token id of the contract.
   */
  async getProofTokId() {
    if (!this._proofTokId) {
      const rawVal = await this.queryDbKey(DBKey.forProofTokId());
      this._proofTokId = new md.TokenID(rawVal);
    }
    return this._proofTokId;
  }

  /**
   * getBaseTokCtrt queries & returns the instance of base token contract.
   * @returns {ctrt.BaseTokCtrt} The instance of base token contract.
   */
  async getBaseTokCtrt() {
    if (!this._baseTokCtrt) {
      const baseTokId = await this.getBaseTokId();
      this._baseTokCtrt = await tcf.fromTokId(baseTokId, this.chain);
    }
    return this._baseTokCtrt;
  }

  /**
   * getTargetTokCtrt queries & returns the instance of target token contract.
   * @returns {ctrt.BaseTokCtrt} The instance of target token contract.
   */
  async getTargetTokCtrt() {
    if (!this._targetTokCtrt) {
      const targetTokId = await this.getTargetTokId();
      this._targetTokCtrt = await tcf.fromTokId(targetTokId, this.chain);
    }
    return this._targetTokCtrt;
  }

  /**
   * getOptionTokCtrt queries & returns the instance of option token contract.
   * @returns {ctrt.BaseTokCtrt} The instance of option token contract.
   */
  async getOptionTokCtrt() {
    if (!this._optionTokCtrt) {
      const optionTokId = await this.getOptionTokId();
      this._optionTokCtrt = await tcf.fromTokId(optionTokId, this.chain);
    }
    return this._optionTokCtrt;
  }

  /**
   * getProofTokCtrt queries & returns the instance of proof token contract.
   * @returns {ctrt.BaseTokCtrt} The instance of proof token contract.
   */
  async getProofTokCtrt() {
    if (!this._proofTokCtrt) {
      const proofTokId = await this.getProofTokId();
      this._proofTokCtrt = await tcf.fromTokId(proofTokId, this.chain);
    }
    return this._proofTokCtrt;
  }

  /**
   * getBaseTokUnit queries & returns the base token's unit.
   * @returns {number} The unit of base token.
   */
  async getBaseTokUnit() {
    const tc = await this.getBaseTokCtrt();
    return await tc.getUnit();
  }

  /**
   * getTargetTokUnit queries & returns the target token's unit.
   * @returns {number} The unit of target token.
   */
  async getTargetTokUnit() {
    const tc = await this.getTargetTokCtrt();
    return await tc.getUnit();
  }

  /**
   * getOptionTokUnit queries & returns the option token's unit.
   * @returns {number} The unit of option token.
   */
  async getOptionTokUnit() {
    const tc = await this.getOptionTokCtrt();
    return await tc.getUnit();
  }

  /**
   * getProofTokUnit queries & returns the proof token's unit.
   * @returns {number} The unit of proof token.
   */
  async getProofTokUnit() {
    const tc = await this.getProofTokCtrt();
    return await tc.getUnit();
  }

  /**
   * getExeTime queries & returns the execute time.
   * @returns {md.VSYSTimestamp} The execute time.
   */
  async getExeTime() {
    if (!this._exeTime) {
      const time = await this.queryDbKey(DBKey.forExecuteTime());
      this._exeTime = md.VSYSTimestamp.fromNumber(time);
    }
    return this._exeTime;
  }

  /**
   * getExeDeadline queries & returns the execute deadline.
   * @returns {md.VSYSTimestamp} The execute deadline.
   */
  async getExeDeadline() {
    if (!this._executeDeadline) {
      const time = await this.queryDbKey(DBKey.forExecuteDeadline());
      this._executeDeadline = md.VSYSTimestamp.fromNumber(time);
    }
    return this._executeDeadline;
  }

  /**
   * getOptionStatus queries & returns the option contract's status.
   * @returns {boolean} The option contract's status.
   */
  async getOptionStatus() {
    const rawVal = await this.queryDbKey(DBKey.forOptionStatus());
    return rawVal === true;
  }

  /**
   * getMaxIssueNum queries & returns the maximum issue of the option tokens.
   * @returns {md.Token} The maximum issue of the option tokens.
   */
  async getMaxIssueNum() {
    if (!this._maxIssueNumber) {
      const rawVal = await this.queryDbKey(DBKey.forMaxIssueNum());
      this._maxIssueNumber = md.Token.forAmount(
        rawVal,
        await this.getBaseTokUnit()
      );
    }
    return this._maxIssueNumber;
  }

  /**
   * getReversedOption queries & returns the reserved option tokens remaining in the pool.
   * @returns {md.Token} The reserved option tokens remaining in the pool.
   */
  async getReversedOption() {
    const rawVal = await this.queryDbKey(DBKey.forReversedOption());
    return md.Token.forAmount(rawVal, await this.getOptionTokUnit());
  }

  /**
   * getReversedProof queries & returns the reserved proof tokens remaining in the pool.
   * @returns {md.Token} The reserved proof tokens remaining in the pool.
   */
  async getReversedProof() {
    const rawVal = await this.queryDbKey(DBKey.forReversedProof());
    return md.Token.forAmount(rawVal, await this.getProofTokUnit());
  }

  /**
   * getPrice queries & returns the price of the contract creator.
   * @returns {md.Token} The price of the contract creator.
   */
  async getPrice() {
    if (!this._price) {
      const rawVal = await this.queryDbKey(DBKey.forPrice());
      this._price = md.Token.fromNumber(rawVal, 1);
    }
    return this._price;
  }

  /**
   * getPriceUnit queries & returns the price unit of the contract creator.
   * @returns {md.Token} The price unit of the contract creator.
   */
  async getPriceUnit() {
    if (!this._priceUnit) {
      const rawVal = await this.queryDbKey(DBKey.forPriceUnit());
      this._priceUnit = md.Token.fromNumber(rawVal, 1);
    }
    return this._priceUnit;
  }

  /**
   * getTokenLocked queries & returns the locked token amount.
   * @returns {md.Token} the locked token amount.
   */
  async getTokenLocked() {
    const rawVal = await this.queryDbKey(DBKey.forTokenLocked());
    return md.Token.forAmount(rawVal, await this.getTargetTokUnit());
  }

  /**
   * getTokenCollected queries & returns the amount of the base tokens in the pool.
   * @returns {md.Token} The amount of the base tokens in the pool.
   */
  async getTokenCollected() {
    const rawVal = await this.queryDbKey(DBKey.forTokenCollect());
    return md.Token.forAmount(rawVal, await this.getBaseTokUnit());
  }

  /**
   * getBaseTokBal queries & returns the balance of base token stored within the contract belonging to the given user address.
   * @param {string} addr - The account address that deposits the token.
   * @returns {md.Token} The balance of base token stored within the contract belonging to the given address.
   */
  async getBaseTokBal(addr) {
    const rawVal = await this.queryDbKey(DBKey.forBaseTokBal(addr));
    const unit = await this.getBaseTokUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getTargetTokBal queries & returns the balance of target token stored within the contract belonging to the given user address.
   * @param {string} addr - The account address that deposits the token.
   * @returns {md.Token} The balance of target token stored within the contract belonging to the given address.
   */
  async getTargetTokBal(addr) {
    const rawVal = await this.queryDbKey(DBKey.forTargetTokBal(addr));
    const unit = await this.getTargetTokUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getOptionTokBal queries & returns the balance of option token stored within the contract belonging to the given user address.
   * @param {string} addr - The account address that deposits the token.
   * @returns {md.Token} The balance of option token stored within the contract belonging to the given address.
   */
  async getOptionTokBal(addr) {
    const rawVal = await this.queryDbKey(DBKey.forOptionTokBal(addr));
    const unit = await this.getOptionTokUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getProofTokBal queries & returns the balance of proof token stored within the contract belonging to the given user address.
   * @param {string} addr - The account address that deposits the token.
   * @returns {md.Token} The balance of proof token stored within the contract belonging to the given address.
   */
  async getProofTokBal(addr) {
    const rawVal = await this.queryDbKey(DBKey.forProofTokBal(addr));
    const unit = await this.getProofTokUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * register registers a V Option Contract.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} baseTokId - The base token's id.
   * @param {string} targetTokId - The target token's id.
   * @param {string} optionTokId - The option token's id.
   * @param {string} proofTokId - The proof token's id.
   * @param {number} executeTime - The execute time.
   * @param {number} executeDeadline - The execute deadline.
   * @param {string} ctrtDescription - The description of the contract. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.RegCtrtFee.DEFAULT.
   * @returns {VSwapCtrt} - The VSwapCtrt object of the registered V Swap Contract.
   */
  static async register(
    by,
    baseTokId,
    targetTokId,
    optionTokId,
    proofTokId,
    executeTime,
    executeDeadline,
    ctrtDescription = '',
    fee = md.RegCtrtFee.DEFAULT
  ) {
    const data = await by.registerContractImpl(
      new tx.RegCtrtTxReq(
        new de.DataStack(
          de.TokenID.fromStr(baseTokId),
          de.TokenID.fromStr(targetTokId),
          de.TokenID.fromStr(optionTokId),
          de.TokenID.fromStr(proofTokId),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(executeTime)),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(executeDeadline))
        ),
        this.CTRT_META,
        md.VSYSTimestamp.now(),
        new md.Str(ctrtDescription),
        md.RegCtrtFee.fromNumber(fee)
      )
    );
    return new this(data.contractId, by.chain);
  }

  /**
   * supersede transfers the issuer role of the contract to a new account.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} newOwner - The account address of the new owner.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async supersede(by, newOwner, attachment = '', fee = md.ExecCtrtFee.DEFAULT) {
    const newOwnerMd = new md.Addr(newOwner);
    newOwnerMd.mustOn(by.chain);

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.SUPERSEDE,
        new de.DataStack(new de.Addr(newOwnerMd)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * activate activates the V Option contract to store option token and proof token into the pool.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} maxIssueNum - The number of the maximum issue of the option tokens.
   * @param {number} price - The price of the creator of the V Option contract.
   * @param {number} priceUnit - The price unit of the creator of the V Option contract.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async activate(
    by,
    maxIssueNum,
    price,
    priceUnit,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.ACTIVATE,
        new de.DataStack(
          de.Amount.forTokAmount(maxIssueNum, await this.getOptionTokUnit()),
          de.Amount.forTokAmount(price, await this.getOptionTokUnit()),
          de.Amount.forTokAmount(priceUnit, await this.getOptionTokUnit())
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * mint locks target tokens into the pool to get option tokens and proof tokens.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amount - The mint amount.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async mint(by, amount, attachment = '', fee = md.ExecCtrtFee.DEFAULT) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.MINT,
        new de.DataStack(
          de.Amount.forTokAmount(amount, await this.getTargetTokUnit())
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * unlock gets the remaining option tokens and proof tokens from the pool before the execute time.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amount - The amount.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async unlock(by, amount, attachment = '', fee = md.ExecCtrtFee.DEFAULT) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.UNLOCK,
        new de.DataStack(
          de.Amount.forTokAmount(amount, await this.getTargetTokUnit())
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * execute executes the V Option contract to get target token after execute time.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amount - The amount.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async execute(by, amount, attachment = '', fee = md.ExecCtrtFee.DEFAULT) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.EXECUTE,
        new de.DataStack(
          de.Amount.forTokAmount(amount, await this.getTargetTokUnit())
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * collect collects the base tokens or/and target tokens from the pool depending on the amount of proof tokens after execute deadline.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amount - The amount.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async collect(by, amount, attachment = '', fee = md.ExecCtrtFee.DEFAULT) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.COLLECT,
        new de.DataStack(
          de.Amount.forTokAmount(amount, await this.getOptionTokUnit())
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }
}
