///|
/// Per-part metadata for a caller-produced multipart/byteranges body.
pub struct MultipartPartPlan {
  range : ConcreteRange
  content_range : ContentRangeValue
  content_length : Int64
} derive(Eq, Debug)

///|
pub struct MultipartPlan {
  boundary : String
  content_type : String
  parts : Array[MultipartPartPlan]
} derive(Eq, Debug)

///|
pub fn plan_multipart_ranges(
  ranges : Array[ConcreteRange],
  representation_length : Int64,
  boundary : String,
) -> Result[MultipartPlan, RangeError] {
  if boundary.length() == 0 || boundary.length() > 70 {
    return Err(
      range_error(
        Plan,
        InvalidBoundary,
        0,
        "multipart boundary must contain 1 to 70 characters",
      ),
    )
  }
  for c in boundary {
    if c.to_int() <= 0x20 || c.to_int() >= 0x7F || c == '"' {
      return Err(
        range_error(
          Plan,
          InvalidBoundary,
          0,
          "multipart boundary must be visible ASCII without quotes",
        ),
      )
    }
  }
  if representation_length < 0L {
    return Err(
      range_error(
        Plan,
        InvalidRepresentationLength,
        0,
        "representation length cannot be negative",
      ),
    )
  }
  let parts : Array[MultipartPartPlan] = []
  for range in ranges {
    if range.first() < 0L ||
      range.last() < range.first() ||
      range.last() >= representation_length {
      return Err(
        range_error(
          Plan,
          InvalidRange,
          0,
          "multipart range is outside the representation",
        ),
      )
    }
    parts.push({
      range,
      content_range: Satisfied(
        Bytes,
        range.first(),
        range.last(),
        Some(representation_length),
      ),
      content_length: range.length(),
    })
  }
  Ok({ boundary, content_type: multipart_content_type(boundary), parts })
}

///|
pub fn multipart_content_type(boundary : String) -> String {
  "multipart/byteranges; boundary=\{boundary}"
}

///|
pub fn MultipartPartPlan::range(self : MultipartPartPlan) -> ConcreteRange {
  self.range
}

///|
pub fn MultipartPartPlan::content_range(
  self : MultipartPartPlan,
) -> ContentRangeValue {
  self.content_range
}

///|
pub fn MultipartPartPlan::content_length(self : MultipartPartPlan) -> Int64 {
  self.content_length
}

///|
pub fn MultipartPlan::boundary(self : MultipartPlan) -> String {
  self.boundary
}

///|
pub fn MultipartPlan::content_type(self : MultipartPlan) -> String {
  self.content_type
}

///|
pub fn MultipartPlan::parts(self : MultipartPlan) -> Array[MultipartPartPlan] {
  self.parts.copy()
}