///|
/// Body parsing modes
priv enum BodyMode {
  None
  ContentLength(Int)
  Chunked
}

///|
/// Decoder state machine
pub(all) enum DecodeState {
  Start
  StartLine
  Headers
  Body(Int)
  Complete
} derive(Show, Eq)

///|
/// Sans I/O HTTP request decoder
pub(all) struct RequestDecoder {
  mut buf : Array[Byte]
  mut state : DecodeState
  mut parsed_method : String?
  mut parsed_uri : String?
  mut parsed_version : String?
  mut headers : Array[(String, String)]
  mut body_buf : Array[Byte]
  mut content_length : Int?
  limits : DecoderLimits
} derive(Show)

///|
pub fn RequestDecoder::new() -> RequestDecoder {
  {
    buf: [],
    state: Start,
    parsed_method: None,
    parsed_uri: None,
    parsed_version: None,
    headers: [],
    body_buf: [],
    content_length: None,
    limits: DecoderLimits::default(),
  }
}

///|
pub fn RequestDecoder::with_limits(limits : DecoderLimits) -> RequestDecoder {
  {
    buf: [],
    state: Start,
    parsed_method: None,
    parsed_uri: None,
    parsed_version: None,
    headers: [],
    body_buf: [],
    content_length: None,
    limits,
  }
}

///|
pub fn RequestDecoder::feed(
  self : RequestDecoder,
  data : Array[Byte],
) -> Result[Unit, HttpError] {
  let new_len = self.buf.length() + data.length()
  if new_len > self.limits.max_buffer_size {
    return Err(BufferOverflow(new_len, self.limits.max_buffer_size))
  }
  self.buf = decoder_array_append(self.buf, data)
  Ok(())
}

///|
pub fn RequestDecoder::decode(
  self : RequestDecoder,
) -> Result[Request?, HttpError] {
  match self.state {
    Complete =>
      if self.buf.length() > 0 {
        self.reset()
        self.decode()
      } else {
        Ok(None)
      }
    Start => {
      self.state = StartLine
      self.decode()
    }
    StartLine =>
      match read_crlf_line(self.buf, self.limits.max_header_line_size) {
        Err(e) => Err(e)
        Ok(line_and_rest) => {
          let (line, remaining) = line_and_rest
          self.buf = remaining
          if line.length() == 0 {
            Ok(None)
          } else {
            match parse_request_line(line) {
              Ok(parts) => {
                let (http_method, uri, version) = parts
                self.parsed_method = Some(http_method)
                self.parsed_uri = Some(uri)
                self.parsed_version = Some(version)
                self.state = Headers
                self.decode()
              }
              Err(e) => Err(e)
            }
          }
        }
      }
    Headers =>
      match read_crlf_line(self.buf, self.limits.max_header_line_size) {
        Err(e) => Err(e)
        Ok(line_and_rest) => {
          let (line, remaining) = line_and_rest
          self.buf = remaining
          if line.length() == 0 {
            match determine_body_mode(self.headers) {
              Err(e) => Err(e)
              Ok(mode) =>
                match mode {
                  None => {
                    self.state = Complete
                    self.build_request()
                  }
                  ContentLength(len) => {
                    self.content_length = Some(len)
                    self.state = Body(len)
                    self.decode()
                  }
                  Chunked => {
                    self.state = Complete
                    self.build_request()
                  }
                }
            }
          } else {
            match parse_header_line(line) {
              Err(e) => Err(e)
              Ok(header) => {
                let (name, value) = header
                self.headers.push((name, value))
                if self.headers.length() > self.limits.max_headers_count {
                  Err(
                    TooManyHeaders(
                      self.headers.length(),
                      self.limits.max_headers_count,
                    ),
                  )
                } else {
                  self.decode()
                }
              }
            }
          }
        }
      }
    Body(content_length) => {
      let have = self.body_buf.length()
      let needed = content_length - have
      let available = self.buf.length()
      let take = if needed < available { needed } else { available }
      let split_result = split_at(self.buf, take)
      let (to_take, rest) = split_result
      self.body_buf = decoder_array_append(self.body_buf, to_take)
      self.buf = rest
      if self.body_buf.length() >= content_length {
        self.state = Complete
        self.build_request()
      } else {
        Ok(None)
      }
    }
  }
}

///|
pub fn RequestDecoder::reset(self : RequestDecoder) -> Unit {
  self.buf = []
  self.state = Start
  self.parsed_method = None
  self.parsed_uri = None
  self.parsed_version = None
  self.headers = []
  self.body_buf = []
  self.content_length = None
}

///|
pub fn RequestDecoder::remaining(self : RequestDecoder) -> Array[Byte] {
  self.buf
}

///|
fn RequestDecoder::build_request(
  self : RequestDecoder,
) -> Result[Request?, HttpError] {
  match (self.parsed_method, self.parsed_uri) {
    (Some(http_method), Some(uri)) => {
      let version = match self.parsed_version {
        Some(v) => v
        None => "HTTP/1.1"
      }
      let mut req = Request::with_version(http_method, uri, version)
      for item in self.headers {
        let (name, value) = item
        req = req.header(name, value)
      }
      req = req.body(self.body_buf)
      Ok(Some(req))
    }
    _ => Ok(None)
  }
}

///|
/// Sans I/O HTTP response decoder
pub(all) struct ResponseDecoder {
  mut buf : Array[Byte]
  mut state : DecodeState
  mut parsed_version : String?
  mut parsed_status_code : Int?
  mut parsed_reason : String?
  mut headers : Array[(String, String)]
  mut body_buf : Array[Byte]
  mut content_length : Int?
  limits : DecoderLimits
} derive(Show)

///|
pub fn ResponseDecoder::new() -> ResponseDecoder {
  {
    buf: [],
    state: Start,
    parsed_version: None,
    parsed_status_code: None,
    parsed_reason: None,
    headers: [],
    body_buf: [],
    content_length: None,
    limits: DecoderLimits::default(),
  }
}

///|
pub fn ResponseDecoder::with_limits(limits : DecoderLimits) -> ResponseDecoder {
  {
    buf: [],
    state: Start,
    parsed_version: None,
    parsed_status_code: None,
    parsed_reason: None,
    headers: [],
    body_buf: [],
    content_length: None,
    limits,
  }
}

///|
pub fn ResponseDecoder::feed(
  self : ResponseDecoder,
  data : Array[Byte],
) -> Result[Unit, HttpError] {
  let new_len = self.buf.length() + data.length()
  if new_len > self.limits.max_buffer_size {
    return Err(BufferOverflow(new_len, self.limits.max_buffer_size))
  }
  self.buf = decoder_array_append(self.buf, data)
  Ok(())
}

///|
pub fn ResponseDecoder::decode(
  self : ResponseDecoder,
) -> Result[Response?, HttpError] {
  match self.state {
    Complete =>
      if self.buf.length() > 0 {
        self.reset()
        self.decode()
      } else {
        Ok(None)
      }
    Start => {
      self.state = StartLine
      self.decode()
    }
    StartLine =>
      match read_crlf_line(self.buf, self.limits.max_header_line_size) {
        Err(e) => Err(e)
        Ok(line_and_rest) => {
          let (line, remaining) = line_and_rest
          self.buf = remaining
          if line.length() == 0 {
            Ok(None)
          } else {
            match parse_status_line(line) {
              Err(e) => Err(e)
              Ok(parts) => {
                let (version, status_code, reason) = parts
                self.parsed_version = Some(version)
                self.parsed_status_code = Some(status_code)
                self.parsed_reason = Some(reason)
                self.state = Headers
                self.decode()
              }
            }
          }
        }
      }
    Headers =>
      match read_crlf_line(self.buf, self.limits.max_header_line_size) {
        Err(e) => Err(e)
        Ok(line_and_rest) => {
          let (line, remaining) = line_and_rest
          self.buf = remaining
          if line.length() == 0 {
            match determine_body_mode(self.headers) {
              Err(e) => Err(e)
              Ok(mode) =>
                match mode {
                  None => {
                    self.state = Complete
                    self.build_response()
                  }
                  ContentLength(len) => {
                    self.content_length = Some(len)
                    self.state = Body(len)
                    self.decode()
                  }
                  Chunked => {
                    self.state = Complete
                    self.build_response()
                  }
                }
            }
          } else {
            match parse_header_line(line) {
              Err(e) => Err(e)
              Ok(header) => {
                let (name, value) = header
                self.headers.push((name, value))
                if self.headers.length() > self.limits.max_headers_count {
                  Err(
                    TooManyHeaders(
                      self.headers.length(),
                      self.limits.max_headers_count,
                    ),
                  )
                } else {
                  self.decode()
                }
              }
            }
          }
        }
      }
    Body(content_length) => {
      let have = self.body_buf.length()
      let needed = content_length - have
      let available = self.buf.length()
      let take = if needed < available { needed } else { available }
      let split_result = split_at(self.buf, take)
      let (to_take, rest) = split_result
      self.body_buf = decoder_array_append(self.body_buf, to_take)
      self.buf = rest
      if self.body_buf.length() >= content_length {
        self.state = Complete
        self.build_response()
      } else {
        Ok(None)
      }
    }
  }
}

///|
pub fn ResponseDecoder::reset(self : ResponseDecoder) -> Unit {
  self.buf = []
  self.state = Start
  self.parsed_version = None
  self.parsed_status_code = None
  self.parsed_reason = None
  self.headers = []
  self.body_buf = []
  self.content_length = None
}

///|
pub fn ResponseDecoder::remaining(self : ResponseDecoder) -> Array[Byte] {
  self.buf
}

///|
fn ResponseDecoder::build_response(
  self : ResponseDecoder,
) -> Result[Response?, HttpError] {
  match (self.parsed_version, self.parsed_status_code) {
    (Some(version), Some(status_code)) => {
      let reason = match self.parsed_reason {
        Some(r) => r
        None => ""
      }
      let mut resp = Response::with_version(version, status_code, reason)
      for item in self.headers {
        let (name, value) = item
        resp = resp.header(name, value)
      }
      resp = resp.body(self.body_buf)
      Ok(Some(resp))
    }
    _ => Ok(None)
  }
}

///|
/// Helper functions

///|
fn decoder_array_append(a : Array[Byte], b : Array[Byte]) -> Array[Byte] {
  let result = a
  for byte in b {
    result.push(byte)
  }
  result
}

///|
fn split_at(arr : Array[Byte], n : Int) -> (Array[Byte], Array[Byte]) {
  let first = []
  let mut idx = 0
  while idx < n && idx < arr.length() {
    first.push(arr[idx])
    idx = idx + 1
  }
  let second = []
  while idx < arr.length() {
    second.push(arr[idx])
    idx = idx + 1
  }
  (first, second)
}

///|
fn read_crlf_line(
  buf : Array[Byte],
  max_size : Int,
) -> Result[(String, Array[Byte]), HttpError] {
  let mut i = 0
  let len = buf.length()
  while i < len - 1 {
    if buf[i] == 13 && buf[i + 1] == 10 {
      if i > max_size {
        return Err(HeaderLineTooLong(i, max_size))
      }
      let line = decoder_bytes_to_string_slice(buf, 0, i)
      let remaining = decoder_bytes_slice(buf, i + 2, len)
      return Ok((line, remaining))
    }
    i = i + 1
  }
  Err(UnexpectedEof)
}

///|
fn decoder_bytes_to_string_slice(
  arr : Array[Byte],
  start : Int,
  end : Int,
) -> String {
  let mut result = ""
  let mut i = start
  while i < end {
    result = result + Int::unsafe_to_char(arr[i].to_int()).to_string()
    i = i + 1
  }
  result
}

///|
fn decoder_bytes_slice(
  arr : Array[Byte],
  start : Int,
  end : Int,
) -> Array[Byte] {
  let result = []
  let mut i = start
  while i < end {
    result.push(arr[i])
    i = i + 1
  }
  result
}

///|
fn parse_request_line(
  line : String,
) -> Result[(String, String, String), HttpError] {
  let parts = split_to_array(line, " ")
  if parts.length() < 2 {
    return Err(InvalidData("Invalid request line"))
  }
  let http_method = parts[0]
  let uri = parts[1]
  let version = if parts.length() >= 3 { parts[2] } else { "HTTP/1.1" }
  Ok((http_method, uri, version))
}

///|
fn parse_status_line(line : String) -> Result[(String, Int, String), HttpError] {
  let parts = split_to_array(line, " ")
  if parts.length() < 2 {
    return Err(InvalidData("Invalid status line"))
  }
  let version = parts[0]
  let status_code = match decoder_parse_int(parts[1]) {
    Some(n) => n
    None => return Err(InvalidStatusCode)
  }
  let reason = if parts.length() >= 3 {
    let mut r = ""
    let mut i = 2
    while i < parts.length() {
      if i > 2 {
        r = r + " "
      }
      r = r + parts[i]
      i = i + 1
    }
    r
  } else {
    ""
  }
  Ok((version, status_code, reason))
}

///|
fn parse_header_line(line : String) -> Result[(String, String), HttpError] {
  let colon_idx = decoder_string_index_of(line, ":")
  match colon_idx {
    Some(idx) => {
      let name = (try! line[0:idx]).to_string()
      let value = (try! line[idx + 1:line.length()]).to_string()
      let trimmed = trim_whitespace(value)
      if name.length() == 0 {
        Err(InvalidHeaderValue)
      } else {
        Ok((name, trimmed))
      }
    }
    None => Err(InvalidHeaderValue)
  }
}

///|
fn trim_whitespace(s : String) -> String {
  let mut start = 0
  let len = s.length()
  while start < len {
    let ch = s[start]
    if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
      start = start + 1
    } else {
      break
    }
  }
  let mut end = len
  while end > start {
    let ch = s[end - 1]
    if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
      end = end - 1
    } else {
      break
    }
  }
  (try! s[start:end]).to_string()
}

///|
fn determine_body_mode(
  headers : Array[(String, String)],
) -> Result[BodyMode, HttpError] {
  let mut idx = 0
  while idx < headers.length() {
    let (name, value) = headers[idx]
    if decoder_string_to_lower(name) == "transfer-encoding" &&
      decoder_string_to_lower(value) == "chunked" {
      return Ok(Chunked)
    }
    idx = idx + 1
  }
  idx = 0
  while idx < headers.length() {
    let (name, value) = headers[idx]
    if decoder_string_to_lower(name) == "content-length" {
      match decoder_parse_int(value) {
        Some(len) => {
          if len < 0 {
            return Err(InvalidData("Negative content-length"))
          }
          return Ok(ContentLength(len))
        }
        None => return Err(InvalidData("Invalid content-length"))
      }
    }
    idx = idx + 1
  }
  Ok(None)
}

///|
fn decoder_string_to_lower(s : String) -> String {
  let mut result = ""
  let mut idx = 0
  while idx < s.length() {
    let c = s[idx]
    let ci = c.to_int()
    // Use Int::unsafe_to_char for all characters, as Char.to_string() may produce numeric output
    let new_ci = if ci >= 65 && ci <= 90 { ci + 32 } else { ci }
    result = result + Int::unsafe_to_char(new_ci).to_string()
    idx = idx + 1
  }
  result
}

///|
fn decoder_string_index_of(s : String, needle : String) -> Int? {
  if needle.length() == 0 {
    return Some(0)
  }
  if needle.length() > s.length() {
    return None
  }
  let mut idx = 0
  while idx <= s.length() - needle.length() {
    let mut is_match = true
    let mut j = 0
    while j < needle.length() {
      if s[idx + j] != needle[j] {
        is_match = false
        break
      }
      j = j + 1
    }
    if is_match {
      return Some(idx)
    }
    idx = idx + 1
  }
  None
}

///|
fn decoder_parse_int(s : String) -> Int? {
  let mut result = 0
  let mut idx = 0
  let len = s.length()
  if len == 0 {
    return None
  }
  while idx < len {
    let c = s[idx]
    let ci = c.to_int()
    if ci >= 48 && ci <= 57 {
      result = result * 10 + (ci - 48)
    } else {
      return None
    }
    idx = idx + 1
  }
  Some(result)
}

///|
/// Helper to split string and convert to array
fn split_to_array(s : String, sep : String) -> Array[String] {
  let result = []
  let mut start = 0
  let sep_len = sep.length()
  let mut idx = 0
  while idx <= s.length() - sep_len {
    let mut is_match = true
    let mut j = 0
    while j < sep_len {
      if s[idx + j] != sep[j] {
        is_match = false
        break
      }
      j = j + 1
    }
    if is_match {
      result.push((try! s[start:idx]).to_string())
      start = idx + sep_len
      idx = start
    } else {
      idx = idx + 1
    }
  }
  result.push((try! s[start:s.length()]).to_string())
  result
}