///|
priv struct MatchAllWeight {}

///|
priv struct MatchAllScorer {
  document_count : Int
  mut current_doc : DocId
}

///|
pub struct MatchAllQuery {}

///|
pub fn MatchAllQuery::new() -> MatchAllQuery {
  MatchAllQuery::{  }
}

///|
impl Scorer for MatchAllScorer with fn advance(self) {
  Scorer::advance_to(self, DocId::new(self.current_doc.value + 1))
}

///|
impl Scorer for MatchAllScorer with fn advance_to(self, target) {
  let candidate = if target.value < 0 { 0 } else { target.value }
  if candidate < self.document_count {
    self.current_doc = DocId::new(candidate)
    true
  } else {
    self.current_doc = DocId::new(-1)
    false
  }
}

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

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

///|
impl Weight for MatchAllWeight with fn scorer(_self, segment) {
  MatchAllScorer::{
    document_count: segment.doc_count(),
    current_doc: DocId::new(-1),
  }
  as &Scorer
}

///|
pub impl Query for MatchAllQuery with fn weight(_self, _statistics) {
  MatchAllWeight::{  } as &Weight
}

///|
pub struct ConstantScoreQuery {
  query : &Query
  score : Double
}

///|
pub fn ConstantScoreQuery::new(
  query : &Query,
  score : Double,
) -> ConstantScoreQuery {
  { query, score }
}

///|
priv struct ConstantScoreWeight {
  child : &Weight
  score : Double
}

///|
priv struct ConstantScoreScorer {
  child : &Scorer
  score_value : Double
}

///|
impl Scorer for ConstantScoreScorer with fn advance(self) {
  self.child.advance()
}

///|
impl Scorer for ConstantScoreScorer with fn advance_to(self, target) {
  self.child.advance_to(target)
}

///|
impl Scorer for ConstantScoreScorer with fn doc(self) {
  self.child.doc()
}

///|
impl Scorer for ConstantScoreScorer with fn score(self) {
  self.score_value
}

///|
impl Weight for ConstantScoreWeight with fn scorer(self, segment) {
  ConstantScoreScorer::{
    child: self.child.scorer(segment),
    score_value: self.score,
  }
  as &Scorer
}

///|
pub impl Query for ConstantScoreQuery with fn weight(self, statistics) {
  ConstantScoreWeight::{
    child: self.query.weight(statistics),
    score: self.score,
  }
  as &Weight
}

///|
pub struct TermSetQuery {
  terms : ReadOnlyArray[Term]
  score : Double
}

///|
pub fn TermSetQuery::new(terms : Array[Term]) -> TermSetQuery {
  { terms: ReadOnlyArray::from_array(terms.copy()), score: 1.0 }
}

///|
pub fn TermSetQuery::with_score(
  self : TermSetQuery,
  score : Double,
) -> TermSetQuery {
  { terms: self.terms, score }
}

///|
pub impl Query for TermSetQuery with fn weight(self, statistics) {
  let clauses : Array[BooleanClause] = []
  for term in self.terms {
    clauses.push(BooleanClause::new(Should, TermQuery::new(term)))
  }
  Query::weight(
    ConstantScoreQuery::new(BooleanQuery::new(clauses), self.score),
    statistics,
  )
}

///|
pub struct DisjunctionMaxQuery {
  disjuncts : ReadOnlyArray[&Query]
  tie_breaker : Double
}

///|
pub fn DisjunctionMaxQuery::new(
  disjuncts : Array[&Query],
  tie_breaker : Double,
) -> DisjunctionMaxQuery {
  guard tie_breaker >= 0.0 && tie_breaker <= 1.0 else {
    abort("disjunction-max tie breaker must be between zero and one")
  }
  { disjuncts: ReadOnlyArray::from_array(disjuncts.copy()), tie_breaker }
}

///|
priv struct DisjunctionMaxWeight {
  weights : ReadOnlyArray[&Weight]
  tie_breaker : Double
}

///|
priv struct DisjunctionMaxScorer {
  scorers : ReadOnlyArray[&Scorer]
  tie_breaker : Double
  mut current_doc : DocId
  mut current_score : Double
}

///|
fn DisjunctionMaxScorer::seek(
  self : DisjunctionMaxScorer,
  target : DocId,
) -> Bool {
  let mut candidate : DocId? = None
  for scorer in self.scorers {
    if scorer.advance_to(target) {
      candidate = match candidate {
        None => Some(scorer.doc())
        Some(current) =>
          Some(
            if scorer.doc().value < current.value {
              scorer.doc()
            } else {
              current
            },
          )
      }
    }
  }
  match candidate {
    None => {
      self.current_doc = DocId::new(-1)
      self.current_score = 0.0
      false
    }
    Some(doc_id) => {
      let mut maximum = 0.0
      let mut total = 0.0
      for scorer in self.scorers {
        if scorer.doc() == doc_id {
          let score = scorer.score()
          total += score
          if score > maximum {
            maximum = score
          }
        }
      }
      self.current_doc = doc_id
      self.current_score = maximum + (total - maximum) * self.tie_breaker
      true
    }
  }
}

///|
impl Scorer for DisjunctionMaxScorer with fn advance(self) {
  self.seek(DocId::new(self.current_doc.value + 1))
}

///|
impl Scorer for DisjunctionMaxScorer with fn advance_to(self, target) {
  if self.current_doc.value >= target.value {
    true
  } else {
    self.seek(target)
  }
}

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

///|
impl Scorer for DisjunctionMaxScorer with fn score(self) {
  self.current_score
}

///|
impl Weight for DisjunctionMaxWeight with fn scorer(self, segment) {
  let scorers : Array[&Scorer] = []
  for weight in self.weights {
    scorers.push(weight.scorer(segment))
  }
  DisjunctionMaxScorer::{
    scorers: ReadOnlyArray::from_array(scorers),
    tie_breaker: self.tie_breaker,
    current_doc: DocId::new(-1),
    current_score: 0.0,
  }
  as &Scorer
}

///|
pub impl Query for DisjunctionMaxQuery with fn weight(self, statistics) {
  let weights : Array[&Weight] = []
  for disjunct in self.disjuncts {
    weights.push(disjunct.weight(statistics))
  }
  DisjunctionMaxWeight::{
    weights: ReadOnlyArray::from_array(weights),
    tie_breaker: self.tie_breaker,
  }
  as &Weight
}