import { ChartInternal } from './core'

function c3LogScale(d3, linearScale, logScale) {
  var PROJECTION = [0.01, 10]

  if (!linearScale) {
    linearScale = d3.scaleLinear()
    linearScale.range(PROJECTION)
  }

  if (!logScale) {
    logScale = d3.scaleLog()
    logScale.domain(PROJECTION)
    logScale.nice()
  }

  // copied from https://github.com/compute-io/logspace
  function logspace(a, b, len) {
    var arr, end, tmp, d

    if (arguments.length < 3) {
      len = 10
    } else {
      if (len === 0) {
        return []
      }
    }
    // Calculate the increment:
    end = len - 1
    d = (b - a) / end

    // Build the output array...
    arr = new Array(len)
    tmp = a
    arr[0] = Math.pow(10, tmp)
    for (var i = 1; i < end; i++) {
      tmp += d
      arr[i] = Math.pow(10, tmp)
    }
    arr[end] = Math.pow(10, b)
    return arr
  }

  function scale(x) {
    return logScale(linearScale(x))
  }

  scale.domain = function(x) {
    if (!arguments.length) {
      return linearScale.domain()
    }
    linearScale.domain(x)
    return scale
  }

  scale.range = function(x) {
    if (!arguments.length) {
      return logScale.range()
    }
    logScale.range(x)
    return scale
  }

  scale.ticks = function(m) {
    return logspace(-2, 1, m || 10).map(function(v) {
      return linearScale.invert(v)
    })
  }

  scale.copy = function() {
    return c3LogScale(d3, linearScale.copy(), logScale.copy())
  }

  return scale
}

ChartInternal.prototype.getScale = function(min, max, forTimeseries) {
  return (forTimeseries ? this.d3.scaleTime() : this.d3.scaleLinear()).range([
    min,
    max
  ])
}
ChartInternal.prototype.getX = function(min, max, domain, offset) {
  var $$ = this,
    scale = $$.getScale(min, max, $$.isTimeSeries()),
    _scale = domain ? scale.domain(domain) : scale,
    key
  // Define customized scale if categorized axis
  if ($$.isCategorized()) {
    offset =
      offset ||
      function() {
        return 0
      }
    scale = function(d, raw) {
      var v = _scale(d) + offset(d)
      return raw ? v : Math.ceil(v)
    }
  } else {
    scale = function(d, raw) {
      var v = _scale(d)
      return raw ? v : Math.ceil(v)
    }
  }
  // define functions
  for (key in _scale) {
    scale[key] = _scale[key]
  }
  scale.orgDomain = function() {
    return _scale.domain()
  }
  // define custom domain() for categorized axis
  if ($$.isCategorized()) {
    scale.domain = function(domain) {
      if (!arguments.length) {
        domain = this.orgDomain()
        return [domain[0], domain[1] + 1]
      }
      _scale.domain(domain)
      return scale
    }
  }
  return scale
}

/**
 * Creates and configures a D3 scale instance for the given type.
 *
 * By defaults it returns a Linear scale.
 *
 * @param {String} type Type of d3-scale to create. Type can be 'linear', 'time', 'timeseries' or 'log'.
 * @param {Array} domain The scale domain such as [from, to]
 * @param {Array} range The scale's range such as [from, to]
 *
 * @return A d3-scale instance
 */
ChartInternal.prototype.getY = function(type, domain, range) {
  let scale
  if (type === 'timeseries' || type === 'time') {
    scale = this.d3.scaleTime()
  } else if (type === 'log') {
    scale = c3LogScale(this.d3)
  } else if (type === 'linear' || type === undefined) {
    scale = this.d3.scaleLinear()
  } else {
    throw new Error(`Invalid Y axis type: "${type}"`)
  }

  if (domain) {
    scale.domain(domain)
  }

  if (range) {
    scale.range(range)
  }

  return scale
}
ChartInternal.prototype.getYScale = function(id) {
  return this.axis.getId(id) === 'y2' ? this.y2 : this.y
}
ChartInternal.prototype.getSubYScale = function(id) {
  return this.axis.getId(id) === 'y2' ? this.subY2 : this.subY
}
ChartInternal.prototype.updateScales = function() {
  var $$ = this,
    config = $$.config,
    forInit = !$$.x
  // update edges
  $$.xMin = config.axis_rotated ? 1 : 0
  $$.xMax = config.axis_rotated ? $$.height : $$.width
  $$.yMin = config.axis_rotated ? 0 : $$.height
  $$.yMax = config.axis_rotated ? $$.width : 1
  $$.subXMin = $$.xMin
  $$.subXMax = $$.xMax
  $$.subYMin = config.axis_rotated ? 0 : $$.height2
  $$.subYMax = config.axis_rotated ? $$.width2 : 1
  // update scales
  $$.x = $$.getX(
    $$.xMin,
    $$.xMax,
    forInit ? undefined : $$.x.orgDomain(),
    function() {
      return $$.xAxis.tickOffset()
    }
  )
  $$.y = $$.getY(
    config.axis_y_type,
    forInit ? config.axis_y_default : $$.y.domain(),
    [$$.yMin, $$.yMax]
  )
  $$.y2 = $$.getY(
    config.axis_y2_type,
    forInit ? config.axis_y2_default : $$.y2.domain(),
    [$$.yMin, $$.yMax]
  )
  $$.subX = $$.getX($$.xMin, $$.xMax, $$.orgXDomain, function(d) {
    return d % 1 ? 0 : $$.subXAxis.tickOffset()
  })
  $$.subY = $$.getY(
    config.axis_y_type,
    forInit ? config.axis_y_default : $$.subY.domain(),
    [$$.subYMin, $$.subYMax]
  )
  $$.subY2 = $$.getY(
    config.axis_y2_type,
    forInit ? config.axis_y2_default : $$.subY2.domain(),
    [$$.subYMin, $$.subYMax]
  )
  // update axes
  $$.xAxisTickFormat = $$.axis.getXAxisTickFormat()
  $$.xAxisTickValues = $$.axis.getXAxisTickValues()
  $$.yAxisTickValues = $$.axis.getYAxisTickValues()
  $$.y2AxisTickValues = $$.axis.getY2AxisTickValues()

  $$.xAxis = $$.axis.getXAxis(
    $$.x,
    $$.xOrient,
    $$.xAxisTickFormat,
    $$.xAxisTickValues,
    config.axis_x_tick_outer
  )
  $$.subXAxis = $$.axis.getXAxis(
    $$.subX,
    $$.subXOrient,
    $$.xAxisTickFormat,
    $$.xAxisTickValues,
    config.axis_x_tick_outer
  )
  $$.yAxis = $$.axis.getYAxis(
    'y',
    $$.y,
    $$.yOrient,
    $$.yAxisTickValues,
    config.axis_y_tick_outer
  )
  $$.y2Axis = $$.axis.getYAxis(
    'y2',
    $$.y2,
    $$.y2Orient,
    $$.y2AxisTickValues,
    config.axis_y2_tick_outer
  )

  // Set initialized scales to brush and zoom
  if (!forInit) {
    if ($$.brush) {
      $$.brush.updateScale($$.subX)
    }
  }
  // update for arc
  if ($$.updateArc) {
    $$.updateArc()
  }
}
