// eslint-disable-next-line import/no-extraneous-dependencies
import proj4 from 'proj4'
import {defaultValue, Projection, EPSG, Config} from '@mapgis/webclient-common'
import { Log, defined } from '@mapgis/webclient-common/util'
import {ServiceConfigRequestInterceptors, ServiceConfigToken} from '@mapgis/webclient-common/service/Utils';

/**
 * 从SpatialReference中获取TilingScheme对象给cesium使用
 * @param {SpatialReference} spatialReference 坐标系对象
 * @return {Object} TilingScheme对象
 * */
function getTilingSchemeBySpatialReference(
  spatialReference,
  extent = undefined,
  tileInfo = undefined
) {
  // 若spatialReference中的wkid未定义，则默认返回Web墨卡托瓦片平铺规则
  if (!defined(spatialReference.wkid)) {
    return new Cesium.WebMercatorTilingScheme()
  }

  // 若wkid定义，则根据wkid从EPSG中获取wkt
  const targetSpatialReference = EPSG.filter(function (item) {
    return item.id === spatialReference.wkid
  })

  // Array.prototype.filter方法会将符合条件的值通过数组返回
  // 若数组的长度为0，则说明没有匹配到wkt值，此时默认返回Web墨卡托瓦片平铺规则
  if (targetSpatialReference.length === 0) {
    Log.error('根据该空间参考系的wkid未匹配到wkt值')
    return new Cesium.WebMercatorTilingScheme()
  }

  const wkt = targetSpatialReference[0].strProject
  const wkid = spatialReference.wkid
  let tilingScheme = undefined

  switch (spatialReference.wkid) {
    case 4326:
    case 4490:
    case 4610:
    case 4214:
      // 若tileInfo定义但lods未定义，默认返回经纬度瓦片平铺规则，否则返回自定义瓦片平铺规则
      if (defined(tileInfo) && !defined(tileInfo.lods)) {
        tilingScheme = new Cesium.GeographicTilingScheme()
      } else {
        // 若extent未定义，则设置默认的extent值，默认为EPSG:4326的投影范围
        if (!defined(extent)) {
          extent = {
            xmin: -180,
            ymin: -90,
            xmax: 180,
            ymax: 90
          }
        }
        tilingScheme = new Cesium.CustomTilingScheme({
          wkt,
          wkid,
          proj4,
          tileInfo,
          numberOfLevelZeroTilesX: defined(tileInfo) ? 2 : 1,
          rectangleSouthwest: new Cesium.Cartesian2(extent.xmin, extent.ymin),
          rectangleNortheast: new Cesium.Cartesian2(extent.xmax, extent.ymax)
        })
      }
      break
    default:
      // 若tileInfo定义但lods未定义，默认返回Web墨卡托瓦片平铺规则，否则返回自定义瓦片平铺规则
      if (defined(tileInfo) && !defined(tileInfo.lods)) {
        tilingScheme = new Cesium.WebMercatorTilingScheme()
      } else {
        // 若extent未定义，则设置默认的extent值，默认为EPSG:3857的投影范围
        if (!defined(extent)) {
          extent = {
            xmin: -20037508.34,
            ymin: -20037508.34,
            xmax: 20037508.34,
            ymax: 20037508.34
          }
        }
        tilingScheme = new Cesium.CustomTilingScheme({
          wkt,
          wkid,
          proj4,
          tileInfo,
          rectangleSouthwest: new Cesium.Cartesian2(extent.xmin, extent.ymin),
          rectangleNortheast: new Cesium.Cartesian2(extent.xmax, extent.ymax)
        })
      }
  }

  return tilingScheme
}

/**
 * 获取图层在cesium中要添加的顺序
 * @param {Object} layer 基础图层对象
 * @param {Object} innerView cesium的view对象
 * */
function getCesiumLayerIndex(layer, innerView) {
  // 首先获取当前imageryLayers中图层数量
  let index = innerView.imageryLayers._layers.length
  // 找到第一个比当前图层顺序大且存在_realIndex属性的图层，并返回其序号，作为当前图层要添加的位置
  for (let i = 0; i < innerView.imageryLayers._layers.length; i++) {
    // layer._realIndex + 1是因为cesium第0个是底图，因此顺序从1开始
    if (
      innerView.imageryLayers._layers[i].hasOwnProperty('_realIndex') &&
      layer._realIndex + 1 < innerView.imageryLayers._layers[i]._realIndex
    ) {
      index = i
      break
    }
  }
  // 如果没有找到，则添加到最后
  return index
}

/**
 * 从layer中获取extent对象，如果不是4326坐标系，则进行投影
 * @param {Object} layer 基本图层对象
 * @return {Extent} 地图范围
 * */
function getExtent(layer) {
  let extent = layer.extent
  if (layer.spatialReference.wkid === 3857) {
    const min = Projection.projectMercatorToLonLat({
      x: extent.xmin,
      y: extent.ymin
    })
    const max = Projection.projectMercatorToLonLat({
      x: extent.xmax,
      y: extent.ymax
    })
    extent = JSON.parse(JSON.stringify(extent))
    extent.xmin = min.longitude < -180 ? -180 : min.longitude
    extent.ymin = min.latitude < -90 ? -90 : min.latitude
    extent.xmax = max.longitude > 180 ? 180 : max.longitude
    extent.ymax = max.latitude > 90 ? 90 : max.latitude
  }
  return extent
}

/**
 * 获取m3d构造参数
 * @param {Object} options 构造参数
 * @return {Object} m3d构造参数
 * */
function getM3DOptions(options) {
  const m3dOptions = {}
  m3dOptions.url = defaultValue(options.url, undefined)
  m3dOptions.id = defaultValue(options.id, undefined)
  m3dOptions.show = defaultValue(options.show, true)
  m3dOptions.autoReset = defaultValue(options.autoReset, true)
  m3dOptions.layerIndex = defaultValue(options.layerIndex, 0)
  m3dOptions.layerRenderIndex = defaultValue(options.layerRenderIndex, 0)
  return m3dOptions
}

/**
 * 获取地形参数
 * @param {Object} options 地形参数
 * @return {Object} 地形参数
 * */
function getTerrainOptions(options) {
  const terrainOptions = {}
  const range = options.range
  const terrainRange = {
    xMin: range.xmin,
    yMin: range.ymin,
    xMax: range.xmax,
    yMax: range.ymax
  }
  const range3D = options.range3D
  const terrainRange3D = {
    xMin: range3D.xmin,
    yMin: range3D.ymin,
    xMax: range3D.xmax,
    yMax: range3D.ymax,
    zMin: range3D.zmin,
    zMax: range3D.zmax
  }
  terrainOptions.scale = defaultValue(options.elevationScale, undefined)
  terrainOptions.range = terrainRange
  terrainOptions.range3D = terrainRange3D
  terrainOptions.requestVertexNormals = defaultValue(
    options.requestVertexNormals,
    false
  )
  terrainOptions.sceneIndex = 0
  terrainOptions.layerIndex = defaultValue(options.layerIndex, 0)
  terrainOptions.layerRenderIndex = defaultValue(options.layerRenderIndex, 0)
  terrainOptions.show = defaultValue(options.show, true)
  terrainOptions.autoReset = defaultValue(options.autoReset, true)
  return terrainOptions
}

/**
 * 添加地形图层
 * @param {View} innerView Cesium的view对象
 * @param {Object} options 构造参数
 * */
function addTerrainLayer(innerView, options) {
  options = defaultValue(options, {})
  // const autoReset = defaultValue(options.autoReset, true)
  const sceneIndex = options.sceneIndex
  const layerIndex = options.layerIndex
  const layerRenderIndex = options.layerRenderIndex
  const requestVertexNormals = defaultValue(options.requestVertexNormals, false)
  let terrainUrl
  if (options.url.indexOf('/SceneServer') > -1) {
    terrainUrl = `${options.url}/layers/${layerIndex}/terrains?level={z}&row={x}&col={y}&xDensity=65&yDensity=65&hasNormals=${requestVertexNormals}`
  } else {
    terrainUrl = `${options.url}/GetTerrain?sceneIndex=${sceneIndex}&layerIndex=${layerRenderIndex}&Level={z}&Row={x}&Col={y}&xdensity=65&ydensity=65&webGL=true&hasNormals=${requestVertexNormals}`
  }
  const terrainLayer = new Cesium.MapGISTerrainProvider({
    url: terrainUrl,
    range: options.range,
    scale: options.scale,
    requestVertexNormals,
    terrainColorTblInfo: options.terrainColorTblInfo,
    range3D: options.range3D,
    waterHeight: options.waterHeight
  })
  innerView.scene.terrainProvider = terrainLayer
  terrainLayer.type = 'terrain'
  return terrainLayer
}

/**
 * 添加M3D图层
 * @param {Object} mapGISM3DSetOptions M3D构造参数
 * @param {View} innerView Cesium的view对象
 * @param {SceneView} sceneView plugin的SceneView对象
 * @param {Function} callback 图层加载成功的回调函数
 * @param {Function} errorCallback 图层加载失败的回调函数
 * */
function addM3DLayer(
  mapGISM3DSetOptions,
  innerView,
  sceneView,
  callback,
  errorCallback
) {
  // 获取M3D构造参数
  mapGISM3DSetOptions = defaultValue(mapGISM3DSetOptions, {})
  // 如果有headers，则设置headers
  if (mapGISM3DSetOptions.headers) {
    mapGISM3DSetOptions.url = new Cesium.Resource({
      url: mapGISM3DSetOptions.url,
      headers: mapGISM3DSetOptions.headers
    })
  }

  // 保存cesium的scene对象
  mapGISM3DSetOptions._scene = innerView.scene
  // 添加图层
  const m3dLayer = innerView.scene.primitives.add(
    new Cesium.MapGISM3DSet(mapGISM3DSetOptions)
  )
  // 添加图层到图层管理容器中
  sceneView._layersMap.put(mapGISM3DSetOptions.id, m3dLayer)
  // 开始请求图层数据
  m3dLayer.readyPromise
    .then(function (tileSet) {
      tileSet.id = mapGISM3DSetOptions.id
      tileSet.type = 'm3d'
      // 执行加载完成回调，发送图层添加事件
      if (callback && typeof callback === 'function') {
        callback(tileSet)
      }
    })
    .otherwise(function (result) {
      // 加载失败回调
      if (errorCallback && typeof errorCallback === 'function') {
        errorCallback(result)
      }
    })
}

/**
 * 根据M3D的外包盒进行跳转
 * @param {Object} boundingSphere M3D的外包球
 * @param {Object} options 跳转参数
 * @param {View} innerView Cesium的View对象
 * */
function zoomToM3DLayer(boundingSphere, options, innerView) {
  options = defaultValue(options, {})
  const duration = defaultValue(options.duration, 0)
  const orientation = defaultValue(
    options.orientation,
    new Cesium.HeadingPitchRange(0.0, -0.5, boundingSphere.radius * 2.5)
  )
  innerView.scene.camera.flyToBoundingSphere(boundingSphere, {
    duration,
    offset: orientation
  })
  innerView.scene.camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
}

/**
 * 添加注记图层
 * @param {String} url     服务地址
 * @param {Object} options 可选参数
 * @param {Object} layerInfo 从IGS查询到的图层的信息，如最大最小比例尺，避让等
 * @param {View} innerView Cesium的view对象
 */
function appendLabelLayer(url, options, layerInfo, innerView) {
  options = defaultValue(options, {})

  options.url = url

  let dataType = undefined
  if (url.indexOf('igs/rest/mrfs') !== -1) {
    dataType = 'MapGIS2D'
  }
  if (url.indexOf('igs/rest/g3d') !== -1) {
    dataType = 'MapGIS3D'
  }

  let layer = {}
  switch (dataType) {
    case 'MapGIS2D':
      layer = new Cesium.MapGISLabelLayer(
        url,
        innerView.scene,
        options,
        layerInfo
      )
      break
    case 'MapGIS3D':
      options.is3d = true
      layer = new Cesium.MapGISLabelLayer(
        url,
        innerView.scene,
        options,
        layerInfo
      )
      break
    default:
      break
  }
  layer.type = 'label'
  return layer
}

function formatServiceConfigToUrl(url) {
  const tokenStr = `${ServiceConfigToken(Config)}`
  const _url = ServiceConfigRequestInterceptors(
    Config,
    `${url}?${tokenStr}`
  )
  return _url
}

export {
  getTilingSchemeBySpatialReference,
  getCesiumLayerIndex,
  getExtent,
  getM3DOptions,
  getTerrainOptions,
  addTerrainLayer,
  addM3DLayer,
  zoomToM3DLayer,
  appendLabelLayer,
  formatServiceConfigToUrl
}
