# Source: src/main/js/wallet/model/MoneroTxConfig.js

    const assert = require("assert");
    const BigInteger = require("../../common/biginteger").BigInteger;
    const GenUtils = require("../../common/GenUtils");
    const MoneroDestination = require("./MoneroDestination");
    const MoneroError = require("../../common/MoneroError");

    /**
     * Configures a transaction to send, sweep, or create a payment URI.
     */
    class MoneroTxConfig {

      /**
       * <p>Generic request to transfer funds from a wallet.</p>
       *
       * <p>Examples:</p>
       *
       * <code>
       * let config1 = new MoneroTxConfig({<br>
       * &nbsp;&nbsp; accountIndex: 0,<br>
       * &nbsp;&nbsp; address: "59aZULsUF3YN...",<br>
       * &nbsp;&nbsp; amount: new BigInteger("500000"),<br>
       * &nbsp;&nbsp; priority: MoneroTxPriority.NORMAL,<br>
       * &nbsp;&nbsp; relay: true<br>
       * });<br><br>
       * </code>
       *
       * @param {MoneroTxConfig|object} config - configures the transaction to create (optional)
       * @param {string} config.address - single destination address
       * @param {BigInteger} config.amount - single destination amount
       * @param {int} config.accountIndex - source account index to transfer funds from
       * @param {int} config.subaddressIndex - source subaddress index to transfer funds from
       * @param {int[]} config.subaddressIndices - source subaddress indices to transfer funds from
       * @param {boolean} config.relay - relay the transaction to peers to commit to the blockchain
       * @param {MoneroTxPriority} config.priority - transaction priority (default MoneroTxPriority.NORMAL)
       * @param {MoneroDestination[]} config.destinations - addresses and amounts in a multi-destination tx
       * @param {int[]} config.subtractFeeFrom - list of destination indices to split the transaction fee
       * @param {string} config.paymentId - transaction payment ID
       * @param {BigInteger} config.unlockTime - minimum height or timestamp for the transaction to unlock (default 0)
       * @param {string} config.note - transaction note saved locally with the wallet
       * @param {string} config.recipientName - recipient name saved locally with the wallet
       * @param {boolean} config.canSplit - allow funds to be transferred using multiple transactions
       * @param {BigInteger} config.belowAmount - for sweep requests, include outputs below this amount when sweeping wallet, account, subaddress, or all unlocked funds
       * @param {boolean} config.sweepEachSubaddress - for sweep requests, sweep each subaddress individually instead of together if true
       * @param {string} config.keyImage - key image to sweep (ignored except in sweepOutput() requests)
       */
      constructor(config, relaxValidation) {  // relax validation for internal use to process json from rpc or cpp
        if (arguments.length > 2) throw new MoneroError("MoneroTxConfig can be constructed with only two parameters but was given " + arguments.length)

        // initialize internal state
        if (!config) this.state = {};
        else if (config instanceof MoneroTxConfig) this.state = config.toJson();
        else if (typeof config === "object") {
          this.state = Object.assign({}, config);
          if (relaxValidation) {
            if (typeof this.state.amount === "number") this.state.amount = BigInteger.parse(this.state.amount);
            if (typeof this.state.unlockTime === "number") this.state.unlockTime = BigInteger.parse(this.state.unlockTime);
            if (typeof this.state.belowAmount === "number") this.state.belowAmount = BigInteger.parse(this.state.belowAmount);
          }

          // check for unsupported fields
          for (let key of Object.keys(config)) {
            if (!GenUtils.arrayContains(MoneroTxConfig.SUPPORTED_FIELDS, key)) {
              throw new MoneroError("Unsupported field in MoneroTxConfig: '" + key + "'");
            }
          }
        }
        else throw new MoneroError("Invalid argument given to MoneroTxConfig: " + typeof config);

        // deserialize BigIntegers
        if (this.state.fee !== undefined && !(this.state.fee instanceof BigInteger)) this.state.fee = BigInteger.parse(this.state.fee);
        if (this.state.unlockTime !== undefined && !(this.state.unlockTime instanceof BigInteger)) this.state.unlockTime = BigInteger.parse(this.state.unlockTime);
        if (this.state.belowAmount !== undefined && !(this.state.belowAmount instanceof BigInteger)) this.state.belowAmount = BigInteger.parse(this.state.belowAmount);

        // deserialize destinations
        if (this.state.destinations) {
          assert(this.state.address === undefined && this.state.amount === undefined, "Tx configuration may specify destinations or an address/amount but not both");
          this.setDestinations(this.state.destinations.map(destination => destination instanceof MoneroDestination ? destination : new MoneroDestination(destination)));
        }

        // alias 'address' and 'amount' to single destination to support e.g. createTx({address: "..."})
        if (this.state.address || this.state.amount) {
          assert(!this.state.destinations, "Tx configuration may specify destinations or an address/amount but not both");
          this.setAddress(this.state.address);
          this.setAmount(this.state.amount);
          delete this.state.address;
          delete this.state.amount;
        }

        // alias 'subaddressIndex' to subaddress indices
        if (this.state.subaddressIndex !== undefined) {
          this.setSubaddressIndices([this.state.subaddressIndex]);
          delete this.state.subaddressIndex;
        }
      }

      copy() {
        return new MoneroTxConfig(this);
      }

      toJson() {
        let json = Object.assign({}, this.state); // copy state
        if (this.getDestinations()) {
          json.destinations = [];
          for (let destination of this.getDestinations()) json.destinations.push(destination.toJson());
        }
        if (this.getFee()) json.fee = this.getFee().toString();
        if (this.getUnlockTime()) json.unlockTime = this.getUnlockTime().toString();
        if (this.getBelowAmount()) json.belowAmount = this.getBelowAmount().toString();
        return json;
      }

      /**
       * Set the address of a single-destination configuration.
       *
       * @param {string} address - the address to set for the single destination
       * @return {MoneroTxConfig} this configuration for chaining
       */
      setAddress(address) {
        if (this.state.destinations !== undefined && this.state.destinations.length > 1) throw new MoneroError("Cannot set address because MoneroTxConfig already has multiple destinations");
        if (this.state.destinations === undefined || this.state.destinations.length === 0) this.addDestination(new MoneroDestination(address));
        else this.state.destinations[0].setAddress(address);
        return this;
      }

      /**
       * Get the address of a single-destination configuration.
       *
       * @return {string} the address of the single destination
       */
      getAddress() {
        if (this.state.destinations === undefined || this.state.destinations.length !== 1) throw new MoneroError("Cannot get address because MoneroTxConfig does not have exactly one destination");
        return this.state.destinations[0].getAddress();
      }

      /**
       * Set the amount of a single-destination configuration.
       *
       * @param {BigInteger|string} amount - the amount to set for the single destination
       * @return {MoneroTxConfig} this configuration for chaining
       */
      setAmount(amount) {
        if (amount !== undefined && !(this.state.amount instanceof BigInteger)) {
          if (typeof amount === "number") throw new MoneroError("Destination amount must be BigInteger or string");
          try { amount = BigInteger.parse(amount); }
          catch (err) { throw new MoneroError("Invalid destination amount: " + amount); }
        }
        if (this.state.destinations !== undefined && this.state.destinations.length > 1) throw new MoneroError("Cannot set amount because MoneroTxConfig already has multiple destinations");
        if (this.state.destinations === undefined || this.state.destinations.length === 0) this.addDestination(new MoneroDestination(undefined, amount));
        else this.state.destinations[0].setAmount(amount);
        return this;
      }

      /**
       * Get the amount of a single-destination configuration.
       *
       * @return {BigInteger} the amount of the single destination
       */
      getAmount() {
        if (this.state.destinations === undefined || this.state.destinations.length !== 1) throw new MoneroError("Cannot get amount because MoneroTxConfig does not have exactly one destination");
        return this.state.destinations[0].getAmount();
      }

      addDestination(destinationOrAddress, amount) {
        if (typeof destinationOrAddress === "string") return this.addDestination(new MoneroDestination(destinationOrAddress, amount));
        assert(destinationOrAddress instanceof MoneroDestination);
        if (this.state.destinations === undefined) this.state.destinations = [];
        this.state.destinations.push(destinationOrAddress);
        return this;
      }

      getDestinations() {
        return this.state.destinations;
      }

      setDestinations(destinations) {
        if (arguments.length > 1) destinations = Array.from(arguments);
        this.state.destinations = destinations;
        return this;
      }

      setDestination(destination) {
        return this.setDestinations(destination ? [destination] : destination);
      }

      getSubtractFeeFrom() {
        return this.state.subtractFeeFrom;
      }

      setSubtractFeeFrom(destinationIndices) {
        if (arguments.length > 1) destinationIndices = Array.from(arguments);
        this.state.subtractFeeFrom = destinationIndices;
        return this;
      }

      getPaymentId() {
        return this.state.paymentId;
      }

      setPaymentId(paymentId) {
        this.state.paymentId = paymentId;
        return this;
      }

      getPriority() {
        return this.state.priority;
      }

      setPriority(priority) {
        this.state.priority = priority;
        return this;
      }

      getFee() {
        return this.state.fee;
      }

      setFee(fee) {
        this.state.fee = fee;
        return this;
      }

      getAccountIndex() {
        return this.state.accountIndex;
      }

      setAccountIndex(accountIndex) {
        this.state.accountIndex = accountIndex;
        return this;
      }

      setSubaddressIndex(subaddressIndex) {
        this.setSubaddressIndices([subaddressIndex]);
        return this;
      }

      getSubaddressIndices() {
        return this.state.subaddressIndices;
      }

      setSubaddressIndices(subaddressIndices) {
        if (arguments.length > 1) subaddressIndices = Array.from(arguments);
        this.state.subaddressIndices = subaddressIndices;
        return this;
      }

      getUnlockTime() {
        return this.state.unlockTime;
      }

      setUnlockTime(unlockTime) {
        if (unlockTime !== undefined) {
          if (typeof unlockTime === "number") unlockTime = "" + unlockTime;
          if (!(unlockTime instanceof BigInteger)) {
            try { unlockTime = BigInteger.parse(unlockTime); }
            catch (err) { throw new MoneroError("Invalid unlock time: " + unlockTime); }
          }
        }
        this.state.unlockTime = unlockTime;
        return this;
      }

      getRelay() {
        return this.state.relay;
      }

      setRelay(relay) {
        this.state.relay = relay;
        return this;
      }

      getCanSplit() {
        return this.state.canSplit;
      }

      setCanSplit(canSplit) {
        this.state.canSplit = canSplit;
        return this;
      }

      getNote() {
        return this.state.note;
      }

      setNote(note) {
        this.state.note = note;
        return this;
      }

      getRecipientName() {
        return this.state.recipientName;
      }

      setRecipientName(recipientName) {
        this.state.recipientName = recipientName;
        return this;
      }

      // --------------------------- SPECIFIC TO SWEEP ----------------------------

      getBelowAmount() {
        return this.state.belowAmount;
      }

      setBelowAmount(belowAmount) {
        this.state.belowAmount = belowAmount;
        return this;
      }

      getSweepEachSubaddress() {
        return this.state.sweepEachSubaddress;
      }

      setSweepEachSubaddress(sweepEachSubaddress) {
        this.state.sweepEachSubaddress = sweepEachSubaddress;
        return this;
      }

      /**
       * Get the key image hex of the output to sweep.
       *
       * return {string} is the key image hex of the output to sweep
       */
      getKeyImage() {
        return this.state.keyImage;
      }

      /**
       * Set the key image hex of the output to sweep.
       *
       * @param {string} keyImage is the key image hex of the output to sweep
       */
      setKeyImage(keyImage) {
        this.state.keyImage = keyImage;
        return this;
      }
    }

    MoneroTxConfig.SUPPORTED_FIELDS = ["address", "amount", "accountIndex", "subaddressIndex", "subaddressIndices", "relay", "priority", "destinations", "subtractFeeFrom", "paymentId", "unlockTime", "note", "recipientName", "canSplit", "belowAmount", "sweepEachSubaddress", "keyImage"];

    module.exports = MoneroTxConfig