// serialize.mbt — re-emission and the one-call analysis façade.
//
// Round-trip is a property, not a format: `to_bytes(parse_message(x))` returns `x`
// byte-for-byte for any message whose lines are CRLF-terminated. That property is
// what lets the conformance suite assert on *both* the parse verdict and the
// preserved octets, instead of trusting a pretty-printer.
//
// A message received with bare LF is normalised to CRLF on re-emission. That is
// intentional: bare LF is tolerated on input (every deployed stack tolerates it)
// but is not something we are willing to produce.

///|
/// Renders a parsed message back to octets without normalising anything that was
/// preserved verbatim (header case, fold positions, header order, body).
pub fn to_bytes(msg : SipMessage) -> Bytes {
  let out : Array[Byte] = []
  let start_raw = match msg.start {
    Request(r) => r.raw
    Response(r) => r.raw
  }
  for i in 0.. SipMessage? {
  self.message
}

///|
/// The syntax error kind and offset, when parsing failed.
pub fn Analysis::syntax(self : Analysis) -> (SyntaxKind, Int32)? {
  self.syntax
}

///|
/// Semantic findings. Empty when parsing failed.
pub fn Analysis::diagnostics(self : Analysis) -> Array[Diagnostic] {
  self.diagnostics
}

///|
/// Three-way verdict, mirroring the split RFC 4475 itself makes between the
/// grammar tests (§3.1.2) and the semantic tests (§3.2, §3.3).
pub enum Verdict {
  /// Parsed, and no error-severity diagnostic fired.
  WellFormed
  /// The grammar rejected it.
  SyntaxRejected
  /// Parsed, but a semantic rule fired.
  SemanticRejected
}

///|
pub extend Verdict with Show::{to_string, output}

///|
pub impl Show for Verdict with fn to_string(self : Verdict) -> String {
  match self {
    WellFormed => "well-formed"
    SyntaxRejected => "syntax-rejected"
    SemanticRejected => "semantic-rejected"
  }
}

///|
/// Three-way verdict, mirroring the split RFC 4475 makes.
pub fn Analysis::verdict(self : Analysis) -> Verdict {
  match self.message {
    None => SyntaxRejected
    Some(_) => {
      let mut rejected = false
      for d in self.diagnostics {
        match d.severity {
          Error => rejected = true
          Warning => ()
        }
      }
      if rejected {
        SemanticRejected
      } else {
        WellFormed
      }
    }
  }
}

///|
/// True when at least one diagnostic carries `code`. Comparison goes through the
/// stable string form so the enum needs no hand-written `Eq` instance.
pub fn Analysis::has_code(self : Analysis, code : DiagCode) -> Bool {
  for d in self.diagnostics() {
    if d.code() == code {
      return true
    }
  }
  false
}

///|
/// Parses and diagnoses in one step. Never raises: a syntax failure is data, not
/// an exception, because the whole point of the conformance suite is to report on
/// malformed input.
pub fn analyze(input : Bytes) -> Analysis {
  // One parse, one outcome: the failure reason and the message are produced by the
  // same call, so a malformed input is never parsed twice just to explain itself.
  let outcome : Result[SipMessage, (SyntaxKind, Int32)] = Ok(
    parse_message(input),
  ) catch {
    Syntax(kind, at) => Err((kind, at))
  }
  match outcome {
    Ok(msg) => { message: Some(msg), syntax: None, diagnostics: diagnose(msg), }
    Err(reason) => { message: None, syntax: Some(reason), diagnostics: [], }
  }
}