import CLASS from './class'
import { Chart, ChartInternal } from './core'
import { isValue, isDefined, diffDomain } from './util'

Chart.prototype.flow = function(args) {
  var $$ = this.internal,
    targets,
    data,
    notfoundIds = [],
    orgDataCount = $$.getMaxDataCount(),
    dataCount,
    domain,
    baseTarget,
    baseValue,
    length = 0,
    tail = 0,
    diff,
    to

  if (args.json) {
    data = $$.convertJsonToData(args.json, args.keys)
  } else if (args.rows) {
    data = $$.convertRowsToData(args.rows)
  } else if (args.columns) {
    data = $$.convertColumnsToData(args.columns)
  } else {
    return
  }
  targets = $$.convertDataToTargets(data, true)

  // Update/Add data
  $$.data.targets.forEach(function(t) {
    var found = false,
      i,
      j
    for (i = 0; i < targets.length; i++) {
      if (t.id === targets[i].id) {
        found = true

        if (t.values[t.values.length - 1]) {
          tail = t.values[t.values.length - 1].index + 1
        }
        length = targets[i].values.length

        for (j = 0; j < length; j++) {
          targets[i].values[j].index = tail + j
          if (!$$.isTimeSeries()) {
            targets[i].values[j].x = tail + j
          }
        }
        t.values = t.values.concat(targets[i].values)

        targets.splice(i, 1)
        break
      }
    }
    if (!found) {
      notfoundIds.push(t.id)
    }
  })

  // Append null for not found targets
  $$.data.targets.forEach(function(t) {
    var i, j
    for (i = 0; i < notfoundIds.length; i++) {
      if (t.id === notfoundIds[i]) {
        tail = t.values[t.values.length - 1].index + 1
        for (j = 0; j < length; j++) {
          t.values.push({
            id: t.id,
            index: tail + j,
            x: $$.isTimeSeries() ? $$.getOtherTargetX(tail + j) : tail + j,
            value: null
          })
        }
      }
    }
  })

  // Generate null values for new target
  if ($$.data.targets.length) {
    targets.forEach(function(t) {
      var i,
        missing = []
      for (i = $$.data.targets[0].values[0].index; i < tail; i++) {
        missing.push({
          id: t.id,
          index: i,
          x: $$.isTimeSeries() ? $$.getOtherTargetX(i) : i,
          value: null
        })
      }
      t.values.forEach(function(v) {
        v.index += tail
        if (!$$.isTimeSeries()) {
          v.x += tail
        }
      })
      t.values = missing.concat(t.values)
    })
  }
  $$.data.targets = $$.data.targets.concat(targets) // add remained

  // check data count because behavior needs to change when it's only one
  dataCount = $$.getMaxDataCount()
  baseTarget = $$.data.targets[0]
  baseValue = baseTarget.values[0]

  // Update length to flow if needed
  if (isDefined(args.to)) {
    length = 0
    to = $$.isTimeSeries() ? $$.parseDate(args.to) : args.to
    baseTarget.values.forEach(function(v) {
      if (v.x < to) {
        length++
      }
    })
  } else if (isDefined(args.length)) {
    length = args.length
  }

  // If only one data, update the domain to flow from left edge of the chart
  if (!orgDataCount) {
    if ($$.isTimeSeries()) {
      if (baseTarget.values.length > 1) {
        diff = baseTarget.values[baseTarget.values.length - 1].x - baseValue.x
      } else {
        diff = baseValue.x - $$.getXDomain($$.data.targets)[0]
      }
    } else {
      diff = 1
    }
    domain = [baseValue.x - diff, baseValue.x]
    $$.updateXDomain(null, true, true, false, domain)
  } else if (orgDataCount === 1) {
    if ($$.isTimeSeries()) {
      diff =
        (baseTarget.values[baseTarget.values.length - 1].x - baseValue.x) / 2
      domain = [new Date(+baseValue.x - diff), new Date(+baseValue.x + diff)]
      $$.updateXDomain(null, true, true, false, domain)
    }
  }

  // Set targets
  $$.updateTargets($$.data.targets)

  // Redraw with new targets
  $$.redraw({
    flow: {
      index: baseValue.index,
      length: length,
      duration: isValue(args.duration)
        ? args.duration
        : $$.config.transition_duration,
      done: args.done,
      orgDataCount: orgDataCount
    },
    withLegend: true,
    withTransition: orgDataCount > 1,
    withTrimXDomain: false,
    withUpdateXAxis: true
  })
}

ChartInternal.prototype.generateFlow = function(args) {
  var $$ = this,
    config = $$.config,
    d3 = $$.d3

  return function() {
    var targets = args.targets,
      flow = args.flow,
      drawBar = args.drawBar,
      drawLine = args.drawLine,
      drawArea = args.drawArea,
      cx = args.cx,
      cy = args.cy,
      xv = args.xv,
      xForText = args.xForText,
      yForText = args.yForText,
      duration = args.duration

    var translateX,
      scaleX = 1,
      transform,
      flowIndex = flow.index,
      flowLength = flow.length,
      flowStart = $$.getValueOnIndex($$.data.targets[0].values, flowIndex),
      flowEnd = $$.getValueOnIndex(
        $$.data.targets[0].values,
        flowIndex + flowLength
      ),
      orgDomain = $$.x.domain(),
      domain,
      durationForFlow = flow.duration || duration,
      done = flow.done || function() {},
      wait = $$.generateWait()

    var xgrid,
      xgridLines,
      mainRegion,
      mainText,
      mainBar,
      mainLine,
      mainArea,
      mainCircle

    // set flag
    $$.flowing = true

    // remove head data after rendered
    $$.data.targets.forEach(function(d) {
      d.values.splice(0, flowLength)
    })

    // update x domain to generate axis elements for flow
    domain = $$.updateXDomain(targets, true, true)
    // update elements related to x scale
    if ($$.updateXGrid) {
      $$.updateXGrid(true)
    }

    xgrid = $$.xgrid || d3.selectAll([]) // xgrid needs to be obtained after updateXGrid
    xgridLines = $$.xgridLines || d3.selectAll([])
    mainRegion = $$.mainRegion || d3.selectAll([])
    mainText = $$.mainText || d3.selectAll([])
    mainBar = $$.mainBar || d3.selectAll([])
    mainLine = $$.mainLine || d3.selectAll([])
    mainArea = $$.mainArea || d3.selectAll([])
    mainCircle = $$.mainCircle || d3.selectAll([])

    // generate transform to flow
    if (!flow.orgDataCount) {
      // if empty
      if ($$.data.targets[0].values.length !== 1) {
        translateX = $$.x(orgDomain[0]) - $$.x(domain[0])
      } else {
        if ($$.isTimeSeries()) {
          flowStart = $$.getValueOnIndex($$.data.targets[0].values, 0)
          flowEnd = $$.getValueOnIndex(
            $$.data.targets[0].values,
            $$.data.targets[0].values.length - 1
          )
          translateX = $$.x(flowStart.x) - $$.x(flowEnd.x)
        } else {
          translateX = diffDomain(domain) / 2
        }
      }
    } else if (
      flow.orgDataCount === 1 ||
      (flowStart && flowStart.x) === (flowEnd && flowEnd.x)
    ) {
      translateX = $$.x(orgDomain[0]) - $$.x(domain[0])
    } else {
      if ($$.isTimeSeries()) {
        translateX = $$.x(orgDomain[0]) - $$.x(domain[0])
      } else {
        translateX = $$.x(flowStart.x) - $$.x(flowEnd.x)
      }
    }
    scaleX = diffDomain(orgDomain) / diffDomain(domain)
    transform = 'translate(' + translateX + ',0) scale(' + scaleX + ',1)'

    $$.hideXGridFocus()

    var flowTransition = d3
      .transition()
      .ease(d3.easeLinear)
      .duration(durationForFlow)
    wait.add($$.xAxis($$.axes.x, flowTransition))
    wait.add(mainBar.transition(flowTransition).attr('transform', transform))
    wait.add(mainLine.transition(flowTransition).attr('transform', transform))
    wait.add(mainArea.transition(flowTransition).attr('transform', transform))
    wait.add(mainCircle.transition(flowTransition).attr('transform', transform))
    wait.add(mainText.transition(flowTransition).attr('transform', transform))
    wait.add(
      mainRegion
        .filter($$.isRegionOnX)
        .transition(flowTransition)
        .attr('transform', transform)
    )
    wait.add(xgrid.transition(flowTransition).attr('transform', transform))
    wait.add(xgridLines.transition(flowTransition).attr('transform', transform))
    wait(function() {
      var i,
        shapes = [],
        texts = []

      // remove flowed elements
      if (flowLength) {
        for (i = 0; i < flowLength; i++) {
          shapes.push('.' + CLASS.shape + '-' + (flowIndex + i))
          texts.push('.' + CLASS.text + '-' + (flowIndex + i))
        }
        $$.svg
          .selectAll('.' + CLASS.shapes)
          .selectAll(shapes)
          .remove()
        $$.svg
          .selectAll('.' + CLASS.texts)
          .selectAll(texts)
          .remove()
        $$.svg.select('.' + CLASS.xgrid).remove()
      }

      // draw again for removing flowed elements and reverting attr
      xgrid
        .attr('transform', null)
        .attr('x1', $$.xgridAttr.x1)
        .attr('x2', $$.xgridAttr.x2)
        .attr('y1', $$.xgridAttr.y1)
        .attr('y2', $$.xgridAttr.y2)
        .style('opacity', $$.xgridAttr.opacity)
      xgridLines.attr('transform', null)
      xgridLines
        .select('line')
        .attr('x1', config.axis_rotated ? 0 : xv)
        .attr('x2', config.axis_rotated ? $$.width : xv)
      xgridLines
        .select('text')
        .attr('x', config.axis_rotated ? $$.width : 0)
        .attr('y', xv)
      mainBar.attr('transform', null).attr('d', drawBar)
      mainLine.attr('transform', null).attr('d', drawLine)
      mainArea.attr('transform', null).attr('d', drawArea)
      mainCircle
        .attr('transform', null)
        .attr('cx', cx)
        .attr('cy', cy)
      mainText
        .attr('transform', null)
        .attr('x', xForText)
        .attr('y', yForText)
        .style('fill-opacity', $$.opacityForText.bind($$))
      mainRegion.attr('transform', null)
      mainRegion
        .filter($$.isRegionOnX)
        .attr('x', $$.regionX.bind($$))
        .attr('width', $$.regionWidth.bind($$))

      // callback for end of flow
      done()

      $$.flowing = false
    })
  }
}
