'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.Unpacker = exports.Packer = undefined;

var _freeze = require('babel-runtime/core-js/object/freeze');

var _freeze2 = _interopRequireDefault(_freeze);

var _create = require('babel-runtime/core-js/object/create');

var _create2 = _interopRequireDefault(_create);

var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');

var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);

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

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

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

var _createClass3 = _interopRequireDefault(_createClass2);

var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');

var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);

var _get2 = require('babel-runtime/helpers/get');

var _get3 = _interopRequireDefault(_get2);

var _inherits2 = require('babel-runtime/helpers/inherits');

var _inherits3 = _interopRequireDefault(_inherits2);

var _packstreamV = require('./packstream-v1');

var v1 = _interopRequireWildcard(_packstreamV);

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

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

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

var _temporalUtil = require('../internal/temporal-util');

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

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

var POINT_2D = 0x58; /**
                      * 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 POINT_2D_STRUCT_SIZE = 3;

var POINT_3D = 0x59;
var POINT_3D_STRUCT_SIZE = 4;

var DURATION = 0x45;
var DURATION_STRUCT_SIZE = 4;

var LOCAL_TIME = 0x74;
var LOCAL_TIME_STRUCT_SIZE = 1;

var TIME = 0x54;
var TIME_STRUCT_SIZE = 2;

var DATE = 0x44;
var DATE_STRUCT_SIZE = 1;

var LOCAL_DATE_TIME = 0x64;
var LOCAL_DATE_TIME_STRUCT_SIZE = 2;

var DATE_TIME_WITH_ZONE_OFFSET = 0x46;
var DATE_TIME_WITH_ZONE_OFFSET_STRUCT_SIZE = 3;

var DATE_TIME_WITH_ZONE_ID = 0x66;
var DATE_TIME_WITH_ZONE_ID_STRUCT_SIZE = 3;

var Packer = exports.Packer = function (_v1$Packer) {
  (0, _inherits3.default)(Packer, _v1$Packer);

  /**
   * @constructor
   * @param {Chunker} chunker the chunker backed by a network channel.
   */
  function Packer(chunker) {
    (0, _classCallCheck3.default)(this, Packer);
    return (0, _possibleConstructorReturn3.default)(this, (Packer.__proto__ || (0, _getPrototypeOf2.default)(Packer)).call(this, chunker));
  }

  (0, _createClass3.default)(Packer, [{
    key: 'disableByteArrays',
    value: function disableByteArrays() {
      throw new Error('Bolt V2 should always support byte arrays');
    }
  }, {
    key: 'packable',
    value: function packable(obj, onError) {
      var _this2 = this;

      if ((0, _spatialTypes.isPoint)(obj)) {
        return function () {
          return packPoint(obj, _this2, onError);
        };
      } else if ((0, _temporalTypes.isDuration)(obj)) {
        return function () {
          return packDuration(obj, _this2, onError);
        };
      } else if ((0, _temporalTypes.isLocalTime)(obj)) {
        return function () {
          return packLocalTime(obj, _this2, onError);
        };
      } else if ((0, _temporalTypes.isTime)(obj)) {
        return function () {
          return packTime(obj, _this2, onError);
        };
      } else if ((0, _temporalTypes.isDate)(obj)) {
        return function () {
          return packDate(obj, _this2, onError);
        };
      } else if ((0, _temporalTypes.isLocalDateTime)(obj)) {
        return function () {
          return packLocalDateTime(obj, _this2, onError);
        };
      } else if ((0, _temporalTypes.isDateTime)(obj)) {
        return function () {
          return packDateTime(obj, _this2, onError);
        };
      } else {
        return (0, _get3.default)(Packer.prototype.__proto__ || (0, _getPrototypeOf2.default)(Packer.prototype), 'packable', this).call(this, obj, onError);
      }
    }
  }]);
  return Packer;
}(v1.Packer);

var Unpacker = exports.Unpacker = function (_v1$Unpacker) {
  (0, _inherits3.default)(Unpacker, _v1$Unpacker);

  /**
   * @constructor
   * @param {boolean} disableLosslessIntegers if this unpacker should convert all received integers to native JS numbers.
   */
  function Unpacker() {
    var disableLosslessIntegers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    (0, _classCallCheck3.default)(this, Unpacker);
    return (0, _possibleConstructorReturn3.default)(this, (Unpacker.__proto__ || (0, _getPrototypeOf2.default)(Unpacker)).call(this, disableLosslessIntegers));
  }

  (0, _createClass3.default)(Unpacker, [{
    key: '_unpackUnknownStruct',
    value: function _unpackUnknownStruct(signature, structSize, buffer) {
      if (signature == POINT_2D) {
        return unpackPoint2D(this, structSize, buffer);
      } else if (signature == POINT_3D) {
        return unpackPoint3D(this, structSize, buffer);
      } else if (signature == DURATION) {
        return unpackDuration(this, structSize, buffer);
      } else if (signature == LOCAL_TIME) {
        return unpackLocalTime(this, structSize, buffer, this._disableLosslessIntegers);
      } else if (signature == TIME) {
        return unpackTime(this, structSize, buffer, this._disableLosslessIntegers);
      } else if (signature == DATE) {
        return unpackDate(this, structSize, buffer, this._disableLosslessIntegers);
      } else if (signature == LOCAL_DATE_TIME) {
        return unpackLocalDateTime(this, structSize, buffer, this._disableLosslessIntegers);
      } else if (signature == DATE_TIME_WITH_ZONE_OFFSET) {
        return unpackDateTimeWithZoneOffset(this, structSize, buffer, this._disableLosslessIntegers);
      } else if (signature == DATE_TIME_WITH_ZONE_ID) {
        return unpackDateTimeWithZoneId(this, structSize, buffer, this._disableLosslessIntegers);
      } else {
        return (0, _get3.default)(Unpacker.prototype.__proto__ || (0, _getPrototypeOf2.default)(Unpacker.prototype), '_unpackUnknownStruct', this).call(this, signature, structSize, buffer, this._disableLosslessIntegers);
      }
    }
  }]);
  return Unpacker;
}(v1.Unpacker);

/**
 * Pack given 2D or 3D point.
 * @param {Point} point the point value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */


function packPoint(point, packer, onError) {
  var is2DPoint = point.z === null || point.z === undefined;
  if (is2DPoint) {
    packPoint2D(point, packer, onError);
  } else {
    packPoint3D(point, packer, onError);
  }
}

/**
 * Pack given 2D point.
 * @param {Point} point the point value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */
function packPoint2D(point, packer, onError) {
  var packableStructFields = [packer.packable((0, _integer.int)(point.srid), onError), packer.packable(point.x, onError), packer.packable(point.y, onError)];
  packer.packStruct(POINT_2D, packableStructFields, onError);
}

/**
 * Pack given 3D point.
 * @param {Point} point the point value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */
function packPoint3D(point, packer, onError) {
  var packableStructFields = [packer.packable((0, _integer.int)(point.srid), onError), packer.packable(point.x, onError), packer.packable(point.y, onError), packer.packable(point.z, onError)];
  packer.packStruct(POINT_3D, packableStructFields, onError);
}

/**
 * Unpack 2D point value using the given unpacker.
 * @param {Unpacker} unpacker the unpacker to use.
 * @param {number} structSize the retrieved struct size.
 * @param {BaseBuffer} buffer the buffer to unpack from.
 * @return {Point} the unpacked 2D point value.
 */
function unpackPoint2D(unpacker, structSize, buffer) {
  unpacker._verifyStructSize('Point2D', POINT_2D_STRUCT_SIZE, structSize);

  return new _spatialTypes.Point(unpacker.unpack(buffer), // srid
  unpacker.unpack(buffer), // x
  unpacker.unpack(buffer), // y
  undefined // z
  );
}

/**
 * Unpack 3D point value using the given unpacker.
 * @param {Unpacker} unpacker the unpacker to use.
 * @param {number} structSize the retrieved struct size.
 * @param {BaseBuffer} buffer the buffer to unpack from.
 * @return {Point} the unpacked 3D point value.
 */
function unpackPoint3D(unpacker, structSize, buffer) {
  unpacker._verifyStructSize('Point3D', POINT_3D_STRUCT_SIZE, structSize);

  return new _spatialTypes.Point(unpacker.unpack(buffer), // srid
  unpacker.unpack(buffer), // x
  unpacker.unpack(buffer), // y
  unpacker.unpack(buffer) // z
  );
}

/**
 * Pack given duration.
 * @param {Duration} value the duration value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */
function packDuration(value, packer, onError) {
  var months = (0, _integer.int)(value.months);
  var days = (0, _integer.int)(value.days);
  var seconds = (0, _integer.int)(value.seconds);
  var nanoseconds = (0, _integer.int)(value.nanoseconds);

  var packableStructFields = [packer.packable(months, onError), packer.packable(days, onError), packer.packable(seconds, onError), packer.packable(nanoseconds, onError)];
  packer.packStruct(DURATION, packableStructFields, onError);
}

/**
 * Unpack duration value using the given unpacker.
 * @param {Unpacker} unpacker the unpacker to use.
 * @param {number} structSize the retrieved struct size.
 * @param {BaseBuffer} buffer the buffer to unpack from.
 * @return {Duration} the unpacked duration value.
 */
function unpackDuration(unpacker, structSize, buffer) {
  unpacker._verifyStructSize('Duration', DURATION_STRUCT_SIZE, structSize);

  var months = unpacker.unpack(buffer);
  var days = unpacker.unpack(buffer);
  var seconds = unpacker.unpack(buffer);
  var nanoseconds = unpacker.unpack(buffer);

  return new _temporalTypes.Duration(months, days, seconds, nanoseconds);
}

/**
 * Pack given local time.
 * @param {LocalTime} value the local time value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */
function packLocalTime(value, packer, onError) {
  var nanoOfDay = (0, _temporalUtil.localTimeToNanoOfDay)(value.hour, value.minute, value.second, value.nanosecond);

  var packableStructFields = [packer.packable(nanoOfDay, onError)];
  packer.packStruct(LOCAL_TIME, packableStructFields, onError);
}

/**
 * Unpack local time value using the given unpacker.
 * @param {Unpacker} unpacker the unpacker to use.
 * @param {number} structSize the retrieved struct size.
 * @param {BaseBuffer} buffer the buffer to unpack from.
 * @param {boolean} disableLosslessIntegers if integer properties in the result local time should be native JS numbers.
 * @return {LocalTime} the unpacked local time value.
 */
function unpackLocalTime(unpacker, structSize, buffer, disableLosslessIntegers) {
  unpacker._verifyStructSize('LocalTime', LOCAL_TIME_STRUCT_SIZE, structSize);

  var nanoOfDay = unpacker.unpackInteger(buffer);
  var result = (0, _temporalUtil.nanoOfDayToLocalTime)(nanoOfDay);
  return convertIntegerPropsIfNeeded(result, disableLosslessIntegers);
}

/**
 * Pack given time.
 * @param {Time} value the time value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */
function packTime(value, packer, onError) {
  var nanoOfDay = (0, _temporalUtil.localTimeToNanoOfDay)(value.hour, value.minute, value.second, value.nanosecond);
  var offsetSeconds = (0, _integer.int)(value.timeZoneOffsetSeconds);

  var packableStructFields = [packer.packable(nanoOfDay, onError), packer.packable(offsetSeconds, onError)];
  packer.packStruct(TIME, packableStructFields, onError);
}

/**
 * Unpack time value using the given unpacker.
 * @param {Unpacker} unpacker the unpacker to use.
 * @param {number} structSize the retrieved struct size.
 * @param {BaseBuffer} buffer the buffer to unpack from.
 * @param {boolean} disableLosslessIntegers if integer properties in the result time should be native JS numbers.
 * @return {Time} the unpacked time value.
 */
function unpackTime(unpacker, structSize, buffer, disableLosslessIntegers) {
  unpacker._verifyStructSize('Time', TIME_STRUCT_SIZE, structSize);

  var nanoOfDay = unpacker.unpackInteger(buffer);
  var offsetSeconds = unpacker.unpackInteger(buffer);

  var localTime = (0, _temporalUtil.nanoOfDayToLocalTime)(nanoOfDay);
  var result = new _temporalTypes.Time(localTime.hour, localTime.minute, localTime.second, localTime.nanosecond, offsetSeconds);
  return convertIntegerPropsIfNeeded(result, disableLosslessIntegers);
}

/**
 * Pack given neo4j date.
 * @param {Date} value the date value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */
function packDate(value, packer, onError) {
  var epochDay = (0, _temporalUtil.dateToEpochDay)(value.year, value.month, value.day);

  var packableStructFields = [packer.packable(epochDay, onError)];
  packer.packStruct(DATE, packableStructFields, onError);
}

/**
 * Unpack neo4j date value using the given unpacker.
 * @param {Unpacker} unpacker the unpacker to use.
 * @param {number} structSize the retrieved struct size.
 * @param {BaseBuffer} buffer the buffer to unpack from.
 * @param {boolean} disableLosslessIntegers if integer properties in the result date should be native JS numbers.
 * @return {Date} the unpacked neo4j date value.
 */
function unpackDate(unpacker, structSize, buffer, disableLosslessIntegers) {
  unpacker._verifyStructSize('Date', DATE_STRUCT_SIZE, structSize);

  var epochDay = unpacker.unpackInteger(buffer);
  var result = (0, _temporalUtil.epochDayToDate)(epochDay);
  return convertIntegerPropsIfNeeded(result, disableLosslessIntegers);
}

/**
 * Pack given local date time.
 * @param {LocalDateTime} value the local date time value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */
function packLocalDateTime(value, packer, onError) {
  var epochSecond = (0, _temporalUtil.localDateTimeToEpochSecond)(value.year, value.month, value.day, value.hour, value.minute, value.second, value.nanosecond);
  var nano = (0, _integer.int)(value.nanosecond);

  var packableStructFields = [packer.packable(epochSecond, onError), packer.packable(nano, onError)];
  packer.packStruct(LOCAL_DATE_TIME, packableStructFields, onError);
}

/**
 * Unpack local date time value using the given unpacker.
 * @param {Unpacker} unpacker the unpacker to use.
 * @param {number} structSize the retrieved struct size.
 * @param {BaseBuffer} buffer the buffer to unpack from.
 * @param {boolean} disableLosslessIntegers if integer properties in the result local date-time should be native JS numbers.
 * @return {LocalDateTime} the unpacked local date time value.
 */
function unpackLocalDateTime(unpacker, structSize, buffer, disableLosslessIntegers) {
  unpacker._verifyStructSize('LocalDateTime', LOCAL_DATE_TIME_STRUCT_SIZE, structSize);

  var epochSecond = unpacker.unpackInteger(buffer);
  var nano = unpacker.unpackInteger(buffer);
  var result = (0, _temporalUtil.epochSecondAndNanoToLocalDateTime)(epochSecond, nano);
  return convertIntegerPropsIfNeeded(result, disableLosslessIntegers);
}

/**
 * Pack given date time.
 * @param {DateTime} value the date time value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */
function packDateTime(value, packer, onError) {
  if (value.timeZoneId) {
    packDateTimeWithZoneId(value, packer, onError);
  } else {
    packDateTimeWithZoneOffset(value, packer, onError);
  }
}

/**
 * Pack given date time with zone offset.
 * @param {DateTime} value the date time value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */
function packDateTimeWithZoneOffset(value, packer, onError) {
  var epochSecond = (0, _temporalUtil.localDateTimeToEpochSecond)(value.year, value.month, value.day, value.hour, value.minute, value.second, value.nanosecond);
  var nano = (0, _integer.int)(value.nanosecond);
  var timeZoneOffsetSeconds = (0, _integer.int)(value.timeZoneOffsetSeconds);

  var packableStructFields = [packer.packable(epochSecond, onError), packer.packable(nano, onError), packer.packable(timeZoneOffsetSeconds, onError)];
  packer.packStruct(DATE_TIME_WITH_ZONE_OFFSET, packableStructFields, onError);
}

/**
 * Unpack date time with zone offset value using the given unpacker.
 * @param {Unpacker} unpacker the unpacker to use.
 * @param {number} structSize the retrieved struct size.
 * @param {BaseBuffer} buffer the buffer to unpack from.
 * @param {boolean} disableLosslessIntegers if integer properties in the result date-time should be native JS numbers.
 * @return {DateTime} the unpacked date time with zone offset value.
 */
function unpackDateTimeWithZoneOffset(unpacker, structSize, buffer, disableLosslessIntegers) {
  unpacker._verifyStructSize('DateTimeWithZoneOffset', DATE_TIME_WITH_ZONE_OFFSET_STRUCT_SIZE, structSize);

  var epochSecond = unpacker.unpackInteger(buffer);
  var nano = unpacker.unpackInteger(buffer);
  var timeZoneOffsetSeconds = unpacker.unpackInteger(buffer);

  var localDateTime = (0, _temporalUtil.epochSecondAndNanoToLocalDateTime)(epochSecond, nano);
  var result = new _temporalTypes.DateTime(localDateTime.year, localDateTime.month, localDateTime.day, localDateTime.hour, localDateTime.minute, localDateTime.second, localDateTime.nanosecond, timeZoneOffsetSeconds, null);
  return convertIntegerPropsIfNeeded(result, disableLosslessIntegers);
}

/**
 * Pack given date time with zone id.
 * @param {DateTime} value the date time value to pack.
 * @param {Packer} packer the packer to use.
 * @param {function} onError the error callback.
 */
function packDateTimeWithZoneId(value, packer, onError) {
  var epochSecond = (0, _temporalUtil.localDateTimeToEpochSecond)(value.year, value.month, value.day, value.hour, value.minute, value.second, value.nanosecond);
  var nano = (0, _integer.int)(value.nanosecond);
  var timeZoneId = value.timeZoneId;

  var packableStructFields = [packer.packable(epochSecond, onError), packer.packable(nano, onError), packer.packable(timeZoneId, onError)];
  packer.packStruct(DATE_TIME_WITH_ZONE_ID, packableStructFields, onError);
}

/**
 * Unpack date time with zone id value using the given unpacker.
 * @param {Unpacker} unpacker the unpacker to use.
 * @param {number} structSize the retrieved struct size.
 * @param {BaseBuffer} buffer the buffer to unpack from.
 * @param {boolean} disableLosslessIntegers if integer properties in the result date-time should be native JS numbers.
 * @return {DateTime} the unpacked date time with zone id value.
 */
function unpackDateTimeWithZoneId(unpacker, structSize, buffer, disableLosslessIntegers) {
  unpacker._verifyStructSize('DateTimeWithZoneId', DATE_TIME_WITH_ZONE_ID_STRUCT_SIZE, structSize);

  var epochSecond = unpacker.unpackInteger(buffer);
  var nano = unpacker.unpackInteger(buffer);
  var timeZoneId = unpacker.unpack(buffer);

  var localDateTime = (0, _temporalUtil.epochSecondAndNanoToLocalDateTime)(epochSecond, nano);
  var result = new _temporalTypes.DateTime(localDateTime.year, localDateTime.month, localDateTime.day, localDateTime.hour, localDateTime.minute, localDateTime.second, localDateTime.nanosecond, null, timeZoneId);
  return convertIntegerPropsIfNeeded(result, disableLosslessIntegers);
}

function convertIntegerPropsIfNeeded(obj, disableLosslessIntegers) {
  if (!disableLosslessIntegers) {
    return obj;
  }

  var clone = (0, _create2.default)((0, _getPrototypeOf2.default)(obj));
  for (var prop in obj) {
    if (obj.hasOwnProperty(prop)) {
      var value = obj[prop];
      clone[prop] = (0, _integer.isInt)(value) ? value.toNumberOrInfinity() : value;
    }
  }
  (0, _freeze2.default)(clone);
  return clone;
}