// Typed views over the arrays and maps a generated message stores its fields
// in. The reflective contract speaks `Value`, `MapKey` and `ListField`; a
// generated message keeps `Array[Int32]` and `Map[String, Child]`. These are
// the two places the two meet, and the only code that knows how an element
// moves between them.

///|
/// How to move one element of a repeated or map field between its typed form
/// and a `Value`. `copy_into` exists because a message element cannot be
/// reconstructed from a `Value`: the concrete type is only known here, so the
/// value is copied into storage instead.
pub(all) struct Element[T] {
  to_value : (T) -> Value
  from_value : (Value) -> T
  make : () -> T
  copy_into : (T, &Message) -> Unit
  is_message : Bool
}

///|
pub fn[T] Element::new(
  to_value : (T) -> Value,
  from_value : (Value) -> T,
  make : () -> T,
  copy_into : (T, &Message) -> Unit,
  is_message : Bool,
) -> Element[T] {
  { to_value, from_value, make, copy_into, is_message, }
}

///|
/// The element of a repeated field: a scalar converts both ways.
pub fn[T] scalar_element(
  to_value : (T) -> Value,
  from_value : (Value) -> T,
  make : () -> T,
) -> Element[T] {
  Element::new(to_value, from_value, make, fn(_item, _source) { () }, false)
}

///|
/// The element of a repeated message field: a new element is the concrete type,
/// and a value is copied into it.
pub fn[T] message_element(
  to_value : (T) -> Value,
  make : () -> T,
  copy_into : (T, &Message) -> Unit,
) -> Element[T] {
  Element::new(
    to_value,
    fn(_value) { abort("a message element cannot be built from a value") },
    make,
    copy_into,
    true,
  )
}

///|
/// A repeated field backed by the array the message stores.
pub struct TypedList[T] {
  items : Array[T]
  element : Element[T]
}

///|
pub impl[T] ListField for TypedList[T] with fn length(self) -> Int32 {
  self.items.length()
}

///|
pub impl[T] ListField for TypedList[T] with fn get(self, index : Int32) -> Value {
  (self.element.to_value)(self.items[index])
}

///|
pub impl[T] ListField for TypedList[T] with fn set(
  self,
  index : Int32,
  value : Value,
) -> Unit {
  if self.element.is_message {
    (self.element.copy_into)(self.items[index], value.message())
  } else {
    self.items[index] = (self.element.from_value)(value)
  }
}

///|
pub impl[T] ListField for TypedList[T] with fn append(self, value : Value) -> Unit {
  if self.element.is_message {
    let item = (self.element.make)()
    (self.element.copy_into)(item, value.message())
    self.items.push(item)
  } else {
    self.items.push((self.element.from_value)(value))
  }
}

///|
pub impl[T] ListField for TypedList[T] with fn append_mutable(self) -> Value {
  let item = (self.element.make)()
  self.items.push(item)
  (self.element.to_value)(item)
}

///|
pub impl[T] ListField for TypedList[T] with fn truncate(self, length : Int32) -> Unit {
  if length < self.items.length() {
    self.items.resize(length, (self.element.make)())
  }
}

///|
pub impl[T] ListField for TypedList[T] with fn new_element(self) -> Value {
  (self.element.to_value)((self.element.make)())
}

///|
pub impl[T] ListField for TypedList[T] with fn is_valid(_self) -> Bool {
  true
}

///|
pub extend TypedList with ListField::{
  length,
  get,
  set,
  append,
  append_mutable,
  truncate,
  new_element,
  is_valid,
}

///|
/// The view a generated message returns for a repeated field.
pub fn[T] list_view(items : Array[T], element : Element[T]) -> &ListField {
  TypedList::{ items, element, }
}

///|
/// Replace the contents of `target` with the elements of `source`.
pub fn[T] assign_list(
  target : Array[T],
  source : &ListField,
  element : Element[T],
) -> Unit {
  target.clear()
  for i in 0.. MapKey
  key_of : (MapKey) -> K
  element : Element[V]
}

///|
pub impl[K : Hash + Eq, V] MapField for TypedMap[K, V] with fn length(self) -> Int32 {
  self.entries.length()
}

///|
pub impl[K : Hash + Eq, V] MapField for TypedMap[K, V] with fn range(
  self,
  f : (MapKey, Value) -> Bool,
) -> Unit {
  let mut keep_going = true
  self.entries.each(fn(key, value) {
    if keep_going && !f((self.key_value)(key), (self.element.to_value)(value)) {
      keep_going = false
    }
  })
}

///|
pub impl[K : Hash + Eq, V] MapField for TypedMap[K, V] with fn has(
  self,
  key : MapKey,
) -> Bool {
  self.entries.contains((self.key_of)(key))
}

///|
pub impl[K : Hash + Eq, V] MapField for TypedMap[K, V] with fn clear(
  self,
  key : MapKey,
) -> Unit {
  self.entries.remove((self.key_of)(key))
}

///|
pub impl[K : Hash + Eq, V] MapField for TypedMap[K, V] with fn get(
  self,
  key : MapKey,
) -> Value {
  match self.entries.get((self.key_of)(key)) {
    Some(value) => (self.element.to_value)(value)
    None => Invalid
  }
}

///|
pub impl[K : Hash + Eq, V] MapField for TypedMap[K, V] with fn set(
  self,
  key : MapKey,
  value : Value,
) -> Unit {
  let typed = (self.key_of)(key)
  if self.element.is_message {
    match self.entries.get(typed) {
      Some(existing) => (self.element.copy_into)(existing, value.message())
      None => {
        let item = (self.element.make)()
        (self.element.copy_into)(item, value.message())
        self.entries.set(typed, item)
      }
    }
  } else {
    self.entries.set(typed, (self.element.from_value)(value))
  }
}

///|
pub impl[K : Hash + Eq, V] MapField for TypedMap[K, V] with fn mutable(
  self,
  key : MapKey,
) -> Value {
  let typed = (self.key_of)(key)
  match self.entries.get(typed) {
    Some(existing) => (self.element.to_value)(existing)
    None => {
      let item = (self.element.make)()
      self.entries.set(typed, item)
      (self.element.to_value)(item)
    }
  }
}

///|
pub impl[K : Hash + Eq, V] MapField for TypedMap[K, V] with fn new_value(self) -> Value {
  (self.element.to_value)((self.element.make)())
}

///|
pub impl[K : Hash + Eq, V] MapField for TypedMap[K, V] with fn is_valid(_self) -> Bool {
  true
}

///|
pub extend TypedMap with MapField::{
  length,
  range,
  has,
  clear,
  get,
  set,
  mutable,
  new_value,
  is_valid,
}

///|
/// The view a generated message returns for a map field.
pub fn[K : Hash + Eq, V] map_view(
  entries : Map[K, V],
  key_value : (K) -> MapKey,
  key_of : (MapKey) -> K,
  element : Element[V],
) -> &MapField {
  TypedMap::{ entries, key_value, key_of, element, }
}

///|
/// Replace the contents of `target` with the entries of `source`.
pub fn[K : Hash + Eq, V] assign_map(
  target : Map[K, V],
  source : &MapField,
  key_of : (MapKey) -> K,
  element : Element[V],
) -> Unit {
  target.clear()
  source.range(fn(key, value) {
    let typed = key_of(key)
    if element.is_message {
      let item = (element.make)()
      (element.copy_into)(item, value.message())
      target.set(typed, item)
    } else {
      target.set(typed, (element.from_value)(value))
    }
    true
  })
}