    import * as T from '@turf/turf'
    import { defaultValue, isNumber } from '../../util'
    import Zondy from '../Zondy'
    import Geometry from './Geometry'
    import { GeometryType, IGSGeometryType } from '../enum'
    import Point from './Point'
    import Extent from './Extent'
    import Log from '../../util/Log'

    /**
     * 多点几何对象
     * <br><br>[ES5引入方式]：<br/>
     * Zondy.Geometry.MultiPoint() <br/>
     * [ES6引入方式]：<br/>
     * import { MultiPoint } from "@mapgis/webclient-common" <br/>
     * <br/>
     * @class MultiPoint
     * @moduleEX BaseModule
     * @extends Geometry
     * @param {Object} options 构造参数
     * @param {Number[]} [options.coordinates = []] 几何点的坐标，支持任意单位，参考示例：<a href='#MultiPoint'>[多点几何对象]</a>
     * @param {SpatialReference} [options.spatialReference = new Zondy.SpatialReference('EPSG:4326')] 几何点的空间参考系，默认4326，当不是4326时请指定坐标系，方便进行投影转换，参考示例：<a href='#SpatialReference'>[指定坐标系]</a>
     * @summary <h5>支持如下方法：</h5>
     * <a href='#addPoint'>[1、添加点坐标]</a><br/>
     * <a href='#getPoint'>[2、返回指定索引处的点]</a><br/>
     * <a href='#removePoint'>[3、从多点中移除一个点]</a><br/>
     * <a href='#setPoint'>[4、更新指定索引处的点]</a><br/>
     * <a href='#getIGSType'>[5、返回IGS所对应的GeometryModule型]</a><br/>
     * <a href='#toOldIGSGeometry'>[6、返回igs1.0的几何对象]</a><br/>
     * <a href='#toString'>[7、返回字符串]</a><br/>
     * <a href='#toDots'>[8、返回Dots对象，仅包括多边形的外圈]</a>
     * <a href='#fromJSON'>[9、通过传入的json构造并返回一个新的几何对象]</a><br/>
     * <a href='#toJSON'>[10、导出为json对象]</a><br/>
     * [11、克隆几何对象]{@link Geometry#clone}
     *
     * @example <caption><h5 id='MultiPoint'>创建几何对象</h5></caption>
     * // ES5引入方式
     * const { MultiPoint } = Zondy.Geometry
     * // ES6引入方式
     * import { MultiPoint } from "@mapgis/webclient-common"
     * new MultiPoint({
     *   coordinates:[
     *     // 第一个点
     *     [100, 0.0],
     *     // 第二个点
     *     [101, 1.0]
     *   ]
     * })
     *
     * @example <caption><h5 id='SpatialReference'>指定坐标系</h5></caption>
     * // ES5引入方式
     * const { Point } = Zondy.Geometry
     * const { SpatialReference } = Zondy
     * // ES6引入方式
     * import { Point, SpatialReference } from "@mapgis/webclient-common"
     * new Point({
     *   // 现在为3857坐标系
     *   coordinates:[
     *     // 第一个点
     *     [12929863.44711455, 3934286.575385226],
     *     // 第二个点
     *     [12060733.232006868, 3377247.5680546067]
     *   ],
     *   // 当不是4326时请指定坐标系，方便进行投影转换
     *   spatialReference: new SpatialReference('EPSG:3857')
     * })
     */

    class MultiPoint extends Geometry {
      constructor(options) {
        super(options)
        // 构造几何对象
        options = defaultValue(options, {})
        /**
         * 几何点的坐标
         * @member {Array} MultiPoint.prototype.coordinates
         */
        this.coordinates = defaultValue(options.coordinates, [])
        if (!Array.isArray(this.coordinates)) {
          throw new Error('坐标必须是数组')
        }
        for (let i = 0; i < this.coordinates.length; i++) {
          if (this.coordinates[i].length < 2 || this.coordinates[i].length > 3) {
            throw new Error('多点的数组长度必须在合理范围内(2或者3)')
          }
          // 确保所有点都有第三个值才是有z值
          if (this.coordinates[i].length === 3) {
            this.hasZ = true
          } else {
            this.hasZ = false
          }
        }
        this.type = GeometryType.multiPoint
      }

      /**
       * 添加点坐标，可以为Point对象、[x, y, z(可选)]数组、Point对象数组、[x, y, z(可选)]对象数组
       * @param {Point|Number[]} point 可以是point，也可以是表示XY坐标的数字数组
       * @param {String} [mode = 'add'] 添加模式，add:正常添加，deduplication:去除重复点
       * @return {MultiPoint} 多点对象
       * @example <caption><h7 id='addPoint'>添加点坐标</h7></caption>
       * // ES5引入方式
       * const { MultiPoint } = Zondy.Geometry
       * // ES6引入方式
       * import { MultiPoint } from "@mapgis/webclient-common"
       * const multiPoint = new MultiPoint({
       *   coordinates:[
       *     // 第一个点
       *     [100, 0.0],
       *     // 第二个点
       *     [101, 1.0]
       *   ]
       * })
       * multiPoint.addPoint([102,2.0])
       */
      addPoint(point, mode) {
        mode = defaultValue(mode, 'add')
        // 是点几何对象
        if (point instanceof Point) {
          this._addPoint(point, mode)
        }
        // 添加数组
        else if (point instanceof Array) {
          // 判断是否是点坐标数组
          const isNumber = this._isNumberArray(point)

          // 是点坐标数组
          if (isNumber) {
            // 添加点
            this._addPoint(
              new Point({
                coordinates: point
              }),
              mode
            )
          }
          // 不是点坐标数组
          else {
            for (let i = 0; i < point.length; i++) {
              // 是点几何对象
              if (point[i] instanceof Point) {
                this._addPoint(point[i], mode)
              }
              // 是数组
              else if (point[i] instanceof Array) {
                const pointIsNumber = this._isNumberArray(point[i])
                // 是点坐标对象
                if (pointIsNumber) {
                  // 添加点
                  this._addPoint(
                    new Point({
                      coordinates: point[i]
                    }),
                    mode
                  )
                }
                // 点几何对象
                else if (point instanceof Point) {
                  this._addPoint(point[i], mode)
                }
                // 不是点坐标对象或点几何
                else {
                  Log.error('point的子元素类型不是点几何对象或者点坐标数组！')
                }
              } else {
                Log.error('point的类型不是点几何对象或者点坐标数组！')
              }
            }
          }
        } else {
          Log.error('point的类型不是点几何对象或者点坐标数组！')
        }

        // 更新外包盒
        if (this.coordinates.length >= 2) {
          this.extent = this._calcExtent(this.coordinates, this.hasZ)
        }
        return this
      }

      /**
       * 添加点坐标
       * @private
       * @param {Point} point 点几何对象
       * @param {String} [mode = 'add'] 添加模式，add:正常添加，deduplication:去除重复点
       * */
      _addPoint(point, mode) {
        // 添加模式
        if (mode === 'add') {
          this.coordinates.push(point.coordinates)
        }
        // 去重添加模式
        else if (!this.contains(point)) {
          this.coordinates.push(point.coordinates)
        }
      }

      /**
       * 判断是否是点坐标数组
       * @private
       * @param {Array} point 要判断的对象
       * @return {Boolean} 是否是点坐标数组
       * */
      _isNumberArray(point) {
        // 先判断类型
        if (!(point instanceof Array)) {
          return false
        }

        // 判断是否是点坐标数组
        let isNumber = true
        if (point.length === 2 || point.length === 3) {
          for (let i = 0; i < point.length; i++) {
            if (!(typeof point[i] === 'number')) {
              isNumber = false
              break
            }
          }
        } else {
          isNumber = false
        }

        return isNumber
      }

      /**
       * 判断多点对象是否包含某一个点
       * @param {Point | Array<Number>} point 点对象
       * @return {Boolean} 是否包含
       * */
      contains(point) {
        // 点对象
        if (point instanceof Point) {
          for (let i = 0; i < this.coordinates.length; i++) {
            if (point.equals(this.coordinates[i])) {
              return true
            }
          }
        }
        // 坐标数组
        else if (
          point instanceof Array &&
          (point.length === 2 || point.length === 3)
        ) {
          for (let i = 0; i < this.coordinates.length; i++) {
            const _point = new Point({
              coordinates: point
            })
            if (_point.equals(this.coordinates[i])) {
              return true
            }
          }
        } else {
          Log.error('必须为点对象或坐标数组才能比较是否相等!')
        }

        return false
      }

      /**
       * 返回指定索引处的点
       * @param {Number} index 点对象
       * @return {Point} 在指定下标处的点
       * @example <caption><h7 id='getPoint'>返回指定索引处的点</h7></caption>
       * // ES5引入方式
       * const { MultiPoint } = Zondy.Geometry
       * // ES6引入方式
       * import { MultiPoint } from "@mapgis/webclient-common"
       * const multiPoint = new MultiPoint({
       *   coordinates:[
       *     // 第一个点
       *     [100, 0.0],
       *     // 第二个点
       *     [101, 1.0]
       *   ]
       * })
       * const point = multiPoint.getPoint(0)
       */
      getPoint(index) {
        index = Number(index)
        if (!this._isValidate(index)) {
          throw new Error('未找到指定索引，请输入正确的索引')
        }
        const points = this._cloneCoordinates()
        return new Point({
          coordinates: points[index]
        })
      }

      /**
       * 从多点中移除一个点。索引指定要删除的点
       * @param {Number} index 点对象
       * @return {Point | null} 返回被移除的点
       * @example <caption><h7 id='removePoint'>从多点中移除一个点</h7></caption>
       * // ES5引入方式
       * const { MultiPoint } = Zondy.Geometry
       * // ES6引入方式
       * import { MultiPoint } from "@mapgis/webclient-common"
       * const multiPoint = new MultiPoint({
       *   coordinates:[
       *     // 第一个点
       *     [100, 0.0],
       *     // 第二个点
       *     [101, 1.0]
       *   ]
       * })
       * const point = multiPoint.removePoint(0)
       */
      removePoint(index) {
        index = Number(index)
        if (!this._isValidate(index)) return null
        const points = this._cloneCoordinates()
        this.coordinates.splice(index, 1)
        return new Point({
          coordinates: points[index]
        })
      }

      /**
       * 更新指定索引处的点
       * @param {Number} index 点在points属性中的索引
       * @param {Point|Number[]} point 在新位置指定的几何点
       * @return {MultiPoint} 返回更新后的多点
       * @example <caption><h7 id='setPoint'>更新指定索引处的点</h7></caption>
       * // ES5引入方式
       * const { MultiPoint } = Zondy.Geometry
       * // ES6引入方式
       * import { MultiPoint } from "@mapgis/webclient-common"
       * const multiPoint = new MultiPoint({
       *   coordinates:[
       *     // 第一个点
       *     [100, 0.0],
       *     // 第二个点
       *     [101, 1.0]
       *   ]
       * })
       * multiPoint.setPoint(1, [103, 3.0])
       */
      setPoint(index, point) {
        if (!this._isValidate(index)) {
          throw new Error('未找到指定索引，请输入正确的索引')
        }
        const points = this._cloneCoordinates()
        if (Array.isArray(points)) {
          points.splice(index, 1, Point.toCoordinates(point))
          this.coordinates = points
        }
        return this
      }

      /**
       * 返回IGS所对应的GeometryModule型<a id='getIGSType'></a>
       * @returns string GeometryModule型
       */
      getIGSType() {
        return IGSGeometryType.multiPoint
      }

      /**
       * 通过传入的json构造并返回一个新的几何对象
       * @param {Object} [json] JSON对象
       * @example <caption><h7 id='fromJSON'>通过传入的json构造并返回一个新的几何对象</h7></caption>
       * // ES5引入方式
       * const { MultiPoint } = Zondy.Geometry
       * // ES6引入方式
       * import { MultiPoint } from "@mapgis/webclient-common"
       * const json = {
       *   coordinates:[
       *     // 第一个点
       *     [100, 0.0],
       *     // 第二个点
       *     [101, 1.0]
       *   ]
       * }
       * const multiPoint = MultiPoint.fromJSON(json)
       */
      static fromJSON(json) {
        json = defaultValue(json, {})
        return new MultiPoint(json)
      }

      /**
       * <a id='toJSON'></a>
       * 导出为json对象
       * @return {Object} json对象
       */
      toJSON() {
        const json = super.toJSON()
        json.coordinates = JSON.parse(JSON.stringify(this.coordinates))
        json.extent = this.extent.toJSON()

        return json
      }

      /**
       * 返回igs1.0的几何对象<a id='toOldIGSGeometry'></a>
       * @returns Object igs1.0的几何对象
       */
      toOldIGSGeometry() {
        const PntGeom = []
        for (let i = 0; i < this.coordinates.length; i++) {
          let Dot = {}
          Dot = {
            x: this.coordinates[i][0],
            y: this.coordinates[i][1]
          }
          PntGeom.push({
            Dot,
            GID: i
          })
        }
        return {
          PntGeom
        }
      }

      /**
       * 返回如下格式的字符串："x0,y0,x1,y1,x2,y2"<a id='toString'></a>
       * @returns string
       */
      toString() {
        let _str = ''
        // 开始循环线内的点
        for (let i = 0; i < this.coordinates.length; i++) {
          // 线内的点
          const _point = this.coordinates[i]
          // 有可能是xyz，因此for循环
          for (let k = 0; k < _point.length; k++) {
            // 加,号
            _str += `${_point[k]},`
          }
        }
        // 删除最后一个,
        _str = _str.substring(0, _str.length - 1)
        return _str
      }

      /**
       * 返回Dots对象，仅包括多边形的外圈<a id='toDots'></a>
       * @returns Array Dots对象
       */
      toDots() {
        const Dots = []
        const coordinates = this.coordinates

        for (let i = 0; i < coordinates.length; i++) {
          Dots.push({
            x: coordinates[i][0],
            y: coordinates[i][1]
          })
        }

        return Dots
      }

      /**
       * @function MultiPoint.prototype._isValidate
       * @private
       * @description 判断输入是否合法
       * @param {Number} pointIndex 数组下标
       * @returns {Boolean} 输入是否合法
       */
      _isValidate(pointIndex) {
        const len = this.coordinates.length
        if (!isNumber(pointIndex) || pointIndex < 0 || pointIndex >= len) {
          return false
        }
        return true
      }

      /**
       * @function MultiPoint.prototype._cloneCoordinates
       * @private
       * @description 拷贝几何信息
       * @returns {Array}
       */
      _cloneCoordinates() {
        return JSON.parse(JSON.stringify(this.coordinates))
      }

      /**
       * @function LineString.prototype._calcExtent
       * @private
       * @description 计算外包盒
       * @param {Array} coordinates 坐标点
       * @param {Boolean} hasZ 是否是三维
       * @returns {Extent}
       */
      _calcExtent(coordinates, hasZ) {
        const line = T.lineString(coordinates)
        const bbox = T.bbox(line)

        if (!hasZ) {
          return new Extent({
            xmin: bbox[0],
            ymin: bbox[1],
            xmax: bbox[2],
            ymax: bbox[3]
          })
        }
        return new Extent({
          xmin: bbox[0],
          ymin: bbox[1],
          zmin: bbox[2],
          xmax: bbox[3],
          ymax: bbox[4],
          zmax: bbox[5]
        })
      }

      /**
       * 克隆几何对象
       * @return {Geometry} 克隆后的几何对象
       */
      clone() {
        return new MultiPoint(this.toJSON())
      }
    }
    Object.defineProperties(MultiPoint.prototype, {
      /**
       * 外包盒
       * @member {Number} MultiPoint.prototype.extent
       * */
      extent: {
        configurable: false,
        get() {
          return this._calcExtent(this.coordinates, this.hasZ)
        }
      }
    })

    Zondy.Geometry.MultiPoint = MultiPoint
    export default MultiPoint