// Archive indexing and querying.
//
// The index is a set of parallel (key, record-index) pairs kept in
// file order, so lookups are linear scans — appropriate for a
// library whose archives are held in memory. Keys come only from
// record metadata: field values are compared as strings and are
// never interpreted as file-system paths.

///|
/// An in-memory index over an archive's records.
pub struct WarcIndex {
  by_id : Array[(String, Int)]
  by_target : Array[(String, Int)]
  by_type : Array[(String, Int)]
}

///|
/// Build an index over an archive. Records whose keys are absent or
/// malformed are simply not indexed.
pub fn WarcIndex::build(a : WarcArchive) -> WarcIndex {
  let by_id : Array[(String, Int)] = []
  let by_target : Array[(String, Int)] = []
  let by_type : Array[(String, Int)] = []
  for i = 0; i < a.record_count(); i = i + 1 {
    let rec = a.record(i).unwrap()
    let id = field_first_of(rec.fields, "WARC-Record-ID")
    match id {
      Some(v) =>
        match parse_uri_ref(v, i.to_int64()) {
          Ok(interior) => by_id.push((interior, i))
          Err(_) => ()
        }
      None => ()
    }
    let target = field_first_of(rec.fields, "WARC-Target-URI")
    match target {
      Some(v) =>
        match parse_uri_ref(v, i.to_int64()) {
          Ok(interior) => by_target.push((interior, i))
          Err(_) => ()
        }
      None => ()
    }
    let t = rec.record_type()
    match t {
      Some(x) => by_type.push((x.type_name(), i))
      None => ()
    }
  }
  { by_id, by_target, by_type }
}

///|
/// The index of the first record whose WARC-Record-ID interior is
/// `id`, or `None` when no record carries it.
pub fn WarcIndex::by_record_id(self : WarcIndex, id : String) -> Int? {
  for i = 0; i < self.by_id.length(); i = i + 1 {
    let (k, ix) = self.by_id[i]
    if k == id {
      return Some(ix)
    }
  }
  None
}

///|
/// The indices of all records whose WARC-Target-URI interior is
/// `uri`, in file order.
pub fn WarcIndex::by_target_uri(self : WarcIndex, uri : String) -> Array[Int] {
  let out : Array[Int] = []
  for i = 0; i < self.by_target.length(); i = i + 1 {
    let (k, ix) = self.by_target[i]
    if k == uri {
      out.push(ix)
    }
  }
  out
}

///|
/// The indices of all records of the given type, in file order.
pub fn WarcIndex::by_type(self : WarcIndex, t : WarcRecordType) -> Array[Int] {
  let out : Array[Int] = []
  let name = t.type_name()
  for i = 0; i < self.by_type.length(); i = i + 1 {
    let (k, ix) = self.by_type[i]
    if k == name {
      out.push(ix)
    }
  }
  out
}

///|
/// The total number of indexed keys across all three key kinds.
pub fn WarcIndex::size(self : WarcIndex) -> Int {
  self.by_id.length() + self.by_target.length() + self.by_type.length()
}