import isEqual from 'lodash/isEqual'

function diff(before, after) {
  var cmds = []
  if (!after || !after.layers) {
    cmds.push({ command: 'destroy', args: [] })
    return cmds
  }
  if (!before.layers) {
    cmds.push({ command: 'setStyle', args: [after] })
    return cmds
  }
  if (!isEqual(before.basemap, after.basemap)) {
    cmds.push({ command: 'setBaseMap', args: [after.basemap] })
  }
  if (before.zoom !== after.zoom) {
    cmds.push({ command: 'setZoom', args: [after.zoom] })
  }
  if (!isEqual(before.center, after.center)) {
    cmds.push({ command: 'setCenter', args: [after.center] })
  }
  if (before.pitch !== after.pitch) {
    cmds.push({ command: 'setPitch', args: [after.pitch] })
  }
  if (before.bearing !== after.bearing) {
    cmds.push({ command: 'setBearing', args: [after.bearing] })
  }
  diffLayers(before.layers, after.layers, cmds)
  return cmds
}

function pluckId(layer) {
  return layer.id
}

function indexById(group, layer) {
  group[layer.id] = layer
  return group
}

function diffLayers(before, after, commands) {
  before = before || []
  after = after || []

  // order of layers by id
  var beforeOrder = before.map(pluckId)
  var afterOrder = after.map(pluckId)

  // index of layer by id
  var beforeIndex = before.reduce(indexById, {})
  var afterIndex = after.reduce(indexById, {})

  // track order of layers as if they have been mutated
  var tracker = beforeOrder.slice()

  // layers that have been added do not need to be diffed
  var clean = Object.create(null)

  var i, d, layerId, beforeLayer, afterLayer, insertBeforeLayerId

  // remove layers
  for (i = 0, d = 0; i < beforeOrder.length; i++) {
    layerId = beforeOrder[i]
    if (!afterIndex.hasOwnProperty(layerId)) {
      commands.push({ command: 'removeLayer', args: [layerId] })
      tracker.splice(tracker.indexOf(layerId, d), 1)
    } else {
      // limit where in tracker we need to look for a match
      d++
    }
  }

  // add/reorder layers
  for (i = 0, d = 0; i < afterOrder.length; i++) {
    // work backwards as insert is before an existing layer
    layerId = afterOrder[afterOrder.length - 1 - i]

    if (tracker[tracker.length - 1 - i] === layerId) continue

    if (beforeIndex.hasOwnProperty(layerId)) {
      // remove the layer before we insert at the correct position
      commands.push({ command: 'removeLayer', args: [layerId] })
      tracker.splice(tracker.lastIndexOf(layerId, tracker.length - d), 1)
    } else {
      // limit where in tracker we need to look for a match
      d++
    }

    // add layer at correct position
    insertBeforeLayerId = tracker[tracker.length - i]
    commands.push({
      command: 'addLayer',
      args: [afterIndex[layerId], insertBeforeLayerId]
    })
    tracker.splice(tracker.length - i, 0, layerId)
    clean[layerId] = true
  }

  // update layers
  for (i = 0; i < afterOrder.length; i++) {
    layerId = afterOrder[i]
    beforeLayer = beforeIndex[layerId]
    afterLayer = afterIndex[layerId]

    // no need to update if previously added (new or moved)
    if (clean[layerId] || isEqual(beforeLayer, afterLayer)) continue

    // If source, source-layer, or type have changes, then remove the layer
    // and add it back 'from scratch'.
    if (!isEqual(beforeLayer, afterLayer)) {
      if (beforeLayer.visible !== afterLayer.visible) {
        commands.push({
          command: 'setLayerVisibility',
          args: [layerId, afterLayer.visible]
        })
      }
      if (
        beforeLayer.type !== afterLayer.type ||
        beforeLayer.sub_type !== afterLayer.sub_type
      ) {
        commands.push({ command: 'removeLayer', args: [layerId] })
        commands.push({
          command: 'addLayer',
          args: [afterLayer, afterOrder[i + 1]]
        })
        continue
      }
      let styleEqual = isEqual(beforeLayer.style, afterLayer.style)
      let sourceEqual = isEqual(beforeLayer.source, afterLayer.source)
      let actionEqual = isEqual(beforeLayer.action, afterLayer.action)
      let scaleEqual = isEqual(beforeLayer.scale, afterLayer.scale)
      let shapeEqual = isEqual(beforeLayer.shape, afterLayer.shape)
      // 数据源变化后重新渲染图层
      if (!sourceEqual) {
        commands.push({ command: 'removeLayer', args: [layerId] })
        commands.push({
          command: 'addLayer',
          args: [afterLayer, afterOrder[i + 1]]
        })
        continue
        /*commands.push({
          command: 'setLayerSource',
          args: [layerId, afterLayer.source]
        })*/
      }
      if (!styleEqual) {
        if (afterLayer.type === 'label') {
          // 标注图层暂时无法通过api更新样式，只能移出了重新添加
          commands.push({ command: 'removeLayer', args: [layerId] })
          commands.push({
            command: 'addLayer',
            args: [afterLayer, afterOrder[i + 1]]
          })
        } else {
          commands.push({
            command: 'setLayerStyle',
            args: [layerId, afterLayer.style]
          })
        }
      }
      if (!actionEqual) {
        commands.push({
          command: 'setLayerAction',
          args: [layerId, afterLayer.action]
        })
      }
      if (!scaleEqual) {
        commands.push({
          command: 'setLayerScale',
          args: [layerId, afterLayer.scale]
        })
      }

      if (!shapeEqual) {
        commands.push({
          command: 'setLayerShape',
          args: [layerId, afterLayer.shape]
        })
      }
    }
  }
}

export default diff
