// High level events that make up HTTP/1.1 conversations. Loosely inspired by
// the corresponding events in hyper-h2.

///|
/// The beginning of an HTTP request.
///
/// - `method`: an HTTP method, e.g. `b"GET"` or `b"POST"`.
/// - `target`: the target of the request, e.g. `b"/index.html"`, or one of
///   the more exotic formats described in RFC 7230 section 5.3.
/// - `headers`: request headers, see `Headers`.
/// - `http_version`: the protocol version, e.g. `b"1.1"`.
pub struct Request {
  method_ : Bytes
  target : Bytes
  headers : Headers
  http_version : Bytes
} derive(Eq)

///|
/// Construct and validate a `Request`.
///
/// Raises `LocalProtocolError` if the method or target contain illegal
/// characters, if the headers are invalid, if an HTTP/1.1 request lacks a
/// `Host` header, or if there are multiple `Host` headers.
///
/// ```mbt check
/// test {
///   let req = @h11.Request::new(method_=b"GET", target=b"/", headers=[
///     (b"Host", b"example.com"),
///   ])
///   inspect(req.headers.length(), content="1")
/// }
/// ```
pub fn Request::new(
  method_~ : Bytes,
  target~ : Bytes,
  headers~ : ArrayView[(Bytes, Bytes)],
  http_version? : Bytes = b"1.1",
) -> Request raise ProtocolError {
  Request::make(
    method_,
    target,
    normalize_and_validate(headers, parsed=false),
    http_version,
  )
}

///|
fn Request::make(
  method_ : Bytes,
  target : Bytes,
  headers : Headers,
  http_version : Bytes,
) -> Request raise ProtocolError {
  // "A server MUST respond with a 400 (Bad Request) status code to any
  // HTTP/1.1 request message that lacks a Host header field and to any
  // request message that contains more than one Host header field or a
  // Host header field with an invalid field-value."
  // -- https://tools.ietf.org/html/rfc7230#section-5.4
  let mut host_count = 0
  for header in headers {
    if header.0 == b"host" {
      host_count += 1
    }
  }
  if http_version == b"1.1" && host_count == 0 {
    raise local_error("Missing mandatory Host: header")
  }
  if host_count > 1 {
    raise local_error("Found multiple Host: headers")
  }
  if !match_token(method_) {
    raise local_error("Illegal method characters")
  }
  if target.is_empty() || !target.iter().all(is_vchar) {
    raise local_error("Illegal target characters")
  }
  { method_, target, headers, http_version, }
}

///|
pub impl Debug for Request with fn to_repr(self) {
  Repr::ctor("Request", [
    (Some("method_"), bytes_debug(self.method_)),
    (Some("target"), bytes_debug(self.target)),
    (Some("headers"), Repr(self.headers)),
    (Some("http_version"), bytes_debug(self.http_version)),
  ])
}

///|
/// An HTTP informational response; `status_code` is always in the range
/// [100, 200).
pub struct InformationalResponse {
  status_code : Int
  headers : Headers
  http_version : Bytes
  reason : Bytes
} derive(Eq)

///|
/// Construct and validate an `InformationalResponse`.
pub fn InformationalResponse::new(
  status_code~ : Int,
  headers~ : ArrayView[(Bytes, Bytes)],
  http_version? : Bytes = b"1.1",
  reason? : Bytes = b"",
) -> InformationalResponse raise ProtocolError {
  InformationalResponse::make(
    status_code,
    normalize_and_validate(headers, parsed=false),
    http_version,
    reason,
  )
}

///|
fn InformationalResponse::make(
  status_code : Int,
  headers : Headers,
  http_version : Bytes,
  reason : Bytes,
) -> InformationalResponse raise ProtocolError {
  if !(status_code >= 100 && status_code < 200) {
    raise local_error(
      "InformationalResponse status_code should be in range [100, 200), not \{status_code}",
    )
  }
  { status_code, headers, http_version, reason, }
}

///|
pub impl Debug for InformationalResponse with fn to_repr(self) {
  Repr::ctor("InformationalResponse", [
    (Some("status_code"), Repr(self.status_code)),
    (Some("headers"), Repr(self.headers)),
    (Some("http_version"), bytes_debug(self.http_version)),
    (Some("reason"), bytes_debug(self.reason)),
  ])
}

///|
/// The beginning of an HTTP response; `status_code` is always in the range
/// [200, 1000).
pub struct Response {
  status_code : Int
  headers : Headers
  http_version : Bytes
  reason : Bytes
} derive(Eq)

///|
/// Construct and validate a `Response`.
///
/// ```mbt check
/// test {
///   let resp = @h11.Response::new(status_code=200, headers=[], reason=b"OK")
///   inspect(resp.status_code, content="200")
/// }
/// ```
pub fn Response::new(
  status_code~ : Int,
  headers~ : ArrayView[(Bytes, Bytes)],
  http_version? : Bytes = b"1.1",
  reason? : Bytes = b"",
) -> Response raise ProtocolError {
  Response::make(
    status_code,
    normalize_and_validate(headers, parsed=false),
    http_version,
    reason,
  )
}

///|
fn Response::make(
  status_code : Int,
  headers : Headers,
  http_version : Bytes,
  reason : Bytes,
) -> Response raise ProtocolError {
  if !(status_code >= 200 && status_code < 1000) {
    raise local_error(
      "Response status_code should be in range [200, 1000), not \{status_code}",
    )
  }
  { status_code, headers, http_version, reason, }
}

///|
pub impl Debug for Response with fn to_repr(self) {
  Repr::ctor("Response", [
    (Some("status_code"), Repr(self.status_code)),
    (Some("headers"), Repr(self.headers)),
    (Some("http_version"), bytes_debug(self.http_version)),
    (Some("reason"), bytes_debug(self.reason)),
  ])
}

///|
/// Part of an HTTP message body.
///
/// `chunk_start` and `chunk_end` mark whether this data is from the start /
/// the end of a chunk in chunked transfer encoding. They are only meaningful
/// on events returned by `Connection::next_event` and are ignored by
/// `Connection::send`. You probably shouldn't rely on them at all.
pub struct Data {
  data : Bytes
  chunk_start : Bool
  chunk_end : Bool
} derive(Eq)

///|
pub fn Data::new(
  data : Bytes,
  chunk_start? : Bool = false,
  chunk_end? : Bool = false,
) -> Data {
  { data, chunk_start, chunk_end, }
}

///|
pub impl Debug for Data with fn to_repr(self) {
  Repr::ctor("Data", [
    (Some("data"), bytes_debug(self.data)),
    (Some("chunk_start"), Repr(self.chunk_start)),
    (Some("chunk_end"), Repr(self.chunk_end)),
  ])
}

///|
/// The end of an HTTP message. `headers` holds any trailing headers, which
/// must be empty unless `Transfer-Encoding: chunked` is in use.
pub struct EndOfMessage {
  headers : Headers
} derive(Eq)

///|
pub fn EndOfMessage::new(
  headers? : ArrayView[(Bytes, Bytes)] = [],
) -> EndOfMessage raise ProtocolError {
  { headers: normalize_and_validate(headers, parsed=false), }
}

///|
pub impl Debug for EndOfMessage with fn to_repr(self) {
  Repr::ctor("EndOfMessage", [(Some("headers"), Repr(self.headers))])
}

///|
/// An h11 event. `ConnectionClosed` indicates that the sender has closed
/// their outgoing connection (which does not necessarily mean that they
/// can't receive further data).
pub(all) enum Event {
  Request(Request)
  InformationalResponse(InformationalResponse)
  Response(Response)
  Data(Data)
  EndOfMessage(EndOfMessage)
  ConnectionClosed
} derive(Eq, Debug)

///|
/// The kind of an event, without its payload. Used by the state machine.
priv enum EventType {
  Request
  InformationalResponse
  Response
  Data
  EndOfMessage
  ConnectionClosed
}

///|
impl Show for EventType with fn output(self, logger) {
  logger.write_string(
    match self {
      Request => "Request"
      InformationalResponse => "InformationalResponse"
      Response => "Response"
      Data => "Data"
      EndOfMessage => "EndOfMessage"
      ConnectionClosed => "ConnectionClosed"
    },
  )
}

///|
fn Event::event_type(self : Event) -> EventType {
  match self {
    Request(_) => Request
    InformationalResponse(_) => InformationalResponse
    Response(_) => Response
    Data(_) => Data
    EndOfMessage(_) => EndOfMessage
    ConnectionClosed => ConnectionClosed
  }
}