import CLASS from './class'
import { ChartInternal } from './core'
import { isFunction } from './util'

ChartInternal.prototype.initPie = function() {
  var $$ = this,
    d3 = $$.d3
  $$.pie = d3
    .pie()
    .padAngle(this.getPadAngle.bind(this))
    .value(function(d) {
      return d.values.reduce(function(a, b) {
        return a + b.value
      }, 0)
    })

  let orderFct = $$.getOrderFunction()

  // we need to reverse the returned order if asc or desc to have the slice in expected order.
  if (orderFct && ($$.isOrderAsc() || $$.isOrderDesc())) {
    let defaultSort = orderFct
    orderFct = (t1, t2) => defaultSort(t1, t2) * -1
  }

  $$.pie.sort(orderFct || null)
}

ChartInternal.prototype.updateRadius = function() {
  var $$ = this,
    config = $$.config,
    w = config.gauge_width || config.donut_width,
    gaugeArcWidth =
      $$.filterTargetsToShow($$.data.targets).length *
      $$.config.gauge_arcs_minWidth
  $$.radiusExpanded =
    (Math.min($$.arcWidth, $$.arcHeight) / 2) * ($$.hasType('gauge') ? 0.85 : 1)
  $$.radius = $$.radiusExpanded * 0.95
  $$.innerRadiusRatio = w ? ($$.radius - w) / $$.radius : 0.6
  $$.innerRadius =
    $$.hasType('donut') || $$.hasType('gauge')
      ? $$.radius * $$.innerRadiusRatio
      : 0
  $$.gaugeArcWidth = w
    ? w
    : gaugeArcWidth <= $$.radius - $$.innerRadius
    ? $$.radius - $$.innerRadius
    : gaugeArcWidth <= $$.radius
    ? gaugeArcWidth
    : $$.radius
}

ChartInternal.prototype.getPadAngle = function() {
  if (this.hasType('pie')) {
    return this.config.pie_padAngle || 0
  } else if (this.hasType('donut')) {
    return this.config.donut_padAngle || 0
  } else {
    return 0
  }
}

ChartInternal.prototype.updateArc = function() {
  var $$ = this
  $$.svgArc = $$.getSvgArc()
  $$.svgArcExpanded = $$.getSvgArcExpanded()
  $$.svgArcExpandedSub = $$.getSvgArcExpanded(0.98)
}

ChartInternal.prototype.updateAngle = function(d) {
  var $$ = this,
    config = $$.config,
    found = false,
    index = 0,
    gMin,
    gMax,
    gTic,
    gValue

  if (!config) {
    return null
  }

  $$.pie($$.filterTargetsToShow($$.data.targets)).forEach(function(t) {
    if (!found && t.data.id === d.data.id) {
      found = true
      d = t
      d.index = index
    }
    index++
  })
  if (isNaN(d.startAngle)) {
    d.startAngle = 0
  }
  if (isNaN(d.endAngle)) {
    d.endAngle = d.startAngle
  }
  if ($$.isGaugeType(d.data)) {
    gMin = config.gauge_min
    gMax = config.gauge_max
    gTic = (Math.PI * (config.gauge_fullCircle ? 2 : 1)) / (gMax - gMin)
    gValue = d.value < gMin ? 0 : d.value < gMax ? d.value - gMin : gMax - gMin
    d.startAngle = config.gauge_startingAngle
    d.endAngle = d.startAngle + gTic * gValue
  }
  return found ? d : null
}

ChartInternal.prototype.getSvgArc = function() {
  var $$ = this,
    hasGaugeType = $$.hasType('gauge'),
    singleArcWidth =
      $$.gaugeArcWidth / $$.filterTargetsToShow($$.data.targets).length,
    arc = $$.d3
      .arc()
      .outerRadius(function(d) {
        return hasGaugeType ? $$.radius - singleArcWidth * d.index : $$.radius
      })
      .innerRadius(function(d) {
        return hasGaugeType
          ? $$.radius - singleArcWidth * (d.index + 1)
          : $$.innerRadius
      }),
    newArc = function(d, withoutUpdate) {
      var updated
      if (withoutUpdate) {
        return arc(d)
      } // for interpolate
      updated = $$.updateAngle(d)
      return updated ? arc(updated) : 'M 0 0'
    }
  // TODO: extends all function
  newArc.centroid = arc.centroid
  return newArc
}

ChartInternal.prototype.getSvgArcExpanded = function(rate) {
  rate = rate || 1
  var $$ = this,
    hasGaugeType = $$.hasType('gauge'),
    singleArcWidth =
      $$.gaugeArcWidth / $$.filterTargetsToShow($$.data.targets).length,
    expandWidth = Math.min(
      $$.radiusExpanded * rate - $$.radius,
      singleArcWidth * 0.8 - (1 - rate) * 100
    ),
    arc = $$.d3
      .arc()
      .outerRadius(function(d) {
        return hasGaugeType
          ? $$.radius - singleArcWidth * d.index + expandWidth
          : $$.radiusExpanded * rate
      })
      .innerRadius(function(d) {
        return hasGaugeType
          ? $$.radius - singleArcWidth * (d.index + 1)
          : $$.innerRadius
      })
  return function(d) {
    var updated = $$.updateAngle(d)
    return updated ? arc(updated) : 'M 0 0'
  }
}

ChartInternal.prototype.getArc = function(d, withoutUpdate, force) {
  return force || this.isArcType(d.data)
    ? this.svgArc(d, withoutUpdate)
    : 'M 0 0'
}

ChartInternal.prototype.transformForArcLabel = function(d) {
  var $$ = this,
    config = $$.config,
    updated = $$.updateAngle(d),
    c,
    x,
    y,
    h,
    ratio,
    translate = '',
    hasGauge = $$.hasType('gauge')
  if (updated && !hasGauge) {
    c = this.svgArc.centroid(updated)
    x = isNaN(c[0]) ? 0 : c[0]
    y = isNaN(c[1]) ? 0 : c[1]
    h = Math.sqrt(x * x + y * y)
    if ($$.hasType('donut') && config.donut_label_ratio) {
      ratio = isFunction(config.donut_label_ratio)
        ? config.donut_label_ratio(d, $$.radius, h)
        : config.donut_label_ratio
    } else if ($$.hasType('pie') && config.pie_label_ratio) {
      ratio = isFunction(config.pie_label_ratio)
        ? config.pie_label_ratio(d, $$.radius, h)
        : config.pie_label_ratio
    } else {
      ratio =
        $$.radius && h
          ? ((36 / $$.radius > 0.375 ? 1.175 - 36 / $$.radius : 0.8) *
              $$.radius) /
            h
          : 0
    }
    translate = 'translate(' + x * ratio + ',' + y * ratio + ')'
  } else if (
    updated &&
    hasGauge &&
    $$.filterTargetsToShow($$.data.targets).length > 1
  ) {
    var y1 = Math.sin(updated.endAngle - Math.PI / 2)
    x = Math.cos(updated.endAngle - Math.PI / 2) * ($$.radiusExpanded + 25)
    y = y1 * ($$.radiusExpanded + 15 - Math.abs(y1 * 10)) + 3
    translate = 'translate(' + x + ',' + y + ')'
  }
  return translate
}

/**
 * @deprecated Use `getRatio('arc', d)` instead.
 */
ChartInternal.prototype.getArcRatio = function(d) {
  return this.getRatio('arc', d)
}

ChartInternal.prototype.convertToArcData = function(d) {
  return this.addName({
    id: d.data.id,
    value: d.value,
    ratio: this.getRatio('arc', d),
    index: d.index
  })
}

ChartInternal.prototype.textForArcLabel = function(d) {
  var $$ = this,
    updated,
    value,
    ratio,
    id,
    format
  if (!$$.shouldShowArcLabel()) {
    return ''
  }
  updated = $$.updateAngle(d)
  value = updated ? updated.value : null
  ratio = $$.getRatio('arc', updated)
  id = d.data.id
  if (!$$.hasType('gauge') && !$$.meetsArcLabelThreshold(ratio)) {
    return ''
  }
  format = $$.getArcLabelFormat()
  return format
    ? format(value, ratio, id)
    : $$.defaultArcValueFormat(value, ratio)
}

ChartInternal.prototype.textForGaugeMinMax = function(value, isMax) {
  var $$ = this,
    format = $$.getGaugeLabelExtents()

  return format ? format(value, isMax) : value
}

ChartInternal.prototype.expandArc = function(targetIds) {
  var $$ = this,
    interval

  // MEMO: avoid to cancel transition
  if ($$.transiting) {
    interval = window.setInterval(function() {
      if (!$$.transiting) {
        window.clearInterval(interval)
        if ($$.legend.selectAll('.c3-legend-item-focused').size() > 0) {
          $$.expandArc(targetIds)
        }
      }
    }, 10)
    return
  }

  targetIds = $$.mapToTargetIds(targetIds)

  $$.svg
    .selectAll($$.selectorTargets(targetIds, '.' + CLASS.chartArc))
    .each(function(d) {
      if (!$$.shouldExpand(d.data.id)) {
        return
      }
      $$.d3
        .select(this)
        .selectAll('path')
        .transition()
        .duration($$.expandDuration(d.data.id))
        .attr('d', $$.svgArcExpanded)
        .transition()
        .duration($$.expandDuration(d.data.id) * 2)
        .attr('d', $$.svgArcExpandedSub)
        .each(function(d) {
          if ($$.isDonutType(d.data)) {
            // callback here
          }
        })
    })
}

ChartInternal.prototype.unexpandArc = function(targetIds) {
  var $$ = this

  if ($$.transiting) {
    return
  }

  targetIds = $$.mapToTargetIds(targetIds)

  $$.svg
    .selectAll($$.selectorTargets(targetIds, '.' + CLASS.chartArc))
    .selectAll('path')
    .transition()
    .duration(function(d) {
      return $$.expandDuration(d.data.id)
    })
    .attr('d', $$.svgArc)
  $$.svg.selectAll('.' + CLASS.arc)
}

ChartInternal.prototype.expandDuration = function(id) {
  var $$ = this,
    config = $$.config

  if ($$.isDonutType(id)) {
    return config.donut_expand_duration
  } else if ($$.isGaugeType(id)) {
    return config.gauge_expand_duration
  } else if ($$.isPieType(id)) {
    return config.pie_expand_duration
  } else {
    return 50
  }
}

ChartInternal.prototype.shouldExpand = function(id) {
  var $$ = this,
    config = $$.config
  return (
    ($$.isDonutType(id) && config.donut_expand) ||
    ($$.isGaugeType(id) && config.gauge_expand) ||
    ($$.isPieType(id) && config.pie_expand)
  )
}

ChartInternal.prototype.shouldShowArcLabel = function() {
  var $$ = this,
    config = $$.config,
    shouldShow = true
  if ($$.hasType('donut')) {
    shouldShow = config.donut_label_show
  } else if ($$.hasType('pie')) {
    shouldShow = config.pie_label_show
  }
  // when gauge, always true
  return shouldShow
}

ChartInternal.prototype.meetsArcLabelThreshold = function(ratio) {
  var $$ = this,
    config = $$.config,
    threshold = $$.hasType('donut')
      ? config.donut_label_threshold
      : config.pie_label_threshold
  return ratio >= threshold
}

ChartInternal.prototype.getArcLabelFormat = function() {
  var $$ = this,
    config = $$.config,
    format = config.pie_label_format
  if ($$.hasType('gauge')) {
    format = config.gauge_label_format
  } else if ($$.hasType('donut')) {
    format = config.donut_label_format
  }
  return format
}

ChartInternal.prototype.getGaugeLabelExtents = function() {
  var $$ = this,
    config = $$.config
  return config.gauge_label_extents
}

ChartInternal.prototype.getArcTitle = function() {
  var $$ = this
  return $$.hasType('donut') ? $$.config.donut_title : ''
}

ChartInternal.prototype.updateTargetsForArc = function(targets) {
  var $$ = this,
    main = $$.main,
    mainPies,
    mainPieEnter,
    classChartArc = $$.classChartArc.bind($$),
    classArcs = $$.classArcs.bind($$),
    classFocus = $$.classFocus.bind($$)
  mainPies = main
    .select('.' + CLASS.chartArcs)
    .selectAll('.' + CLASS.chartArc)
    .data($$.pie(targets))
    .attr('class', function(d) {
      return classChartArc(d) + classFocus(d.data)
    })
  mainPieEnter = mainPies
    .enter()
    .append('g')
    .attr('class', classChartArc)
  mainPieEnter.append('g').attr('class', classArcs)
  mainPieEnter
    .append('text')
    .attr('dy', $$.hasType('gauge') ? '-.1em' : '.35em')
    .style('opacity', 0)
    .style('text-anchor', 'middle')
    .style('pointer-events', 'none')
  // MEMO: can not keep same color..., but not bad to update color in redraw
  //mainPieUpdate.exit().remove();
}

ChartInternal.prototype.initArc = function() {
  var $$ = this
  $$.arcs = $$.main
    .select('.' + CLASS.chart)
    .append('g')
    .attr('class', CLASS.chartArcs)
    .attr('transform', $$.getTranslate('arc'))
  $$.arcs
    .append('text')
    .attr('class', CLASS.chartArcsTitle)
    .style('text-anchor', 'middle')
    .text($$.getArcTitle())
}

ChartInternal.prototype.redrawArc = function(
  duration,
  durationForExit,
  withTransform
) {
  var $$ = this,
    d3 = $$.d3,
    config = $$.config,
    main = $$.main,
    arcs,
    mainArc,
    arcLabelLines,
    mainArcLabelLine,
    hasGaugeType = $$.hasType('gauge')
  arcs = main
    .selectAll('.' + CLASS.arcs)
    .selectAll('.' + CLASS.arc)
    .data($$.arcData.bind($$))
  mainArc = arcs
    .enter()
    .append('path')
    .attr('class', $$.classArc.bind($$))
    .style('fill', function(d) {
      return $$.color(d.data)
    })
    .style('cursor', function(d) {
      return config.interaction_enabled && config.data_selection_isselectable(d)
        ? 'pointer'
        : null
    })
    .each(function(d) {
      if ($$.isGaugeType(d.data)) {
        d.startAngle = d.endAngle = config.gauge_startingAngle
      }
      this._current = d
    })
    .merge(arcs)
  if (hasGaugeType) {
    arcLabelLines = main
      .selectAll('.' + CLASS.arcs)
      .selectAll('.' + CLASS.arcLabelLine)
      .data($$.arcData.bind($$))
    mainArcLabelLine = arcLabelLines
      .enter()
      .append('rect')
      .attr('class', function(d) {
        return (
          CLASS.arcLabelLine +
          ' ' +
          CLASS.target +
          ' ' +
          CLASS.target +
          '-' +
          d.data.id
        )
      })
      .merge(arcLabelLines)

    if ($$.filterTargetsToShow($$.data.targets).length === 1) {
      mainArcLabelLine.style('display', 'none')
    } else {
      mainArcLabelLine
        .style('fill', function(d) {
          return $$.levelColor
            ? $$.levelColor(
                d.data.values.reduce(function(total, item) {
                  return total + item.value
                }, 0)
              )
            : $$.color(d.data)
        })
        .style('display', config.gauge_labelLine_show ? '' : 'none')
        .each(function(d) {
          var lineLength = 0,
            lineThickness = 2,
            x = 0,
            y = 0,
            transform = ''
          if ($$.hiddenTargetIds.indexOf(d.data.id) < 0) {
            var updated = $$.updateAngle(d),
              innerLineLength =
                ($$.gaugeArcWidth /
                  $$.filterTargetsToShow($$.data.targets).length) *
                (updated.index + 1),
              lineAngle = updated.endAngle - Math.PI / 2,
              arcInnerRadius = $$.radius - innerLineLength,
              linePositioningAngle =
                lineAngle - (arcInnerRadius === 0 ? 0 : 1 / arcInnerRadius)
            lineLength = $$.radiusExpanded - $$.radius + innerLineLength
            x = Math.cos(linePositioningAngle) * arcInnerRadius
            y = Math.sin(linePositioningAngle) * arcInnerRadius
            transform =
              'rotate(' +
              (lineAngle * 180) / Math.PI +
              ', ' +
              x +
              ', ' +
              y +
              ')'
          }
          d3.select(this)
            .attr('x', x)
            .attr('y', y)
            .attr('width', lineLength)
            .attr('height', lineThickness)
            .attr('transform', transform)
            .style(
              'stroke-dasharray',
              '0, ' + (lineLength + lineThickness) + ', 0'
            )
        })
    }
  }
  mainArc
    .attr('transform', function(d) {
      return !$$.isGaugeType(d.data) && withTransform ? 'scale(0)' : ''
    })
    .on(
      'mouseover',
      config.interaction_enabled
        ? function(d) {
            var updated, arcData
            if ($$.transiting) {
              // skip while transiting
              return
            }
            updated = $$.updateAngle(d)
            if (updated) {
              arcData = $$.convertToArcData(updated)
              // transitions
              $$.expandArc(updated.data.id)
              $$.api.focus(updated.data.id)
              $$.toggleFocusLegend(updated.data.id, true)
              $$.config.data_onmouseover(arcData, this)
            }
          }
        : null
    )
    .on(
      'mousemove',
      config.interaction_enabled
        ? function(d) {
            var updated = $$.updateAngle(d),
              arcData,
              selectedData
            if (updated) {
              ;(arcData = $$.convertToArcData(updated)),
                (selectedData = [arcData])
              $$.showTooltip(selectedData, this)
            }
          }
        : null
    )
    .on(
      'mouseout',
      config.interaction_enabled
        ? function(d) {
            var updated, arcData
            if ($$.transiting) {
              // skip while transiting
              return
            }
            updated = $$.updateAngle(d)
            if (updated) {
              arcData = $$.convertToArcData(updated)
              // transitions
              $$.unexpandArc(updated.data.id)
              $$.api.revert()
              $$.revertLegend()
              $$.hideTooltip()
              $$.config.data_onmouseout(arcData, this)
            }
          }
        : null
    )
    .on(
      'click',
      config.interaction_enabled
        ? function(d, i) {
            var updated = $$.updateAngle(d),
              arcData
            if (updated) {
              arcData = $$.convertToArcData(updated)
              if ($$.toggleShape) {
                $$.toggleShape(this, arcData, i)
              }
              $$.config.data_onclick.call($$.api, arcData, this)
            }
          }
        : null
    )
    .each(function() {
      $$.transiting = true
    })
    .transition()
    .duration(duration)
    .attrTween('d', function(d) {
      var updated = $$.updateAngle(d),
        interpolate
      if (!updated) {
        return function() {
          return 'M 0 0'
        }
      }
      //                if (this._current === d) {
      //                    this._current = {
      //                        startAngle: Math.PI*2,
      //                        endAngle: Math.PI*2,
      //                    };
      //                }
      if (isNaN(this._current.startAngle)) {
        this._current.startAngle = 0
      }
      if (isNaN(this._current.endAngle)) {
        this._current.endAngle = this._current.startAngle
      }
      interpolate = d3.interpolate(this._current, updated)
      this._current = interpolate(0)
      return function(t) {
        // prevents crashing the charts once in transition and chart.destroy() has been called
        if ($$.config === null) {
          return 'M 0 0'
        }
        var interpolated = interpolate(t)
        interpolated.data = d.data // data.id will be updated by interporator
        return $$.getArc(interpolated, true)
      }
    })
    .attr('transform', withTransform ? 'scale(1)' : '')
    .style('fill', function(d) {
      return $$.levelColor
        ? $$.levelColor(
            d.data.values.reduce(function(total, item) {
              return total + item.value
            }, 0)
          )
        : $$.color(d.data.id)
    }) // Where gauge reading color would receive customization.
    .call($$.endall, function() {
      $$.transiting = false
    })
  arcs
    .exit()
    .transition()
    .duration(durationForExit)
    .style('opacity', 0)
    .remove()
  main
    .selectAll('.' + CLASS.chartArc)
    .select('text')
    .style('opacity', 0)
    .attr('class', function(d) {
      return $$.isGaugeType(d.data) ? CLASS.gaugeValue : ''
    })
    .text($$.textForArcLabel.bind($$))
    .attr('transform', $$.transformForArcLabel.bind($$))
    .style('font-size', function(d) {
      return $$.isGaugeType(d.data) &&
        $$.filterTargetsToShow($$.data.targets).length === 1
        ? Math.round($$.radius / 5) + 'px'
        : ''
    })
    .transition()
    .duration(duration)
    .style('opacity', function(d) {
      return $$.isTargetToShow(d.data.id) && $$.isArcType(d.data) ? 1 : 0
    })
  main
    .select('.' + CLASS.chartArcsTitle)
    .style('opacity', $$.hasType('donut') || hasGaugeType ? 1 : 0)

  if (hasGaugeType) {
    let index = 0
    const backgroundArc = $$.arcs
      .select('g.' + CLASS.chartArcsBackground)
      .selectAll('path.' + CLASS.chartArcsBackground)
      .data($$.data.targets)

    backgroundArc
      .enter()
      .append('path')
      .attr(
        'class',
        (d, i) =>
          CLASS.chartArcsBackground + ' ' + CLASS.chartArcsBackground + '-' + i
      )
      .merge(backgroundArc)
      .attr('d', d1 => {
        if ($$.hiddenTargetIds.indexOf(d1.id) >= 0) {
          return 'M 0 0'
        }

        var d = {
          data: [{ value: config.gauge_max }],
          startAngle: config.gauge_startingAngle,
          endAngle:
            -1 *
            config.gauge_startingAngle *
            (config.gauge_fullCircle ? Math.PI : 1),
          index: index++
        }
        return $$.getArc(d, true, true)
      })

    backgroundArc.exit().remove()

    $$.arcs
      .select('.' + CLASS.chartArcsGaugeUnit)
      .attr('dy', '.75em')
      .text(config.gauge_label_show ? config.gauge_units : '')
    $$.arcs
      .select('.' + CLASS.chartArcsGaugeMin)
      .attr(
        'dx',
        -1 *
          ($$.innerRadius +
            ($$.radius - $$.innerRadius) / (config.gauge_fullCircle ? 1 : 2)) +
          'px'
      )
      .attr('dy', '1.2em')
      .text(
        config.gauge_label_show
          ? $$.textForGaugeMinMax(config.gauge_min, false)
          : ''
      )
    $$.arcs
      .select('.' + CLASS.chartArcsGaugeMax)
      .attr(
        'dx',
        $$.innerRadius +
          ($$.radius - $$.innerRadius) / (config.gauge_fullCircle ? 1 : 2) +
          'px'
      )
      .attr('dy', '1.2em')
      .text(
        config.gauge_label_show
          ? $$.textForGaugeMinMax(config.gauge_max, true)
          : ''
      )
  }
}
ChartInternal.prototype.initGauge = function() {
  var arcs = this.arcs
  if (this.hasType('gauge')) {
    arcs.append('g').attr('class', CLASS.chartArcsBackground)
    arcs
      .append('text')
      .attr('class', CLASS.chartArcsGaugeUnit)
      .style('text-anchor', 'middle')
      .style('pointer-events', 'none')
    arcs
      .append('text')
      .attr('class', CLASS.chartArcsGaugeMin)
      .style('text-anchor', 'middle')
      .style('pointer-events', 'none')
    arcs
      .append('text')
      .attr('class', CLASS.chartArcsGaugeMax)
      .style('text-anchor', 'middle')
      .style('pointer-events', 'none')
  }
}
ChartInternal.prototype.getGaugeLabelHeight = function() {
  return this.config.gauge_label_show ? 20 : 0
}
