///|
// SEG scalars: positive multiplies, negative divides, zero means one.
fn scalar(value : Int) -> Double {
  if value == 0 {
    1.0
  } else if value > 0 {
    value.to_double()
  } else {
    -1.0 / value.to_double()
  }
}

///|
/// Apply only the designated SEG header scalar. No CRS conversion or unit
/// guessing: length coordinates stay in declared meters/feet, angular fields
/// in their recorded units. Use coordinate_degrees for explicit angular units.
pub fn Trace::scaled_field(self : Trace, name : String) -> Double raise {
  let scale_name = match name {
    "source_x" | "source_y" | "group_x" | "group_y" | "cdp_x" | "cdp_y" =>
      "coordinate_scalar"
    "receiver_elevation" | "source_elevation" | "source_depth" =>
      "elevation_scalar"
    "shotpoint" => "shotpoint_scalar"
    "delay_ms" => "time_scalar"
    _ => raise Failure("field has no supported scalar")
  }
  let scale = if self.revision == 0 && scale_name == "time_scalar" {
    1.0
  } else {
    scalar(self.field(scale_name))
  }
  self.field(name).to_double() * scale
}

///|
/// Angular units 2 (arc-seconds), 3 (degrees), 4 (signed DDDMMSS.ss).
/// Unknown/linear coordinates raise; do not assume a geographic CRS.
pub fn Trace::coordinate_degrees(self : Trace, name : String) -> Double raise {
  if !(name == "source_x" ||
    name == "source_y" ||
    name == "group_x" ||
    name == "group_y" ||
    name == "cdp_x" ||
    name == "cdp_y") {
    raise Failure("coordinate field required")
  }
  let v = self.scaled_field(name)
  match self.field("coordinate_units") {
    2 => v / 3600.0
    3 => v
    4 => {
      let a = v.abs()
      let degrees = (a / 10000.0).trunc()
      let rest = a - degrees * 10000.0
      let minutes = (rest / 100.0).trunc()
      let seconds = rest - minutes * 100.0
      if minutes >= 60.0 || seconds >= 60.0 {
        raise Failure("invalid packed DMS coordinate")
      }
      (if v < 0.0 { -1.0 } else { 1.0 }) *
      (degrees + minutes / 60.0 + seconds / 3600.0)
    }
    _ => raise Failure("coordinate units are not explicitly angular")
  }
}

///|
/// Time relative to trace source time; scaled delay (ms) plus index*dt (us).
pub fn Trace::time_seconds(self : Trace, index : Int) -> Double raise {
  if index < 0 || index >= self.count {
    raise Failure("sample index outside trace")
  }
  let value = self.scaled_field("delay_ms") / 1000.0 +
    index.to_double() * self.dt / 1000000.0
  if !finite(value) {
    raise Failure("sample time overflow")
  }
  value
}

///|
/// Optional 2^(-weighting) header gain only; does not apply transduction/units.
pub fn Trace::amplitude(
  self : Trace,
  index : Int,
  weighted? : Bool = false,
) -> Double raise {
  let raw = self.sample(index).as_double()
  if !weighted {
    return raw
  }
  let weight = self.field("weighting")
  if weight < 0 {
    raise Failure("negative trace weighting is unsupported")
  }
  let result = raw * @math.pow(2.0, -weight.to_double())
  if !finite(result) || (raw != 0.0 && result == 0.0) {
    raise Failure("trace weighting loses numerical range")
  }
  result
}