//! ConstantScoreQuery: wrap a query with a fixed score.
//!
//! Runs the inner query in filter context and returns a constant score
//! for all matching documents.
//!
//! See [[query-dsl#Compound Queries]] and [[architecture-query-execution#Step 6]].

use luci_core::{DocId, Result, ScoreMode, Scorer, TwoPhaseIterator};

use crate::query::{Query, ScorerSupplier, Weight};
use crate::search::searcher::Searcher;
use crate::segment::reader::SegmentReader;

pub struct ConstantScoreQuery {
    pub inner: Box<dyn Query>,
    pub boost: f32,
}

impl Query for ConstantScoreQuery {
    fn create_weight(
        &self,
        searcher: &Searcher,
        _score_mode: ScoreMode,
    ) -> Result<Box<dyn Weight>> {
        // Inner query runs in filter context
        let inner_weight = self.inner.create_weight(searcher, ScoreMode::CompleteNoScores)?;
        Ok(Box::new(ConstantScoreWeight {
            inner: inner_weight,
            boost: self.boost,
        }))
    }
}

struct ConstantScoreWeight {
    inner: Box<dyn Weight>,
    boost: f32,
}

impl Weight for ConstantScoreWeight {
    fn scorer_supplier(
        &self,
        reader: &SegmentReader,
    ) -> Result<Option<Box<dyn ScorerSupplier>>> {
        match self.inner.scorer_supplier(reader)? {
            Some(supplier) => Ok(Some(Box::new(ConstantScoreScorerSupplier {
                inner: supplier,
                boost: self.boost,
            }))),
            None => Ok(None),
        }
    }
}

struct ConstantScoreScorerSupplier {
    inner: Box<dyn ScorerSupplier>,
    boost: f32,
}

unsafe impl Send for ConstantScoreScorerSupplier {}

impl ScorerSupplier for ConstantScoreScorerSupplier {
    fn cost(&self) -> u64 {
        self.inner.cost()
    }

    fn scorer(self: Box<Self>) -> Result<Box<dyn Scorer>> {
        let inner = self.inner.scorer()?;
        Ok(Box::new(ConstantScoreScorer {
            inner,
            boost: self.boost,
        }))
    }
}

struct ConstantScoreScorer {
    inner: Box<dyn Scorer>,
    boost: f32,
}

impl Scorer for ConstantScoreScorer {
    fn doc_id(&self) -> DocId { self.inner.doc_id() }
    fn next(&mut self) -> DocId { self.inner.next() }
    fn advance(&mut self, target: DocId) -> DocId { self.inner.advance(target) }
    fn score(&mut self) -> f32 { self.boost }
    fn two_phase(&mut self) -> Option<&mut dyn TwoPhaseIterator> { self.inner.two_phase() }
}
