///|
pub fn text_part(name : String, value : String) -> FormPart {
  { name, filename: None, content_type: None, headers: [], body: value }
}

///|
pub fn file_part(
  name : String,
  filename : String,
  body : String,
  content_type? : String,
) -> FormPart {
  {
    name,
    filename: Some(safe_filename(filename)),
    content_type,
    headers: [],
    body,
  }
}

///|
pub fn empty_file_part(
  name : String,
  filename : String,
  content_type? : String,
) -> FormPart {
  match content_type {
    Some(value) => file_part(name, filename, "", content_type=value)
    None => file_part(name, filename, "")
  }
}

///|
pub fn part_with_content_type(
  part : FormPart,
  content_type : String,
) -> FormPart {
  {
    name: part.name,
    filename: part.filename,
    content_type: Some(content_type),
    headers: clone_header_array(part.headers),
    body: part.body,
  }
}

///|
pub fn part_without_content_type(part : FormPart) -> FormPart {
  {
    name: part.name,
    filename: part.filename,
    content_type: None,
    headers: clone_header_array(part.headers),
    body: part.body,
  }
}

///|
pub fn part_with_body(part : FormPart, body : String) -> FormPart {
  {
    name: part.name,
    filename: part.filename,
    content_type: part.content_type,
    headers: clone_header_array(part.headers),
    body,
  }
}

///|
pub fn part_with_name(part : FormPart, name : String) -> FormPart {
  {
    name,
    filename: part.filename,
    content_type: part.content_type,
    headers: clone_header_array(part.headers),
    body: part.body,
  }
}

///|
pub fn part_with_filename(part : FormPart, filename : String) -> FormPart {
  {
    name: part.name,
    filename: Some(safe_filename(filename)),
    content_type: part.content_type,
    headers: clone_header_array(part.headers),
    body: part.body,
  }
}

///|
pub fn part_without_filename(part : FormPart) -> FormPart {
  {
    name: part.name,
    filename: None,
    content_type: part.content_type,
    headers: clone_header_array(part.headers),
    body: part.body,
  }
}

///|
pub fn part_add_header(
  part : FormPart,
  name : String,
  value : String,
) -> Result[FormPart, MultipartError] {
  if !is_safe_header_name(name) {
    return Err(InvalidHeader(name))
  }
  let headers = clone_header_array(part.headers)
  headers.push((name, strip_header_linebreaks(value)))
  Ok({
    name: part.name,
    filename: part.filename,
    content_type: part.content_type,
    headers,
    body: part.body,
  })
}

///|
pub fn part_replace_header(
  part : FormPart,
  name : String,
  value : String,
) -> Result[FormPart, MultipartError] {
  if !is_safe_header_name(name) {
    return Err(InvalidHeader(name))
  }
  let headers = Array::new()
  let mut replaced = false
  let mut i = 0
  while i < part.headers.length() {
    let (header_name, header_value) = part.headers[i]
    if header_name.compare_ignore_ascii_case(name) == 0 {
      headers.push((header_name, strip_header_linebreaks(value)))
      replaced = true
    } else {
      headers.push((header_name, header_value))
    }
    i = i + 1
  }
  if !replaced {
    headers.push((name, strip_header_linebreaks(value)))
  }
  Ok({
    name: part.name,
    filename: part.filename,
    content_type: part.content_type,
    headers,
    body: part.body,
  })
}

///|
pub fn part_remove_header(part : FormPart, name : String) -> FormPart {
  let headers = Array::new()
  let mut i = 0
  while i < part.headers.length() {
    let (header_name, value) = part.headers[i]
    if header_name.compare_ignore_ascii_case(name) != 0 {
      headers.push((header_name, value))
    }
    i = i + 1
  }
  {
    name: part.name,
    filename: part.filename,
    content_type: part.content_type,
    headers,
    body: part.body,
  }
}

///|
pub fn FormPart::is_file(self : FormPart) -> Bool {
  self.filename is Some(_)
}

///|
pub fn FormPart::is_text_field(self : FormPart) -> Bool {
  self.filename is None
}

///|
pub fn FormPart::body_length(self : FormPart) -> Int {
  self.body.length()
}

///|
pub fn FormPart::is_empty(self : FormPart) -> Bool {
  self.body.length() == 0
}

///|
pub fn FormPart::header(self : FormPart, name : String) -> String? {
  header_value(self.headers, name)
}

///|
pub fn FormPart::has_header(self : FormPart, name : String) -> Bool {
  self.header(name) is Some(_)
}

///|
pub fn FormPart::content_type_or(self : FormPart, fallback : String) -> String {
  self.content_type.unwrap_or(fallback)
}

///|
pub fn FormPart::filename_or(self : FormPart, fallback : String) -> String {
  self.filename.unwrap_or(fallback)
}

///|
pub fn FormPart::safe_filename(self : FormPart) -> String? {
  match self.filename {
    Some(value) => Some(safe_filename(value))
    None => None
  }
}

///|
pub fn FormPart::disposition_header(self : FormPart) -> String {
  let filename_segment = match self.filename {
    Some(filename) =>
      "; filename=\"" + escape_quoted(safe_filename(filename)) + "\""
    None => ""
  }
  "form-data; name=\"" + escape_quoted(self.name) + "\"" + filename_segment
}

///|
pub fn FormPart::encoded_header_block(
  self : FormPart,
) -> Result[String, MultipartError] {
  if self.name == "" {
    return Err(InvalidContentDisposition("part name must not be empty"))
  }
  let lines = Array::new()
  lines.push("Content-Disposition: " + self.disposition_header())
  match self.content_type {
    Some(content_type) =>
      if content_type != "" {
        lines.push("Content-Type: " + content_type)
      }
    None => ()
  }
  let mut i = 0
  while i < self.headers.length() {
    let (name, value) = self.headers[i]
    if !is_safe_header_name(name) {
      return Err(InvalidHeader(name))
    }
    if name.compare_ignore_ascii_case("Content-Disposition") != 0 &&
      name.compare_ignore_ascii_case("Content-Type") != 0 {
      lines.push(name + ": " + strip_header_linebreaks(value))
    }
    i = i + 1
  }
  Ok(lines.join("\r\n"))
}

///|
pub fn FormPart::debug_line(self : FormPart) -> String {
  let kind = if self.is_file() { "file" } else { "field" }
  let filename = match self.filename {
    Some(value) => "; filename=" + value
    None => ""
  }
  kind +
  "; name=" +
  self.name +
  filename +
  "; bytes=" +
  self.body.length().to_string()
}

///|
pub(all) struct FormBuilder {
  boundary : String?
  parts : Array[FormPart]
} derive(Debug, Eq)

///|
pub fn FormBuilder::new() -> FormBuilder {
  { boundary: None, parts: Array::new() }
}

///|
pub fn new_form_builder() -> FormBuilder {
  FormBuilder::new()
}

///|
pub fn FormBuilder::with_boundary(
  self : FormBuilder,
  boundary : String,
) -> Result[FormBuilder, MultipartError] {
  match validate_boundary(boundary) {
    Ok(value) =>
      Ok({ boundary: Some(value), parts: clone_part_array(self.parts) })
    Err(err) => Err(err)
  }
}

///|
pub fn FormBuilder::add_part(
  self : FormBuilder,
  part : FormPart,
) -> FormBuilder {
  self.parts.push(part)
  self
}

///|
pub fn FormBuilder::add_field(
  self : FormBuilder,
  name : String,
  value : String,
) -> FormBuilder {
  self.parts.push(text_part(name, value))
  self
}

///|
pub fn FormBuilder::add_file(
  self : FormBuilder,
  name : String,
  filename : String,
  body : String,
  content_type? : String,
) -> FormBuilder {
  match content_type {
    Some(value) =>
      self.parts.push(file_part(name, filename, body, content_type=value))
    None => self.parts.push(file_part(name, filename, body))
  }
  self
}

///|
pub fn FormBuilder::add_empty_file(
  self : FormBuilder,
  name : String,
  filename : String,
  content_type? : String,
) -> FormBuilder {
  match content_type {
    Some(value) =>
      self.parts.push(empty_file_part(name, filename, content_type=value))
    None => self.parts.push(empty_file_part(name, filename))
  }
  self
}

///|
pub fn FormBuilder::part_count(self : FormBuilder) -> Int {
  self.parts.length()
}

///|
pub fn FormBuilder::is_empty(self : FormBuilder) -> Bool {
  self.parts.length() == 0
}

///|
pub fn FormBuilder::to_parts(self : FormBuilder) -> Array[FormPart] {
  clone_part_array(self.parts)
}

///|
pub fn FormBuilder::build(
  self : FormBuilder,
) -> Result[EncodedForm, MultipartError] {
  match self.boundary {
    Some(boundary) => encode_multipart(self.parts, boundary~)
    None => encode_multipart(self.parts)
  }
}

///|
pub fn FormBuilder::build_and_parse(
  self : FormBuilder,
) -> Result[MultipartForm, MultipartError] {
  match self.build() {
    Ok(encoded) => parse_multipart(encoded.body, encoded.boundary)
    Err(err) => Err(err)
  }
}

///|
fn clone_part_array(parts : Array[FormPart]) -> Array[FormPart] {
  let out = Array::new()
  let mut i = 0
  while i < parts.length() {
    out.push(parts[i])
    i = i + 1
  }
  out
}

///|
fn clone_header_array(
  headers : Array[(String, String)],
) -> Array[(String, String)] {
  let out = Array::new()
  let mut i = 0
  while i < headers.length() {
    out.push(headers[i])
    i = i + 1
  }
  out
}