/** @file
 *  @brief SubMatch class for a local database.
 */
/* Copyright (C) 2006-2026 Olly Betts
 * Copyright (C) 2007 Lemur Consulting Ltd
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see
 * <https://www.gnu.org/licenses/>.
 */

#ifndef XAPIAN_INCLUDED_LOCALSUBMATCH_H
#define XAPIAN_INCLUDED_LOCALSUBMATCH_H

#include "api/queryinternal.h"
#include "backends/databaseinternal.h"
#include "backends/leafpostlist.h"
#include "estimateop.h"
#include "weight/weightinternal.h"
#include "xapian/enquire.h"
#include "xapian/weight.h"

class PostListTree;

namespace Xapian {
namespace Internal {
class PostList;
}
}

using Xapian::Internal::PostList;

class LocalSubMatch {
    /// Don't allow assignment.
    LocalSubMatch& operator=(const LocalSubMatch &) = delete;

    /// Don't allow copying.
    LocalSubMatch(const LocalSubMatch &) = delete;

    /// The statistics for the collection.
    Xapian::Weight::Internal* total_stats = nullptr;

    /// The query.
    Xapian::Query query;

    /// The query length (used by some weighting schemes).
    Xapian::termcount qlen;

    /// The (sub-)Database we're searching.
    const Xapian::Database::Internal* db;

    /// Weight object (used as a factory by calling create on it).
    const Xapian::Weight& wt_factory;

    /// 0-based index for the subdatabase.
    Xapian::doccount shard_index;

  public:
    /// Constructor.
    LocalSubMatch(const Xapian::Database::Internal* db_,
                  const Xapian::Query& query_,
                  Xapian::termcount qlen_,
                  const Xapian::Weight& wt_factory_,
                  Xapian::doccount shard_index_)
        : query(query_), qlen(qlen_), db(db_),
          wt_factory(wt_factory_),
          shard_index(shard_index_)
    {}

    Estimates resolve(EstimateOp* estimate_op) {
        Assert(estimate_op);
        auto db_size = db->get_doccount();
        // We shortcut an empty shard and avoid creating a postlist tree for
        // it so shouldn't need to resolve estimates for it.
        Assert(db_size);
        Xapian::docid db_first, db_last;
        db->get_used_docid_range(db_first, db_last);
        return estimate_op->resolve(db_size, db_first, db_last);
    }

    /** Fetch and collate statistics.
     *
     *  Before we can calculate term weights we need to fetch statistics from
     *  each database involved and collate them.
     *
     *  @param rset     The RSet for this shard.
     *  @param stats    Weight::Internal object to add the statistics to.
     */
    void prepare_match(const Xapian::RSet& rset,
                       Xapian::Weight::Internal& stats)
    {
        stats.accumulate_stats(*db, rset);
    }

    /** Set the collated statistics.
     *
     *  These will be used when generating the PostList tree.
     */
    void start_match(Xapian::Weight::Internal& total_stats_)
    {
        total_stats = &total_stats_;
    }

    /// Get PostList.
    PostListAndEstimate get_postlist(PostListTree* matcher,
                                     Xapian::termcount* total_subqs_ptr);

    /** Convert a postlist into a synonym postlist.
     */
    PostListAndEstimate make_synonym_postlist(PostListTree* pltree,
                                              PostListAndEstimate or_pl,
                                              double factor,
                                              const TermFreqs& termfreqs);

    PostListAndEstimate
    open_post_list(const std::string& term,
                   Xapian::termcount wqf,
                   double factor,
                   bool need_positions,
                   bool compound_weight,
                   Xapian::Internal::QueryOptimiser* qopt,
                   bool lazy_weight,
                   TermFreqs* termfreqs);

    void register_lazy_postlist_for_stats(LeafPostList* pl,
                                          TermFreqs* termfreqs) {
        auto res = total_stats->termfreqs.emplace(pl->get_term(), TermFreqs());
        if (res.second) {
            // Only register if the term isn't already registered - e.g. a term
            // from a wildcard expansion which is also present in the query
            // verbatim such as: foo* food
            res.first->second.termfreq = pl->get_termfreq();
            res.first->second.collfreq = pl->get_collfreq();
#ifdef XAPIAN_ASSERTIONS
            Xapian::doccount tf;
            Xapian::termcount cf;
            db->get_freqs(pl->get_term(), &tf, &cf);
            AssertEq(res.first->second.termfreq, tf);
            AssertEq(res.first->second.collfreq, cf);
#endif
        }
        if (termfreqs) *termfreqs = res.first->second;
    }

    bool weight_needs_wdf() const {
        return wt_factory.get_sumpart_needs_wdf_();
    }

    const Xapian::Weight::Internal* get_stats() const {
        return total_stats;
    }
};

#endif /* XAPIAN_INCLUDED_LOCALSUBMATCH_H */
