///|
pub(all) struct Representation {
  payload : Bytes
  content_format : Int
  etag : Bytes
  max_age : Int64
} derive(Eq, Debug)

///|
pub extend Representation with Eq::{equal, not_equal}

///|
pub extend Representation with @debug.Debug::{to_repr}

///|
pub fn Representation::validate(
  self : Representation,
  limits : Limits,
) -> Result[Unit, Failure] {
  if self.payload.length() > limits.max_payload {
    return Err(Capacity("representation exceeds payload limit"))
  }
  if self.content_format < 0 || self.content_format > 65535 {
    return Err(Invalid("content format out of range"))
  }
  if self.etag.length() < 1 || self.etag.length() > 8 {
    return Err(Invalid("ETag must contain 1..8 bytes"))
  }
  if self.max_age < 0L || self.max_age > 4294967295L {
    return Err(Invalid("Max-Age exceeds unsigned 32 bits"))
  }
  Ok(())
}

///|
pub fn Representation::response(
  self : Representation,
  valid : Bool,
) -> Response {
  let options = [
    { number: 4, value: self.etag, },
    option_uint(14, self.max_age).unwrap(),
  ]
  if !valid {
    options.push(option_uint(12, self.content_format.to_int64()).unwrap())
  }
  {
    code: { value: if valid { 67 } else { 69 }, },
    options,
    payload: if valid {
      b""
    } else {
      self.payload
    },
  }
}

///|
pub fn preconditions(message : Message, current : Representation?) -> Bool {
  if message.first_option(5) is Some(_) && current is Some(_) {
    return false
  }
  let matches = message.option_values(1)
  if matches.is_empty() {
    return true
  }
  match current {
    None => false
    Some(value) => matches.any(fn(tag) { tag.is_empty() || tag == value.etag })
  }
}

///|
pub fn select_representation(
  message : Message,
  current : Representation,
) -> Response {
  match message_uint(message, 17, current.content_format.to_int64()) {
    Err(_) => return Response::error(130)
    Ok(format) =>
      if format != current.content_format.to_int64() {
        return Response::error(134)
      }
  }
  if !preconditions(message, Some(current)) {
    return Response::error(140)
  }
  current.response(message.option_values(4).contains(current.etag))
}

///|
priv struct StoredResource {
  path : Array[Bytes]
  representation : Representation
  writable : Bool
}

///|
pub(all) struct ResourceInfo {
  path : String
  content_format : Int
  size : Int
  etag : Bytes
  writable : Bool
} derive(Eq, Debug)

///|
pub extend ResourceInfo with Eq::{equal, not_equal}

///|
pub extend ResourceInfo with @debug.Debug::{to_repr}

///|
pub struct ResourceStore {
  priv limits : Limits
  priv capacity : Int
  priv max_bytes : Int
  priv allowed_formats : Array[Int]
  priv allow_create : Bool
  priv mut version : Int64
  priv resources : Array[StoredResource]
}

///|
pub fn ResourceStore::new(
  capacity : Int,
  max_bytes : Int,
  formats : Array[Int],
  allow_create : Bool,
  limits : Limits,
) -> Result[ResourceStore, Failure] {
  match limits.validate() {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  if capacity < 1 || capacity > 4096 || max_bytes < 1 || max_bytes > 16777216 {
    return Err(Invalid("invalid resource store capacity"))
  }
  if formats.is_empty() || formats.any(fn(v) { v < 0 || v > 65535 }) {
    return Err(Invalid("invalid allowed content formats"))
  }
  Ok({
    limits,
    capacity,
    max_bytes,
    allowed_formats: formats.copy(),
    allow_create,
    version: 0L,
    resources: [],
  })
}

///|
pub fn ResourceStore::byte_size(self : ResourceStore) -> Int {
  let mut result = 0
  for resource in self.resources {
    result = result + resource.representation.payload.length()
  }
  result
}

///|
pub fn ResourceStore::inventory(self : ResourceStore) -> Array[ResourceInfo] {
  self.resources.map(fn(resource) {
    {
      path: canonical_path(resource.path),
      content_format: resource.representation.content_format,
      size: resource.representation.payload.length(),
      etag: resource.representation.etag,
      writable: resource.writable,
    }
  })
}

///|
fn ResourceStore::index(self : ResourceStore, path : Array[Bytes]) -> Int? {
  for i = 0; i < self.resources.length(); i = i + 1 {
    if self.resources[i].path == path {
      return Some(i)
    }
  }
  None
}

///|
fn ResourceStore::next_etag(self : ResourceStore) -> Result[Bytes, Failure] {
  if self.version == 9223372036854775807L {
    return Err(Capacity("resource revision space exhausted"))
  }
  self.version = self.version + 1L
  Ok(
    Bytes::makei(8, fn(i) {
      ((self.version >> ((7 - i) * 8)) & 255L).to_int().to_byte()
    }),
  )
}

///|
pub fn ResourceStore::get(
  self : ResourceStore,
  path : String,
) -> Result[Representation, Failure] {
  let parts = match resource_path(path, self.limits) {
    Err(e) => return Err(e)
    Ok(v) => v
  }
  match self.index(parts) {
    None => Err(NotFound("resource does not exist"))
    Some(i) => Ok(self.resources[i].representation)
  }
}

///|
pub fn ResourceStore::put(
  self : ResourceStore,
  path : String,
  payload : Bytes,
  format : Int,
  max_age : Int64,
  writable : Bool,
) -> Result[Representation, Failure] {
  let parts = match resource_path(path, self.limits) {
    Err(e) => return Err(e)
    Ok(v) => v
  }
  let index = self.index(parts)
  let old_size = match index {
    None => 0
    Some(i) => self.resources[i].representation.payload.length()
  }
  if index is None && self.resources.length() >= self.capacity {
    return Err(Capacity("resource store is full"))
  }
  if !self.allowed_formats.contains(format) {
    return Err(Unsupported("unsupported content format"))
  }
  let candidate : Representation = {
    payload,
    content_format: format,
    etag: b"x",
    max_age,
  }
  match candidate.validate(self.limits) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  if self.byte_size() - old_size + payload.length() > self.max_bytes {
    return Err(Capacity("resource byte budget exhausted"))
  }
  let tag = match self.next_etag() {
    Err(e) => return Err(e)
    Ok(v) => v
  }
  let value = { ..candidate, etag: tag, }
  match index {
    None =>
      self.resources.push({ path: parts, representation: value, writable, })
    Some(i) =>
      self.resources[i] = { path: parts, representation: value, writable, }
  }
  Ok(value)
}

///|
pub fn ResourceStore::remove(
  self : ResourceStore,
  path : String,
) -> Result[Bool, Failure] {
  let parts = match resource_path(path, self.limits) {
    Err(e) => return Err(e)
    Ok(v) => v
  }
  match self.index(parts) {
    None => Ok(false)
    Some(i) => {
      self.resources.remove(i) |> ignore
      Ok(true)
    }
  }
}

///|
pub fn ResourceStore::handle(
  self : ResourceStore,
  message : Message,
) -> Response {
  match validate_options(message, []) {
    Err(_) => return Response::error(130)
    Ok(_) => ()
  }
  let verb = match message.code.verb() {
    None => return Response::error(133)
    Some(v) => v
  }
  let parts = path_segments(message)
  let index = self.index(parts)
  let current = match index {
    None => None
    Some(i) => Some(self.resources[i].representation)
  }
  if verb == Get {
    return match current {
      None => Response::error(132)
      Some(value) => select_representation(message, value)
    }
  }
  if verb == Post {
    return Response::error(133)
  }
  if index is None && (verb != Put || !self.allow_create) {
    return Response::error(132)
  }
  match index {
    Some(i) => if !self.resources[i].writable { return Response::error(133) }
    None => ()
  }
  if !preconditions(message, current) {
    return Response::error(140)
  }
  if verb == Delete {
    match index {
      Some(i) => self.resources.remove(i) |> ignore
      None => ()
    }
    return Response::error(66)
  }
  let format = match message_uint(message, 12, 0L) {
    Err(_) => return Response::error(130)
    Ok(v) => v.to_int()
  }
  if !self.allowed_formats.contains(format) {
    return Response::error(143)
  }
  if message.payload.length() > self.limits.max_payload {
    return {
      code: { value: 141, },
      options: [option_uint(60, self.limits.max_payload.to_int64()).unwrap()],
      payload: b"",
    }
  }
  let age = match current {
    None => 60L
    Some(value) => value.max_age
  }
  let representation = match
    self.put(canonical_path(parts), message.payload, format, age, true) {
    Err(_) => return Response::error(163)
    Ok(value) => value
  }
  {
    code: { value: if current is None { 65 } else { 68 }, },
    options: [{ number: 4, value: representation.etag, }],
    payload: b"",
  }
}