///|
pub(open) trait Responder {
  fn options(Self, res : HttpResponse) -> Unit
  async fn output(Self, writer : &@io.Writer) -> Unit
}

///|
async fn &ToJson::write_response(self : &ToJson, writer : &@io.Writer) -> Unit {
  writer.write(@utf8.encode(self.to_json().stringify()))
}

///|
pub impl Responder for &ToJson with fn options(_, res) -> Unit {
  res.headers["Content-Type"] = "application/json; charset=utf-8"
}

///|
pub impl Responder for &ToJson with fn output(self, writer) {
  self.write_response(writer)
}

///|
async fn HttpResponse::write_response(
  self : HttpResponse,
  writer : &@io.Writer,
) -> Unit {
  match self.body {
    Some(body) => body.output(writer)
    None => ()
  }
}

///|
pub impl Responder for HttpResponse with fn options(self, res) -> Unit {
  res.status_code = self.status_code
  res.headers.merge_in_place(self.headers)
}

///|
pub impl Responder for HttpResponse with fn output(self, writer) {
  self.write_response(writer)
}

///|
async fn Json::write_response(self : Json, writer : &@io.Writer) -> Unit {
  writer.write(@utf8.encode(@json.to_json(self).stringify()))
}

///|
pub impl Responder for Json with fn options(_, res) -> Unit {
  res.headers["Content-Type"] = "application/json; charset=utf-8"
}

///|
pub impl Responder for Json with fn output(self, writer) {
  self.write_response(writer)
}

///|
async fn Bytes::write_response(self : Bytes, writer : &@io.Writer) -> Unit {
  writer.write(self)
}

///|
pub impl Responder for Bytes with fn options(_, res) -> Unit {
  res.headers["Content-Type"] = "application/octet-stream"
}

///|
pub impl Responder for Bytes with fn output(self, writer) {
  self.write_response(writer)
}

///|
async fn String::write_response(self : String, writer : &@io.Writer) -> Unit {
  writer.write(self)
}

///|
pub impl Responder for String with fn options(_, res) -> Unit {
  res.headers["Content-Type"] = "text/plain; charset=utf-8"
}

///|
pub impl Responder for String with fn output(self, writer) {
  self.write_response(writer)
}

///|
async fn StringView::write_response(
  self : StringView,
  writer : &@io.Writer,
) -> Unit {
  writer.write(self)
}

///|
pub impl Responder for StringView with fn options(_, res) -> Unit {
  res.headers["Content-Type"] = "text/plain; charset=utf-8"
}

///|
pub impl Responder for StringView with fn output(self, writer) {
  self.write_response(writer)
}

///|
fn multipart_response_boundary() -> String {
  let random = @random.Rand::new()
  let suffix = StringBuilder()
  let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  for _ in 0..<24 {
    suffix.write_char(alphabet.get_char(random.int(limit=62)).unwrap())
  }
  "----WebKitFormBoundary\{suffix}"
}

///|
fn multipart_quote(value : String) -> String {
  let escaped_backslashes = value.replace_all(old="\\", new="\\\\")
  escaped_backslashes.replace_all(old="\"", new="\\\"")
}

///|
fn FormData::response_boundary(self : FormData) -> String {
  match self.response_boundary {
    Some(boundary) => boundary
    None => {
      let boundary = multipart_response_boundary()
      self.response_boundary = Some(boundary)
      boundary
    }
  }
}

///|
async fn FormData::write_response(
  self : FormData,
  writer : &@io.Writer,
) -> Unit {
  let multipart = @multipart.Writer(writer, boundary=self.response_boundary())
  for name, value in self.fields {
    let disposition = match value.filename {
      Some(filename) =>
        "form-data; name=\"\{multipart_quote(name)}\"; filename=\"\{multipart_quote(filename)}\""
      None => "form-data; name=\"\{multipart_quote(name)}\""
    }
    let headers : @http.Headers = { "Content-Disposition": disposition }
    match value.content_type {
      Some(content_type) => headers["Content-Type"] = content_type
      None => ()
    }
    multipart.write_part(headers~, target => target.write(value.data))
  }
  multipart.finish()
}

///|
pub impl Responder for FormData with fn options(self, res) -> Unit {
  res.headers["Content-Type"] = "multipart/form-data; boundary=\{self.response_boundary()}"
}

///|
pub impl Responder for FormData with fn output(self, writer) {
  self.write_response(writer)
}

///|
struct Html(String)

///|
async fn Html::write_response(self : Html, writer : &@io.Writer) -> Unit {
  writer.write(self.0)
}

///|
pub impl Responder for Html with fn options(_, res) -> Unit {
  res.headers["Content-Type"] = "text/html; charset=utf-8"
}

///|
pub impl Responder for Html with fn output(self, writer) {
  self.write_response(writer)
}

///|
pub fn html(html : &Show) -> &Responder {
  Html(html.to_string())
}

///|
pub fn text(text : &Show) -> &Responder {
  text.to_string()
}