///|
/// netascii, the text transfer mode of RFC 1350 section 2.
///
/// On the wire netascii is a 7-bit ASCII stream with CR LF for a line ending
/// and CR NUL for a lone CR; on this side it is ordinary bytes. The two
/// directions are not symmetric, because a newline has exactly one encoding
/// while a lone CR has two possible sources:
///
/// ```text
/// decode (wire -> local):  \r\n -> \n        \r\0 -> \r
/// encode (local -> wire):  \n   -> \r\n      \r   -> \r\0
/// ```
///
/// Both directions therefore need one carried flag, because a `\r` can land on
/// the last byte of a DATA block and its partner then belongs to the next one.
/// `NetasciiState` is that carried flag, and it is why the codec is a pair of
/// stateful functions rather than one `Bytes -> Bytes` conversion. On encoding
/// the CR is held back and re-emitted when the next block says whether it is a
/// line ending or a lone CR; on decoding the held CR is likewise resolved by
/// the next block's first byte. The one thing either direction must never do
/// is drop a byte.

///|
/// The byte a pending escape belongs to, carried across a block boundary.
pub(all) enum NetasciiState {
  /// Nothing pending: the next byte starts a fresh character.
  Clean
  /// A `\r` was seen on this side with no partner encoded yet.
  LocalCr
} derive(Eq, Debug)

///|
pub extend NetasciiState with Eq::{not_equal, equal}

///|
pub extend NetasciiState with @moonbitlang/core/debug.Debug::{to_repr}

///|
/// Encodes one block of local bytes for the netascii wire.
///
/// Every `\n` becomes `\r\n` and every `\r` becomes `\r\0`. A `\r` that lands
/// on the last byte of a block is *held back* rather than written: the next
/// block's first byte then decides whether the held CR becomes `\r\n` (a line
/// ending split across the boundary) or `\r\0` (a real lone CR). A stream that
/// ends with a held CR gets its NUL from `netascii_encode_finish`.
pub fn netascii_encode(
  state : NetasciiState,
  chunk : Bytes,
) -> (NetasciiState, Bytes) {
  let out = Writer::new()
  let mut pending_cr = state is LocalCr
  for byte in chunk {
    if pending_cr {
      pending_cr = false
      out.write_byte(b'\r')
      if byte == b'\n' {
        out.write_byte(b'\n')
        continue
      }
      out.write_byte(0)
    }
    if byte == b'\n' {
      out.write_byte(b'\r')
      out.write_byte(b'\n')
    } else if byte == b'\r' {
      pending_cr = true
    } else {
      out.write_byte(byte)
    }
  }
  (if pending_cr { LocalCr } else { Clean }, out.to_bytes())
}

///|
/// Decodes one block of netascii received from the wire.
///
/// `\r\n` becomes `\n` and `\r\0` becomes `\r`. A `\r` on the last byte of the
/// block cannot be resolved yet: its partner may be the first byte of the next
/// block, so the decoder *holds it back* and returns `LocalCr`, exactly as the
/// encoder does. The caller then folds the next block in with that state, and
/// the held CR is emitted (or joined to its `\n`) once its partner is known.
/// A stream that ends on a held CR gets it from `netascii_decode_finish`.
pub fn netascii_decode(
  state : NetasciiState,
  chunk : Bytes,
) -> (NetasciiState, Bytes) {
  let out = Writer::new()
  let mut pending_cr = state is LocalCr
  let mut i = 0
  while i < chunk.length() {
    let byte = chunk[i]
    if pending_cr {
      // The partner of the previous block's CR, which was held back.
      pending_cr = false
      if byte == b'\n' {
        out.write_byte(b'\n')
        i = i + 1
        continue
      }
      // A held CR followed by NUL is the wire spelling of a lone CR: emit the
      // CR and swallow the NUL. Any other byte means the CR was on its own
      // (a tolerant bare CR), so emit it and reprocess this byte normally.
      out.write_byte(b'\r')
      if byte == 0 {
        i = i + 1
        continue
      }
    }
    if byte == b'\r' {
      if i + 1 < chunk.length() {
        let partner = chunk[i + 1]
        if partner == b'\n' {
          out.write_byte(b'\n')
          i = i + 2
          continue
        } else if partner == 0 {
          out.write_byte(b'\r')
          i = i + 2
          continue
        }
      }
      // No partner inside this block: hold the CR for the next block.
      pending_cr = true
      i = i + 1
    } else {
      out.write_byte(byte)
      i = i + 1
    }
  }
  (if pending_cr { LocalCr } else { Clean }, out.to_bytes())
}

///|
/// Encodes a whole buffer in one go, for the cases with no block boundary to
/// straddle (a file read into memory, a `tsize` computation).
pub fn netascii_encode_all(bytes : Bytes) -> Bytes {
  let (state, encoded) = netascii_encode(Clean, bytes)
  // At the end of the stream a pending CR is a real lone CR and gets its NUL.
  netascii_encode_finish(state, encoded)
}

///|
/// Decodes a whole buffer in one go.
pub fn netascii_decode_all(bytes : Bytes) -> Bytes {
  let (state, decoded) = netascii_decode(Clean, bytes)
  // A whole buffer has no next block, so a held CR is a real lone CR here.
  netascii_decode_finish(state, decoded)
}

///|
/// Closes the stream on the encoding side: whatever is still pending is a real
/// lone CR and gets its `\0` partner.
pub fn netascii_encode_finish(state : NetasciiState, tail : Bytes) -> Bytes {
  if state is LocalCr {
    let out = Writer::new()
    out.write_bytes(tail)
    out.write_byte(b'\r')
    out.write_byte(0)
    out.to_bytes()
  } else {
    tail
  }
}

///|
/// Closes the stream on the decoding side. A `\r` still held back at the end of
/// the stream had no partner on the wire, so it is a real lone CR and is
/// emitted here; nothing else is pending.
pub fn netascii_decode_finish(state : NetasciiState, tail : Bytes) -> Bytes {
  if state is LocalCr {
    let out = Writer::new()
    out.write_bytes(tail)
    out.write_byte(b'\r')
    out.to_bytes()
  } else {
    tail
  }
}