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

    /**
     * 区几何，由多个环(ring)构成的几何对象，仅支持带洞区和非带洞区，不支持带岛区，即三个圈重叠<br/>
     * 多边形的第一个环(ring)即外圈，从第二个环开始为内圈，内圈可以重合、相交、自相交，但是不能超过外圈
     * <br><br>[ES5引入方式]：<br/>
     * Zondy.Geometry.Polygon() <br/>
     * [ES6引入方式]：<br/>
     * import { Polygon } from "@mapgis/webclient-common" <br/>
     * <br/>
     * @class Polygon
     * @moduleEX GeometryModule
     * @extends Geometry
     * @param {Object} options 构造参数
     * @param {Array} [options.coordinates = []] 几何点的坐标数组，参考示例如下：<br/>
     * <a href='#Polygon'>[1、不带洞的区几何对象]</a><br/>
     * <a href='#Polygon-innder'>[2、带洞的区几何对象]</a>
     * @param {SpatialReference} [options.spatialReference = new Zondy.SpatialReference('EPSG:4326')] 几何点的空间参考系，默认4326，当不是4326时请指定坐标系，方便进行投影转换，参考示例：<a href='#SpatialReference'>[指定坐标系]</a>
     * @summary <h5>支持如下方法：</h5>
     * <a href='#fromExtent'>[1、将给定的Extent转换为多边形实例]</a><br/>
     * <a href='#addRing'>[2、在多边形中添加一个环]</a><br/>
     * <a href='#removeRing'>[3、根据索引从多边形中移除一个环]</a><br/>
     * <a href='#contains'>[4、检查输入点是否在多边形内]</a><br/>
     * <a href='#getPoint'>[5、根据下标返回指定的点]</a><br/>
     * <a href='#insertPoint'>[6、在多边形中插入一个新点]</a><br/>
     * <a href='#removePoint'>[7、根据下标从多边形中移除一个点]</a><br/>
     * <a href='#toXMl'>[8、导出为OGC服务要求的xml字符串]</a><br/>
     * <a href='#toString'>[9、返回字符串]</a><br/>
     * <a href='#toOldIGSGeometry'>[10、返回igs1.0的几何对象]</a><br/>
     * <a href='#getIGSType'>[11、返回IGS所对应的GeometryModule型]</a><br/>
     * <a href='#toDots'>[12、返回Dots对象，仅包括多边形的外圈]</a>
     * <a href='#fromJSON'>[13、通过传入的json构造并返回一个新的几何对象]</a><br/>
     * <a href='#toJSON'>[14、导出为json对象]</a><br/>
     * [15、克隆几何对象]{@link Geometry#clone}
     *
     * @example <caption><h7 id='Polygon'>不带洞的区几何</h7></caption>
     * // ES5引入方式
     * const { Polygon } = Zondy.Geometry
     * // ES6引入方式
     * import { Polygon } from "@mapgis/webclient-common"
     * new Polygon({
     *   coordinates:[
     *     // 外圈
     *     [
     *       [100.0, 0.0],
     *       [101.0, 0.0],
     *       [101.0, 1.0],
     *       [100.0, 1.0],
     *       [100.0, 0.0]
     *     ]
     *   ]
     * })
     *
     * @example <caption><h7 id='Polygon-innder'>带洞的区几何</h7></caption>
     * // ES5引入方式
     * const { Polygon } = Zondy.Geometry
     * // ES6引入方式
     * import { Polygon } from "@mapgis/webclient-common"
     * new Polygon({
     *   coordinates:[
     *     // 外圈
     *     [
     *       [100.0, 0.0],
     *       [101.0, 0.0],
     *       [101.0, 1.0],
     *       [100.0, 1.0],
     *       [100.0, 0.0]
     *     ],
     *     // 第一个内圈
     *     [
     *       [100.8, 0.8],
     *       [100.8, 0.2],
     *       [100.2, 0.2],
     *       [100.2, 0.8],
     *       [100.8, 0.8]
     *     ],
     *     // 第二个内圈
     *     [],
     *     ...
     *   ]
     * })
     *
     * @example <caption><h7 id='spatialReference'>指定坐标系</h7></caption>
     * // ES5引入方式
     * const { Polygon } = Zondy.Geometry
     * // ES6引入方式
     * import { Polygon } from "@mapgis/webclient-common"
     * new Polygon({
     *   coordinates:[
     *     // 外圈
     *     [
     *       [12060733.232006868, 3377247.5680546067],
     *       [12929863.44711455, 3377247.5680546067],
     *       [12929863.44711455, 3934286.575385226],
     *       [12060733.232006868, 3934286.575385226],
     *       [12060733.232006868, 3377247.5680546067]
     *     ]
     *   ]
     * })
     */
    class Polygon extends Geometry {
      constructor(options) {
        super(options)
        options = defaultValue(options, {})
        /**
         * 几何点的坐标
         * @member {Array} Polygon.prototype.coordinates
         */
        this.coordinates = defaultValue(options.coordinates, [])

        this.type = GeometryType.polygon

        if (!Array.isArray(this.coordinates)) {
          throw new Error('坐标必须为数组')
        }
        for (const ring of this.coordinates) {
          if (ring.length < 4) {
            throw new Error('每个环至少有四个点')
          }

          if (ring[ring.length - 1].length !== ring[0].length) {
            throw new Error('首尾点维度不相等')
          }

          for (let j = 0; j < ring[ring.length - 1].length; j++) {
            // Check if first point of Polygon contains two numbers
            if (ring[ring.length - 1][j] !== ring[0][j]) {
              throw new Error('第一个位置和最后一个位置不相等')
            }
          }
        }

        if (this.coordinates[0][0].length === 3) {
          this.hasZ = true
        }
      }

      /**
       * 将给定的Extent转换为多边形实例
       * @param {Extent} extent
       * @return {Polygon} 表示给定范围的多边形实例
       * @example <caption><h7 id='fromExtent'>将给定的Extent转换为多边形实例</h7></caption>
       * // ES5引入方式
       * const { Extent, Polygon } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent, Polygon } from "@mapgis/webclient-common"
       * const extent = new Extent({
       *   "xmin":10,
       *   "xmax":210,
       *   "ymin":0,
       *   "ymax":100
       * })
       * const polygon = Polygon.fromExtent(extent)
       */
      static fromExtent(extent) {
        const coordinates = []
        if (extent instanceof Extent) {
          const ring = [
            [extent.xmin, extent.ymin],
            [extent.xmin, extent.ymax],
            [extent.xmax, extent.ymax],
            [extent.xmax, extent.ymin],
            [extent.xmin, extent.ymin]
          ]
          if (extent && extent.hasZ) {
            const n = extent.zmin + 0.5 * (extent.zmax - extent.zmin)
            ring.forEach((p) => {
              n.push(p)
            })
          }
          coordinates.push(ring)
        }
        return new Polygon({
          coordinates
        })
      }

      /**
       * 在多边形中添加一个环,环可以是一个数字数组或一个点数组
       * @param {Point[]} points 一个多边形环。环中的第一个和最后一个坐标/点必须相同。可以将其定义为Point几何图形数组或XY坐标数组。
       * @return {Polygon} 返回包含新环的多边形
       * @example <caption><h7 id='addRing'>在多边形中添加一个环</h7></caption>
       * // ES5引入方式
       * const { Polygon, Point } = Zondy.Geometry
       * // ES6引入方式
       * import { Polygon, Point } from "@mapgis/webclient-common"
       * const polygon = new Polygon({
       *   coordinates:[
       *     // 外圈
       *     [
       *       [100.0, 0.0],
       *       [101.0, 0.0],
       *       [101.0, 1.0],
       *       [100.0, 1.0],
       *       [100.0, 0.0]
       *     ]
       *   ]
       * })
       * const ring = [
       *   new Point({
       *     coordinates: [100.2, 0.0]
       *   }),
       *   new Point({
       *     coordinates: [101.3, 0.0]
       *   }),
       *   new Point({
       *     coordinates: [100.3, 0.7]
       *   }),
       *   new Point({
       *     coordinates: [104.2, 0.7]
       *   }),
       *   new Point({
       *     coordinates: [100.2, 0.0]
       *   })
       * ]
       * polygon.addRing(ring)
       */
      addRing(points) {
        if (!Array.isArray(points)) throw new Error('传入参数错误')
        const pnts = []
        points.forEach((point) => {
          pnts.push(Point.toCoordinates(point))
        })
        this.coordinates.push(pnts)
        this.coordinates = JSON.parse(JSON.stringify(this.coordinates))
        return this
      }

      /**
       * 根据索引从多边形中移除一个环
       * @param {Number} ringIndex 要移除的环的索引
       * @return {Array}返回表示已移除的环的点数组
       * @example <caption><h7 id='removeRing'>根据索引从多边形中移除一个环</h7></caption>
       * // ES5引入方式
       * const { Polygon } = Zondy.Geometry
       * // ES6引入方式
       * import { Polygon } from "@mapgis/webclient-common"
       * const polygon = new Polygon({
       *   coordinates:[
       *     // 外圈
       *     [
       *       [100.0, 0.0],
       *       [101.0, 0.0],
       *       [101.0, 1.0],
       *       [100.0, 1.0],
       *       [100.0, 0.0]
       *     ]
       *   ]
       * })
       * polygon.removeRing(1)
       */
      removeRing(ringIndex) {
        ringIndex = Number(ringIndex)
        if (!this._isValidate(ringIndex, 0)) return null
        const coords = JSON.parse(
          JSON.stringify(this._cloneCoordinates()[ringIndex])
        )
        this.coordinates.splice(ringIndex, 1)
        this.coordinates = JSON.parse(JSON.stringify(this.coordinates))
        const pointsArray = []
        for (let i = 0; i < coords.length; i++) {
          const point = new Point({
            coordinates: coords[i]
          })
          pointsArray.push(point)
        }
        return pointsArray
      }

      /**
       * 通过传入的json构造并返回一个新的几何对象
       * @param {Object} [json] JSON对象
       * @example <caption><h7 id='fromJSON'>通过传入的json构造并返回一个新的几何对象</h7></caption>
       * // ES5引入方式
       * const { Polygon } = Zondy.Geometry
       * // ES6引入方式
       * import { Polygon } from "@mapgis/webclient-common"
       * const json = {
       *   coordinates:[
       *     // 外圈
       *     [
       *       [100.0, 0.0],
       *       [101.0, 0.0],
       *       [101.0, 1.0],
       *       [100.0, 1.0],
       *       [100.0, 0.0]
       *     ]
       *   ]
       * }
       * const polygon = new Polygon.fromJSON(json)
       */
      static fromJSON(json) {
        json = defaultValue(json, {})
        return new Polygon(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()
        json.centroid = this.centroid.toJSON()
        return json
      }

      /**
       * 检查输入点是否在多边形内。多边形线上的一个点被认为是内部
       * @param {Point} point 用于测试是否包含在测试多边形中的点
       * @return {Boolean} 如果该点位于多边形内，则返回true
       * @example <caption><h7 id='contains'>检查输入点是否在多边形内</h7></caption>
       * // ES5引入方式
       * const { Polygon, Point } = Zondy.Geometry
       * // ES6引入方式
       * import { Polygon, Point } from "@mapgis/webclient-common"
       * const polygon = new Polygon({
       *   coordinates:[
       *     // 外圈
       *     [
       *       [100.0, 0.0],
       *       [101.0, 0.0],
       *       [101.0, 1.0],
       *       [100.0, 1.0],
       *       [100.0, 0.0]
       *     ]
       *   ]
       * })
       * const point = new Point({
       *   coordinates: [100.0, 0.0]
       * })
       * const isOnPolygon = polygon.contains(point)
       */
      contains(point) {
        if (!point) throw new Error('传入参数错误')
        const polygon = T.polygon(this._cloneCoordinates())
        const p = T.point(Point.toCoordinates(point))
        return T.booleanPointInPolygon(p, polygon)
      }

      /**
       * 根据下标返回指定的点
       * @param {Number} ringIndex 包含所需点的环的下标
       * @param {Number} pointIndex 环内所需点的下标
       * @return {Point | null} 返回位于指定环索引和点索引处的点
       * @example <caption><h7 id='getPoint'>根据下标返回指定的点</h7></caption>
       * // ES5引入方式
       * const { Polygon } = Zondy.Geometry
       * // ES6引入方式
       * import { Polygon } from "@mapgis/webclient-common"
       * const polygon = new Polygon({
       *   coordinates:[
       *     // 外圈
       *     [
       *       [100.0, 0.0],
       *       [101.0, 0.0],
       *       [101.0, 1.0],
       *       [100.0, 1.0],
       *       [100.0, 0.0]
       *     ]
       *   ]
       * })
       * const point = polygon.getPoint(0, 1)
       */
      getPoint(ringIndex, pointIndex) {
        if (!this._isValidate(ringIndex, pointIndex)) return null
        const path = this._cloneCoordinates()[ringIndex]
        return new Point({
          coordinates: path[pointIndex]
        })
      }

      /**
       * 在多边形中插入一个新点
       * @param {Number} ringIndex 插入点的环的下标。
       * @param {Number} pointIndex 要插入环内的点的下标
       * @param {Point} point 插入点
       * @return {Polygon | null} 返回更新后的多边形
       * @example <caption><h7 id='insertPoint'>在多边形中插入一个新点</h7></caption>
       * // ES5引入方式
       * const { Polygon, Point } = Zondy.Geometry
       * // ES6引入方式
       * import { Polygon, Point } from "@mapgis/webclient-common"
       * const polygon = new Polygon({
       *   coordinates:[
       *     // 外圈
       *     [
       *       [100.0, 0.0],
       *       [101.0, 0.0],
       *       [101.0, 1.0],
       *       [100.0, 1.0],
       *       [100.0, 0.0]
       *     ]
       *   ]
       * })
       * const point = new Point({
       *   coordinates: [99, 1.0],
       * })
       * polygon.insertPoint(0, 3, point)
       */
      insertPoint(ringIndex, pointIndex, point) {
        if (!this._isValidate(ringIndex, pointIndex)) return null
        const path = this.coordinates[ringIndex]
        if (Array.isArray(path)) {
          path.splice(pointIndex, 0, point._cloneCoordinates())
        }
        this.coordinates[ringIndex] = path
        this.coordinates = JSON.parse(JSON.stringify(this.coordinates))
        return this
      }

      /**
       * 根据下标从多边形中移除一个点
       * @param {Number} ringIndex 包含要移除的点的环的下标。
       * @param {Number} pointIndex 要在环内移除的点的下标。
       * @return {Point|null} 返回被移除点
       * @example <caption><h7 id='removePoint'>根据下标从多边形中移除一个点</h7></caption>
       * // ES5引入方式
       * const { Polygon } = Zondy.Geometry
       * // ES6引入方式
       * import { Polygon } from "@mapgis/webclient-common"
       * const polygon = new Polygon({
       *   coordinates:[
       *     // 外圈
       *     [
       *       [100.0, 0.0],
       *       [101.0, 0.0],
       *       [101.0, 1.0],
       *       [100.0, 1.0],
       *       [100.0, 0.0]
       *     ]
       *   ]
       * })
       * polygon.removePoint(0, 1)
       */
      removePoint(ringIndex, pointIndex) {
        if (!this._isValidate(ringIndex, pointIndex)) return null
        const _polygon = this.clone()
        const path = _polygon.coordinates[ringIndex]
        const point = new Point({
          coordinates: path[pointIndex]
        })
        if (Array.isArray(path)) {
          path.splice(pointIndex, 1)
        }
        this.coordinates = _polygon.coordinates
        this.coordinates = JSON.parse(JSON.stringify(this.coordinates))
        return point
      }

      /**
       * 更新多边形中的一个点
       * @param {Number} ringIndex 包含要更新点的环的索引。
       * @param {Number} pointIndex 在环内要更新的点的索引。
       * @param {Point|Number} point 新的点
       * @return {Polygon} 返回更新后的多边形
       * @example <caption><h7 id='setPoint'>更新多边形中的一个点</h7></caption>
       * // ES5引入方式
       * const { Polygon, Point } = Zondy.Geometry
       * // ES6引入方式
       * import { Polygon, Point } from "@mapgis/webclient-common"
       * const polygon = new Polygon({
       *   coordinates:[
       *     // 外圈
       *     [
       *       [100.0, 0.0],
       *       [101.0, 0.0],
       *       [101.0, 1.0],
       *       [100.0, 1.0],
       *       [100.0, 0.0]
       *     ]
       *   ]
       * })
       * const point = new Point({
       *   coordinates: [99, 1.0],
       * })
       * polygon.setPoint(0, 3, point)
       */
      setPoint(ringIndex, pointIndex, point) {
        if (!this._isValidate(ringIndex, pointIndex)) return this
        if (point instanceof Point) {
          point = point._cloneCoordinates()
        }
        const path = this.coordinates[ringIndex]
        if (Array.isArray(path)) {
          path.splice(pointIndex, 1, point)
        }
        this.coordinates[ringIndex] = path
        this.coordinates = JSON.parse(JSON.stringify(this.coordinates))
        return this
      }

      /**
       * 导出为OGC服务要求的xml字符串<a id='toXMl'></a>
       * @return {String} 字符串
       */
      toXMl() {
        return ''
      }

      /**
       * 返回如下格式的字符串："x0,y0,x1,y1,x2,y2,x0,y0|x3,y3,x4,y4,x5,y5,x3,y3"<a id='toString'></a>
       * 多边形内每个的多边形由|号分割
       * @returns string
       */
      toString() {
        let _str = ''
        // 开始循环多边形内的多边形
        for (let i = 0; i < this.coordinates.length; i++) {
          // 内部多边形，第一个是多边形本身
          const _innerPolygon = this.coordinates[i]
          // 开始循环内多边形里的点
          for (let j = 0; j < _innerPolygon.length; j++) {
            // 多边形里的点
            const _point = _innerPolygon[j]
            // 有可能是xyz，因此for循环
            for (let k = 0; k < _point.length; k++) {
              // 加,号
              _str += `${_point[k]},`
            }
          }
          // 删除最后一个,
          _str = _str.substring(0, _str.length - 1)
          // 加上|号
          _str += '|'
        }
        // 删除最后一个|
        _str = _str.substring(0, _str.length - 1)
        return _str
      }

      /**
       * 返回igs1.0的几何对象<a id='toOldIGSGeometry'></a>
       * @returns Object igs1.0的几何对象
       */
      toOldIGSGeometry() {
        const RegGeom = []
        const reg = {
          Rings: [],
          GID: 0
        }
        let ring = {}
        const polygon = this.coordinates
        for (let i = 0; i < polygon.length; i++) {
          ring = {
            Arcs: []
          }
          const Dots = []
          for (let j = 0; j < polygon[i].length; j++) {
            const ringCos = polygon[i][j]
            Dots.push({
              x: ringCos[0],
              y: ringCos[1]
            })
          }
          ring.Arcs.push({
            Dots,
            ArcID: i
          })
          reg.Rings.push(ring)
        }
        RegGeom.push(reg)
        return {
          RegGeom
        }
      }

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

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

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

        return Dots
      }

      /**
       * 多边形的环是否是顺时针
       * @param {Array | Array<Point>} ring 多边形的环，坐标数组或者点几何数组
       * @return {Boolean} 是否是顺时针
       * */
      isClockwise(ring) {
        let isClockwise = false
        const points = []

        if (!(ring instanceof Array)) {
          throw new Error('ring不是数组类型！')
        }

        for (let i = 0; i < ring.length; i++) {
          if (ring[i] instanceof Point) {
            points.push(ring[i].coordinates)
          } else if (ring[i] instanceof Array && ring[i].length >= 2) {
            points.push(ring[i])
          } else {
            throw new Error('ring中的点不是数组或Point类型！')
          }
        }
        const line = T.lineString(points)
        isClockwise = T.booleanClockwise(line)

        return isClockwise
      }

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

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

      /**
       * @function Polygon.prototype._calcExtent
       * @private
       * @description 计算外包盒
       * @param {Array} coordinates 坐标点
       * @param {Boolean} hasZ 是否是三维
       * @returns {Extent}
       */
      _calcExtent(coordinates, hasZ) {
        return calcExtent(coordinates, hasZ)
      }

      /**
       * 判断多边形内部是否自相交
       * @private
       * */
      _isSelfIntersecting() {
        const polygon = T.polygon(this.coordinates)
        const kinks = T.kinks(polygon)
        if (kinks.features.length > 0) {
          return true
        }
        return false
      }

      /**
       * 设置多边形质心
       * @private
       * */
      _setCentroid() {
        const polygon = T.polygon(this.coordinates)
        const centroid = T.centroid(polygon)

        // 计算高度平均值
        if (this.hasZ) {
          let length = 0
          let height = 0
          for (let i = 0; i < this.coordinates.length; i++) {
            length += this.coordinates[i].length
            for (let j = 0; j < this.coordinates[i].length; j++) {
              height += this.coordinates[i][j][2]
            }
          }
          height /= length
          return new Point({
            coordinates: [
              centroid.geometry.coordinates[0],
              centroid.geometry.coordinates[1],
              height
            ]
          })
        } else {
          return new Point({
            coordinates: [
              centroid.geometry.coordinates[0],
              centroid.geometry.coordinates[1]
            ]
          })
        }
      }

      /**
       * 克隆几何对象
       * @return {Geometry} 克隆后的几何对象
       */
      clone() {
        return new Polygon(this.toJSON())
      }
    }
    Object.defineProperties(Polygon.prototype, {
      /**
       * 外包盒
       * @member {Number} Polygon.prototype.extent
       * */
      extent: {
        configurable: false,
        get() {
          return this._calcExtent(this.coordinates, this.hasZ)
        }
      },
      /**
       * 多边形是否自相交；判断多边形内的环是否互相相交；或多边形内部的环是否自相交
       * @member {Boolean} Polygon.prototype.isSelfIntersecting
       * @default false
       */
      isSelfIntersecting: {
        configurable: false,
        get() {
          return this._isSelfIntersecting()
        }
      },
      /**
       * 多边形质心
       * @member {Point} Polygon.prototype.centroid
       */
      centroid: {
        configurable: false,
        get() {
          return this._setCentroid()
        }
      }
    })
    Zondy.Geometry.Polygon = Polygon
    export default Polygon