import cdt2d from 'cdt2d'
import _ from 'lodash'
import TileCache from './tile_cache'
import { getTileCoord, getTilePixelCoord, geographicToCartesian } from '../transform'
import { rgbaToFloat } from '../util'

class Measurement {
  constructor (options) {
    if (!options.loadTileImage) {
      throw new Error('未指定加载瓦片数据的方法')
    }
    this.tileCache = new TileCache({
      loadTileImage: options.loadTileImage,
      maxCacheSize: options.maxCacheSize || 200
    })
  }

  
  // 从dsm中获取指定位置的高程
  async getFloatValue (lngLat, options) {
    if (lngLat[2]) {
      // 如果传入的坐标本身带有高程，则不用去dsm中获取高程
      return _.round(lngLat[2], 4)
    }
    let {tilejson} = options
    if (!tilejson) {
      // 没有dsm
      return 0
    }
    let { max_zoom } = tilejson
    let { min, max } = tilejson.metadata
    let tileCoord = getTileCoord(max_zoom, lngLat)
    let pixelCoord = getTilePixelCoord(max_zoom, lngLat)
    let tile = this.tileCache.getTile(tileCoord, options)
    if (!tile) {
      tile = this.tileCache.addTile(tileCoord, options)
    }
    if (!tile.loaded) {
      await tile.tileData
    }
    if (!tile.tileData) {
      // 没找到瓦片，说明该点超出瓦片范围，默认给一个平均值
      return _.round((min + max) / 2, 4)
    }
    let pixelIndex = (pixelCoord.y * 512 + pixelCoord.x) * 4
    let value = rgbaToFloat([
      tile.tileData[pixelIndex],
      tile.tileData[pixelIndex + 1],
      tile.tileData[pixelIndex + 2],
      tile.tileData[pixelIndex + 3]
    ])
    if (Math.abs(value + 99999) <= Math.max(Math.abs(value), Math.abs(-99999)) * 0.0001) {
      // 无效值
      return _.round((min + max) / 2, 4)
    }
    return _.round(value, 4)
  }

  async getMeasureInfo (options) {
    let { data_type, geometry } = options
    let type = geometry.type
    let properties = {}
    if (type === 'Point') {
      // 点要素
      let value = await this.getFloatValue(geometry.coordinates, options)
      if (data_type === 'infrared') {
        // 红外
        properties.temprature = _.round(value, 4)
        properties.lng = geometry.coordinates[0]
        properties.lat = geometry.coordinates[1]
      } else {
        geometry.coordinates[2] = value
        properties.height = value
        properties.lng = geometry.coordinates[0]
        properties.lat = geometry.coordinates[1]
      }
    } else if (type === 'LineString') {
      let coordinates = geometry.coordinates
      let slopes = []
      let values = await Promise.all(coordinates.map(coord => this.getFloatValue(coord, options)))
      if (data_type === 'infrared') {
        // 红外
        properties.distance2d = this.getDistance2D(coordinates)
        properties.temprature_min = _.round(_.min(values), 4)
        properties.temprature_max = _.round(_.max(values), 4)
      } else {
        coordinates.forEach((coord, index) => {
          coord[2] = values[index]
          if (index > 0) {
            slopes.push(this.getSlope(coordinates[index - 1], coordinates[index]))
          }
        })
        properties.distance2d = this.getDistance2D(coordinates)
        properties.distance3d = this.getDistance3D(coordinates)
        properties.height_min = _.min(values)
        properties.height_max = _.max(values)
        properties.slope_min = _.min(slopes)
        properties.slope_max = _.max(slopes)
      }
    } else if (type === 'Polygon') {
      let coordinates = geometry.coordinates[0]
      let values = await Promise.all(coordinates.map(coord => this.getFloatValue(coord, options)))
      if (data_type === 'infrared') {
        // 红外
        properties.distance2d = this.getDistance2D(coordinates)
        properties.area2d = this.getArea(coordinates).area2d
        properties.temprature_min = _.round(_.min(values), 4)
        properties.temprature_max = _.round(_.max(values), 4)
      } else {
        coordinates.forEach((coord, index) => {
          coord[2] = values[index]
        })
        properties.distance2d = this.getDistance2D(coordinates)
        properties.distance3d = this.getDistance3D(coordinates)
        properties.height_min = _.min(values)
        properties.height_max = _.max(values)
        let area = this.getArea(coordinates)
        properties.area2d = area.area2d
        properties.area3d = area.area3d
      }
    }
    return { geometry, properties }
  }

  getSlope (from, to) {
    let d = this.getDistance3D([from, to])
    let hDiff = Math.abs(from[2] - to[2]) // 高差
    return _.round((Math.asin(hDiff / d) * 180) / Math.PI, 4)
  }

  getDistance2D (coordinates) {
    let d = 0
    for (let i = 1; i < coordinates.length; i++) {
      let p1 = geographicToCartesian([coordinates[i - 1][0], coordinates[i - 1][1], 0])
      let p2 = geographicToCartesian([coordinates[i][0], coordinates[i][1], 0])
      d += Math.sqrt(
        Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2) + Math.pow(p1[2] - p2[2], 2)
      )
    }
    return _.round(d, 4)
  }

  getDistance3D (coordinates) {
    let d = 0
    for (let i = 1; i < coordinates.length; i++) {
      let p1 = geographicToCartesian(coordinates[i - 1])
      let p2 = geographicToCartesian(coordinates[i])
      d += Math.sqrt(
        Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2) + Math.pow(p1[2] - p2[2], 2)
      )
    }
    return _.round(d, 4)
  }

  getArea (coordinates) {
    let triangles = []
    let flatCoordinates
    let projs = [coordinates.map(d => [d[0], d[1]]), coordinates.map(d => [d[0], d[2]]), coordinates.map(d => [d[1], d[2]])] // 三个投影平面
    let flatCoordinates2d = projs[0] // 2D地图的投影平面

    while (!triangles.length && projs.length) { // 寻找可成功分解的投影平面
      flatCoordinates = projs.shift()
      flatCoordinates = this._reduceCoordinates(flatCoordinates)
      let edges = []
      flatCoordinates.forEach((coord, index) => {
        if (index === flatCoordinates.length - 1) {
          edges.push([index, 0])
        } else {
          edges.push([index, index + 1])
        }
      })
      triangles = cdt2d(flatCoordinates, edges, { exterior: false })
    }

    let area2d = 0,
      area3d = 0
    triangles.forEach(triangle => {
      let [i1, i2, i3] = triangle
      area2d += this.getTriangleArea(
        [flatCoordinates2d[i1][0], flatCoordinates2d[i1][1], 0],
        [flatCoordinates2d[i2][0], flatCoordinates2d[i2][1], 0],
        [flatCoordinates2d[i3][0], flatCoordinates2d[i3][1], 0]
      )
      area3d += this.getTriangleArea(coordinates[i1], coordinates[i2], coordinates[i3])
    })
    return { area2d: _.round(area2d, 4), area3d: _.round(area3d, 4) }
  }

  // 海伦公式：空间中三角形三个顶点坐标(经纬度），计算三角形面积
  getTriangleArea (p1, p2, p3) {
    let a = this.getDistance3D([p1, p2])
    let b = this.getDistance3D([p2, p3])
    let c = this.getDistance3D([p3, p1])
    return Math.sqrt((a + b + c) * (a + b - c) * (a + c - b) * (b + c - a)) / 4
  }

  _reduceCoordinates (coords) { // 去重
    for(let i = 0;i < coords.length; i++) {
      let nextIndex = i === coords.length - 1 ? 0 : (i + 1)
      if (_.isEqual(coords[i], coords[nextIndex])) {
        coords.splice(i, 1)
        i--
      }
    }
    return coords
  }
}

export default Measurement