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

    /**
     * 几何范围对象，即左下角和右上角组成的矩形范围几何对象，参考示例：<a href='#Extent'>[几何范围对象]</a>
     * <br><br>[ES5引入方式]：<br/>
     * Zondy.Geometry.Extent() <br/>
     * [ES6引入方式]：<br/>
     * import { Extent } from "@mapgis/webclient-common" <br/>
     * <br/>
     * @class Extent
     * @moduleEX GeometryModule
     * @extends Geometry
     * @param {Object} options 构造参数
     * @param {Number} [options.xmin = 0] x轴最小坐标
     * @param {Number} [options.ymin = 0] y轴最小坐标
     * @param {Number} [options.xmax = 0] x轴最大坐标
     * @param {Number} [options.ymax = 0] y轴最大坐标
     * @param {Number} [options.zmin0] Z轴最小坐标
     * @param {Number} [options.zmax0] Z轴最大坐标
     * @param {SpatialReference} [options.spatialReference = new Zondy.SpatialReference('EPSG:4326')] 几何点的空间参考系，默认4326，当不是4326时请指定坐标系，方便进行投影转换，参考示例：<a href='#SpatialReference'>[指定坐标系]</a>
     *
     * @summary <h5>支持如下方法：</h5>
     * <a href='#centerAt'>[1、根据中心点生成新的范围]</a><br/>
     * <a href='#contains'>[2、输入的几何是否包含在范围内]</a><br/>
     * <a href='#intersection'>[3、将原始范围与输入范围求交]</a><br/>
     * <a href='#intersects'>[4、输入的几何图形是否与范围相交]</a><br/>
     * <a href='#equals'>[5、是否和输入范围相等]</a><br/>
     * <a href='#expand'>[6、按给定的因子扩大范围]</a><br/>
     * <a href='#offset'>[7、平移extent]</a><br/>
     * <a href='#union'>[8、求并]</a><br/>
     * <a href='#toString'>[9、返回字符串]</a><br/>
     * <a href='#toIGSOldString'>[10、返回IGS1.0的字符串]</a><br/>
     * <a href='#getIGSType'>[11、返回IGS所对应的GeometryModule型]</a><br/>
     * <a href='#fromJSON'>[12、通过传入的json构造并返回一个新的几何对象]</a><br/>
     * <a href='#toJSON'>[13、导出为json对象]</a><br/>
     * [14、克隆几何对象]{@link Geometry#clone}
     *
     * @example <caption><h7 id='Extent'>创建几何对象</h7></caption>
     * // ES5引入方式
     * const { Extent } = Zondy.Geometry
     * // ES6引入方式
     * import { Extent } from "@mapgis/webclient-common"
     * new Extent({
     *   xmin: 10,
     *   xmax: 210,
     *   ymin: 0,
     *   ymax: 100
     * })
     *
     * @example <caption><h7 id='spatialReference'>指定坐标系</h7></caption>
     * // ES5引入方式
     * const { Extent } = Zondy.Geometry
     * const { SpatialReference } = Zondy
     * // ES6引入方式
     * import { Extent, SpatialReference } from "@mapgis/webclient-common"
     * new Extent({
     *   // 3857坐标系的点
     *   xmin: 12060733.232006868,
     *   xmax: 12929863.44711455,
     *   ymin: 3377247.5680546067,
     *   ymax: 3934286.575385226,
     *   // 当不是4326时请指定坐标系，方便进行投影转换
     *   spatialReference: new SpatialReference('EPSG:3857')
     * })
     */

    class Extent extends Geometry {
      constructor(options) {
        super(options)

        options = defaultValue(options, {})
        /**
         * x坐标最小值
         * @default 0
         * @member {Number} Extent.prototype.xmin
         */
        this.xmin = defaultValue(options.xmin, 0)
        /**
         * y坐标最小值
         * @default 0
         * @member {Number} Extent.prototype.ymin
         */
        this.ymin = defaultValue(options.ymin, 0)
        /**
         * x坐标最大值
         * @default 0
         * @member {Number} Extent.prototype.xmax
         */
        this.xmax = defaultValue(options.xmax, 0)
        /**
         * y坐标最大值
         * @default 0
         * @member {Number} Extent.prototype.ymax
         */
        this.ymax = defaultValue(options.ymax, 0)
        /**
         * z坐标最小值
         * @member {Number} Extent.prototype.zmin
         */
        this.zmin = defaultValue(options.zmin, undefined)
        /**
         * z坐标最大值
         * @member {Number} Extent.prototype.zmax
         */
        this.zmax = defaultValue(options.zmax, undefined)
        this.type = GeometryType.extent
        // 判断hasZ
        this.hasZ = !(!isNumber(this.zmin) || !isNumber(this.zmax))
        // 判断坐标是否为数字
        if (
          !isNumber(this.xmin) ||
          !isNumber(this.ymin) ||
          !isNumber(this.xmax) ||
          !isNumber(this.ymax) ||
          (typeof this.zmin !== 'undefined' && !isNumber(this.zmin)) ||
          (typeof this.zmax !== 'undefined' && !isNumber(this.zmax))
        ) {
          throw new Error('坐标必须为数字')
        }
      }

      /**
       * 根据中心点生成新的范围，新范围的宽高为当前范围的宽高
       * @param {Point} point 中心点
       * @returns {Extent} 以点为中心的新范围。
       * @example <caption><h7 id='centerAt'>根据中心点生成新的范围</h7></caption>
       * // ES5引入方式
       * const { Extent, Point } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent, Point } from "@mapgis/webclient-common"
       * const extent = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * const point = new Point({
       *   coordinates: [100.0, 0.0]
       * })
       * const newConst = extent.centerAt(point)
       */
      centerAt(point) {
        if (!point) throw new Error('传入参数错误')
        // 对传入参数为Number[]或者Point的处理
        const coordinates = Point.toCoordinates(point)
        const { width } = this
        const { height } = this
        this.xmin = coordinates[0] - width / 2
        this.xmax = coordinates[0] + width / 2
        this.ymin = coordinates[1] - height / 2
        this.ymax = coordinates[1] + height / 2
        if (this.hasZ && point.hasZ) {
          const zlength = this.zmax - this.zmin
          this.zmin = coordinates[2] - zlength / 2
          this.zmax = coordinates[2] + zlength / 2
        }
        return this
      }

      /**
       * 输入的几何是否包含在范围内
       * @param {Point|Extent} geometry 输入的几何图形
       * @returns {Boolean} 如果输入几何图形包含在范围内，则返回true
       * @example <caption><h7 id='contains'>输入的几何是否包含在范围内</h7></caption>
       * // ES5引入方式
       * const { Extent, Point } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent, Point } from "@mapgis/webclient-common"
       * const extent = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * const point = new Point({
       *   coordinates: [100.0, 0.0]
       * })
       * const isIn = extent.contains(point)
       */
      contains(geometry) {
        // 使用turf判断面与面，面与点的包含关系
        if (!(geometry instanceof Point) && !(geometry instanceof Extent)) {
          return false
        }

        // 因为turf的精度问题，将几何转为4326进行比较
        let _geometry = geometry
        if (!_geometry.isGeographic) {
          _geometry = Projection.project(
            geometry,
            new SpatialReference({ wkid: 4326 })
          )
        }
        let _extent = this
        if (!_extent.isGeographic) {
          _extent = Projection.project(this, new SpatialReference({ wkid: 4326 }))
        }

        const extentPolygonOri = T.polygon([
          [
            [_extent.xmin, _extent.ymin],
            [_extent.xmin, _extent.ymax],
            [_extent.xmax, _extent.ymax],
            [_extent.xmax, _extent.ymin],
            [_extent.xmin, _extent.ymin]
          ]
        ])
        let currentGeometry
        if (_geometry instanceof Point) {
          currentGeometry = T.point(_geometry._cloneCoordinates())
        } else {
          currentGeometry = T.polygon([
            [
              [_geometry.xmin, _geometry.ymin],
              [_geometry.xmin, _geometry.ymax],
              [_geometry.xmax, _geometry.ymax],
              [_geometry.xmax, _geometry.ymin],
              [_geometry.xmin, _geometry.ymin]
            ]
          ])
        }
        return T.booleanContains(extentPolygonOri, currentGeometry)
      }

      /**
       * 将原始范围与输入范围求交
       * @param {Extent}  extent 输入的范围，用于相交。
       * @returns {Extent} 返回输入Extent与原始Extent的交集，没有交集返回 null
       * @example <caption><h7 id='intersection'>将原始范围与输入范围求交</h7></caption>
       * // ES5引入方式
       * const { Extent } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent } from "@mapgis/webclient-common"
       * const extent = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * const extent2 = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * const newExtent = extent.intersection(extent2)
       */
      intersection(extent) {
        if (!extent) Log.error('传入参数错误!')
        if (!(extent instanceof Extent)) Log.error('extent不是Extent几何!')
        if (!this.spatialReference.equals(extent.spatialReference)) {
          Log.error('不是同一个坐标系!')
        }

        // 因为turf的精度问题，将几何转为4326进行比较
        let _geometry = extent
        if (!_geometry.isGeographic) {
          _geometry = Projection.project(
            extent,
            new SpatialReference({ wkid: 4326 })
          )
        }
        let _extent = this
        if (!_extent.isGeographic) {
          _extent = Projection.project(this, new SpatialReference({ wkid: 4326 }))
        }

        const extentPolygonOri = T.polygon([
          [
            [_extent.xmin, _extent.ymin],
            [_extent.xmin, _extent.ymax],
            [_extent.xmax, _extent.ymax],
            [_extent.xmax, _extent.ymin],
            [_extent.xmin, _extent.ymin]
          ]
        ])
        const extentPolygonAdd = T.polygon([
          [
            [_geometry.xmin, _geometry.ymin],
            [_geometry.xmin, _geometry.ymax],
            [_geometry.xmax, _geometry.ymax],
            [_geometry.xmax, _geometry.ymin],
            [_geometry.xmin, _geometry.ymin]
          ]
        ])

        let result = null
        const geoJSON = T.intersect(extentPolygonOri, extentPolygonAdd)
        if (geoJSON && geoJSON.geometry) {
          let bbox
          if (geoJSON.geometry.type === 'Polygon') {
            bbox = T.bbox(T.polygon(geoJSON.geometry.coordinates))
          } else if (geoJSON.geometry.type === 'MultiPolygon') {
            bbox = T.bbox(T.multiPolygon(geoJSON.geometry.coordinates))
          }
          if (bbox) {
            result = new Extent({
              xmin: bbox[0],
              ymin: bbox[1],
              xmax: bbox[2],
              ymax: bbox[3]
            })
            if (!this.spatialReference.isGeographic) {
              result = Projection.project(
                result,
                SpatialReference.fromJSON(this.spatialReference)
              )
            }
          }
        }

        return result
      }

      /**
       * 输入的几何图形是否与范围相交
       * @param {Geometry}  geometry  用于测试相交的几何图形。可以是 Point, Polyline, Polygon, Extent or Multipoint
       * @returns {Boolean} 如果输入几何图形与区段相交，则返回true
       * @example <caption><h7 id='intersects'>输入的几何图形是否与范围相交</h7></caption>
       * // ES5引入方式
       * const { Extent, Point } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent, Point } from "@mapgis/webclient-common"
       * const extent = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * const point = new Point({
       *   coordinates: [100.0, 0.0]
       * })
       * const isIntersect = extent.intersects(point)
       */
      intersects(geometry) {
        return GeometryEngine.intersects(this, geometry)
      }

      /**
       * 是否和输入范围相等
       * @param {Extent} extent 输入的外包范围
       * @returns {Boolean} 如果输入范围等于调用equals()的范围，则返回true
       * @example <caption><h7 id='equals'>是否和输入范围相等</h7></caption>
       * // ES5引入方式
       * const { Extent } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent } from "@mapgis/webclient-common"
       * const extent = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * const extent2 = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * const isEqual = extent.equals(extent2)
       */
      equals(extent) {
        if (!extent) throw new Error('传入参数错误')
        const condition1 = !!Math.abs(this.xmax - extent.xmax) <= 10e-8
        const condition2 = !!Math.abs(this.xmin - extent.xmin) <= 10e-8
        const condition3 = !!Math.abs(this.ymax - extent.ymax) <= 10e-8
        const condition4 = !!Math.abs(this.ymin - extent.ymin) <= 10e-8
        if (!this.hasZ) {
          return condition1 && condition2 && condition3 && condition4
        }
        const condition5 = !!Math.abs(this.zmax - extent.zmax) <= 10e-8
        const condition6 = !!Math.abs(this.zmin - extent.zmin) <= 10e-8
        return (
          condition1 &&
          condition2 &&
          condition3 &&
          condition4 &&
          condition5 &&
          condition6
        )
      }

      /**
       * 按给定的因子扩大范围。例如，值为1.5将将范围扩展到比原始范围大50%。
       * @param {Number}  factor 乘数的值,必须大于等于1
       * @returns {Extent} 返回扩大的范围
       * @example <caption><h7 id='expand'>按给定的因子扩大范围</h7></caption>
       * // ES5引入方式
       * const { Extent } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent } from "@mapgis/webclient-common"
       * const extent = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * extent.expand(1.5)
       */
      expand(factor) {
        if (typeof factor === 'number') {
          if (factor >= 1) {
            factor = Math.abs(factor)
            const width = this.width * factor
            const height = this.height * factor
            const widthSub = width - this.width
            const heightSub = height - this.height
            this.xmin -= widthSub / 2
            this.xmax += widthSub / 2
            this.ymin -= heightSub / 2
            this.ymax += heightSub / 2
          }
          return this
        } else {
          throw new Error('按照给定的因子扩大范围,需要的是一个num值')
        }
      }

      /**
       * 根据输入的dx, dy, dz值，平移extent
       * @param {Number}  dx 要平移的x值
       * @param {Number}  dy 要平移的y值
       * @param {Number}  dz 要平移的z值
       * @returns {Extent} 偏移后的范围
       * @example <caption><h7 id='offset'>平移extent</h7></caption>
       * // ES5引入方式
       * const { Extent } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent } from "@mapgis/webclient-common"
       * const extent = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * extent.offset(0, 1, 0)
       */
      offset(dx, dy, dz) {
        if (!isNumber(dx) || !isNumber(dy) || (this.hasZ && !isNumber(dz))) {
          throw new Error('传入参数错误')
        }
        this.xmax += dx
        this.xmin += dx
        this.ymax += dy
        this.ymin += dy
        if (this.hasZ) {
          this.zmax += dz
          this.zmin += dz
        }
        return this
      }

      /**
       * 求并
       * @param {Extent}  extent 输入的范围，用于并集
       * @returns {Extent} 返回输入Extent与原始Extent的并集
       * @example <caption><h7 id='union'>求并</h7></caption>
       * // ES5引入方式
       * const { Extent } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent } from "@mapgis/webclient-common"
       * const extent = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * const extent2 = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * const newExtent = extent.union(extent2)
       */
      union(extent) {
        if (!extent) throw new Error('传入参数错误')
        if (!this.hasZ) {
          return new Extent({
            xmin: extent.xmin < this.xmin ? extent.xmin : this.xmin,
            xmax: extent.xmax > this.xmax ? extent.xmax : this.xmax,
            ymin: extent.ymin < this.ymin ? extent.ymin : this.ymin,
            ymax: extent.ymax > this.ymax ? extent.ymax : this.ymax
          })
        }
        return new Extent({
          xmin: extent.xmin < this.xmin ? extent.xmin : this.xmin,
          xmax: extent.xmax > this.xmax ? extent.xmax : this.xmax,
          ymin: extent.ymin < this.ymin ? extent.ymin : this.ymin,
          ymax: extent.ymax > this.ymax ? extent.ymax : this.ymax,
          zmin: extent.zmin < this.zmin ? extent.zmin : this.zmin,
          zmax: extent.zmax > this.zmax ? extent.zmax : this.zmax
        })
      }

      /**
       * 返回如下格式的字符串："xmin,ymin,xmax,ymax"<a id='toString'></a>
       * @returns string
       */
      toString() {
        if (this.hasZ) {
          return `${this.xmin},${this.ymin},${this.zmin},${this.xmax},${this.ymax},${this.zmax}`
        }
        return `${this.xmin},${this.ymin},${this.xmax},${this.ymax}`
      }

      /**
       * 通过传入的json构造并返回一个新的几何对象
       * @param {Object} [json] JSON对象
       * @example <caption><h7 id='fromJSON'>通过传入的json构造并返回一个新的几何对象</h7></caption>
       * // ES5引入方式
       * const { Extent } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent } from "@mapgis/webclient-common"
       * const json = {
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * }
       * const extent = Extent.fromJSON(json)
       */
      static fromJSON(json) {
        json = defaultValue(json, {})
        return new Extent(json)
      }

      /**
       * <a id='toJSON'></a>
       * 导出为json对象
       * @return {Object} json对象
       */
      toJSON() {
        const json = super.toJSON()
        json.xmin = this.xmin
        json.ymin = this.ymin
        json.xmax = this.xmax
        json.ymax = this.ymax
        json.zmin = this.zmin
        json.zmax = this.zmax
        json.width = this.width
        json.height = this.height

        return json
      }

      /**
       * 返回如下格式的字符串："xmin$ymin$xmax$ymax"<a id='toIGSOldString'></a>
       * @returns string
       */
      toIGSOldString() {
        return `${this.xmin}$${this.ymin}$${this.xmax}$${this.ymax}`
      }

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

      /**
       * 归一化计算
       * @returns {Extent} 归一化后的点对象
       * @example <caption><h7 id='normalize'>归一化计算</h7></caption>
       * // ES5引入方式
       * const { Extent } = Zondy.Geometry
       * // ES6引入方式
       * import { Extent } from "@mapgis/webclient-common"
       * const extent = new Extent({
       *   xmin: 10,
       *   xmax: 210,
       *   ymin: 0,
       *   ymax: 100
       * })
       * const normalize = extent.normalize()
       */
      normalize() {
        let pointMin = new Point({
          coordinates: [this.xmin, this.ymin]
        })
        let pointMax = new Point({
          coordinates: [this.xmax, this.ymax]
        })

        pointMin = pointMin.normalize()
        pointMax = pointMax.normalize()
        const extent = new Extent({
          xmin: pointMin.coordinates[0],
          ymin: pointMin.coordinates[1],
          xmax: pointMax.coordinates[0],
          ymax: pointMax.coordinates[1]
        })

        return extent
      }

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

    Object.defineProperties(Extent.prototype, {
      /**
       * 宽度
       * @member {Number} Extent.prototype.width
       * */
      width: {
        configurable: false,
        get() {
          return this.xmax - this.xmin
        }
      },
      /**
       * 高度
       * @member {Number} Extent.prototype.height
       * */
      height: {
        configurable: false,
        get() {
          return this.ymax - this.ymin
        }
      },
      /**
       * 中心点
       * @member {Number[]} Extent.prototype.center
       * */
      center: {
        configurable: false,
        set(center) {
          this.centerAt(center)
        },
        get() {
          if (!this.hasZ) {
            return new Point({
              coordinates: [
                (this.xmax - this.xmin) / 2 + this.xmin,
                (this.ymax - this.ymin) / 2 + this.ymin
              ]
            })
          }
          return new Point({
            coordinates: [
              (this.xmax - this.xmin) / 2 + this.xmin,
              (this.ymax - this.ymin) / 2 + this.ymin,
              (this.zmax - this.zmin) / 2 + this.zmin
            ]
          })
        }
      }
    })

    Zondy.Geometry.Extent = Extent
    export default Extent