///|
/// Tajima DST balanced signed displacement records. Y is inverted for DST.
fn dst_record(dx : Int32, dy : Int32, kind : Kind) -> Array[Byte] {
  if kind == End {
    return [0, 0, 243].map(fn(x) { x.to_byte() })
  }
  if kind == Color {
    return [0, 0, 195].map(fn(x) { x.to_byte() })
  }
  let out = [0, 0, if kind == Jump { 131 } else { 3 }]
  fn axis(
    value : Int32,
    layout : Array[(Int32, Int32, Int32, Int32)],
    out : Array[Int32],
  ) -> Unit {
    let mut remaining = value
    for entry in layout {
      let (weight, index, positive, negative) = entry
      if remaining > weight / 2 {
        out[index] = out[index] | (1 << positive)
        remaining = remaining - weight
      } else if remaining < -weight / 2 {
        out[index] = out[index] | (1 << negative)
        remaining = remaining + weight
      }
    }
  }
  axis(
    dx,
    [(81, 2, 2, 3), (27, 1, 2, 3), (9, 0, 2, 3), (3, 1, 0, 1), (1, 0, 0, 1)],
    out,
  )
  axis(
    -dy,
    [(81, 2, 5, 4), (27, 1, 5, 4), (9, 0, 5, 4), (3, 1, 7, 6), (1, 0, 7, 6)],
    out,
  )
  out.map(fn(x) { x.to_byte() })
}

///|
fn pad(value : Int32, width : Int32) -> String {
  let s = value.to_string()
  " ".repeat(if width > s.length() { width - s.length() } else { 0 }) + s
}

///|
/// 512-byte ASCII header plus three-byte records, including one end record.
/// The file carries color-change commands, not thread RGB values or trim codes.
pub fn Plan::dst(self : Plan) -> Bytes {
  let mut xmin = 0
  let mut xmax = 0
  let mut ymin = 0
  let mut ymax = 0
  let mut colors = 0
  for c in self.moves {
    let p = c.position
    xmin = if p.x < xmin { p.x } else { xmin }
    xmax = if p.x > xmax { p.x } else { xmax }
    ymin = if p.y < ymin { p.y } else { ymin }
    ymax = if p.y > ymax { p.y } else { ymax }
    if c.kind == Color {
      colors = colors + 1
    }
  }
  let last = self.moves[self.moves.length() - 1].position
  let ax = if last.x >= 0 { "+" } else { "-" }
  let ay = if last.y <= 0 { "+" } else { "-" }
  let header = "LA:NEEDLERAIL      \rST:" +
    pad(self.moves.length(), 7) +
    "\rCO:" +
    pad(colors, 3) +
    "\r+X:" +
    pad(xmax, 5) +
    "\r-X:" +
    pad(-xmin, 5) +
    "\r+Y:" +
    pad(ymax, 5) +
    "\r-Y:" +
    pad(-ymin, 5) +
    "\rAX:" +
    ax +
    pad(last.x.abs(), 5) +
    "\rAY:" +
    ay +
    pad(last.y.abs(), 5) +
    "\rMX:+    0\rMY:+    0\rPD:******\r"
  let bytes : Array[Byte] = []
  for c in header.iter() {
    bytes.push(c.to_int().to_byte())
  }
  bytes.push(26)
  while bytes.length() < 512 {
    bytes.push(32)
  }
  let mut previous : Point = { x: 0, y: 0, }
  for c in self.moves {
    for
      b in dst_record(
        c.position.x - previous.x,
        c.position.y - previous.y,
        c.kind,
      ) {
      bytes.push(b)
    }
    previous = c.position
  }
  Bytes::from_array(bytes)
}