'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');

var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);

var _typeof2 = require('babel-runtime/helpers/typeof');

var _typeof3 = _interopRequireDefault(_typeof2);

var _keys = require('babel-runtime/core-js/object/keys');

var _keys2 = _interopRequireDefault(_keys);

var _stringify = require('babel-runtime/core-js/json/stringify');

var _stringify2 = _interopRequireDefault(_stringify);

var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

var _createClass2 = require('babel-runtime/helpers/createClass');

var _createClass3 = _interopRequireDefault(_createClass2);

var _integer = require('../../integer');

var _graphTypes = require('../../graph-types');

var _error = require('../../error');

var _spatialTypes = require('../../spatial-types');

var _temporalTypes = require('../../temporal-types');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var CREDENTIALS_EXPIRED_CODE = 'Neo.ClientError.Security.CredentialsExpired'; /**
                                                                               * Copyright (c) 2002-2018 "Neo4j,"
                                                                               * Neo4j Sweden AB [http://neo4j.com]
                                                                               *
                                                                               * This file is part of Neo4j.
                                                                               *
                                                                               * 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.
                                                                               */

var HttpResponseConverter = function () {
  function HttpResponseConverter() {
    (0, _classCallCheck3.default)(this, HttpResponseConverter);
  }

  (0, _createClass3.default)(HttpResponseConverter, [{
    key: 'encodeStatementParameters',
    value: function encodeStatementParameters(parameters) {
      return encodeQueryParameters(parameters);
    }

    /**
     * Attempts to extract error from transactional cypher endpoint response and convert it to {@link Neo4jError}.
     * @param {object} response the response.
     * @return {Neo4jError|null} new driver friendly error, if exists.
     */

  }, {
    key: 'extractError',
    value: function extractError(response) {
      var errors = response.errors;
      if (errors) {
        var error = errors[0];
        if (error) {
          // endpoint returns 'Neo.ClientError.Security.Forbidden' code and 'password_change' that points to another endpoint
          // this is different from code returned via Bolt and less descriptive
          // make code same as in Bolt, if password change is required
          var code = response.password_change ? CREDENTIALS_EXPIRED_CODE : error.code;
          var message = error.message;
          return new _error.Neo4jError(message, code);
        }
      }
      return null;
    }

    /**
     * Extracts transaction id from the db/data/transaction endpoint response.
     * @param {object} response the response.
     * @return {number} the transaction id.
     */

  }, {
    key: 'extractTransactionId',
    value: function extractTransactionId(response) {
      var commitUrl = response.commit;
      if (commitUrl) {
        // extract id 42 from commit url like 'http://localhost:7474/db/data/transaction/42/commit'
        var url = commitUrl.replace('/commit', '');
        var transactionIdString = url.substring(url.lastIndexOf('/') + 1);
        var transactionId = parseInt(transactionIdString, 10);
        if (transactionId || transactionId === 0) {
          return transactionId;
        }
      }
      throw new _error.Neo4jError('Unable to extract transaction id from the response JSON: ' + (0, _stringify2.default)(response));
    }

    /**
     * Extracts record metadata (array of column names) from transactional cypher endpoint response.
     * @param {object} response the response.
     * @return {object} new metadata object.
     */

  }, {
    key: 'extractRecordMetadata',
    value: function extractRecordMetadata(response) {
      var result = extractResult(response);
      var fields = result ? result.columns : [];
      return { fields: fields };
    }

    /**
     * Extracts raw records (each raw record is just an array of value) from transactional cypher endpoint response.
     * @param {object} response the response.
     * @return {object[][]} raw records from the response.
     */

  }, {
    key: 'extractRawRecords',
    value: function extractRawRecords(response) {
      var result = extractResult(response);
      if (result) {
        var data = result.data;
        if (data) {
          return data.map(function (element) {
            return extractRawRecord(element);
          });
        }
      }
      return [];
    }

    /**
     * Extracts metadata for a completed statement.
     * @param {object} response the response.
     * @return {object} metadata as object.
     */

  }, {
    key: 'extractStatementMetadata',
    value: function extractStatementMetadata(response) {
      var result = extractResult(response);
      if (result) {
        var stats = result.stats;
        if (stats) {
          var convertedStats = (0, _keys2.default)(stats).reduce(function (newStats, key) {
            if (key === 'contains_updates') {
              // skip because such key does not exist in bolt
              return newStats;
            }

            // fix key name for future parsing by StatementStatistics class
            var newKey = (key === 'relationship_deleted' ? 'relationships_deleted' : key).replace('_', '-');
            newStats[newKey] = stats[key];
            return newStats;
          }, {});

          return { stats: convertedStats };
        }
      }
      return {};
    }
  }]);
  return HttpResponseConverter;
}();

exports.default = HttpResponseConverter;


function encodeQueryParameters(parameters) {
  if (parameters && (typeof parameters === 'undefined' ? 'undefined' : (0, _typeof3.default)(parameters)) === 'object') {
    return (0, _keys2.default)(parameters).reduce(function (result, key) {
      result[key] = encodeQueryParameter(parameters[key]);
      return result;
    }, {});
  }
  return parameters;
}

function encodeQueryParameter(value) {
  if (value instanceof _graphTypes.Node) {
    throw new _error.Neo4jError('It is not allowed to pass nodes in query parameters', _error.PROTOCOL_ERROR);
  } else if (value instanceof _graphTypes.Relationship) {
    throw new _error.Neo4jError('It is not allowed to pass relationships in query parameters', _error.PROTOCOL_ERROR);
  } else if (value instanceof _graphTypes.Path) {
    throw new _error.Neo4jError('It is not allowed to pass paths in query parameters', _error.PROTOCOL_ERROR);
  } else if ((0, _spatialTypes.isPoint)(value)) {
    throw newUnsupportedParameterError('points');
  } else if ((0, _temporalTypes.isDate)(value)) {
    throw newUnsupportedParameterError('dates');
  } else if ((0, _temporalTypes.isDateTime)(value)) {
    throw newUnsupportedParameterError('date-time');
  } else if ((0, _temporalTypes.isDuration)(value)) {
    throw newUnsupportedParameterError('durations');
  } else if ((0, _temporalTypes.isLocalDateTime)(value)) {
    throw newUnsupportedParameterError('local date-time');
  } else if ((0, _temporalTypes.isLocalTime)(value)) {
    throw newUnsupportedParameterError('local time');
  } else if ((0, _temporalTypes.isTime)(value)) {
    throw newUnsupportedParameterError('time');
  } else if ((0, _integer.isInt)(value)) {
    return value.toNumber();
  } else if (Array.isArray(value)) {
    return value.map(function (element) {
      return encodeQueryParameter(element);
    });
  } else if ((typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) === 'object') {
    return encodeQueryParameters(value);
  } else {
    return value;
  }
}

function newUnsupportedParameterError(name) {
  return new _error.Neo4jError('It is not allowed to pass ' + name + ' in query parameters when using HTTP endpoint. ' + ('Please consider using Cypher functions to create ' + name + ' so that query parameters are plain objects.'), _error.PROTOCOL_ERROR);
}

function extractResult(response) {
  var results = response.results;
  if (results) {
    var result = results[0];
    if (result) {
      return result;
    }
  }
  return null;
}

function extractRawRecord(data) {
  var row = data.row;

  var nodesById = indexNodesById(data);
  var relationshipsById = indexRelationshipsById(data);

  if (row) {
    return row.map(function (ignore, index) {
      return extractRawRecordElement(index, data, nodesById, relationshipsById);
    });
  }
  return [];
}

function indexNodesById(data) {
  var graph = data.graph;
  if (graph) {
    var nodes = graph.nodes;
    if (nodes) {
      return nodes.reduce(function (result, node) {

        var identity = convertNumber(node.id);
        var labels = node.labels;
        var properties = convertPrimitiveValue(node.properties);
        result[node.id] = new _graphTypes.Node(identity, labels, properties);

        return result;
      }, {});
    }
  }
  return {};
}

function indexRelationshipsById(data) {
  var graph = data.graph;
  if (graph) {
    var relationships = graph.relationships;
    if (relationships) {
      return relationships.reduce(function (result, relationship) {

        var identity = convertNumber(relationship.id);
        var startNode = convertNumber(relationship.startNode);
        var endNode = convertNumber(relationship.endNode);
        var type = relationship.type;
        var properties = convertPrimitiveValue(relationship.properties);
        result[relationship.id] = new _graphTypes.Relationship(identity, startNode, endNode, type, properties);

        return result;
      }, {});
    }
  }
  return {};
}

function extractRawRecordElement(index, data, nodesById, relationshipsById) {
  var element = data.row ? data.row[index] : null;
  var elementMetadata = data.meta ? data.meta[index] : null;

  if (elementMetadata) {
    // element is either a graph, spatial or temporal type
    return convertComplexValue(element, elementMetadata, nodesById, relationshipsById);
  } else {
    // element is a primitive, like number, string, array or object
    return convertPrimitiveValue(element);
  }
}

function convertComplexValue(element, elementMetadata, nodesById, relationshipsById) {
  if (isNodeMetadata(elementMetadata)) {
    return nodesById[elementMetadata.id];
  } else if (isRelationshipMetadata(elementMetadata)) {
    return relationshipsById[elementMetadata.id];
  } else if (isPathMetadata(elementMetadata)) {
    return convertPath(elementMetadata, nodesById, relationshipsById);
  } else if (isPointMetadata(elementMetadata)) {
    return convertPoint(element);
  } else {
    return element;
  }
}

function convertPath(metadata, nodesById, relationshipsById) {
  var startNode = null;
  var relationship = null;
  var pathSegments = [];

  for (var i = 0; i < metadata.length; i++) {
    var element = metadata[i];
    var elementId = element.id;
    var elementType = element.type;

    var nodeExpected = startNode === null && relationship === null || startNode !== null && relationship !== null;
    if (nodeExpected && elementType !== 'node') {
      throw new _error.Neo4jError('Unable to parse path, node expected but got: ' + (0, _stringify2.default)(element) + ' in ' + (0, _stringify2.default)(metadata));
    }
    if (!nodeExpected && elementType === 'node') {
      throw new _error.Neo4jError('Unable to parse path, relationship expected but got: ' + (0, _stringify2.default)(element) + ' in ' + (0, _stringify2.default)(metadata));
    }

    if (nodeExpected) {
      var node = nodesById[elementId];
      if (startNode === null) {
        startNode = node;
      } else if (startNode !== null && relationship !== null) {
        var pathSegment = new _graphTypes.PathSegment(startNode, relationship, node);
        pathSegments.push(pathSegment);
        startNode = node;
        relationship = null;
      } else {
        throw new _error.Neo4jError('Unable to parse path, illegal node configuration: ' + (0, _stringify2.default)(metadata));
      }
    } else {
      if (relationship === null) {
        relationship = relationshipsById[elementId];
      } else {
        throw new _error.Neo4jError('Unable to parse path, illegal relationship configuration: ' + (0, _stringify2.default)(metadata));
      }
    }
  }

  var lastPathSegment = pathSegments[pathSegments.length - 1];
  if (lastPathSegment && lastPathSegment.end !== startNode || relationship !== null) {
    throw new _error.Neo4jError('Unable to parse path: ' + (0, _stringify2.default)(metadata));
  }

  return createPath(pathSegments);
}

function createPath(pathSegments) {
  var pathStartNode = pathSegments[0].start;
  var pathEndNode = pathSegments[pathSegments.length - 1].end;
  return new _graphTypes.Path(pathStartNode, pathEndNode, pathSegments);
}

function convertPoint(element) {
  var type = element.type;
  if (type !== 'Point') {
    throw new _error.Neo4jError('Unexpected Point type received: ' + (0, _stringify2.default)(element));
  }

  var coordinates = element.coordinates;
  if (!Array.isArray(coordinates) && (coordinates.length !== 2 || coordinates.length !== 3)) {
    throw new _error.Neo4jError('Unexpected Point coordinates received: ' + (0, _stringify2.default)(element));
  }

  var srid = convertCrsToId(element);

  return new (Function.prototype.bind.apply(_spatialTypes.Point, [null].concat([srid], (0, _toConsumableArray3.default)(coordinates))))();
}

function convertCrsToId(element) {
  var crs = element.crs;
  if (!crs || !crs.name) {
    throw new _error.Neo4jError('Unexpected Point crs received: ' + (0, _stringify2.default)(element));
  }
  var name = crs.name.toLowerCase();

  if (name === 'wgs-84') {
    return 4326;
  } else if (name === 'wgs-84-3d') {
    return 4979;
  } else if (name === 'cartesian') {
    return 7203;
  } else if (name === 'cartesian-3d') {
    return 9157;
  } else {
    throw new _error.Neo4jError('Unexpected Point crs received: ' + (0, _stringify2.default)(element));
  }
}

function convertPrimitiveValue(element) {
  if (element == null || element === undefined) {
    return null;
  } else if (typeof element === 'number') {
    return convertNumber(element);
  } else if (Array.isArray(element)) {
    return element.map(function (element) {
      return convertPrimitiveValue(element);
    });
  } else if ((typeof element === 'undefined' ? 'undefined' : (0, _typeof3.default)(element)) === 'object') {
    return (0, _keys2.default)(element).reduce(function (result, key) {
      result[key] = convertPrimitiveValue(element[key]);
      return result;
    }, {});
  } else {
    return element;
  }
}

function convertNumber(value) {
  return typeof value === 'number' ? value : Number(value);
}

function isNodeMetadata(metadata) {
  return isMetadataForType('node', metadata);
}

function isRelationshipMetadata(metadata) {
  return isMetadataForType('relationship', metadata);
}

function isPointMetadata(metadata) {
  return isMetadataForType('point', metadata);
}

function isMetadataForType(name, metadata) {
  return !Array.isArray(metadata) && (typeof metadata === 'undefined' ? 'undefined' : (0, _typeof3.default)(metadata)) === 'object' && metadata.type === name;
}

function isPathMetadata(metadata) {
  return Array.isArray(metadata);
}