/*
 * Copyright DataStax, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const utils = require('../../utils');

/**
 * Creates a new instance of <code>GraphResultSet</code>.
 * @class
 * @classdesc
 * Represents the result set of a [graph query execution]{@link Client#executeGraph} containing vertices, edges or
 * scalar values depending on the query.
 * <p>
 * It allows iteration of the items using <code>for..of</code> statements under ES2015 and exposes
 * <code>forEach()</code>, <code>first()</code> and <code>toArray()</code> to access the underlying items.
 * </p>
 * @example
 * for (let vertex of result} { ... }
 * @example
 * const arr = result.toArray();
 * @example
 * const vertex = result.first();
 * @param {ResultSet} result
 * @param {Function} [rowParser]
 * @alias module:datastax/graph~GraphResultSet
 * @constructor
 */
function GraphResultSet(result, rowParser) {
  /**
   * Information on the execution of a successful query:
   * @member {Object}
   * @property {Number} achievedConsistency The consistency level that has been actually achieved by the query.
   * @property {String} queriedHost The Cassandra host that coordinated this query.
   * @property {Object} triedHosts Gets the associative array of host that were queried before getting a valid response,
   * being the last host the one that replied correctly.
   * @property {Uuid} traceId Identifier of the trace session.
   * @property {Array.<string>} warnings Warning messages generated by the server when executing the query.
   */
  this.info = result.info;
  const rows = result.rows;
  rowParser = rowParser || parsePlainJsonRow;

  /**
   * This property has been deprecated because it may return a lower value than the actual length of the results.
   * Use <code>toArray()</code> instead.
   * <p>Gets the length of the result.</p>
   * @deprecated Use <code>toArray()</code> instead. This property will be removed in the following major version.
   * @member {Number}
   */
  this.length = result.rowLength;

  /**
   * A string token representing the current page state of query. It can be used in the following executions to
   * continue paging and retrieve the remained of the result for the query.
   * @member {String}
   */
  this.pageState = result.pageState;

  /**
   * Returns the first element of the result or null if the result is empty.
   * @returns {Object}
   */
  this.first = function first() {
    const iterator = this.values();
    const item = iterator.next();
    if (item.done) {
      return null;
    }

    return item.value;
  };

  /**
   * Executes a provided function once per result element.
   * @param {Function} callback Function to execute for each element, taking two arguments: currentValue and index.
   * @param {Object} [thisArg] Value to use as <code>this</code> when executing callback.
   */
  this.forEach = function forEach(callback, thisArg) {
    if (!rows.length) {
      return;
    }
    const iterator = this.values();
    let item = iterator.next();
    let index = 0;
    while (!item.done) {
      callback.call(thisArg || this, item.value, index++);
      item = iterator.next();
    }
  };

  /**
   * Results an Array of graph result elements (vertex, edge, scalar).
   * @returns {Array}
   */
  this.toArray = function toArray() {
    if (!rows.length) {
      return utils.emptyArray;
    }
    return utils.iteratorToArray(this.values());
  };

  /**
   * Returns a new Iterator object that contains the values for each index in the result.
   * @returns {Iterator}
   */
  this.values = function* values() {
    for (const traverser of this.getTraversers()) {
      const bulk = traverser.bulk || 1;

      for (let j = 0; j < bulk; j++) {
        yield traverser.object;
      }
    }
  };

  /**
   * Gets the traversers represented contained in the result set.
   * @returns {Iterator}
   */
  this.getTraversers = function* () {
    for (const row of rows) {
      yield rowParser(row);
    }
  };
}

if (typeof Symbol !== 'undefined' && typeof Symbol.iterator === 'symbol') {
  // Make iterable
  GraphResultSet.prototype[Symbol.iterator] = function getIterator() {
    return this.values();
  };
}

/**
 * @param {Row} row
 * @private
 */
function parsePlainJsonRow(row) {
  const parsed = JSON.parse(row['gremlin']);
  return { object: parsed.result, bulk: parsed.bulk || 1 };
}

module.exports = GraphResultSet;