///|
/// Owned raw trace. Sample bytes are retained exactly until explicitly changed.
pub struct Trace {
  priv head : Bytes
  priv payload : Bytes
  priv count : Int
  priv dt : Double
  priv byte_order : Endian
  priv code : Int
  priv revision : Int
}

///|
fn field_location(name : String) -> (Int, Int) raise {
  match name {
    "sequence_line" => (0, 4)
    "sequence_file" => (4, 4)
    "field_record" => (8, 4)
    "trace_in_record" => (12, 4)
    "source_point" => (16, 4)
    "ensemble" => (20, 4)
    "trace_in_ensemble" => (24, 4)
    "identification" => (28, 2)
    "offset" => (36, 4)
    "receiver_elevation" => (40, 4)
    "source_elevation" => (44, 4)
    "source_depth" => (48, 4)
    "elevation_scalar" => (68, 2)
    "coordinate_scalar" => (70, 2)
    "source_x" => (72, 4)
    "source_y" => (76, 4)
    "group_x" => (80, 4)
    "group_y" => (84, 4)
    "coordinate_units" => (88, 2)
    "delay_ms" => (108, 2)
    "weighting" => (168, 2)
    "cdp_x" => (180, 4)
    "cdp_y" => (184, 4)
    "inline" => (188, 4)
    "crossline" => (192, 4)
    "shotpoint" => (196, 4)
    "shotpoint_scalar" => (200, 2)
    "time_scalar" => (214, 2)
    _ => raise Failure("unknown trace field: " + name)
  }
}

///|
pub fn Trace::samples(self : Trace) -> Int {
  self.count
}

///|
pub fn Trace::interval_us(self : Trace) -> Double {
  self.dt
}

///|
/// Raw signed header value, before coordinate/elevation/time scaling.
pub fn Trace::field(self : Trace, name : String) -> Int raise {
  let (pos, w) = field_location(name)
  if self.revision == 0 && pos >= 180 {
    raise Failure("field not standardized in rev0")
  }
  int_at(self.head, pos, w, self.byte_order).to_int()
}

///|
pub fn Trace::raw_header(self : Trace) -> Bytes {
  self.head
}

///|
pub fn Trace::raw_samples(self : Trace) -> Bytes {
  self.payload
}

///|
pub fn Trace::sample(self : Trace, index : Int) -> Sample raise {
  if index < 0 || index >= self.count {
    raise Failure("sample index outside trace")
  }
  decode_sample(
    self.payload,
    index * sample_width(self.code),
    self.code,
    self.byte_order,
  )
}

///|
/// Build a trace at the file's byte order/format. fields use named signed raw
/// values, not scaled coordinates. Unknown fields and narrowed overflow raise.
pub fn make_trace(
  header : FileHeader,
  samples : Array[Sample],
  interval_us? : Double,
  fields? : Map[String, Int] = Map([]),
) -> Trace raise {
  let n = samples.length()
  let dt = interval_us.unwrap_or(header.interval_us)
  if n < 1 || n > 1000000 || !finite(dt) || dt <= 0.0 {
    raise Failure("invalid trace count or interval")
  }
  if (n > 65535 || dt > 65535.0 || dt != dt.trunc()) &&
    !(header.revision == 2 &&
    header.fixed_length &&
    n == header.samples &&
    dt == header.interval_us) {
    raise Failure(
      "wide trace needs fixed rev2 binary count/interval; extension headers unsupported",
    )
  }
  let out = Array::make(240, b'\x00')
  for name, value in fields {
    let (pos, w) = field_location(name)
    if header.revision == 0 && pos >= 180 {
      raise Failure("field not standardized in rev0")
    }
    if w == 2 && (value < -32768 || value > 32767) {
      raise Failure("16-bit trace field overflow")
    }
    put_uint(
      out,
      pos,
      w,
      value.to_int64().reinterpret_as_uint64(),
      header.endian,
    )
  }
  put_uint(
    out,
    114,
    2,
    if n <= 65535 {
      n.to_uint64()
    } else {
      0UL
    },
    header.endian,
  )
  put_uint(
    out,
    116,
    2,
    if dt <= 65535.0 && dt == dt.trunc() {
      dt.to_uint64()
    } else {
      0UL
    },
    header.endian,
  )
  {
    head: Bytes::from_array(out),
    payload: encode_samples(samples, header.sample_code, header.endian),
    count: n,
    dt,
    byte_order: header.endian,
    code: header.sample_code,
    revision: header.revision,
  }
}