///|
/// Compares two values of the same logical field type.
pub fn compare_field_values(left : FieldValue, right : FieldValue) -> Int? {
  match (left, right) {
    (@core.Text(left), @core.Text(right)) => Some(left.compare(right))
    (@core.Keyword(left), @core.Keyword(right)) => Some(left.compare(right))
    (@core.I64(left), @core.I64(right)) => Some(left.compare(right))
    (@core.U64(left), @core.U64(right)) => Some(left.compare(right))
    (@core.F64(left), @core.F64(right)) =>
      if left.is_nan() || right.is_nan() {
        None
      } else if left < right {
        Some(-1)
      } else if left > right {
        Some(1)
      } else {
        Some(0)
      }
    (@core.Bool(left), @core.Bool(right)) =>
      Some(if left == right { 0 } else if left { 1 } else { -1 })
    (@core.Date(left), @core.Date(right)) => Some(left.compare(right))
    (@core.Bytes(left), @core.Bytes(right)) => {
      let common = if left.length() < right.length() {
        left.length()
      } else {
        right.length()
      }
      for index in 0.. None
  }
}

///|
pub fn field_value_as_double(value : FieldValue) -> Double? {
  match value {
    @core.I64(value) => Some(value.to_double())
    @core.U64(value) => Some(value.to_double())
    @core.F64(value) if !value.is_nan() => Some(value)
    @core.Date(value) => Some(value.to_double())
    _ => None
  }
}

///|
priv enum TypedPredicate {
  Exact(FieldValue)
  Range(FieldValue?, Bool, FieldValue?, Bool)
  Exists
}

///|
pub struct ExactQuery {
  field_id : FieldId
  value : FieldValue
}

///|
pub fn ExactQuery::new(field_id : FieldId, value : FieldValue) -> ExactQuery {
  { field_id, value }
}

///|
pub struct RangeQuery {
  field_id : FieldId
  lower : FieldValue?
  lower_inclusive : Bool
  upper : FieldValue?
  upper_inclusive : Bool
}

///|
pub fn RangeQuery::new(
  field_id : FieldId,
  lower : FieldValue?,
  lower_inclusive : Bool,
  upper : FieldValue?,
  upper_inclusive : Bool,
) -> RangeQuery {
  { field_id, lower, lower_inclusive, upper, upper_inclusive }
}

///|
pub struct ExistsQuery {
  field_id : FieldId
}

///|
pub fn ExistsQuery::new(field_id : FieldId) -> ExistsQuery {
  { field_id, }
}

///|
priv struct TypedWeight {
  field_id : FieldId
  predicate : TypedPredicate
}

///|
priv struct TypedScorer {
  segment : Segment
  field_id : FieldId
  predicate : TypedPredicate
  mut current_doc : DocId
}

///|
fn TypedScorer::matches(self : TypedScorer, doc_id : DocId) -> Bool {
  let values = self.segment.field_values(doc_id, self.field_id)
  match self.predicate {
    Exists => values.length() > 0
    Exact(expected) =>
      values.search_by(value => compare_field_values(value, expected) == Some(0))
      is Some(_)
    Range(lower, lower_inclusive, upper, upper_inclusive) => {
      for value in values {
        let lower_matches = match lower {
          None => true
          Some(bound) =>
            match compare_field_values(value, bound) {
              Some(order) => order > 0 || (lower_inclusive && order == 0)
              None => false
            }
        }
        let upper_matches = match upper {
          None => true
          Some(bound) =>
            match compare_field_values(value, bound) {
              Some(order) => order < 0 || (upper_inclusive && order == 0)
              None => false
            }
        }
        if lower_matches && upper_matches {
          return true
        }
      }
      false
    }
  }
}

///|
impl Scorer for TypedScorer with fn advance(self) {
  let target = if self.current_doc.value < 0 {
    DocId::new(0)
  } else {
    DocId::new(self.current_doc.value + 1)
  }
  Scorer::advance_to(self, target)
}

///|
impl Scorer for TypedScorer with fn advance_to(self, target) {
  if self.current_doc.value >= target.value {
    return true
  }
  let mut candidate = if target.value < 0 { 0 } else { target.value }
  while candidate < self.segment.doc_count() {
    let doc_id = DocId::new(candidate)
    if self.matches(doc_id) {
      self.current_doc = doc_id
      return true
    }
    candidate += 1
  }
  self.current_doc = DocId::new(-1)
  false
}

///|
impl Scorer for TypedScorer with fn doc(self) {
  self.current_doc
}

///|
impl Scorer for TypedScorer with fn score(_self) {
  1.0
}

///|
impl Weight for TypedWeight with fn scorer(self, segment) {
  TypedScorer::{
    segment,
    field_id: self.field_id,
    predicate: self.predicate,
    current_doc: DocId::new(-1),
  }
  as &Scorer
}

///|
pub impl Query for ExactQuery with fn weight(self, _statistics) {
  TypedWeight::{ field_id: self.field_id, predicate: Exact(self.value) }
  as &Weight
}

///|
pub impl Query for RangeQuery with fn weight(self, _statistics) {
  TypedWeight::{
    field_id: self.field_id,
    predicate: Range(
      self.lower,
      self.lower_inclusive,
      self.upper,
      self.upper_inclusive,
    ),
  }
  as &Weight
}

///|
pub impl Query for ExistsQuery with fn weight(self, _statistics) {
  TypedWeight::{ field_id: self.field_id, predicate: Exists } as &Weight
}