///|
/// An index grid whose origin is the data minimum plus an offset.
/// Parameters retain all Double values; invalid ranges fail when queried.
pub struct IndexLocator {
  priv mut base : Double
  priv mut offset : Double
  priv mut data_interval : (() -> (Double, Double))?
}

///|
pub fn IndexLocator::new(base~ : Double, offset~ : Double) -> IndexLocator {
  { base, offset, data_interval: None, }
}

///|
pub fn IndexLocator::base(self : IndexLocator) -> Double {
  self.base
}

///|
pub fn IndexLocator::offset(self : IndexLocator) -> Double {
  self.offset
}

///|
pub fn IndexLocator::set_params(
  self : IndexLocator,
  base? : Double,
  offset? : Double,
) -> Unit {
  if base is Some(value) {
    self.base = value
  }
  if offset is Some(value) {
    self.offset = value
  }
}

///|
/// Copy the parameters while retaining the currently bound data provider.
pub fn IndexLocator::copy(self : IndexLocator) -> IndexLocator {
  { base: self.base, offset: self.offset, data_interval: self.data_interval, }
}

///|
/// Bind a source of data bounds. View limits do not replace this interval.
pub fn IndexLocator::bind_data_interval(
  self : IndexLocator,
  provider : () -> (Double, Double),
) -> Unit {
  self.data_interval = Some(provider)
}

///|
/// Query the current data interval, matching the source locator's call protocol.
pub fn IndexLocator::call(
  self : IndexLocator,
) -> Array[Double] raise @core.DataError {
  match self.data_interval {
    None =>
      raise @core.DataError::InvalidTicks(
        message="index locator is not bound to a data interval",
      )
    Some(provider) => {
      let (lower, upper) = provider()
      self.tick_values(lower, upper)
    }
  }
}

///|
/// Source arange semantics, retaining the first 100000 values of larger grids.
/// Bounds keep their incoming order. A negative base can produce descending ticks.
pub fn IndexLocator::tick_values(
  self : IndexLocator,
  lower : Double,
  upper : Double,
) -> Array[Double] raise @core.DataError {
  if self.base == 0.0 {
    raise @core.DataError::InvalidTicks(message="index locator step is zero")
  }
  let start = lower + self.offset
  let stop = upper + 1.0e-12
  let delta = stop - start
  let ratio = delta / self.base
  if ratio.is_nan() {
    raise @core.DataError::InvalidTicks(
      message="index locator cannot compute grid length",
    )
  }
  if ratio.is_inf() ||
    ratio < -9223372036854775808.0 ||
    ratio >= 9223372036854775808.0 {
    raise @core.DataError::InvalidTicks(
      message="index locator grid length exceeds supported size",
    )
  }
  let count = if ratio == 0.0 && delta != 0.0 {
    // NumPy preserves a positive underflowed quotient as one element.
    if ratio.reinterpret_as_uint64() >> 63 == 0UL {
      1.0
    } else {
      0.0
    }
  } else {
    ratio.ceil()
  }
  if count <= 0.0 {
    return []
  }
  // NumPy checks size * sizeof(double) against the signed pointer maximum.
  if count >= 1152921504606846976.0 {
    raise @core.DataError::InvalidTicks(
      message="index locator array exceeds supported size",
    )
  }
  let length = Double::min(count, 100000.0).to_int()
  let second = start + self.base
  let actual_step = second - start
  if length <= 2 || start == 0.0 || actual_step == 0.0 || actual_step.is_inf() {
    return Array::makei(length, index => {
      if index == 0 {
        start
      } else if index == 1 {
        second
      } else {
        start + Double::from_int(index) * actual_step
      }
    })
  }
  let (start_integer, start_exponent) = index_binary_parts(start)
  let (step_integer, step_exponent) = index_binary_parts(actual_step)
  let exponent = Int32::min(start_exponent, step_exponent)
  let start_integer = start_integer << (start_exponent - exponent)
  let step_integer = step_integer << (step_exponent - exponent)
  Array::makei(length, index => {
    if index == 0 {
      start
    } else if index == 1 {
      second
    } else {
      index_round_binary(
        start_integer + step_integer * @bigint.BigInt::from_int(index),
        exponent,
      )
    }
  })
}

///|
pub fn IndexLocator::nonsingular(
  _self : IndexLocator,
  lower : Double,
  upper : Double,
) -> (Double, Double) {
  locator_nonsingular(lower, upper, 0.05)
}

///|
pub fn IndexLocator::view_limits(
  _self : IndexLocator,
  lower : Double,
  upper : Double,
) -> (Double, Double) {
  locator_nonsingular(lower, upper, 0.001)
}