    import proj4 from 'proj4'
    import epsg from './EPSG'
    import { Log, defaultValue } from '../util'
    import Zondy from './Zondy'
    import {
      Circle,
      Extent,
      LineString,
      MultiLineString,
      MultiPoint,
      MultiPolygon,
      Point,
      Polygon,
      SpatialReference
    } from './geometry'
    import GeometryType from './enum/GeometryType'

    /**
     * 投影转换工具
     * @class Projection
     * @moduleEX GeometryModule
     * */
    class Projection {}

    /**
     * 将墨卡托的点转为经纬度
     * @private
     * @param {Object} mercator 墨卡托点坐标，{x: 0, y: 0}
     * @return {Object} 经纬度坐标， { longitude: 0, latitude: 0 }
     * */
    Projection.projectMercatorToLonLat = function (mercator) {
      const lonlat = { longitude: 0, latitude: 0 }

      const x = (mercator.x / 20037508.34) * 180
      let y = (mercator.y / 20037508.34) * 180

      y =
        (180 / Math.PI) *
        (2 * Math.atan(Math.exp((y * Math.PI) / 180)) - Math.PI / 2)

      lonlat.longitude = x
      lonlat.latitude = y

      return lonlat
    }

    /**
     * 将任意坐标系的点坐标转为经纬度(EPSG:4326)坐标
     * @private
     * @param {Object} options 投影参数
     * @param {Array} [options.points = []] 要投影的点数组。[[x, y], [x, y],...]
     * @param {String | Number} [options.wkid] wkid号
     * @param {String} [options.proj4Defs] 可选参数，EPSG的椭球体参数，本工具内置了部分EPSG号，如果有EPSG号未收录，请在EPSG官网：https://epsg.io，查找并通过该参数传入
     * @return {Array} 投影后的点坐标数组
     * @example <caption><h7>将4547坐标系的点转为4326坐标系的点</h7></caption>
     * const points = Projection.projectToGeographyPoints({
     *   points: [[-45257.10778559791, 3212885.1836444484]],
     *   wkid: 4547
     * })
     * @example <caption><h7>将未收录的坐标系的点转为4326坐标系的点</h7></caption>
     * const points = Projection.projectToGeographyPoints({
     *   points: [[-45257.10778559791, 3212885.1836444484]],
     *   // 定义了椭球体参数，请在WPSG官网查询
     *   proj4Defs: '+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs'
     * })
     * */
    Projection.projectToGeographyPoints = function (options) {
      options = defaultValue(options, {})
      // 要投影的点数组
      const points = defaultValue(options.points, [])
      // 目的坐标系的wkid
      const wkid = defaultValue(options.wkid, -1)
      // 目的坐标系的椭球体参数
      const proj4Defs = defaultValue(options.proj4Defs, undefined)
      // 投影后的点数组
      const projPoints = []

      let defs = undefined
      if (proj4Defs) {
        defs = proj4Defs
      } else {
        const epsgObj = Projection.getEPSGByWKID(wkid)
        if (epsgObj) {
          defs = epsgObj.strProject
        }
      }

      if (defs) {
        for (let i = 0; i < points.length; i++) {
          projPoints.push(
            proj4(defs, '+proj=longlat +datum=WGS84 +no_defs +type=crs', points[i])
          )
        }
      }

      return projPoints
    }

    /**
     * 将点数组从一个坐标系投影到另一个坐标系
     * @private
     * @param {Object} options 投影参数
     * @param {String | Number} [options.originWKID] 点数组所在的原始坐标系的wkid
     * @param {String | Number} [options.targetWKID] 目的坐标系的wkid
     * @param {Array} [options.points = []] 要投影的点数组
     * @param {String} [options.originDefs] 可选参数，若一个坐标系未收录，则请在EPSG官网：https://epsg.io查询原始坐标系的Defs
     * @param {String} [options.targetDefs] 可选参数，若一个坐标系未收录，则请在EPSG官网：https://epsg.io查询目的坐标系的Defs
     * @example <caption><h7>将3857坐标系的点转为4326坐标系的点</h7></caption>
     * const points = Projection.projectPoints({
     *   originWKID: 3857,
     *   targetWKID: 4326,
     *   points: [[12062959.621822732, 3379793.1381245172]]
     * })
     * @example <caption><h7>将未收录的坐标系的点转为4326坐标系的点</h7></caption>
     * const points = Projection.projectPoints({
     *   // 定义了椭球体参数，请在WPSG官网查询
     *   originDefs: '+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs',
     *   targetWKID: 4326,
     *   points: [[-45257.10778559791, 3212885.1836444484]]
     * })
     * */
    Projection.projectPoints = function (options) {
      options = defaultValue(options, {})
      // 要投影的点数组
      const points = defaultValue(options.points, [])
      // 原始坐标系的wkid
      const originWKID = defaultValue(options.originWKID, -1)
      // 原始坐标系的椭球体参数
      const originDefs = defaultValue(options.originDefs, undefined)
      // 目的坐标系的wkid
      const targetWKID = defaultValue(options.targetWKID, -1)
      // 目的坐标系的椭球体参数
      const targetDefs = defaultValue(options.targetDefs, undefined)
      // 要获取的原始坐标系椭球体参数
      let _originDefs = undefined
      // 要获取的目的坐标系椭球体参数
      let _targetDefs = undefined
      // 投影后的点数组
      const projPoints = []

      if (originDefs) {
        _originDefs = originDefs
      } else {
        const epsgObj = Projection.getEPSGByWKID(originWKID)
        if (epsgObj) {
          _originDefs = epsgObj.strProject
        }
      }

      if (targetDefs) {
        _targetDefs = targetDefs
      } else {
        const epsgObj = Projection.getEPSGByWKID(targetWKID)
        if (epsgObj) {
          _targetDefs = epsgObj.strProject
        }
      }

      if (_originDefs && _targetDefs) {
        for (let i = 0; i < points.length; i++) {
          projPoints.push(proj4(_originDefs, _targetDefs, points[i]))
        }
      }

      return projPoints
    }

    /**
     * 根据wkid获取epsg对象
     * @private
     * @param {Number | String} wkid wkid号
     * @return {Object} EPSG参数对象
     * */
    Projection.getEPSGByWKID = function (wkid) {
      let _epsg = undefined
      for (let i = 0; i < epsg.length; i++) {
        if (String(epsg[i].id) === String(wkid)) {
          _epsg = epsg[i]
          break
        }
      }
      if (!_epsg) {
        Log.error(`epsg未定义wkid为${wkid}的坐标系信息`)
      }
      return _epsg
    }

    /**
     * 将几何对象投影到指定坐标系中
     * @private
     * @param {Geometry | Array<geometry>} geometry 要投影的几何或几何数组
     * @param {SpatialReference} outSpatialReference 目标参考系
     * @return {Geometry | Array<Geometry>} 投影后的几何对象
     * */
    Projection.projectGeometry = function (geometry, outSpatialReference) {
      let coordinates = []
      const projectOptions = {
        originWKID: geometry.spatialReference.wkid,
        originDefs: geometry.spatialReference.wkt,
        targetWKID: outSpatialReference.wkid,
        targetDefs: outSpatialReference.wkt
      }
      switch (geometry.type) {
        case GeometryType.point:
          coordinates = [geometry.coordinates]
          projectOptions.points = coordinates
          coordinates = Projection.projectPoints(projectOptions)
          return new Point({
            coordinates: coordinates[0],
            spatialReference: new SpatialReference(outSpatialReference)
          })
        case GeometryType.multiPoint:
          coordinates = geometry.coordinates
          projectOptions.points = coordinates
          coordinates = Projection.projectPoints(projectOptions)
          return new MultiPoint({
            coordinates,
            spatialReference: new SpatialReference(outSpatialReference)
          })
        case GeometryType.lineString:
          coordinates = geometry.coordinates
          projectOptions.points = coordinates
          coordinates = Projection.projectPoints(projectOptions)
          return new LineString({
            coordinates,
            spatialReference: new SpatialReference(outSpatialReference)
          })
        case GeometryType.multiLineString:
          coordinates = geometry.coordinates
          const multiLines = []
          for (let i = 0; i < coordinates.length; i++) {
            projectOptions.points = coordinates[i]
            multiLines.push(Projection.projectPoints(projectOptions))
          }
          return new MultiLineString({
            coordinates: multiLines,
            spatialReference: new SpatialReference(outSpatialReference)
          })
        case GeometryType.polygon:
          coordinates = geometry.coordinates
          const polygonRings = []
          for (let i = 0; i < coordinates.length; i++) {
            projectOptions.points = coordinates[i]
            polygonRings.push(Projection.projectPoints(projectOptions))
          }
          return new Polygon({
            coordinates: polygonRings,
            spatialReference: new SpatialReference(outSpatialReference)
          })
        case GeometryType.multiPolygon:
          coordinates = geometry.coordinates
          const multiPolygons = []
          for (let i = 0; i < coordinates.length; i++) {
            const polygonRings = []
            for (let j = 0; j < coordinates[i].length; j++) {
              projectOptions.points = coordinates[i][j]
              polygonRings.push(Projection.projectPoints(projectOptions))
            }
            multiPolygons.push(polygonRings)
          }
          return new MultiPolygon({
            coordinates: multiPolygons,
            spatialReference: new SpatialReference(outSpatialReference)
          })
        case GeometryType.circle:
          coordinates = [
            [geometry.center[0], geometry.center[1], geometry.center[2]],
            [
              geometry.center[0] + geometry.radius,
              geometry.center[1] + geometry.radius,
              geometry.center[2]
            ]
          ]
          projectOptions.points = coordinates
          coordinates = Projection.projectPoints(projectOptions)
          const radius = coordinates[1][0] - coordinates[0][0]
          return new Circle({
            center: coordinates[0],
            radius
          })
        case GeometryType.extent:
          coordinates = [
            [geometry.xmin, geometry.ymin, geometry.zmin],
            [geometry.xmax, geometry.ymax, geometry.zmax]
          ]
          projectOptions.points = coordinates
          coordinates = Projection.projectPoints(projectOptions)
          return new Extent({
            xmin: coordinates[0][0],
            ymin: coordinates[0][1],
            zmin: coordinates[0][2],
            xmax: coordinates[1][0],
            ymax: coordinates[1][1],
            zmax: coordinates[1][2],
            spatialReference: new SpatialReference(outSpatialReference)
          })
        default:
          return geometry
      }
    }

    /**
     * 将几何对象投影到指定坐标系中
     * @param {Geometry | Array<geometry>} geometry 要投影的几何或几何数组
     * @param {SpatialReference} outSpatialReference 目标参考系
     * @return {Geometry | Array<Geometry>} 投影后的几何对象
     * @example <caption><h7>将3857坐标系的点转为4326坐标系的点</h7></caption>
     * const projectedGeometry = Zondy.Geometry.Projection.project(
     *   new Zondy.Geometry.Extent({
     *     xmin: 12062959.621822732,
     *     ymin: 3379793.138124517,
     *     xmax: 12927637.10614421,
     *     ymax: 3931626.287996913,
     *     spatialReference: new Zondy.SpatialReference({
     *       wkid: 3857
     *     })
     *   }),
     *   new Zondy.SpatialReference({
     *     wkid: 4326
     *   })
     * )
     * @example <caption><h7>将未收录的坐标系的点转为4326坐标系的点</h7></caption>
     * const projectedGeometry = Zondy.Geometry.Projection.project(
     *   new Zondy.Geometry.Extent({
     *     xmin: -45257.10778559791,
     *     ymin: 3212885.1836444484,
     *     xmax: 705989.8953363781,
     *     ymax: 3691623.86404564,
     *     spatialReference: new Zondy.SpatialReference({
     *       wkid: 4547,
     *       wkt: '+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs'
     *     })
     *   }),
     *   new Zondy.SpatialReference({
     *     wkid: 4326
     *   })
     * )
     * */
    Projection.project = function (geometry, outSpatialReference) {
      if (geometry instanceof Array) {
        const projectGeometries = []
        for (let i = 0; i < geometry.length; i++) {
          projectGeometries.push(
            Projection.projectGeometry(geometry[i], outSpatialReference)
          )
        }
        return projectGeometries
      } else {
        // 4326坐标系下，获取有效范围内经度。proj纬度最大为 89.99999999
        if (geometry.spatialReference.isGeographic) {
          const _validLat = (lat) => {
            const projLimit = 89.99999999
            if (lat > projLimit) {
              lat = projLimit
            } else if (lat < -projLimit) {
              lat = -projLimit
            }
            return lat
          }
          if (geometry.coordinates && geometry.coordinates.length > 1) {
            geometry.coordinates[1] = _validLat(geometry.coordinates[1])
          }
          geometry.ymax = _validLat(geometry.ymax)
          geometry.ymin = _validLat(geometry.ymin)
        }
        return Projection.projectGeometry(geometry, outSpatialReference)
      }
    }

    Zondy.Geometry.Projection = Projection
    Zondy.Geometry.Proj4 = proj4
    export default Projection