///|
pub(all) struct HttpResponse {
  mut status_code : StatusCode
  headers : Map[@http.CaseInsensitiveString, StringView]
  cookies : Array[@http.Cookie]
  mut body : &Responder?
}

///|
/// Buffers output written through `@io.Writer` for backends that require a
/// complete byte slice before they can send a response.
pub(all) struct ResponseBuffer {
  buffer : Buffer
}

///|
pub fn ResponseBuffer::new() -> ResponseBuffer {
  { buffer: Buffer(), }
}

///|
pub fn ResponseBuffer::bytes(self : ResponseBuffer) -> Bytes {
  self.buffer.to_bytes()
}

///|
fn ResponseBuffer::write_data(self : ResponseBuffer, data : &@io.Data) -> Unit {
  self.buffer.write_bytes(data.binary())
}

///|
fn ResponseBuffer::write_chunk(
  self : ResponseBuffer,
  bytes : Bytes,
  offset~ : Int,
  len~ : Int,
) -> Int {
  self.buffer.write_bytes(bytes[offset:offset + len])
  len
}

///|
async fn ResponseBuffer::write_from_reader(
  self : ResponseBuffer,
  reader : &@io.Reader,
) -> Unit {
  self.write_data(reader.read_all())
}

///|
pub impl @io.Writer for ResponseBuffer with fn write(self, data) {
  self.write_data(data)
}

///|
pub impl @io.Writer for ResponseBuffer with fn write_once(
  self,
  bytes,
  offset~,
  len~,
) {
  self.write_chunk(bytes, offset~, len~)
}

///|
pub impl @io.Writer for ResponseBuffer with fn write_reader(self, reader) {
  self.write_from_reader(reader)
}

///|
pub async fn[T : BodyReader] HttpResponse::read_body(self : HttpResponse) -> T {
  let response_writer = ResponseBuffer::new()
  match self.body {
    Some(body) => body.output(response_writer)
    None => ()
  }
  let bytes = response_writer.bytes()
  T::from_request({
    http_method: "",
    url: "",
    query: "",
    headers: self.headers,
    reader: @io.MemoryReader(writer => writer.write(bytes)),
  })
}

///|
#alias(new, deprecated="Use `HttpResponse()` instead")
pub fn HttpResponse::HttpResponse(
  status_code : StatusCode,
  headers? : Map[@http.CaseInsensitiveString, StringView],
  cookies? : Array[@http.Cookie],
  body? : &Responder,
) -> HttpResponse {
  {
    status_code,
    headers: headers.unwrap_or({}),
    cookies: cookies.unwrap_or([]),
    body,
  }
}

///|
/// Sets the response body from any `Responder`.
///
/// The body responder's default `Content-Type` is carried into this
/// response only when no explicit `"Content-Type"` header was set on this
/// response. Explicit response headers always take precedence.
///
/// Only the default `Content-Type` is inferred. The responder's status,
/// other headers, cookies, and any other responder effects are not merged,
/// so `self.status_code` is preserved.
pub fn HttpResponse::body(
  self : HttpResponse,
  body : &Responder,
) -> HttpResponse {
  let probe = HttpResponse(OK)
  body.options(probe)
  if probe.headers.get("Content-Type") is Some(content_type) &&
    !self.headers.contains("Content-Type") {
    self.headers["Content-Type"] = content_type
  }
  self.body = Some(body)
  self
}

///|
/// Sets a JSON response body.
///
/// Infers `Content-Type: application/json; charset=utf-8` under the same
/// rule as `HttpResponse::body`: an explicit `"Content-Type"` header already
/// present on this response takes precedence, and `self.status_code` is
/// preserved.
pub fn HttpResponse::json(self : HttpResponse, obj : &ToJson) -> HttpResponse {
  self.body(obj.to_json())
}

///|
pub fn HttpResponse::to_responder(self : HttpResponse) -> &Responder {
  self
}