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

    const MoneroIncomingTransfer = require("./MoneroIncomingTransfer");
    const MoneroOutgoingTransfer = require("./MoneroOutgoingTransfer");
    const MoneroTransfer = require("./MoneroTransfer");
    const MoneroError = require("../../common/MoneroError")

    /**
     * Configuration to query wallet transfers.
     *
     * @extends {MoneroTransfer}
     */
    class MoneroTransferQuery extends MoneroTransfer {

      /**
       * <p>Construct the transfer query.</p>
       *
       * <p>Example:</p>
       *
       * <code>
       * &sol;&sol; get incoming transfers to account 0, subaddress 1<br>
       * let transfers = await wallet.getTransfers({<br>
       * &nbsp;&nbsp; accountIndex: 0,<br>
       * &nbsp;&nbsp; subaddressIndex: 0<br>
       * });
       * </code>
       *
       * <p>All configuration is optional.  All transfers are returned except those that don't meet criteria defined in this query.</p>
       *
       * @param {object} config - transfer query configuration (optional)
       * @param {BigInteger} config.amount - get transfers with this amount
       * @param {int} config.accountIndex - get transfers to/from this account index
       * @param {int} config.subaddressIndex - get transfers to/from this subaddress index
       * @param {int[]} config.subaddressIndices - get transfers to/from these subaddress indices
       * @param {string} config.address - get transfers to/from this wallet address
       * @param {string[]} config.addresses - get transfers to/from these wallet addresses
       * @param {boolean} config.isIncoming - get transfers which are incoming if true
       * @param {boolean} config.isOutgoing - get transfers which are outgoing if true
       * @param {boolean} config.hasDestinations - get transfers with known destinations if true (destinations are only stored locally with the wallet)
       * @param {object|MoneroTxQuery} config.txQuery - get transfers whose tx match this tx query
       */
      constructor(config) {
        super(config);

        // deserialize if necessary
        const MoneroTxQuery = require("./MoneroTxQuery");
        if (this.state.txQuery && !(this.state.txQuery instanceof MoneroTxQuery)) this.state.txQuery = new MoneroTxQuery(this.state.txQuery);
        if (this.state.txQuery) this.state.txQuery.setTransferQuery(this);

        // alias isOutgoing to isIncoming
        if (this.state.isOutgoing !== undefined) this.state.isIncoming = !this.state.isOutgoing;

        // validate state
        this._validate();
      }

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

      toJson() {
        let json = Object.assign({}, this.state, super.toJson());
        delete json.txQuery;
        return json;
      }

      getTxQuery() {
        return this.state.txQuery;
      }

      setTxQuery(txQuery) {
        this.state.txQuery = txQuery;
        if (txQuery) txQuery.state.transferQuery = this;
        return this;
      }

      isIncoming() {
        return this.state.isIncoming;
      }

      setIsIncoming(isIncoming) {
        this.state.isIncoming = isIncoming;
        return this;
      }

      isOutgoing() {
        return this.state.isIncoming === undefined ? undefined : !this.state.isIncoming;
      }

      setIsOutgoing(isOutgoing) {
        this.state.isIncoming = isOutgoing === undefined ? undefined : !isOutgoing;
        return this;
      }

      getAddress() {
        return this.state.address;
      }

      setAddress(address) {
        this.state.address = address;
        return this;
      }

      getAddresses() {
        return this.state.addresses;
      }

      setAddresses(addresses) {
        this.state.addresses = addresses;
        return this;
      }

      getSubaddressIndex() {
        return this.state.subaddressIndex;
      }

      setSubaddressIndex(subaddressIndex) {
        this.state.subaddressIndex = subaddressIndex;
        this._validate();
        return this;
      }

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

      setSubaddressIndices(subaddressIndices) {
        this.state.subaddressIndices = subaddressIndices;
        this._validate();
        return this;
      }

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

      setDestinations(destinations) {
        this.state.destinations = destinations;
        return this;
      }

      hasDestinations() {
        return this.state.hasDestinations;
      }

      setHasDestinations(hasDestinations) {
        this.state.hasDestinations = hasDestinations;
        return this;
      }

      /**
       * Convenience method to query outputs by the locked state of their tx.
       *
       * @param isLocked specifies if the output's tx must be locked or unlocked (optional)
       * @return {MoneroOutputQuery} this query for chaining
       */
      setIsLocked(isLocked) {
        if (this.state.txQuery === undefined) this.state.txQuery = new MoneroTxQuery();
        this.state.txQuery.setIsLocked(isLocked);
        return this;
      }

      meetsCriteria(transfer, queryParent) {
        if (!(transfer instanceof MoneroTransfer)) throw new Error("Transfer not given to MoneroTransferQuery.meetsCriteria(transfer)");
        if (queryParent === undefined) queryParent = true;

        // filter on common fields
        if (this.isIncoming() !== undefined && this.isIncoming() !== transfer.isIncoming()) return false;
        if (this.isOutgoing() !== undefined && this.isOutgoing() !== transfer.isOutgoing()) return false;
        if (this.getAmount() !== undefined && this.getAmount().compare(transfer.getAmount()) !== 0) return false;
        if (this.getAccountIndex() !== undefined && this.getAccountIndex() !== transfer.getAccountIndex()) return false;

        // filter on incoming fields
        if (transfer instanceof MoneroIncomingTransfer) {
          if (this.hasDestinations()) return false;
          if (this.getAddress() !== undefined && this.getAddress() !== transfer.getAddress()) return false;
          if (this.getAddresses() !== undefined && !this.getAddresses().includes(transfer.getAddress())) return false;
          if (this.getSubaddressIndex() !== undefined && this.getSubaddressIndex() !== transfer.getSubaddressIndex()) return false;
          if (this.getSubaddressIndices() !== undefined && !this.getSubaddressIndices().includes(transfer.getSubaddressIndex())) return false;
        }

        // filter on outgoing fields
        else if (transfer instanceof MoneroOutgoingTransfer) {

          // filter on addresses which must have overlap
          if (this.getAddress() !== undefined && (transfer.getAddresses() === undefined || !transfer.getAddresses().includes(this.getAddress()))) return false;   // TODO: will filter all transfers that don't contain addresses (outgoing txs might not have this field initialized)
          if (this.getAddresses() !== undefined) {
            if (!transfer.getAddresses()) return false;
            if (!this.getAddresses().some(address => transfer.getAddresses().includes(address))) return false;
          }

          // filter on subaddress indices
          if (this.getSubaddressIndex() !== undefined && (transfer.getSubaddressIndices() === undefined || !transfer.getSubaddressIndices().includes(this.getSubaddressIndex()))) return false;
          if (this.getSubaddressIndices() !== undefined) {
            if (!transfer.getSubaddressIndices()) return false;
            if (!this.getSubaddressIndices().some(subaddressIdx => transfer.getSubaddressIndices().includes(subaddressIdx))) return false;
          }

          // filter on having destinations
          if (this.hasDestinations() !== undefined) {
            if (this.hasDestinations() && transfer.getDestinations() === undefined) return false;
            if (!this.hasDestinations() && transfer.getDestinations() !== undefined) return false;
          }

          // filter on destinations TODO: start with test for this
    //    if (this.getDestionations() !== undefined && this.getDestionations() !== transfer.getDestionations()) return false;
        }

        // otherwise invalid type
        else throw new Error("Transfer must be MoneroIncomingTransfer or MoneroOutgoingTransfer");

        // filter with tx filter
        if (queryParent && this.getTxQuery() !== undefined && !this.getTxQuery().meetsCriteria(transfer.getTx())) return false;
        return true;
      }

      _validate() {
        if (this.getSubaddressIndex() !== undefined && this.getSubaddressIndex() < 0) throw new MoneroError("Subaddress index must be >= 0");
        if (this.getSubaddressIndices() !== undefined) for (let subaddressIdx of this.getSubaddressIndices()) if (subaddressIdx < 0) throw new MoneroError("Subaddress indices must be >= 0");
      }
    }

    module.exports = MoneroTransferQuery;