///|
/// Input and buffer shared by Reader and its parts.
priv struct ReaderState {
  /// Stream containing the multipart message.
  source : &@io.Reader
  /// `\r\n--` followed by the boundary string.
  delimiter : Bytes
  /// Buffered input. Unread bytes start at `cursor`.
  mut pending : Bytes
  /// Position of the next unread byte in `pending`.
  mut cursor : Int
  /// Number of body bytes at `cursor` that can be returned without another scan.
  mut ready : Int
  /// The source has no more bytes, though buffered bytes may remain.
  mut eof : Bool
  /// The closing boundary has been read. There are no more parts.
  mut finished : Bool
}

///|
/// Retain only unread input when a delimiter/header spans source reads.
async fn ReaderState::ensure(self : Self, length : Int) -> Bool {
  while self.pending.length() - self.cursor < length && !self.eof {
    if self.source.read_some(max_len=8192) is Some(chunk) {
      let remaining = self.pending.length() - self.cursor
      if remaining == 0 {
        self.pending = chunk
      } else {
        let pending = FixedArray::make(remaining + chunk.length(), b'\x00')
        pending.blit_from_bytesview(0, self.pending[self.cursor:])
        pending.blit_from_bytesview(remaining, chunk)
        // This array is never modified after becoming Bytes.
        self.pending = pending.unsafe_reinterpret_as_bytes()
      }
      self.cursor = 0
    } else {
      self.eof = true
    }
  }
  self.pending.length() - self.cursor >= length
}

///|
/// Read a part's headers through the blank line before its body.
async fn ReaderState::read_headers(self : Self) -> @http.Headers {
  let headers : @http.Headers = Map([])
  let mut previous : @http.CaseInsensitiveString? = None
  for ;; {
    let bytes = self.read_header_line()
    if bytes.is_empty() {
      break
    }
    let line = @utf8.decode(bytes, ignore_bom=false) catch {
      _ => raise MultipartError("Invalid UTF-8 in multipart header")
    }
    if line is [' ' | '\t', ..] {
      guard previous is Some(key) else {
        raise MultipartError("Multipart header continuation without a field")
      }
      validate_header(key.0, line)
      headers[key] = headers[key] + line
    } else {
      if previous is Some(key) {
        headers[key] = headers[key].trim_end(chars=" \t").to_owned()
      }
      guard line.split_once(":") is Some((name, value)) else {
        raise MultipartError("Invalid multipart header")
      }
      let name = name.to_owned()
      let value = value.trim_start(chars=" \t").to_owned()
      validate_header(name, value)
      let key = @http.CaseInsensitiveString(name)
      headers[key] = if headers.get(key) is Some(old) {
        "\{old},\{value}"
      } else {
        value
      }
      previous = Some(key)
    }
  }
  if previous is Some(key) {
    headers[key] = headers[key].trim_end(chars=" \t").to_owned()
  }
  headers
}

///|
async fn ReaderState::read_header_line(self : Self) -> BytesView {
  for searched = 0 {
    if self.pending[self.cursor + searched:].find(b"\r\n") is Some(index) {
      let end = self.cursor + searched + index
      let line = self.pending[self.cursor:end]
      self.cursor = end + 2
      return line
    }
    let length = self.pending.length() - self.cursor
    guard self.ensure(length + 1) else {
      raise MultipartError("Unexpected EOF in multipart headers")
    }
    continue if length == 0 { 0 } else { length - 1 }
  }
}

///|
/// The delimiter prefix has matched at cursor. Check its suffix before
/// consuming it: a prefix followed by other bytes remains body data.
async fn ReaderState::consume_boundary(self : Self, end : Int) -> Bool {
  guard self.ensure(end + 1) else {
    raise MultipartError("Unexpected EOF in multipart boundary")
  }
  let closing = if self.pending[self.cursor + end] == b'-' {
    if self.ensure(end + 2) && self.pending[self.cursor + end + 1] == b'-' {
      true
    } else {
      return false
    }
  } else if self.pending[self.cursor + end] is (b' ' | b'\t' | b'\r' | b'\n') {
    false
  } else {
    return false
  }
  self.cursor += end + (if closing { 2 } else { 0 })
  // After recognizing a delimiter, discard transport padding incrementally.
  // Its length must not make the body buffer grow with the input.
  while self.ensure(1) && self.pending[self.cursor] is (b' ' | b'\t') {
    self.cursor += 1
  }
  if closing && self.eof && self.cursor == self.pending.length() {
    self.finished = true
    return true
  }
  guard self.ensure(2) && self.pending[self.cursor:self.cursor + 2] == b"\r\n" else {
    raise MultipartError("Invalid multipart boundary line")
  }
  self.cursor += 2
  self.finished = closing
  true
}

///|
/// Find body bytes and record their count in `ready`.
/// Return false after consuming the next boundary.
async fn ReaderState::scan_body(self : Self) -> Bool {
  for ;; {
    if self.ready > 0 {
      return true
    }
    let available = self.pending[self.cursor:]
    self.ready = if available.find(self.delimiter) is Some(index) {
      if index == 0 {
        if self.consume_boundary(self.delimiter.length()) {
          return false
        }
        self.delimiter.length()
      } else {
        index
      }
    } else {
      // A partial delimiter fits in this short tail. Since the boundary cannot
      // contain CR, only the last CR can begin a matching prefix.
      let start = @cmp.maximum(
        0,
        available.length() - self.delimiter.length() + 1,
      )
      if available[start:].rev_find(b"\r") is Some(index) &&
        self.delimiter.has_prefix(available[start + index:]) {
        start + index
      } else {
        available.length()
      }
    }
    if self.ready == 0 {
      guard self.ensure(self.pending.length() - self.cursor + 1) else {
        raise MultipartError("Unexpected EOF before multipart boundary")
      }
    }
  }
}

///|
/// Parses an unfolded MIME field value, such as Content-Type or Content-Disposition.
/// The main value is a token or type/subtype; parameter names are case-insensitive.
/// Main values and parameter values retain their case. Comments are ignored and
/// quoted pairs are unescaped; percent escapes and extended parameters stay raw.
/// Grammar: RFC 2045 §5.1 and RFC 2183 §2.
pub fn parse_header_value(
  value : String,
) -> (String, Map[@http.CaseInsensitiveString, String]) raise MultipartError {
  validate_header_value(value)
  let (kind, rest) = header_token(header_space(value))
  let rest = header_space(rest)
  let (kind, rest) = if rest is ['/', .. rest] {
    let (subtype, rest) = header_token(header_space(rest))
    ("\{kind}/\{subtype}", rest)
  } else {
    (kind, rest)
  }
  let parameters : Map[@http.CaseInsensitiveString, String] = Map([])
  for rest = rest {
    let rest = header_space(rest)
    guard rest != "" else { break (kind, parameters) }
    guard rest is [';', .. rest] else {
      raise MultipartError("expected ';' before MIME parameter")
    }
    let (attribute, rest) = header_token(header_space(rest))
    guard header_space(rest) is ['=', .. rest] else {
      raise MultipartError("expected '=' in MIME parameter")
    }
    let rest = header_space(rest)
    let (value, rest) = if rest is ['"', .. rest] {
      let result = StringBuilder()
      let rest = for rest = rest {
        match rest {
          ['"', .. tail] => break tail
          ['\\', c, .. tail] => {
            result.write_char(c)
            continue tail
          }
          [c, .. tail] if c != '\\' => {
            result.write_char(c)
            continue tail
          }
          _ => raise MultipartError("invalid quoted MIME parameter")
        }
      }
      (result.to_string(), rest)
    } else {
      header_token(rest)
    }
    let attribute = @http.CaseInsensitiveString(attribute)
    guard !parameters.contains(attribute) else {
      raise MultipartError("duplicate MIME parameter")
    }
    parameters[attribute] = value
    continue rest
  }
}

///|
fn header_token(
  source : StringView,
) -> (String, StringView) raise MultipartError {
  let end = source
    .find_by(c => !c.is_ascii_graphic() || "()<>@,;:\\\"/[]?=".contains_char(c))
    .unwrap_or(source.length())
  let (token, rest) = source.split_at(end)
  guard token != "" else { raise MultipartError("expected a MIME token") }
  (token.to_owned(), rest)
}

///|
// RFC 2045 §5.1 permits RFC 822 comments between lexical tokens, including
// nested comments and quoted pairs. Input field values are already unfolded.
fn header_space(source : StringView) -> StringView raise MultipartError {
  for rest = source, depth = 0 {
    match rest {
      ['(', .. tail] => continue tail, depth + 1
      [')', .. tail] if depth > 0 => continue tail, depth - 1
      ['\\', _, .. tail] if depth > 0 => continue tail, depth
      [' ' | '\t', .. tail] => continue tail, depth
      [_, .. tail] if depth > 0 => continue tail, depth
      _ if depth > 0 => raise MultipartError("unterminated MIME comment")
      _ => break rest
    }
  }
}

///|
// RFC 5322 section 3.6.8 field-name syntax; RFC 7578 permits UTF-8 metadata.
fn validate_header(name : String, value : String) -> Unit raise MultipartError {
  guard !name.is_empty() else {
    raise MultipartError("multipart header name is empty")
  }
  for ch in name {
    guard ch >= '!' && ch <= '~' && ch != ':' else {
      raise MultipartError("invalid multipart header name")
    }
  }
  validate_header_value(value)
}

///|
fn validate_header_value(value : String) -> Unit raise MultipartError {
  for ch in value {
    guard ch == '\t' || (ch >= ' ' && ch != '\u007f') else {
      raise MultipartError("invalid control character in multipart header")
    }
  }
}