/** @file
 * @brief Details passed around while building PostList tree from Query tree
 */
/* Copyright (C) 2007-2026 Olly Betts
 * Copyright (C) 2008 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_QUERYOPTIMISER_H
#define XAPIAN_INCLUDED_QUERYOPTIMISER_H

#include "backends/databaseinternal.h"
#include "backends/leafpostlist.h"
#include "backends/postlist.h"
#include "localsubmatch.h"

class LeafPostList;
class PostListTree;

namespace Xapian {
namespace Internal {

class QueryOptimiser {
    /// Prevent assignment.
    void operator=(const QueryOptimiser &);

    /// Prevent copying.
    QueryOptimiser(const QueryOptimiser &);

    LocalSubMatch & localsubmatch;

    /** How many weighted leaf subqueries there are.
     *
     *  Used for scaling percentages when the highest weighted document doesn't
     *  "match all terms".
     */
    Xapian::termcount total_subqs = 0;

    LeafPostList* hint = nullptr;

    bool hint_owned = false;

    bool no_estimates = false;

  public:
    bool need_positions = false;

    bool compound_weight = false;

    Xapian::doccount shard_index;

    const Xapian::Database::Internal & db;

    Xapian::doccount db_size;

    PostListTree * matcher;

    QueryOptimiser(const Xapian::Database::Internal & db_,
                   LocalSubMatch & localsubmatch_,
                   PostListTree * matcher_,
                   Xapian::doccount shard_index_)
        : localsubmatch(localsubmatch_),
          shard_index(shard_index_),
          db(db_), db_size(db.get_doccount()),
          matcher(matcher_) { }

    ~QueryOptimiser() {
        if (hint_owned) delete hint;
    }

    void inc_total_subqs() { ++total_subqs; }

    Xapian::termcount get_total_subqs() const { return total_subqs; }

    void set_total_subqs(Xapian::termcount n) { total_subqs = n; }

    bool get_no_estimates() const { return no_estimates; }

    void set_no_estimates(bool f) { no_estimates = f; }

    /** Create a PostList object for @a term.
     *
     *  @param[out] termfreqs If not NULL, the pointed to object is set to the
     *          TermFreqs for @a term.  If the database is sharded, these will
     *          be for the whole database not just the current shard.  This
     *          is used to estimate TermFreqs for an OP_SYNONYM.
     */
    PostListAndEstimate
    open_post_list(const std::string& term,
                   Xapian::termcount wqf,
                   double factor,
                   TermFreqs* termfreqs) {
        return localsubmatch.open_post_list(term, wqf, factor, need_positions,
                                            compound_weight, this, false,
                                            termfreqs);
    }

    PostListAndEstimate
    open_lazy_post_list(const std::string& term,
                        Xapian::termcount wqf,
                        double factor) {
        return localsubmatch.open_post_list(term, wqf, factor, need_positions,
                                            compound_weight, this, true,
                                            NULL);
    }

    /** Register a lazily-created LeafPostList for stats.
     *
     *  @param pl   An object previously returned by open_lazy_post_list().
     *  @param[out] termfreqs If not NULL, the pointed to object is set to the
     *          TermFreqs for @a term.  If the database is sharded, these will
     *          be for the whole database not just the current shard.  This
     *          is used to estimate TermFreqs for an OP_SYNONYM.
     */
    void register_lazy_postlist_for_stats(LeafPostList* pl,
                                          TermFreqs* termfreqs) {
        localsubmatch.register_lazy_postlist_for_stats(pl, termfreqs);
    }

    /** Create a SynonymPostList object.
     *
     *  @param or_pl        An unweighted BoolOrPostList or OrPostList of the
     *                      PostList objects for children of the OP_SYNONYM.
     *  @param termfreqs    Estimated TermFreqs for @a or_pl.
     */
    PostListAndEstimate make_synonym_postlist(PostListAndEstimate or_pl,
                                              double factor,
                                              const TermFreqs& termfreqs) {
        return localsubmatch.make_synonym_postlist(matcher, std::move(or_pl),
                                                   factor, termfreqs);
    }

    const LeafPostList * get_hint_postlist() const { return hint; }

    void set_hint_postlist(LeafPostList * new_hint) {
        if (hint_owned) {
            hint_owned = false;
            delete hint;
        }
        hint = new_hint;
    }

    void own_hint_postlist() { hint_owned = true; }

    void destroy_postlist(PostList* pl) {
        if (!pl) return;
        if (pl == static_cast<PostList*>(hint)) {
            hint_owned = true;
        } else {
            if (!hint_owned) {
                // The hint could be a subpostlist of pl, but we can't easily
                // tell so we have to do the safe thing and reset it.
                //
                // This isn't ideal, but it's much better than use-after-free
                // bugs.
                hint = nullptr;
            }
            delete pl;
        }
    }

    bool need_wdf_for_compound_weight() const {
        return compound_weight && !localsubmatch.weight_needs_wdf();
    }

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

}
}

using Xapian::Internal::QueryOptimiser;

#endif // XAPIAN_INCLUDED_QUERYOPTIMISER_H
