import { ChartInternal } from './core'
import { isValue, isDefined, diffDomain, notEmpty } from './util'

ChartInternal.prototype.getYDomainMin = function(targets) {
  var $$ = this,
    config = $$.config,
    ids = $$.mapToIds(targets),
    ys = $$.getValuesAsIdKeyed(targets),
    j,
    k,
    baseId,
    idsInGroup,
    id,
    hasNegativeValue
  if (config.data_groups.length > 0) {
    hasNegativeValue = $$.hasNegativeValueInTargets(targets)
    for (j = 0; j < config.data_groups.length; j++) {
      // Determine baseId
      idsInGroup = config.data_groups[j].filter(function(id) {
        return ids.indexOf(id) >= 0
      })
      if (idsInGroup.length === 0) {
        continue
      }
      baseId = idsInGroup[0]
      // Consider negative values
      if (hasNegativeValue && ys[baseId]) {
        ys[baseId].forEach(function(v, i) {
          ys[baseId][i] = v < 0 ? v : 0
        })
      }
      // Compute min
      for (k = 1; k < idsInGroup.length; k++) {
        id = idsInGroup[k]
        if (!ys[id]) {
          continue
        }
        ys[id].forEach(function(v, i) {
          if (
            $$.axis.getId(id) === $$.axis.getId(baseId) &&
            ys[baseId] &&
            !(hasNegativeValue && +v > 0)
          ) {
            ys[baseId][i] += +v
          }
        })
      }
    }
  }
  return $$.d3.min(
    Object.keys(ys).map(function(key) {
      return $$.d3.min(ys[key])
    })
  )
}
ChartInternal.prototype.getYDomainMax = function(targets) {
  var $$ = this,
    config = $$.config,
    ids = $$.mapToIds(targets),
    ys = $$.getValuesAsIdKeyed(targets),
    j,
    k,
    baseId,
    idsInGroup,
    id,
    hasPositiveValue
  if (config.data_groups.length > 0) {
    hasPositiveValue = $$.hasPositiveValueInTargets(targets)
    for (j = 0; j < config.data_groups.length; j++) {
      // Determine baseId
      idsInGroup = config.data_groups[j].filter(function(id) {
        return ids.indexOf(id) >= 0
      })
      if (idsInGroup.length === 0) {
        continue
      }
      baseId = idsInGroup[0]
      // Consider positive values
      if (hasPositiveValue && ys[baseId]) {
        ys[baseId].forEach(function(v, i) {
          ys[baseId][i] = v > 0 ? v : 0
        })
      }
      // Compute max
      for (k = 1; k < idsInGroup.length; k++) {
        id = idsInGroup[k]
        if (!ys[id]) {
          continue
        }
        ys[id].forEach(function(v, i) {
          if (
            $$.axis.getId(id) === $$.axis.getId(baseId) &&
            ys[baseId] &&
            !(hasPositiveValue && +v < 0)
          ) {
            ys[baseId][i] += +v
          }
        })
      }
    }
  }
  return $$.d3.max(
    Object.keys(ys).map(function(key) {
      return $$.d3.max(ys[key])
    })
  )
}
ChartInternal.prototype.getYDomain = function(targets, axisId, xDomain) {
  var $$ = this,
    config = $$.config

  if ($$.isAxisNormalized(axisId)) {
    return [0, 100]
  }

  var targetsByAxisId = targets.filter(function(t) {
      return $$.axis.getId(t.id) === axisId
    }),
    yTargets = xDomain
      ? $$.filterByXDomain(targetsByAxisId, xDomain)
      : targetsByAxisId,
    yMin = axisId === 'y2' ? config.axis_y2_min : config.axis_y_min,
    yMax = axisId === 'y2' ? config.axis_y2_max : config.axis_y_max,
    yDomainMin = $$.getYDomainMin(yTargets),
    yDomainMax = $$.getYDomainMax(yTargets),
    domain,
    domainLength,
    padding_top,
    padding_bottom,
    center = axisId === 'y2' ? config.axis_y2_center : config.axis_y_center,
    yDomainAbs,
    lengths,
    diff,
    ratio,
    isAllPositive,
    isAllNegative,
    isZeroBased =
      ($$.hasType('bar', yTargets) && config.bar_zerobased) ||
      ($$.hasType('area', yTargets) && config.area_zerobased),
    isInverted =
      axisId === 'y2' ? config.axis_y2_inverted : config.axis_y_inverted,
    showHorizontalDataLabel = $$.hasDataLabel() && config.axis_rotated,
    showVerticalDataLabel = $$.hasDataLabel() && !config.axis_rotated

  // MEMO: avoid inverting domain unexpectedly
  yDomainMin = isValue(yMin)
    ? yMin
    : isValue(yMax)
    ? yDomainMin < yMax
      ? yDomainMin
      : yMax - 10
    : yDomainMin
  yDomainMax = isValue(yMax)
    ? yMax
    : isValue(yMin)
    ? yMin < yDomainMax
      ? yDomainMax
      : yMin + 10
    : yDomainMax

  if (yTargets.length === 0) {
    // use current domain if target of axisId is none
    return axisId === 'y2' ? $$.y2.domain() : $$.y.domain()
  }
  if (isNaN(yDomainMin)) {
    // set minimum to zero when not number
    yDomainMin = 0
  }
  if (isNaN(yDomainMax)) {
    // set maximum to have same value as yDomainMin
    yDomainMax = yDomainMin
  }
  if (yDomainMin === yDomainMax) {
    yDomainMin < 0 ? (yDomainMax = 0) : (yDomainMin = 0)
  }
  isAllPositive = yDomainMin >= 0 && yDomainMax >= 0
  isAllNegative = yDomainMin <= 0 && yDomainMax <= 0

  // Cancel zerobased if axis_*_min / axis_*_max specified
  if ((isValue(yMin) && isAllPositive) || (isValue(yMax) && isAllNegative)) {
    isZeroBased = false
  }

  // Bar/Area chart should be 0-based if all positive|negative
  if (isZeroBased) {
    if (isAllPositive) {
      yDomainMin = 0
    }
    if (isAllNegative) {
      yDomainMax = 0
    }
  }

  domainLength = Math.abs(yDomainMax - yDomainMin)
  padding_top = padding_bottom = domainLength * 0.1

  if (typeof center !== 'undefined') {
    yDomainAbs = Math.max(Math.abs(yDomainMin), Math.abs(yDomainMax))
    yDomainMax = center + yDomainAbs
    yDomainMin = center - yDomainAbs
  }
  // add padding for data label
  if (showHorizontalDataLabel) {
    lengths = $$.getDataLabelLength(yDomainMin, yDomainMax, 'width')
    diff = diffDomain($$.y.range())
    ratio = [lengths[0] / diff, lengths[1] / diff]
    padding_top += domainLength * (ratio[1] / (1 - ratio[0] - ratio[1]))
    padding_bottom += domainLength * (ratio[0] / (1 - ratio[0] - ratio[1]))
  } else if (showVerticalDataLabel) {
    lengths = $$.getDataLabelLength(yDomainMin, yDomainMax, 'height')

    const pixelsToAxisPadding = $$.getY(
      config[`axis_${axisId}_type`],
      // input domain as pixels
      [0, config.axis_rotated ? $$.width : $$.height],
      // output range as axis padding
      [0, domainLength]
    )

    padding_top += pixelsToAxisPadding(lengths[1])
    padding_bottom += pixelsToAxisPadding(lengths[0])
  }
  if (axisId === 'y' && notEmpty(config.axis_y_padding)) {
    padding_top = $$.axis.getPadding(
      config.axis_y_padding,
      'top',
      padding_top,
      domainLength
    )
    padding_bottom = $$.axis.getPadding(
      config.axis_y_padding,
      'bottom',
      padding_bottom,
      domainLength
    )
  }
  if (axisId === 'y2' && notEmpty(config.axis_y2_padding)) {
    padding_top = $$.axis.getPadding(
      config.axis_y2_padding,
      'top',
      padding_top,
      domainLength
    )
    padding_bottom = $$.axis.getPadding(
      config.axis_y2_padding,
      'bottom',
      padding_bottom,
      domainLength
    )
  }
  // Bar/Area chart should be 0-based if all positive|negative
  if (isZeroBased) {
    if (isAllPositive) {
      padding_bottom = yDomainMin
    }
    if (isAllNegative) {
      padding_top = -yDomainMax
    }
  }
  domain = [yDomainMin - padding_bottom, yDomainMax + padding_top]
  return isInverted ? domain.reverse() : domain
}
ChartInternal.prototype.getXDomainMin = function(targets) {
  var $$ = this,
    config = $$.config
  return isDefined(config.axis_x_min)
    ? $$.isTimeSeries()
      ? this.parseDate(config.axis_x_min)
      : config.axis_x_min
    : $$.d3.min(targets, function(t) {
        return $$.d3.min(t.values, function(v) {
          return v.x
        })
      })
}
ChartInternal.prototype.getXDomainMax = function(targets) {
  var $$ = this,
    config = $$.config
  return isDefined(config.axis_x_max)
    ? $$.isTimeSeries()
      ? this.parseDate(config.axis_x_max)
      : config.axis_x_max
    : $$.d3.max(targets, function(t) {
        return $$.d3.max(t.values, function(v) {
          return v.x
        })
      })
}
ChartInternal.prototype.getXDomainPadding = function(domain) {
  var $$ = this,
    config = $$.config,
    diff = domain[1] - domain[0],
    maxDataCount,
    padding,
    paddingLeft,
    paddingRight
  if ($$.isCategorized()) {
    padding = 0
  } else if ($$.hasType('bar')) {
    maxDataCount = $$.getMaxDataCount()
    padding = maxDataCount > 1 ? diff / (maxDataCount - 1) / 2 : 0.5
  } else {
    padding = diff * 0.01
  }
  if (
    typeof config.axis_x_padding === 'object' &&
    notEmpty(config.axis_x_padding)
  ) {
    paddingLeft = isValue(config.axis_x_padding.left)
      ? config.axis_x_padding.left
      : padding
    paddingRight = isValue(config.axis_x_padding.right)
      ? config.axis_x_padding.right
      : padding
  } else if (typeof config.axis_x_padding === 'number') {
    paddingLeft = paddingRight = config.axis_x_padding
  } else {
    paddingLeft = paddingRight = padding
  }
  return { left: paddingLeft, right: paddingRight }
}
ChartInternal.prototype.getXDomain = function(targets) {
  var $$ = this,
    xDomain = [$$.getXDomainMin(targets), $$.getXDomainMax(targets)],
    firstX = xDomain[0],
    lastX = xDomain[1],
    padding = $$.getXDomainPadding(xDomain),
    min: Date | number = 0,
    max: Date | number = 0
  // show center of x domain if min and max are the same
  if (firstX - lastX === 0 && !$$.isCategorized()) {
    if ($$.isTimeSeries()) {
      firstX = new Date(firstX.getTime() * 0.5)
      lastX = new Date(lastX.getTime() * 1.5)
    } else {
      firstX = firstX === 0 ? 1 : firstX * 0.5
      lastX = lastX === 0 ? -1 : lastX * 1.5
    }
  }
  if (firstX || firstX === 0) {
    min = $$.isTimeSeries()
      ? new Date(firstX.getTime() - padding.left)
      : firstX - padding.left
  }
  if (lastX || lastX === 0) {
    max = $$.isTimeSeries()
      ? new Date(lastX.getTime() + padding.right)
      : lastX + padding.right
  }
  return [min, max]
}
ChartInternal.prototype.updateXDomain = function(
  targets,
  withUpdateXDomain,
  withUpdateOrgXDomain,
  withTrim,
  domain
) {
  var $$ = this,
    config = $$.config

  if (withUpdateOrgXDomain) {
    $$.x.domain(domain ? domain : $$.d3.extent($$.getXDomain(targets)))
    $$.orgXDomain = $$.x.domain()
    if (config.zoom_enabled) {
      $$.zoom.update()
    }
    $$.subX.domain($$.x.domain())
    if ($$.brush) {
      $$.brush.updateScale($$.subX)
    }
  }
  if (withUpdateXDomain) {
    $$.x.domain(
      domain
        ? domain
        : !$$.brush || $$.brush.empty()
        ? $$.orgXDomain
        : $$.brush.selectionAsValue()
    )
  }

  // Trim domain when too big by zoom mousemove event
  if (withTrim) {
    $$.x.domain($$.trimXDomain($$.x.orgDomain()))
  }

  return $$.x.domain()
}
ChartInternal.prototype.trimXDomain = function(domain) {
  var zoomDomain = this.getZoomDomain(),
    min = zoomDomain[0],
    max = zoomDomain[1]
  if (domain[0] <= min) {
    domain[1] = +domain[1] + (min - domain[0])
    domain[0] = min
  }
  if (max <= domain[1]) {
    domain[0] = +domain[0] - (domain[1] - max)
    domain[1] = max
  }
  return domain
}
