    import * as T from '@turf/turf'
    import Evented from '../Evented'
    import { defaultValue, defined, isNull, Log } from '../../util'
    import Zondy from '../Zondy'
    import { GeometryType, IGSGeometryType } from '../enum'
    import { createGeometry, createGeometryByGeoJSON } from './Utiles'
    import SpatialReference from './SpatialReference'
    import { Polygon, MultiPolygon, GeometryEngine } from './index'

    /**
     * 几何图形对象基类
     * <br><br>[ES5引入方式]：<br/>
     * Zondy.Geometry.Geometry() <br/>
     * [ES6引入方式]：<br/>
     * import { Geometry } from "@mapgis/webclient-common" <br/>
     * <br/>
     * @class Geometry
     * @extends Evented
     * @moduleEX BaseModule
     * @param {Object} options 构造参数
     * @param {SpatialReference} [options.spatialReference = new Zondy.SpatialReference('EPSG:4326')] 几何点的空间参考系，默认4326
     */
    class Geometry extends Evented {
      constructor(options) {
        super(options)
        // 构造几何对象
        options = defaultValue(options, {})
        /**
         * 几何点的空间参考系
         * @member {SpatialReference} Geometry.prototype.spatialReference
         */
        this.spatialReference = defaultValue(
          SpatialReference.fromJSON(options.spatialReference),
          new SpatialReference('EPSG:4326')
        )
        /**
         * 是否含有z坐标
         * @readonly
         * @member {Boolean} Geometry.prototype.hasZ
         */
        this.hasZ = false
        /**
         * 几何类型
         * @readonly
         * @member {GeometryType} GeometryType.prototype.type
         */
        this.type = GeometryType.geometry
      }

      static fromGeoJSON(json) {
        return createGeometryByGeoJSON(json)
      }

      /**
       * 克隆几何对象
       * @return {Geometry} 克隆后的几何对象
       */
      clone() {
        return new Geometry(this.toJSON())
      }

      /**
       * 通过一个JSON对象创建一个几何对象
       * @param {Object} json JSON对象
       * @return {Geometry} 几何对象
       *
       * @example <caption><h5 id='fromJSON'>通过一个JSON对象创建一个几何对象</h7></caption>
       * // ES5引入方式
       * const { Geometry } = Zondy
       * const { GeometryType } = Zondy.Enum
       * // ES6引入方式
       * import { Geometry, GeometryType } from "@mapgis/webclient-common"
       * // 通过json构造一个几何对象
       * const polygon = Geometry.fromJSON({
       *   // 注意必须传入一个几何类型
       *   type: GeometryType.polygon,
       *   coordinates: [
       *       [
       *         [113, 29],
       *         [114, 29],
       *         [114, 30],
       *         [113, 30],
       *         [113, 29]
       *       ]
       *   ]
       * })
       */
      static fromJSON(json) {
        json = defaultValue(json, {})
        return createGeometry(json)
      }

      /**
       * 导出为json对象
       * @return {Object} json对象
       */
      toJSON() {
        return {
          spatialReference: this.spatialReference.toJSON(),
          hasZ: this.hasZ,
          type: this.type
        }
      }

      /**
       * 导出为GeoJSON
       * @return {Object} GeoJSON对象
       * */
      toGeoJSON() {
        return {
          type: this.type,
          coordinates: this.coordinates
        }
      }

      /**
       * 导入GeoJSON
       * @param {Object} GeoJSON Object
       * */
      fromGeoJSON(geoJSON) {
        if (geoJSON || geoJSON.type !== this.type) {
          new Error('GeoJSON类型和几何类型不一致')
        }
        if (defined(this.coordinates)) {
          this.coordinates = geoJSON.coordinates
        }
      }

      /**
       * 导出为OGC服务要求的xml字符串，子类实现
       * @return {String} 字符串
       */
      toXML() {
        return ''
      }

      /**
       * 返回所对应的GeometryModule型
       * @returns {String} GeometryModule型
       */
      getType() {
        return this.type
      }

      /**
       * 返回IGS所对应的GeometryModule型
       * @returns {String} GeometryModule型
       */
      getIGSType() {
        return IGSGeometryType.geometry
      }

      /**
       * 获取GeometryModule型
       * @return {String} GeometryModule型
       */
      getGeometryType() {
        return this.type
      }

      /**
       * 导出为字符串，子类实现，这里必须是igs要求的字符串
       * @return {String} 字符串
       */
      toString() {
        return ''
      }

      /**
       * 对点数组进行是否为顺时针判断;<br/>
       * 支持的参数类型为闭合的二维点数组;<br/>
       * 示例如下：<br/>
       * <a href='#isClockwise'>[对点数组进行顺时针判断]</a><br/>
       * @param  {Array} pointArr 点数组
       * @return {Boolean} 是否为顺时针
       *
       * @example <caption><h5 id='isClockwise'>对点数组进行顺时针判断</h5></caption>
       * // ES5引入方式
       * const { Geometry } = Zondy.Geometry
       * // ES6引入方式
       * import { Geometry } from "@mapgis/webclient-common"
       * // 构造线几何对象
       * const pointArr = [
       *      [112, 30],
       *      [114, 30],
       *      [114, 32],
       *      [112, 30],
       *    ];
       * // 对点数组进行顺时针判断
       * const isClockwise = Geometry.isClockwise(pointArr)
       * console.log("是否为顺时针:", isClockwise)
       * */
      static isClockwise(pointArr) {
        pointArr = defaultValue(pointArr, undefined)
        // 1.先判断入参非空
        if (isNull(pointArr)) {
          Log.error('geometry1不能为空！')
        }

        // 2.入参类型检查，必须是数组
        if (!(pointArr instanceof Array)) {
          Log.error('输入参数类型错误，需要输入点数组！')
        }

        // 3.入参类型检查，不能是一维数组三维数组，必须是二维数组
        for (let i = 0; i < pointArr.length; i++) {
          if (!(pointArr[i] instanceof Array)) {
            Log.error('输入参数类型错误，需要输入二维数组！')
          } else {
            for (let j = 0; j < pointArr[i].length; j++) {
              if (pointArr[i][j] instanceof Array) {
                Log.error('输入参数类型错误，需要输入二维数组！')
              }
            }
          }
        }
        // 4. 是否为闭合的点检查
        if (
          !(
            pointArr[0].length === pointArr[pointArr.length - 1].length &&
            JSON.stringify(pointArr[0]) ===
              JSON.stringify(pointArr[pointArr.length - 1])
          )
        ) {
          Log.error('输入的点数组必须首尾闭合！')
        }

        // 6.通过判断点数组
        if (!isNull(pointArr)) {
          const _clockwiseRing = T.lineString(pointArr)
          return T.booleanClockwise(_clockwiseRing)
        }
      }

      /**
       * 对入参进行判断处理，构建polygon或multiPolygon;<br/>
       * 支持的参数类型为闭合的三维点数组(多个ring构成的数组)，不区分顺逆时针;<br/>
       * 示例如下：<br/>
       * <a href='#fromRings1'>[1、根据入参构建polygon或multiPolygon]</a><br/>
       * <a href='#fromRings2'>[1、根据入参构建polygon或multiPolygon，指定rings坐标系]</a><br/>
       * @param  {Array} pointArr 点数组
       * @param  {SpatialReference} spatialReference 几何点的空间参考系，默认4326，非必填，当不是4326时请指定坐标系，方便进行投影转换
       * @return {Object} 构建的polygon或multiPolygon
       *
       * @example <caption><h5 id='fromRings1'>点数组构建polygon</h5></caption>
       * // ES5引入方式
       * const { Geometry} = Zondy.Geometry
       * // ES6引入方式
       * import { Geometry } from "@mapgis/webclient-common"
       * // 输入三维点数组
       * const pointArr = [
       *      [
       *        [108, 30],
       *        [108, 33],
       *        [115, 33],
       *        [115, 30],
       *        [108, 30],
       *      ],
       *      [
       *        [110, 31],
       *        [113, 31],
       *        [113, 32],
       *        [110, 32],
       *        [110, 31],
       *      ],
       *      [
       *        [110, 20],
       *        [110, 25],
       *        [115, 25],
       *        [115, 20],
       *        [110, 20],
       *      ],
       *    ];
       * // 点数组构建polygon
       * const fromRings = Geometry.fromRings(pointArr)
       * console.log("构建的多边形对象:", fromRings)
       *
       * @example <caption><h5 id='fromRings2'>指定坐标系的点数组构建polygon</h5></caption>
       * // ES5引入方式
       * const { Geometry,SpatialReference} = Zondy.Geometry
       * // ES6引入方式
       * import { Geometry,SpatialReference } from "@mapgis/webclient-common"
       * // 输入指定坐标系的点数组
       *  const pointArr_4547 = [
       *    [
       *      [-79450.434008356, 3335318.50110312],
       *      [-61138.8507993389, 3668785.80194059],
       *      [593455.16027313, 3653192.25094895],
       *      [596488.74806744, 3320534.43645214],
       *      [-79450.434008356, 3335318.50110312],
       *    ],
       *    [
       *      [22192.1178133016, 3441729.02787479],
       *      [213425.289259696, 3434840.60529103],
       *      [216463.35881388, 3545788.2250497],
       *      [27269.5496056064, 3552800.06686639],
       *      [22192.1178133016, 3441729.02787479],
       *    ],
       *    [
       *      [307007.697900227, 3441729.02787479],
       *      [500000.000000819, 3441729.02787479],
       *      [500000.00000081, 3545788.2250497],
       *      [308973.125268913, 3545788.2250497],
       *      [307007.697900227, 3441729.02787479],
       *    ],
       *    [
       *      [81149.2212748193, 2217372.5132106],
       *      [81149.2212748193, 2766426.51455527],
       *      [600953.408059909, 2766426.51455527],
       *      [600953.408059909, 2217372.5132106],
       *      [81149.2212748193, 2217372.5132106],
       *    ],
       *  ];
       *
       * // 创建坐标系
       * const spatialReference = new SpatialReference({
       *    wkid: 4547,
       *  });
       * // 点数组构建polygon
       * const fromRings = Geometry.fromRings(pointArr_4547, spatialReference)
       * console.log("构建的指定坐标系的多边形对象:", fromRings)
       * */
      static fromRings(geometry, spatialReference) {
        geometry = defaultValue(geometry, undefined)
        const _spatialReference = defaultValue(
          SpatialReference.fromJSON(spatialReference),
          new SpatialReference('EPSG:4326')
        )

        // 1.先判断入参非空
        if (isNull(geometry)) {
          Log.error('geometry1不能为空！')
        }

        // 2.入参类型检查，必须是数组，字符串也要转数组
        if (!(geometry instanceof Array)) {
          Log.error('输入参数类型错误，需要输入点数组！')
        }

        // 3.入参类型检查，不能是一维数组四维数组，必须是三维数组
        const a = this._multiarr(geometry)
        if (a !== 3) {
          Log.error('输入参数错误，需要输入三维数组！')
        }

        let _polygon
        // 4.三维数组处理
        //  4.1 判断三维数组长度是否为1
        if (geometry.length === 1) {
          // 多了一层数组的二维数组[[[1,2],[3,4],[5,6],[1,2]]]
          const _geometry = geometry[0]
          // 检查闭合点
          if (
            !(
              _geometry[0].length === _geometry[_geometry.length - 1].length &&
              JSON.stringify(_geometry[0]) ===
                JSON.stringify(_geometry[_geometry.length - 1])
            )
          ) {
            _geometry.push(_geometry[0])
          }
          _polygon = new Polygon({
            coordinates: [_geometry],
            spatialReference: _spatialReference
          })
          return _polygon
        } else {
          // 先遍历每个ring
          // 用来记录所有ring的数组
          const _ringsObjArr = []
          // 有多个圈，先遍历每个圈
          for (let i = 0; i < geometry.length; i++) {
            // 每个圈对象化
            const _pointObj = {
              geometry: undefined,
              area: null,
              inner: []
            }
            // 5.闭合点检查
            if (
              !(
                geometry[i][0].length ===
                  geometry[i][geometry[i].length - 1].length &&
                JSON.stringify(geometry[i][0]) ===
                  JSON.stringify(geometry[i][geometry[i].length - 1])
              )
            ) {
              geometry[i].push(geometry[i][0])
            }
            // 先 生成polygon
            const _geometry = new Polygon({
              coordinates: [geometry[i]],
              spatialReference: _spatialReference
            })
            _pointObj.geometry = _geometry
            // 计算面积
            _pointObj.area = GeometryEngine.planarArea(_geometry)
            _ringsObjArr.push(_pointObj)
          }
          // 在这里得到ring对象的数组 _ringsObjArr

          // 用来存放处理好的ring以及他的inner
          const stateObj = []
          const ringArr = this._processArr(stateObj, _ringsObjArr)

          // 开始new
          const coords = []
          for (let i = 0; i < ringArr.length; i++) {
            const coord = [ringArr[i].geometry.coordinates[0]]
            // 检查有无内圈
            if (ringArr[i].inner.length >= 1) {
              ringArr[i].inner.forEach((item) => {
                coord.push(item.geometry.coordinates[0])
              })
            }
            coords.push(coord)
          }
          if (ringArr.length === 1) {
            // new polygon
            _polygon = new Polygon({
              coordinates: coords[0],
              spatialReference: _spatialReference
            })
          } else if (ringArr.length > 1) {
            // new multiPolygon
            _polygon = new MultiPolygon({
              coordinates: coords,
              spatialReference: _spatialReference
            })
          }
          return _polygon
        }
      }

      /**
       * ring 生成 polygon拓扑关系检查
       * @param {Array} arr 多维数组
       * @return {Array} 返回整理好的对象数组
       * */
      static _processArr(stateObj, ringsObjArr) {
        // 根据面积排序
        ringsObjArr.sort((a, b) => {
          return b.area - a.area
        })
        // 得到面积从大到小排序的对象数组 ringsObjArr

        // 取第一个
        const firstObj = ringsObjArr[0]
        // 用来存新的、与第一个ring相离的ring对象
        const _newDisjointObjArr = []
        // 遍历剩下的
        for (let i = 1; i < ringsObjArr.length; i++) {
          if (GeometryEngine.isSimple(firstObj.geometry, ringsObjArr[i].geometry)) {
            // 判断第一个和剩下的是否相离
            if (
              GeometryEngine.disjoint(firstObj.geometry, ringsObjArr[i].geometry)
            ) {
              // 若相离，作为新数组存入
              _newDisjointObjArr.push(ringsObjArr[i])
            }
            // 若不相离,判断第一个ring是否包含第i个ring
            else if (
              GeometryEngine.contains(firstObj.geometry, ringsObjArr[i].geometry)
            ) {
              // 若包含，则第一个ring的inner暂存第i个ring
              firstObj.inner.push(ringsObjArr[i])
            } else {
              Log.error('输入的几何存在拓扑错误，请检查')
            }
          } else {
            // 若不包含，则为拓扑错误
            Log.error('输入的几何存在拓扑错误，请检查')
          }
        }
        // 第一遍结束,得到第一个ring和他互不相交的n个内圈,以及和第一个ring相离的其他外圈
        // 开始处理第一个ring的所有内圈
        if (firstObj.inner.length > 1) {
          for (let i = 0; i < firstObj.inner.length; i++) {
            // 取第一个
            const _one = firstObj.inner[i]
            // 在这里，第一个ring的内圈，要做到不相交，不包含，但面和面的包含也是一种相交，因此在判断完相交后要继续判断是否包含
            for (let j = i + 1; j < firstObj.inner.length; j++) {
              if (
                GeometryEngine.isSimple(firstObj.geometry, ringsObjArr[i].geometry)
              ) {
                if (
                  GeometryEngine.intersects(
                    _one.geometry,
                    firstObj.inner[j].geometry
                  )
                ) {
                  if (
                    GeometryEngine.contains(
                      _one.geometry,
                      firstObj.inner[j].geometry
                    )
                  ) {
                    // 若包含，则需要把这个ring作为一个单独的polygon加入到新数组中，从第一个ring的inner中踢出去
                    _newDisjointObjArr.push(firstObj.inner[j])
                    firstObj.inner.splice(j, 1)
                    // 删除后inner的长度-1，因此j也要-1
                    j--
                  } else {
                    Log.error('输入的几何存在拓扑错误，请检查')
                  }
                }
              } else {
                Log.error('输入的几何存在拓扑错误，请检查')
              }
            }
          }
        }
        stateObj.push(firstObj)
        if (_newDisjointObjArr.length > 1) {
          this._processArr(stateObj, _newDisjointObjArr)
        } else if (_newDisjointObjArr.length === 1) {
          stateObj.push(_newDisjointObjArr[0])
        }

        return stateObj
      }

      /**
       * 判断输入数组的维度
       * @param {Array} arr 多维数组
       * @return {Number} 返回输入数组的维度
       * */
      static _multiarr(arr) {
        const list = []
        let num = 1
        for (let i = 0; i < arr.length; i++) {
          if (arr[i] instanceof Array) {
            for (let j = 0; j < arr[i].length; j++) {
              list.push(arr[i][j])
            }
          }
        }
        if (list.length) {
          num = 1
          num += this._multiarr(list)
        }
        return num
      }

      /**
       * fromRings输入参数为字符串时解析字符串
       * @param {String} pointStr 多维数组
       * @return {Number} 返回输入数组的维度
       * */
      static _parseString(pointStr) {
        const _geometry = []
        // *分割每个圈，先把圈剥出来
        const _ringsAll = pointStr.split('*')
        // 空格分隔每个点
        for (let i = 0; i < _ringsAll.length; i++) {
          // _ring得到的是以，分割的每个圈的所有坐标点
          const _ring = _ringsAll[i].split(' ')
          // 在这里还要进行是否是弧段的判断
          // 遍历每个ring中的每个元素，如果有#
          const _pointsArrStr = []
          for (let j = 0; j < _ring.length; j++) {
            _pointsArrStr.push(_ring[j].split(','))
            if (_ring[j].indexOf('#') > -1) {
              _ring[j] = _ring[j].replace('#', '')
              _pointsArrStr.splice(j - 1, 2)
              // 拼接弧段
              const _arc = [
                _ring[j - 1].split(','),
                _ring[j].split(','),
                _ring[j + 1].split(',')
              ]
              _pointsArrStr.push(_arc)
              j++
            }
          }
          // _pointsArrStr 用来存放转成number的每圈的坐标
          const _pointsArr = []
          for (let j = 0; j < _pointsArrStr.length; j++) {
            let _pointsLonely
            if (_pointsArrStr[j][0] instanceof Array) {
              // 有弧段
              const _temp = []
              for (let k = 0; k < _pointsArrStr[j].length; k++) {
                _pointsLonely = _pointsArrStr[j][k].map((item) => {
                  return Number(item)
                })
                // 这样可以弧度单独为一个数组
                _temp.push(_pointsLonely)
                // 这样可以弧度转点
                // _pointsArr.push(_pointsLonely)
              }
              _pointsArr.push(_temp)
            } else {
              _pointsLonely = _pointsArrStr[j].map((item) => {
                return Number(item)
              })
              _pointsArr.push(_pointsLonely)
            }
          }
          _geometry.push(_pointsArr)
        }
        return _geometry
      }
    }
    Object.defineProperties(Geometry.prototype, {
      /**
       * 几何的范围
       * @member {Number} Geometry.prototype.extent
       * */
      extent: {
        configurable: false,
        get() {
          return undefined
        }
      }
    })

    export default Geometry
    Zondy.Geometry.Geometry = Geometry