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