//! Boost wrapper: multiplies inner query's score by a constant factor.
//!
//! Applied automatically by the parser when any query has a `boost` parameter.

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

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

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

impl Query for BoostQuery {
    fn create_weight(&self, searcher: &Searcher, score_mode: ScoreMode) -> Result<Box<dyn Weight>> {
        let inner = self.inner.create_weight(searcher, score_mode)?;
        Ok(Box::new(BoostWeight { inner, boost: self.boost }))
    }
}

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

impl Weight for BoostWeight {
    fn scorer_supplier(&self, reader: &SegmentReader) -> Result<Option<Box<dyn ScorerSupplier>>> {
        let inner = match self.inner.scorer_supplier(reader)? {
            Some(s) => s,
            None => return Ok(None),
        };
        Ok(Some(Box::new(BoostScorerSupplier { inner, boost: self.boost })))
    }
}

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

impl ScorerSupplier for BoostScorerSupplier {
    fn cost(&self) -> u64 { self.inner.cost() }
    fn scorer(self: Box<Self>) -> Result<Box<dyn Scorer>> {
        let inner = self.inner.scorer()?;
        Ok(Box::new(BoostScorer { inner, boost: self.boost }))
    }
}

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

impl Scorer for BoostScorer {
    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.inner.score() * self.boost }
    fn two_phase(&mut self) -> Option<&mut dyn TwoPhaseIterator> { None }

    fn max_score(&self) -> f32 { self.inner.max_score() * self.boost }
    fn set_min_competitive_score(&mut self, min_score: f32) {
        if self.boost > 0.0 {
            self.inner.set_min_competitive_score(min_score / self.boost);
        }
    }
}
