import MVT from 'ol/format/MVT'
import ReplayGroup from 'ol/render/canvas/ReplayGroup'
import { renderFeature } from 'ol/renderer/vector'
import { Extent, Point } from '@mapgis/webclient-common'
import Font from './FontUtil'

import { TileQueue } from './MapgisVectorTileQueue'

const MapGisCesiumResolution = [
  78271.51696402048, 39135.75848201024, 19567.87924100512, 9783.93962050256,
  4891.96981025128, 2445.98490512564, 1222.99245256282, 611.49622628141,
  305.748113140705, 152.8740565703525, 76.43702828517625, 38.21851414258813,
  19.109257071294063, 9.554628535647032, 4.777314267823516, 2.388657133911758,
  1.194328566955879, 0.5971642834779395, 0.29858214173896974,
  0.14929107086948487, 0.07464553543474244
]

function tranTextToLabelStyle(text) {
  const pointLabelSymbol = {
    type: 'label-symbol',
    // 填充颜色 rgba or 16进制颜色
    color: 'rgba(255,255,255,1)',
    // 描边颜色
    haloColor: 'rgba(0,0,0,0.5)',
    // 描边宽度
    haloSize: 2,
    // 行高
    lineHeight: 1.1,
    // 行宽
    lineWidth: 80,
    // 最大行数
    lineMaxNum: 3,
    // 角度
    angle: 0,
    // 换行
    textWraps: true,
    // 文本间距
    letterSpacing: '2px',
    // x方向偏移
    xoffset: 0,
    // y方向偏移
    yoffset: 0,
    // 字体样式 参考css
    font: {
      size: 12,
      family: '微软雅黑',
      weight: 'normal',
      style: 'normal'
    },
    // 是否开启填充背景
    showBackground: false,
    // 背景填充色
    backgroundColor: 'rgba(42,42,42,1)',
    // 背景边距 number or array
    backgroundPadding: [5, 7],
    // 背景图片 支持image对象、function
    backgroundImage: undefined,
    // 是否开启图标
    showTextExtraIcon: false,
    // 图标图片 支持image对象
    textExtraIcon: undefined,
    // 图标方位 "left" "right" "top" "bottom"
    textExtraIconAnchor: 'top',
    // 图标大小
    textExtraIconSize: 14,
    // 是否开启阴影
    showTextShadow: false,
    textShadowOffsetX: 1,
    textShadowOffsetY: 1,
    textShadowColor: '#ffffff',
    textShadowBlur: 2,
    // 文字删除线
    showTextDecorationThroughline: false,
    textDecorationThroughlineColor: 'rgba(255,255,255,1)',
    textDecorationThroughlineWidth: 1,
    // 文字下划线
    showTextDecorationUnderline: false,
    textDecorationUnderlineColor: 'rgba(255,255,255,1)',
    textDecorationUnderlineWidth: 1
  }

  const styles = {}

  if (text) {
    styles.text = text.getText()
    // 解析文字
    const fontString = text.getFont()
    const font = Font.parse(fontString, {
      fontSize: '12px',
      fontFamily: '微软雅黑',
      fontWeight: 'normal',
      fontStyle: 'normal'
    })
    pointLabelSymbol.font = {
      size: parseFloat(font.fontSize),
      family: font.fontFamily,
      weight: font.fontWeight,
      style: font.fontStyle
    }
    // 解析offset
    pointLabelSymbol.xoffset = text.getOffsetX()
    pointLabelSymbol.yoffset = text.getOffsetY()
    // 解析缩放倍率
    styles.scale = text.getScale() || 1
    // 解析旋转
    pointLabelSymbol.angle = (text.getRotation() * 180) / Math.PI
    // 解析文字对齐方式
    const textAlignMap = {
      left: 'above-left',
      right: 'above-right',
      center: 'above-center',
      start: 'above-left',
      end: 'above-right'
    }
    if (textAlignMap[text.getTextAlign()]) {
      styles.labelPlacement = textAlignMap[text.getTextAlign()]
    }
    // 填充颜色
    const fill = text.getFill()
    if (fill) {
      const fillColorArr = fill.getColor()
      if (fillColorArr) {
        pointLabelSymbol.color = `rgba(${fillColorArr[0] * 255},${
          fillColorArr[1] * 255
        },${fillColorArr[2] * 255},${fillColorArr[3]})`
      }
    }
    // 描边颜色
    const stroke = text.getStroke()
    if (stroke) {
      const strokeColorArr = stroke.getColor()
      const strokeWidth = stroke.getWidth()
      if (strokeColorArr) {
        pointLabelSymbol.haloColor = `rgba(${strokeColorArr[0] * 255},${
          strokeColorArr[1] * 255
        },${strokeColorArr[2] * 255},${strokeColorArr[3]})`
        pointLabelSymbol.haloSize = strokeWidth
      }
    }
    // 背景颜色
    const background = text.getBackgroundFill()
    if (background) {
      const backgroundColorArr = background.getColor()
      if (backgroundColorArr) {
        pointLabelSymbol.showBackground = true
        pointLabelSymbol.backgroundColor = `rgba(${
          backgroundColorArr[0] * 255
        },${backgroundColorArr[1] * 255},${backgroundColorArr[2] * 255},${
          backgroundColorArr[3]
        })`
      }
    }
  }
  styles.symbol = pointLabelSymbol
  styles.heightReference = 1
  return styles
}

export default function VectorTileProvider(Cesium, options) {
  function MVTProvider(options) {
    options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT)
    const self = this
    this.show = Cesium.defined(options.show) ? options.show : true
    this._tilingScheme = Cesium.defined(options.tilingScheme)
      ? options.tilingScheme
      : new Cesium.WebMercatorTilingScheme({ ellipsoid: options.ellipsoid })
    this._tileWidth = Cesium.defaultValue(options.tileWidth, 512)
    this._tileHeight = Cesium.defaultValue(options.tileHeight, 512)
    this._readyPromise = Cesium.when.resolve(true)
    this._mvtParser = new MVT()
    this.scene = options.scene

    this.options = options
    // this._mvtParser = new this._ol.format.MVT();

    this._styleFun = options.style
      ? options.style
      : createMapboxStreetsV6Style()
    this._key = Cesium.defaultValue(options.key, '')
    this._url = Cesium.defaultValue(
      options.url,
      'https://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/{z}/{x}/{y}.vector.pbf?access_token={k}'
    )

    // var sw = this._tilingScheme._rectangleSouthwest;
    // var ne = this._tilingScheme._rectangleNortheast;
    // var mapExtent = [sw.x, sw.y, ne.x, ne.y];
    this._resolutions = MapGisCesiumResolution
    // this._resolutions = ol.tilegrid.resolutionsFromExtent(mapExtent, 22, this._tileWidth);

    this._pixelRatio = 1
    this._transform = [0.125, 0, 0, 0.125, 0, 0]
    this._replays = ['Default', 'Image', 'Polygon', 'LineString', 'Text']

    this._tileQueue = new Cesium.TileReplacementQueue()
    this._tileSends = new TileQueue()
    this._cacheSize = 1024
    this._maximumLevel = options.maximumLevel || 20
    this._minimumLevel = options.minimumLevel || 0

    this._labelsRenderMode = options.labelsRenderMode
    this._labelsNoteStyle = options.labelsNoteStyle

    // 视图类
    this._dynamicEntityVisualizer = Cesium.defined(
      options.dynamicEntityVisualizer
    )
      ? options.dynamicEntityVisualizer
      : null

    this._cacheCollection = {}
    this._cacheCollectionIndex = 0
    // 服务限制区域
    this._wgs84Extent = options.wgs84Extent
  }

  Object.defineProperties(MVTProvider.prototype, {
    proxy: {
      get() {
        return undefined
      }
    },

    tileWidth: {
      get() {
        return this._tileWidth
      }
    },

    tileHeight: {
      get() {
        return this._tileHeight
      }
    },

    maximumLevel: {
      get() {
        return this._maximumLevel
      }
    },
    minimumLevel: {
      get() {
        return this._minimumLevel
      }
    },

    tilingScheme: {
      get() {
        return this._tilingScheme
      }
    },

    rectangle: {
      get() {
        return this._tilingScheme.rectangle
      }
    },

    tileDiscardPolicy: {
      get() {
        return undefined
      }
    },

    errorEvent: {
      get() {
        return this._errorEvent
      }
    },

    ready: {
      get() {
        return true
      }
    },

    readyPromise: {
      get() {
        return this._readyPromise
      }
    },

    credit: {
      get() {
        return undefined
      }
    },

    hasAlphaChannel: {
      get() {
        return true
      }
    }
  })

  MVTProvider.prototype.getTileCredits = function (x, y, level) {
    return undefined
  }

  function findTileInQueue(x, y, level, tileQueue) {
    let item = tileQueue.head
    while (
      item != undefined &&
      !(item.xMvt == x && item.yMvt == y && item.zMvt == level)
    ) {
      item = item.replacementNext
    }
    return item
  }

  function remove(tileReplacementQueue, item) {
    const previous = item.replacementPrevious
    const next = item.replacementNext

    if (item === tileReplacementQueue._lastBeforeStartOfFrame) {
      tileReplacementQueue._lastBeforeStartOfFrame = next
    }

    if (item === tileReplacementQueue.head) {
      tileReplacementQueue.head = next
    } else {
      previous.replacementNext = next
    }

    if (item === tileReplacementQueue.tail) {
      tileReplacementQueue.tail = previous
    } else {
      next.replacementPrevious = previous
    }

    item.replacementPrevious = undefined
    item.replacementNext = undefined

    --tileReplacementQueue.count
  }

  function trimTiles(tileQueue, cacheCollection, maximumTiles) {
    let tileToTrim = tileQueue.tail
    while (tileQueue.count > maximumTiles && Cesium.defined(tileToTrim)) {
      const previous = tileToTrim.replacementPrevious
      remove(tileQueue, tileToTrim)
      removeLabel(tileToTrim, cacheCollection)
      // delete tileToTrim; 严格模式下不能删除
      tileToTrim = null
      tileToTrim = previous
    }
  }

  function removeLabel(tile, cacheCollection) {
    if (!tile || !cacheCollection || !tile._mvtLabelDetail) return
    const labelDetail = tile._mvtLabelDetail

    const keys = Object.keys(labelDetail)

    keys.forEach(function (key) {
      const collection = cacheCollection[key]
      if (!collection || !Array.isArray(labelDetail[key])) return
      labelDetail[key].forEach(function (item) {
        collection.remove(item)
      })
    })
    tile._isLabelRemove = true
  }

  function addLabel(tile, cacheCollection) {
    if (!tile || !cacheCollection || !tile._mvtLabelDetail) return
    if (!tile._isLabelRemove) return
    const labelDetail = tile._mvtLabelDetail

    const keys = Object.keys(labelDetail)

    keys.forEach(function (key) {
      const collection = cacheCollection[key]
      if (!collection || !Array.isArray(labelDetail[key])) return
      labelDetail[key].forEach(function (item) {
        collection.add(item)
      })
    })
    tile._isLabelRemove = false
  }

  function createLayerLabelCollection(layerName, renderType, scene) {
    let collection = undefined
    if (renderType === 'label') {
      collection = new Cesium.LabelCollection({
        blendOption: Cesium.BlendOption.OPAQUE_AND_TRANSLUCENT,
        scene
      })
    } else if (renderType === 'ground') {
      collection = new Cesium.PrimitiveCollection({})
    } else {
      collection = new Cesium.BillboardCollection({
        blendOption: Cesium.BlendOption.OPAQUE_AND_TRANSLUCENT,
        scene
      })
    }
    collection.__labelLayerName = layerName
    return collection
  }
  MVTProvider.prototype.requestImage = function (x, y, level, request) {
    if (this._tilingScheme._rectangleSouthwest == undefined) {
      level += 1
    }
    const isSend = this._tileSends.has({
      x,
      y,
      z: level,
      id: this.options.threadId
    })
    if (isSend) {
      const cacheTile = findTileInQueue(x, y, level, this._tileQueue)
      if (cacheTile != undefined) {
        addLabel(cacheTile, this._cacheCollection)
        return cacheTile
      } else {
        this._tileSends.remove(x, y, level)
      }
    } else {
      this._tileSends.push({ x, y, z: level, id: this.options.threadId })
      this.drawTile(x, y, level)
    }
  }

  MVTProvider.prototype.drawTile = function (x, y, level) {
    const self = this
    let url = this._url
    url = url
      .replace('{x}', x)
      .replace('{y}', y)
      .replace('{z}', level)
      .replace('{k}', this._key)
    if (this._maximumLevel && level > this._maximumLevel) {
      return null
    }

    const tilerequest = (function (x, y, z) {
      const resource = Cesium.Resource.createIfNeeded(url)
      if (z < 12) {
        /* var canvas = document.createElement('canvas');
        return canvas; */
      }
      return resource
        .fetchArrayBuffer()
        .then(function (arrayBuffer) {
          try {
            const canvas = document.createElement('canvas')
            canvas.width = 512
            canvas.height = 512

            canvas.xMvt = x
            canvas.yMvt = y
            canvas.zMvt = z
            self._tileQueue.markTileRendered(canvas)

            const vectorContext = canvas.getContext('2d')

            const features = self._mvtParser.readFeatures(arrayBuffer)

            const styleFun = self._styleFun

            const extent = [0, 0, 4096, 4096]

            let _replayGroup = new ReplayGroup(0, extent, 8, true, 512) // 100->512 流畅了很多

            const otherFeatures = []

            if (self._labelsRenderMode === 'on-screen') {
              var mvtLabelDetail = {}
              canvas._mvtLabelDetail = mvtLabelDetail
            }

            for (let i = 0; i < features.length; i++) {
              const feature = features[i]
              if (
                feature.type_ === 'Point' &&
                self._labelsRenderMode === 'on-screen'
              ) {
                const styles = styleFun(feature, self._resolutions[level])
                self._parseFeature(
                  mvtLabelDetail,
                  feature,
                  styles,
                  x,
                  y,
                  level,
                  extent
                )
              } else {
                otherFeatures.push(feature)
              }
            }

            otherFeatures.forEach((feature) => {
              const styles = styleFun(feature, self._resolutions[level])
              for (let j = 0; j < styles.length; j++) {
                renderFeature(_replayGroup, feature, styles[j], 16)
              }
            })

            _replayGroup.finish()
            _replayGroup.replay(
              vectorContext,
              self._transform,
              0,
              {},
              self._replays
            )
            // _replayGroup.replay(vectorContext, self._transform, 0, {}, self._pixelRatio, self._replays, true);

            if (self._tileQueue.count > self._cacheSize) {
              trimTiles(
                self._tileQueue,
                self._cacheCollection,
                self._cacheSize / 2
              )
            }

            // delete _replayGroup; 严格模式下不能删除
            _replayGroup = null

            return canvas
          } catch (err) {
            console.log('err: ', err)
          }
        })
        .otherwise(function (error) {
          this._tileSends.remove(x, y, level)
          return null
        })
    })(x, y, level)
  }
  MVTProvider.prototype.pickFeatures = function (
    x,
    y,
    level,
    longitude,
    latitude
  ) {
    return undefined
  }

  MVTProvider.prototype._parseFeature = function (
    mvtObject,
    feature,
    styles,
    x,
    y,
    level,
    extent
  ) {
    if (this._tilingScheme && this._tilingScheme.tileXYToRectangle) {
      const offset = this._tilingScheme._rectangleSouthwest ? 0 : 1
      const rect = this._tilingScheme.tileXYToRectangle(x, y, level - offset)
      if (!rect) return
      const properties_ = feature.properties_

      if (!properties_) return

      if (!styles || (Array.isArray(styles) && styles.length === 0)) return

      let layerName = properties_.layer
      if (!properties_.layer) {
        layerName = '未定义图层'
      }
      let collection

      if (this._cacheCollection[layerName]) {
        collection = this._cacheCollection[layerName]
      } else {
        collection = createLayerLabelCollection(layerName, '', this.scene)
        this._cacheCollection[layerName] = collection
        this._dynamicEntityVisualizer.add(
          collection,
          this._cacheCollectionIndex
        )
        this._cacheCollectionIndex += 1
      }

      const northwest = Cesium.Rectangle.northwest(rect)
      const southeast = Cesium.Rectangle.southeast(rect)

      const textStyles = styles[0].getText()

      let labelStyle = tranTextToLabelStyle(textStyles)
      // 合并样式
      if (this._labelsNoteStyle) {
        // eslint-disable-next-line prefer-object-spread
        const symbol = Object.assign({}, labelStyle.symbol,this._labelsNoteStyle.symbol || {})
        // eslint-disable-next-line prefer-object-spread
        const style =  Object.assign({}, labelStyle, this._labelsNoteStyle || {}) 
        style.symbol = symbol
        labelStyle = style
      }

      if (
        feature.flatCoordinates_ &&
        Array.isArray(feature.flatCoordinates_) &&
        feature.flatCoordinates_.length === 2 &&
        labelStyle.text
      ) {
        const relativePoint = feature.flatCoordinates_
        const maxX = extent[2]
        const maxY = extent[3]

        const lngRadian =
          ((Cesium.Math.toDegrees(southeast.longitude) -
            Cesium.Math.toDegrees(northwest.longitude)) *
            relativePoint[0]) /
            maxX +
          Cesium.Math.toDegrees(northwest.longitude)
        const latRadian =
          ((Cesium.Math.toDegrees(southeast.latitude) -
            Cesium.Math.toDegrees(northwest.latitude)) *
            relativePoint[1]) /
            maxY +
          Cesium.Math.toDegrees(northwest.latitude)
        // 超过瓦片范围不解析
        if (relativePoint[0] > maxX || relativePoint[1] > maxY) return
        const point = Cesium.Cartographic.toCartesian(
          new Cesium.Cartographic(
            Cesium.Math.toRadians(lngRadian),
            Cesium.Math.toRadians(latRadian),
            0
          )
        )

        try {
          const labelParam =
            Cesium.DynamicLabelsUtil.createNoteBillBoardOptions(
              'value',
              labelStyle.text,
              point,
              Cesium.DynamicLabelsUtil.parsePointOptions(
                Cesium.DynamicLabelsUtil.parseLabelsOption(
                  new Cesium.Style.LabelNoteStyle(labelStyle)
                )
              )
            )

          if (this._wgs84Extent instanceof Extent) {
            const flag = this._wgs84Extent.intersects(
              new Point({
                coordinates: [lngRadian, latRadian]
              })
            )
            if (!flag) {
              return
            }
          }

          const item = collection.add(labelParam)

          if (mvtObject && !mvtObject[layerName]) {
            // eslint-disable-next-line no-param-reassign
            mvtObject[layerName] = []
          }
          mvtObject[layerName].push(item)
        } catch (error) {
          console.error(error)
        }
      }
    }
  }

  return new MVTProvider(options)
}
