Source: multicall.js

/*
 * (c) Miva Inc <https://www.miva.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * Id: multicall.js 71465 2018-11-09 18:59:55Z gidriss 
 */

const { Request, Response } = require('./abstract');
const util = require('./util');

/** @module MultiCall */

/** 
 * Handles creation of an Operation with Iterations.
 * @see MultiCallRequest
 */
class MultiCallOperation {
  /**
   * MultiCallOperation Constructor.
   * @param {Request[]|Request|?} request
   * @param {Object} sharedData
   * @returns {void}
   */
  constructor(request = null, sharedData = {}) {
    var i;
    var l;

    this.requests   = [];
    this.sharedData = {};

    if (util.isInstanceOf(request, Request)) {
      this.addRequest(request);
    } else if (util.isArray(request)) {
      for (i = 0, l = request.length; i < l; i++) {
        this.addRequest(request[i]);
      }
    }

    if (util.isObject(sharedData)) {
      this.sharedData = sharedData;
    }
  }

  /**
   * Add a request iteration.
   * @param {Request} request
   * @returns {MultiCallOperation}
   */
  addRequest(request) {
    if (!util.isInstanceOf(request, Request)) {
      throw new Error('Expecting an instance of Request');
    } else if (util.isInstanceOf(request, MultiCallRequest)) {
      throw new Error('Can\'t nest a MultiCallRequest in a MultiCallOperation');
    }

    this.requests.push(request);

    return this;
  }

  /**
   * Get the request iterations.
   * @returns {Array}
   */
  getRequests() {
    return this.requests;
  }

  /**
   * Set and override the request iterations.
   * @param {Array} requests
   * @returns {MultiCallOperation}
   * @throws {Error}
   */
  setRequests(requests) {
    var i;
    var l;

    if (!util.isArray(requests)) {
      throw new Error(util.format('Expecting an array of Request but got %s', 
        typeof requests));
    }

    this.requests = [];

    for (i = 0, l = requests.length; i < l; i++) {
      this.addRequest(requests[i]);
    }

    return this;
  }

  /**
   * Add an array of requests iterations.
   * @param {Array} requests
   * @returns {MultiCallOperation}
   * @throws {Error}
   */
  addRequests(requests) {
    var i;
    var l;

    if (!util.isArray(requests)) {
      throw new Error(util.format('Expecting an array of Request but got %s', 
        typeof requests));
    }

    for (i = 0, l = requests.length; i < l; i++) {
      this.addRequest(requests[i]);
    }

    return this;
  }

  /**
   * Add a shared data value for key.
   * @param {string} key
   * @param {string|object} value
   * @returns {MultiCallOperation}
   */
  addSharedData(key, value) {
    this.sharedData[key] = value;

    return this;
  }

  /**
   * Set the shared data object.
   * @param {Object} values
   * @returns {MultiCallOperation}
   */
  setSharedData(values) {
    if (util.isObject(values)) {
      this.sharedData = values;
    }

    return this;
  }

  /**
   * Get the shared data between the iterations.
   * @returns {Object}
   */
  getSharedData() {
    return this.sharedData;
  }

  /**
   * Reduce the operation to an object.
   * @returns {Object}
   */
  toObject() {
    var i;
    var l;

    if (!this.requests.length) {
        return {};
    }

    var data = Object.assign(this.sharedData, {
        'Function'   : this.requests[0].getFunction(),
        'Iterations' : []
    });

    for (i = 0, l = this.requests.length; i < l; i++) {
        data['Iterations'].push(this.requests[i].toObject());
    }

    return data;
  }
}

/** 
 * Handles sending multiple Request objects as one request.
 * @see https://docs.miva.com/json-api/multicall
 */
class MultiCallRequest extends Request {
  /**
   * MultiCallRequest Constructor.
   * @param {?Client} client
   * @param {Array} requests
   * @returns {void}
   */
  constructor(client = null, requests = []) {
    super(client);
    this.requests = [];
    this.useIterations = false;
  }

  /**
   * Add a request to be sent.
   * @param {Request} request
   * @returns {MultiCallRequest}
   * @throws {Error}
   */
  addRequest(request) {
    var multirequests;
    var i;
    var l;

    if (!util.isInstanceOf(request, Request)) {
      throw new Error(util.format('Expecting an instance of Request but got %s', 
        typeof request));
    }
    
    if (util.isInstanceOf(request, MultiCallRequest)) {
      multirequests = request.getRequests();

      for (i = 0, l = multirequests.length; i < l; i++) {
        this.addRequest(multirequests[i]);
      }
    } else {
      this.requests.push(request);
    }

    return this;
  }

  /**
   * Get the requests to be sent.
   * @returns {Array}
   */
  getRequests() {
    return this.requests;
  }

  /**
   * Set and override the requests to be sent.
   * @param {Array} requests
   * @returns {MultiCallRequest}
   * @throws {Error}
   */
  setRequests(requests) {
    var i;
    var l;

    if (!util.isArray(requests)) {
      throw new Error(util.format('Expecting an array of Request but got %s', 
        typeof requests));
    }

    this.requests = [];

    for (i = 0, l = requests.length; i < l; i++) {
      this.addRequest(requests[i]);
    }

    return this;
  }

  /**
   * Add requests to be sent.
   * @param {Array} requests
   * @returns {MultiCallRequest}
   * @throws {Error}
   */
  addRequests(requests) {
    var i;
    var l;

    if (!util.isArray(requests)) {
      throw new Error(util.format('Expecting an array of Request but got %s', 
        typeof requests));
    }

    for (i = 0, l = requests.length; i < l; i++) {
      this.addRequest(requests[i]);
    }

    return this;
  }

  /**
   * Create an operation instance and add it to the request.
   * @param {Request|Request[]|*}
   * @returns {MultiCallOperation}
   */
  operation(request = null, sharedData = {}) {
    var operation = new MultiCallOperation(request, sharedData);

    this.addOperation(operation);

    return operation;
  }

  /**
   * Add a operation to be sent.
   * @param {MultiCallOperation} request
   * @returns {MultiCallRequest}
   */
  addOperation(operation) {
    if (!util.isInstanceOf(operation, MultiCallOperation)) {
      throw new Error('Expecting an instance of MultiCallOperation');
    }

    this.requests.push(operation);

    return this;
  }

  /**
   * Add an array of operations.
   * @param {MultiCallOperation[]} request
   * @returns {MultiCallRequest}
   * @throws {Error}
   */
  addOperations(operations) {
    var i;
    var l;

    if (!util.isArray(operations)) {
      throw new Error(util.format('Expecting an array of MultiCallOperation but got %s', 
        typeof operations));
    }

    for (i = 0, l = operations.length; i < l; i++) {
      this.addOperation(operations[i]);
    }    

    return this;
  }

  /**
   * Reduce the request to an Object.
   * @returns {Object}
   * @override
   */
  toObject() {
    var i;
    var l;
    var groups;

    var data = {
      Operations : []
    };

    for (i = 0, l = this.requests.length; i < l; i++) {
      if (util.isInstanceOf(this.requests[i], MultiCallOperation)) {
        data['Operations'].push(this.requests[i].toObject());
      } else {
        data['Operations'].push(Object.assign(this.requests[i].toObject(), {
          Function : this.requests[i].getFunction()
        }));
      }
    }

    return data;
  }

  /**
   * @param {Object} data
   * @override
   */
  createResponse(data) { {}
    return new MultiCallResponse(this, data);
  }
}

/** 
 * Handles multicall response.
 * @see https://docs.miva.com/json-api/multicall
 */
class MultiCallResponse extends Response {
  /**
   * MultiCallResponse constructor.
   * @param {Request} request
   * @param {Object} data
   * @returns {void}
   */
  constructor(request, data) {
    super(request, {});
    var gi;
    var gl;
    var ri;
    var rl;
    var iter_requests;

    var requests   = request.getRequests();

    this.data      = data;
    this.responses = [];

    if (!this.isSuccess()) {
      return;
    }

    for (gi = 0, gl = requests.length; gi < gl; gi++) {
      if (util.isNullOrUndefined(this.data[gi])) {
        throw new Error(util.format('Unexpected Response. Expected response for request %d.', gi));
      }

      if (util.isInstanceOf(requests[gi], MultiCallOperation)) {
        iter_requests = requests[gi].getRequests();

        for (ri = 0, rl = iter_requests.length; ri < rl; ri++) {
          if (util.isNullOrUndefined(this.data[gi][ri])) {
            throw new Error(util.format('Unexpected Response. Expected response for request %d iteration %d',
              gi, ri));
          }

          this.addResponse(iter_requests[ri].createResponse(this.data[gi][ri]));
        }

      } else {
        this.addResponse(requests[gi].createResponse(this.data[gi]));
      }
    }      
  }

  /**
   * @returns {boolean}
   * @override
   */
  isSuccess() {
    return util.isArray(this.data);
  }

  /**
   * Get the responses.
   * @returns {Response[]}
   */
  getResponses() {
    return this.responses;
  }

  /**
   * Add a response.
   * @param {Response} response
   * @returns {MultiCallResponse}
   * @throws Error
   */
  addResponse(response) {
    if (!util.isInstanceOf(response, Response)) {
      throw new Error("Expecting an instance of Response");
    }

    this.responses.push(response);

    return this;
  }

  /**
   * Set and overwrite the responses.
   * @param {Response[]} responses
   * @returns {MultiCallResponse}
   */
  setResponses(responses) {
    var i;
    var l;

    for (i = 0, l = responses.length; i < l; i++) {
      this.addResponse(responses[i]);
    }

    return this;
  }
}

module.exports = {
  MultiCallRequest,
  MultiCallResponse,
  MultiCallOperation
};