/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 util = require('util');
const utils = require('../utils');
const Geometry = require('./geometry');
const Point = require('./point');

/**
 * Creates a new {@link LineString} instance.
 * @classdesc
 * A LineString is a one-dimensional object representing a sequence of points and the line segments connecting them.
 * @param {...Point}[point] A sequence of [Point]{@link module:geometry~Point} items as arguments.
 * @example
 * new LineString(new Point(10.99, 20.02), new Point(14, 26), new Point(34, 1.2));
 * @constructor
 * @alias module:geometry~LineString
 * @extends {Geometry}
 */
function LineString(point) {
  let points = Array.prototype.slice.call(arguments);
  if (points.length === 1 && Array.isArray(points) && Array.isArray(points[0])) {
    //The first argument is an array of the points
    points = points[0];
  }
  if (points.length === 1) {
    throw new TypeError('LineString can be either empty or contain 2 or more points');
  }
  /**
   * Returns a frozen Array of points that represent the line.
   * @type {Array.<Point>}
   */
  this.points = Object.freeze(points);
}

//noinspection JSCheckFunctionSignatures
util.inherits(LineString, Geometry);

/**
 * Creates a {@link LineString} instance from
 * a <a href="https://en.wikipedia.org/wiki/Well-known_text">Well-known Text (WKT)</a>
 * representation of a line.
 * @param {Buffer} buffer
 * @returns {LineString}
 */
LineString.fromBuffer = function (buffer) {
  if (!buffer || buffer.length < 9) {
    throw new TypeError('A linestring buffer should contain at least 9 bytes');
  }
  const endianness = Geometry.getEndianness(buffer.readInt8(0, true));
  let offset = 1;
  if (Geometry.readInt32(buffer, endianness, offset) !== Geometry.types.LineString) {
    throw new TypeError('Binary representation was not a LineString');
  }
  offset += 4;
  const length = Geometry.readInt32(buffer, endianness, offset);
  offset += 4;
  if (buffer.length !== offset + length * 16) {
    throw new TypeError(util.format('Length of the buffer does not match %d !== %d', buffer.length, offset + length * 8));
  }
  const points = new Array(length);
  for (let i = 0; i < length; i++) {
    points[i] = new Point(
      Geometry.readDouble(buffer, endianness, offset),
      Geometry.readDouble(buffer, endianness, offset + 8));
    offset += 16;
  }
  //noinspection JSCheckFunctionSignatures
  return new LineString(points);
};

/**
 * Creates a {@link LineString} instance from
 * a <a href="https://en.wikipedia.org/wiki/Well-known_text">Well-known Text (WKT)</a>
 * representation of a line.
 * @param {String} textValue
 * @returns {LineString}
 */
LineString.fromString = function (textValue) {
  const wktRegex = /^LINESTRING ?\(([-0-9. ,]+)\)+$/g;
  const matches = wktRegex.exec(textValue);
  if (!matches || matches.length !== 2) {
    throw new TypeError('Invalid WKT: ' + textValue);
  }
  const points = LineString.parseSegments(matches[1]);
  return new LineString(points);
};

/**
 * Internal method that parses a series of WKT points.
 * @param {String} textValue
 * @returns {Array<Point>}
 * @internal
 * @ignore
 */
LineString.parseSegments = function (textValue) {
  const points = [];
  const pointParts = textValue.split(',');
  for (let i = 0; i < pointParts.length; i++) {
    const p = pointParts[i].trim();
    if (p.length === 0) {
      throw new TypeError('Invalid WKT segment: ' + textValue);
    }
    const xyText = p.split(' ').filter(function (element) {
      return (element.trim().length > 0);
    });
    if (xyText.length !== 2) {
      throw new TypeError('Invalid WKT segment: ' + textValue);
    }
    points.push(new Point(parseFloat(xyText[0]), parseFloat(xyText[1])));
  }
  return points;
};

/**
 * Returns a <a href="https://en.wikipedia.org/wiki/Well-known_text#Well-known_binary">Well-known Binary</a> (WKB)
 * representation of this instance.
 * @returns {Buffer}
 */
LineString.prototype.toBuffer = function () {
  const buffer = utils.allocBufferUnsafe(9 + this.points.length * 16);
  this.writeEndianness(buffer, 0);
  let offset = 1;
  this.writeInt32(Geometry.types.LineString, buffer, offset);
  offset += 4;
  this.writeInt32(this.points.length, buffer, offset);
  offset += 4;
  this.points.forEach(function (p) {
    this.writeDouble(p.x, buffer, offset);
    this.writeDouble(p.y, buffer, offset + 8);
    offset += 16;
  }, this);
  return buffer;
};

/**
 * Returns true if the values of the linestrings are the same, otherwise it returns false.
 * @param {LineString} other
 * @returns {Boolean}
 */
LineString.prototype.equals = function (other) {
  if (!(other instanceof LineString)) {
    return false;
  }
  if (this.points.length !== other.points.length) {
    return false;
  }
  for (let i = 0; i < this.points.length; i++) {
    if (!this.points[i].equals(other.points[i])) {
      return false;
    }
  }
  return true;
};

/**
 * Returns Well-known text (WKT) representation of the geometry object.
 * @returns {String}
 */
LineString.prototype.toString = function () {
  if (this.points.length === 0) {
    return 'LINESTRING EMPTY';
  }
  return 'LINESTRING ('
    + this.points.map(function (p) {
      return p.x + ' ' + p.y;
    }).join(', ')
    + ')';
};

LineString.prototype.useBESerialization = function () {
  return false;
};

/**
 * Returns a JSON representation of this geo-spatial type.
 */
LineString.prototype.toJSON = function () {
  return { type: 'LineString', coordinates: this.points.map(function (p) {
    return [p.x, p.y];
  })};
};

module.exports = LineString;