/**
 * v escrow contract module provides functionalities for V Escrow contract.
 * @module contract/VEscrowCtrt
 */

'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 msacnt from '../multisign_account.js';

/** FuncIdx is the class for function indexes */
export class FuncIdx extends ctrt.FuncIdx {
  static elems = {
    SUPERSEDE: 0,
    CREATE: 1,
    RECIPIENT_DEPOSIT: 2,
    JUDGE_DEPOSIT: 3,
    PAYER_CANCEL: 4,
    RECIPIENT_CANCEL: 5,
    JUDGE_CANCEL: 6,
    SUBMIT_WORK: 7,
    APPROVE_WORK: 8,
    APPLY_TO_JUDGE: 9,
    JUDGE: 10,
    SUBMIT_PENALTY: 11,
    PAYER_REFUND: 12,
    RECIPIENT_REFUND: 13,
    COLLECT: 14,
  };
  static _ = this.createElems();
}

/** StateVar is the class for state variables */
export class StateVar extends ctrt.StateVar {
  static elems = {
    MAKER: 0,
    JUDGE: 1,
    TOKEN_ID: 2,
    DURATION: 3,
    JUDGE_DURATION: 4,
  };
  static _ = this.createElems();
}

/** StateMapIdx is the class for state map index */
export class StateMapIdx extends ctrt.StateMapIdx {
  static elems = {
    CONTRACT_BALANCE: 0,
    ORDER_PAYER: 1,
    ORDER_RECIPIENT: 2,
    ORDER_AMOUNT: 3,
    ORDER_RECIPIENT_DEPOSIT: 4,
    ORDER_JUDGE_DEPOSIT: 5,
    ORDER_FEE: 6,
    ORDER_RECIPIENT_AMOUNT: 7,
    ORDER_REFUND: 8,
    ORDER_RECIPIENT_REFUND: 9,
    ORDER_EXPIRATION_TIME: 10,
    ORDER_STATUS: 11,
    ORDER_RECIPIENT_DEPOSIT_STATUS: 12,
    ORDER_JUDGE_DEPOSIT_STATUS: 13,
    ORDER_SUBMIT_STATUS: 14,
    ORDER_JUDGE_STATUS: 15,
    ORDER_RECIPIENT_LOCKED_AMOUNT: 16,
    ORDER_JUDGE_LOCKED_AMOUNT: 17,
  };
  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());
  }

  static forJudge() {
    return new this(StateVar.JUDGE.serialize());
  }

  static forTokId() {
    return new this(StateVar.TOKEN_ID.serialize());
  }

  static forDuration() {
    return new this(StateVar.DURATION.serialize());
  }

  static forJudgeDuration() {
    return new this(StateVar.JUDGE_DURATION.serialize());
  }

  /**
   * forContractBalance returns the DBKey object for querying the contract balance.
   * @param {string} addr - The address.
   * @returns {DBKey} The DBKey object for querying the contract balance.
   */
  static forContractBalance(addr) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.CONTRACT_BALANCE,
      de.Addr.fromStr(addr)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderPayer returns the DBKey object for querying the payer of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the payer of the order.
   */
  static forOrderPayer(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_PAYER,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderRcpt returns the DBKey object for querying the recipient of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the recipient of the order.
   */
  static forOrderRcpt(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_RECIPIENT,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderAmount returns the DBKey object for querying the amount of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the amount of the order.
   */
  static forOrderAmount(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_AMOUNT,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderRcptDeposit returns the DBKey object for querying the amount the recipient deposits of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the amount the recipient deposits of the order.
   */
  static forOrderRcptDeposit(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_RECIPIENT_DEPOSIT,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderJudgeDeposit returns the DBKey object for querying the amount the judge deposits in the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the amount the judge deposits in the order.
   */
  static forOrderJudgeDeposit(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_JUDGE_DEPOSIT,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderFee returns the DBKey object for querying the fee of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the fee of the order.
   */
  static forOrderFee(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_FEE,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderRcptAmount returns the DBKey object for querying the recipient amount of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the recipient amount of the order.
   */
  static forOrderRcptAmount(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_RECIPIENT_AMOUNT,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderRefund returns the DBKey object for querying the refund of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the refund of the order.
   */
  static forOrderRefund(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_REFUND,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderRcptRefund returns the DBKey object for querying the recipient refund of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the recipient refund of the order.
   */
  static forOrderRcptRefund(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_RECIPIENT_REFUND,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderExpTime returns the DBKey object for querying the expired time of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the expired time of the order.
   */
  static forOrderExpTime(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_EXPIRATION_TIME,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderStatus returns the DBKey object for querying the status of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the status of the order.
   */
  static forOrderStatus(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_STATUS,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderRcptDepositStatus returns the DBKey object for querying the recipient deposit 
     status of the order.
    * @param {string} orderId - The order id.
    * @returns {DBKey} The DBKey object for querying the recipient deposit status of the order.
    */
  static forOrderRcptDepositStatus(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_RECIPIENT_DEPOSIT_STATUS,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderJudgeDepositStatus returns the DBKey object for querying the judge deposit 
     status of the order.
    * @param {string} orderId - The order id.
    * @returns {DBKey} The DBKey object for querying the judge deposit status of the order.
    */
  static forOrderJudgeDepositStatus(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_JUDGE_DEPOSIT_STATUS,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderSubmitStatus returns the DBKey object for querying the submit status of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the submit status of the order.
   */
  static forOrderSubmitStatus(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_SUBMIT_STATUS,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderJudgeStatus returns the DBKey object for querying the judge status of the order.
   * @param {string} orderId - The order id.
   * @returns {DBKey} The DBKey object for querying the judge status of the order.
   */
  static forOrderJudgeStatus(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_JUDGE_STATUS,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderRcptLockedAmount returns the DBKey object for querying the recipient locked amount 
     of the order.
    * @param {string} orderId - The order id.
    * @returns {DBKey} The DBKey object for querying the recipient locked amount of the order.
    */
  static forOrderRcptLockedAmount(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_RECIPIENT_LOCKED_AMOUNT,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }

  /**
   * forOrderJudgeLockedAmount returns the DBKey object for querying the judge locked amount 
     of the order.
    * @param {string} orderId - The order id.
    * @returns {DBKey} The DBKey object for querying the judge locked amount of the order.
    */
  static forOrderJudgeLockedAmount(orderId) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.ORDER_JUDGE_LOCKED_AMOUNT,
      de.Bytes.fromBase58Str(orderId)
    );
    return new this(stmp.serialize());
  }
}

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

  constructor(ctrtId, chain) {
    super(ctrtId, chain);
    this._tokId = undefined;
    this._unit = 0;
  }
  /**
   * getMaker queries & returns the maker of the contract.
   * @returns {md.Addr} The address of the maker of the contract.
   */
  async getMaker() {
    const rawVal = await this.queryDbKey(DBKey.forMaker());
    return new md.Addr(rawVal);
  }

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

  /**
   * getTokId queries & returns the token id of the contract.
   * @returns {md.Token} The address of the maker of the contract.
   */
  async getTokId() {
    if (!this._tokId) {
      const rawVal = await this.queryDbKey(DBKey.forTokId());
      this._tokId = new md.TokenID(rawVal);
      return this._tokId;
    }
    return this._tokId;
  }

  /**
   * getDuration queries & returns the duration of the contract.
   * @returns {md.VSYSTimestamp} The duration.
   */
  async getDuration() {
    const rawVal = await this.queryDbKey(DBKey.forDuration());
    return md.VSYSTimestamp.fromUnixTs(rawVal);
  }

  /**
   * getUnit returns the unit of the token of this contract.
   * @returns {number} The unit of the token.
   */
  async getUnit() {
    if (this._unit <= 0) {
      const tokId = await this.getTokId();
      const tokInfo = await this.chain.api.ctrt.getTokInfo(tokId.data);
      this._unit = tokInfo.unity;
      return this._unit;
    }
    return this._unit;
  }

  /**
   * getJudgeDuration queries & returns the judge duration of the contract.
   * @returns {md.VSYSTimestamp} The duration.
   */
  async getJudgeDuration() {
    const rawVal = await this.queryDbKey(DBKey.forJudgeDuration());
    return md.VSYSTimestamp.fromUnixTs(rawVal);
  }

  /**
   * getCtrtBal gets the token balance within this contract belonging to the user address.
   * @param {string} addr - The account address.
   * @returns {md.Token} The token balance.
   */
  async getCtrtBal(addr) {
    const rawVal = await this.queryDbKey(DBKey.forContractBalance(addr));
    const unit = await this.getUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getOrderPayer gets the payer of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Addr} The payer address.
   */
  async getOrderPayer(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderPayer(orderId));
    return new md.Addr(rawVal);
  }

  /**
   * getOrderRcpt gets the recipient of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Addr} The recipient address.
   */
  async getOrderRcpt(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderRcpt(orderId));
    return new md.Addr(rawVal);
  }

  /**
   * getOrderAmount gets the amount of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Token} The amount of the order.
   */
  async getOrderAmount(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderAmount(orderId));
    const unit = await this.getUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getOrderRcptDeposit gets the amount of the recipient deposit of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Token} The amount of the recipient deposit of the order.
   */
  async getOrderRcptDeposit(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderRcptDeposit(orderId));
    const unit = await this.getUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getOrderJudgeDeposit gets the amount of the judge deposit of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Token} The amount of the judge deposit of the order.
   */
  async getOrderJudgeDeposit(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderJudgeDeposit(orderId));
    const unit = await this.getUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getOrderFee gets the fee of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Token} The order fee.
   */
  async getOrderFee(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderFee(orderId));
    const unit = await this.getUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getOrderRcptAmount gets the recipient amount of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Token} The recipient amount of the order.
   */
  async getOrderRcptAmount(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderRcptAmount(orderId));
    const unit = await this.getUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getOrderRefund gets the refund of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Token} The refund of the order.
   */
  async getOrderRefund(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderRefund(orderId));
    const unit = await this.getUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getOrderRcptRefund gets the recipient refund of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Addr} The recipient refund of the order.
   */
  async getOrderRcptRefund(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderRcptRefund(orderId));
    const unit = await this.getUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getOrderExpTime gets the expire time of the order.
   * @param {string} orderId - The order id.
   * @returns {md.VSYSTimestamp} The expire time of the order.
   */
  async getOrderExpTime(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderExpTime(orderId));
    return md.VSYSTimestamp.fromUnixTs(rawVal);
  }

  /**
   * getOrderStatus gets the status of the order.
   * @param {string} orderId - The order id.
   * @returns {boolean} The status of the order.
   */
  async getOrderStatus(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderStatus(orderId));
    return rawVal === 'true';
  }

  /**
   * getOrderRcptDepositStatus gets the recipient deposit status of the order.
   * @param {string} orderId - The order id.
   * @returns {boolean} The recipient deposit status of the order.
   */
  async getOrderRcptDepositStatus(orderId) {
    const rawVal = await this.queryDbKey(
      DBKey.forOrderRcptDepositStatus(orderId)
    );
    return rawVal === 'true';
  }

  /**
   * getOrderJudgeDepositStatus gets the judge deposit status of the order.
   * @param {string} orderId - The order id.
   * @returns {boolean} The judge deposit status of the order.
   */
  async getOrderJudgeDepositStatus(orderId) {
    const rawVal = await this.queryDbKey(
      DBKey.forOrderJudgeDepositStatus(orderId)
    );
    return rawVal === 'true';
  }

  /**
   * getOrderSubmitStatus gets the submit status of the order.
   * @param {string} orderId - The order id.
   * @returns {boolean} The submit status of the order.
   */
  async getOrderSubmitStatus(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderSubmitStatus(orderId));
    return rawVal === 'true';
  }

  /**
   * getOrderJudgeStatus gets the submit status of the order.
   * @param {string} orderId - The order id.
   * @returns {boolean} The submit status of the order.
   */
  async getOrderJudgeStatus(orderId) {
    const rawVal = await this.queryDbKey(DBKey.forOrderJudgeStatus(orderId));
    return rawVal === 'true';
  }

  /**
   * getOrderRcptLockedAmount gets the recipient locked amount of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Token} The recipient locked amount of the order.
   */
  async getOrderRcptLockedAmount(orderId) {
    const rawVal = await this.queryDbKey(
      DBKey.forOrderRcptLockedAmount(orderId)
    );
    const unit = await this.getUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getOrderJudgeLockedAmount gets the judge locked amount of the order.
   * @param {string} orderId - The order id.
   * @returns {md.Addr} The judge locked amount of the order.
   */
  async getOrderJudgeLockedAmount(orderId) {
    const rawVal = await this.queryDbKey(
      DBKey.forOrderJudgeLockedAmount(orderId)
    );
    const unit = await this.getUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * register registers a V Escrow Contract.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action maker.
   * @param {string} tokId - The ID of the token deposited into the contract.
   * @param {number} duration - The duration where the recipient can take actions.
   * @param {number} judgeDuration - The duration where the judge can take actions.
   * @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 {VEscrowCtrt} The VEscrowCtrt object of the registered V Escrow Contract.
   */
  static async register(
    by,
    tokId,
    duration,
    judgeDuration,
    ctrtDescription,
    fee = md.RegCtrtFee.DEFAULT
  ) {
    const data = await by.registerContractImpl(
      new tx.RegCtrtTxReq(
        new de.DataStack(
          de.TokenID.fromStr(tokId),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(duration)),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(judgeDuration))
        ),
        this.CTRT_META,
        md.VSYSTimestamp.now(),
        new md.Str(ctrtDescription),
        md.RegCtrtFee.fromNumber(fee)
      )
    );
    return new this(data.contractId, by.chain);
  }

  /**
   * supersede transfers the judge right of the contract to another account.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} newJudge - The new judge of the 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 supersede(by, newJudge, attachment = '', fee = md.ExecCtrtFee.DEFAULT) {
    const judgeMd = md.Addr(newJudge);
    judgeMd.mustOn(by.chain);

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

  /**
  * create creates an escrow order.
    NOTE that the transaction id of this action is the order ID.
  * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
  * @param {string} recipient - The recipient account.
  * @param {number} amount - The amount of tokens.
  * @param {number} rcptDepositAmount - The amount that the recipient needs to deposit.
  * @param {number} judgeDepositAmount - The amount that the judge needs to deposit.
  * @param {number} orderFee - The fee for this order.
  * @param {number} refundAmount - The amount to refund.
  * @param {number} expireTime - The expiration time of the order.
  * @param {string} attachment - The attachment of this 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 create(
    by,
    recipient,
    amount,
    rcptDepositAmount,
    judgeDepositAmount,
    orderFee,
    refundAmount,
    expireTime,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const rcptMd = new md.Addr(recipient);
    rcptMd.mustOn(by.chain);

    const unit = await this.getUnit();

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.CREATE,
        new de.DataStack(
          new de.Addr(rcptMd),
          de.Amount.forTokAmount(amount, unit),
          de.Amount.forTokAmount(rcptDepositAmount, unit),
          de.Amount.forTokAmount(judgeDepositAmount, unit),
          de.Amount.forTokAmount(orderFee, unit),
          de.Amount.forTokAmount(refundAmount, unit),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(expireTime))
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * recipientDeposit deposits tokens the recipient deposited into the contract into the order.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} orderId - The order id.
   * @param {string} attachment - The attachment of this 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 recipientDeposit(
    by,
    orderId,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.RECIPIENT_DEPOSIT,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * judgeDeposit deposits tokens the judge deposited into the contract into the order.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} orderId - The order id.
   * @param {string} attachment - The attachment of this 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 judgeDeposit(
    by,
    orderId,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.JUDGE_DEPOSIT,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * payerCancel cancels the order by the payer.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} orderId - The order id.
   * @param {string} attachment - The attachment of this 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 payerCancel(
    by,
    orderId,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.PAYER_CANCEL,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * recipientCancel cancels the order by the recipient.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} orderId - The order id.
   * @param {string} attachment - The attachment of this 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 recipientCancel(
    by,
    orderId,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.RECIPIENT_CANCEL,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * judgeCancel cancels the order by the judge.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} orderId - The order id.
   * @param {string} attachment - The attachment of this 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 judgeCancel(
    by,
    orderId,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.JUDGE_CANCEL,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * submitWork submits the work by the recipient.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} orderId - The order id.
   * @param {string} attachment - The attachment of this 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 submitWork(by, orderId, attachment = '', fee = md.ExecCtrtFee.DEFAULT) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.SUBMIT_WORK,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * approveWork approves the work and agrees the amounts are paid by the payer.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} orderId - The order id.
   * @param {string} attachment - The attachment of this 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 approveWork(
    by,
    orderId,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.APPROVE_WORK,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * applyToJudge applies for the help from judge by the payer.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} orderId - The order id.
   * @param {string} attachment - The attachment of this 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 applyToJudge(
    by,
    orderId,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.APPLY_TO_JUDGE,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
  * doJudge judges the work and decides on how much the payer & recipient
    will receive.
  * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
  * @param {string} orderId - The order id.
  * @param {number} payerAmount - The amount the payer will get.
  * @param {number} rcptAmount - The amount the recipient will get.
  * @param {string} attachment - The attachment of this 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 doJudge(
    by,
    orderId,
    payerAmount,
    rcptAmount,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const unit = await this.getUnit();

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.JUDGE,
        new de.DataStack(
          de.Bytes.fromBase58Str(orderId),
          de.Amount.forTokAmount(payerAmount, unit),
          de.Amount.forTokAmount(rcptAmount, unit)
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
  * submitPenalty submits penalty by the payer for the case where the recipient does not submit
    work before the expiration time. The payer will obtain the recipient deposit amount and the payer amount(fee deducted).
    The judge will still get the fee.
  * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
  * @param {string} orderId - The order id.
  * @param {string} attachment - The attachment of this 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 submitPenalty(
    by,
    orderId,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.SUBMIT_PENALTY,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
  * payerRefund makes the refund action by the payer when the judge does not judge the work in time
    after the apply_to_judge function is invoked.
    The judge loses his deposit amount and the payer receives the refund amount.
    The recipient receives the rest.
  * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
  * @param {string} orderId - The order id.
  * @param {string} attachment - The attachment of this 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 payerRefund(
    by,
    orderId,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.PAYER_REFUND,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
 * rcptRefund makes the refund action by the recipient when the judge does not judge the work in time
   after the apply_to_judge function is invoked.
    The judge loses his deposit amount and the payer receives the refund amount.
    The recipient receives the rest.
  * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
  * @param {string} orderId - The order id.
  * @param {string} attachment - The attachment of this 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 rcptRefund(by, orderId, attachment = '', fee = md.ExecCtrtFee.DEFAULT) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.RECIPIENT_REFUND,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
  * collect collects the order amount & recipient deposited amount by the recipient when the work is submitted
    while the payer doesn't either approve or apply to judge in his action duration.
    The judge will get judge deposited amount & fee.
  * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
  * @param {string} orderId - The order id.
  * @param {string} attachment - The attachment of this 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, orderId, attachment = '', fee = md.ExecCtrtFee.DEFAULT) {
    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.COLLECT,
        new de.DataStack(de.Bytes.fromBase58Str(orderId)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }
}
