/** @file
 * @brief Matcher class
 */
/* Copyright (C) 2017,2018,2019 Olly Betts
 *
 * 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_MATCHER_H
#define XAPIAN_INCLUDED_MATCHER_H

#ifndef PACKAGE
# error config.h must be included first in each C++ source file
#endif

#include "api/enquireinternal.h"
#include "localsubmatch.h"
#include "remotesubmatch.h"
#include "weight/weightinternal.h"

#include "xapian/database.h"

#include <memory>
#include <vector>

namespace Xapian {
    class KeyMaker;
    class MatchDecider;
    class MSet;
    class Query;
    class Weight;
}

class Matcher {
    typedef Xapian::Internal::opt_intrusive_ptr<Xapian::MatchSpy> opt_ptr_spy;

    Xapian::Database db;

    /** LocalSubMatch objects for local databases.
     *
     *  The entries are at the same index as the corresponding shard in the
     *  Database object, with NULL entries for any remote shards.
     */
    std::vector<std::unique_ptr<LocalSubMatch>> locals;

#ifdef XAPIAN_HAS_REMOTE_BACKEND
    /** RemoteSubMatch objects for remote databases.
     *
     *  Unlike @a locals, this *only* contains entries for remote shards, and
     *  each RemoteSubMatch object knows its shard index.
     *
     *  If poll() isn't available then select() has to be used to wait for fds
     *  to become ready to read, which imposes some limitations:
     *
     *  * Generally select() only works for fds < FD_SETSIZE
     *  * On Microsoft Windows, select() only works for sockets and FD_SETSIZE
     *    has a different meaning - it's the maximum number of sockets which
     *    we can select() on at once.
     *
     *  To work within these limitations, we order the objects in the vector
     *  so the ones we can select() on all come before the ones we can't
     *  select() on.
     *
     *  The partition point is recorded in @a first_nonselectable.
     */
    std::vector<std::unique_ptr<RemoteSubMatch>> remotes;

# ifndef HAVE_POLL
    /** Partition point in @a remotes.
     *
     *  We call select() on remotes[i]->get_read_fd() for all i <
     *  first_nonselectable.  See doc comment for remotes for more details.
     */
    std::size_t first_nonselectable;
# endif
#endif

    Matcher(const Matcher&) = delete;

    Matcher& operator=(const Matcher&) = delete;

    Xapian::MSet get_local_mset(Xapian::doccount first,
				Xapian::doccount maxitems,
				Xapian::doccount check_at_least,
				const Xapian::Weight& wtscheme,
				const Xapian::MatchDecider* mdecider,
				const Xapian::KeyMaker* sorter,
				Xapian::valueno collapse_key,
				Xapian::doccount collapse_max,
				int percent_threshold,
				double percent_threshold_factor,
				double weight_threshold,
				Xapian::Enquire::docid_order order,
				Xapian::valueno sort_key,
				Xapian::Enquire::Internal::sort_setting sort_by,
				bool sort_val_reverse,
				double time_limit,
				const std::vector<opt_ptr_spy>& matchspies);

    /// Perform action on remotes as they become ready using poll() or select().
    template<typename Action> void for_all_remotes(Action action);

  public:
    /** Constructor.
     *
     *  @param db_		Database to search
     *  @param query		Query object
     *  @param query_length	Query length
     *  @param rset		Relevance set (NULL for none)
     *  @param stats		Object to collate stats into
     *  @param wtscheme		Weight object to use as factory
     *  @param have_mdecider	MatchDecider specified?
     *  @param collapse_key	value slot to collapse on (Xapian::BAD_VALUENO
     *				which means no collapsing)
     *  @param collapse_max	Maximum number of documents with the same key
     *				to allow
     *  @param percent_threshold Lower bound on percentage score
     *  @param weight_threshold	Lower bound on weight
     *  @param order		Xapian::docid sort order
     *  @param sort_key		Value slot to sort on
     *  @param sort_by		What to sort results on
     *  @param sort_val_reverse	Reverse direction keys sort in?
     *  @param time_limit	time in seconds after which to disable
     *				check_at_least (0.0 means don't).
     *  @param matchspies	MatchSpy objects to use
     */
    Matcher(const Xapian::Database& db_,
	    const Xapian::Query& query,
	    Xapian::termcount query_length,
	    const Xapian::RSet* rset,
	    Xapian::Weight::Internal& stats,
	    const Xapian::Weight& wtscheme,
	    bool have_mdecider,
	    Xapian::valueno collapse_key,
	    Xapian::doccount collapse_max,
	    int percent_threshold,
	    double weight_threshold,
	    Xapian::Enquire::docid_order order,
	    Xapian::valueno sort_key,
	    Xapian::Enquire::Internal::sort_setting sort_by,
	    bool sort_val_reverse,
	    double time_limit,
	    const std::vector<opt_ptr_spy>& matchspies);

    /** Run the match and produce an MSet object.
     *
     *  @param first		Zero-based index of the first result to return
     *				(which supports retrieving pages of results).
     *  @param maxitems		The maximum number of documents to return.
     *  @param checkatleast	Check at least this many documents.  By default
     *				Xapian will avoiding considering documents
     *				which it can prove can't match, which is faster
     *				but can result in a loose bounds on and a poor
     *				estimate of the total number of matches -
     *				setting checkatleast higher allows trading off
     *				speed for tighter bounds and a more accurate
     *				estimate.
     *  @param mset		MSet object to full in
     *  @param stats		Collated stats
     *  @param wtscheme		Weight object to use as factory
     *  @param mdecider		MatchDecider to use (NULL for none)
     *  @param sorter		KeyMaker for sort keys (NULL for none)
     *  @param collapse_key	value slot to collapse on (Xapian::BAD_VALUENO
     *				which means no collapsing)
     *  @param collapse_max	Maximum number of documents with the same key
     *				to allow
     *  @param collapse_key	value slot to collapse on (Xapian::BAD_VALUENO
     *				which means no collapsing)
     *  @param collapse_max	Maximum number of documents with the same key
     *				to allow
     *  @param percent_threshold Lower bound on percentage score
     *  @param weight_threshold	Lower bound on weight
     *  @param order		Xapian::docid sort order
     *  @param sort_key		Value slot to sort on
     *  @param sort_by		What to sort results on
     *  @param sort_val_reverse	Reverse direction keys sort in?
     *  @param time_limit	time in seconds after which to disable
     *				check_at_least (0.0 means don't).
     *  @param matchspies	MatchSpy objects to use
     */
    Xapian::MSet get_mset(Xapian::doccount first,
			  Xapian::doccount maxitems,
			  Xapian::doccount check_at_least,
			  Xapian::Weight::Internal& stats,
			  const Xapian::Weight& wtscheme,
			  const Xapian::MatchDecider* mdecider,
			  const Xapian::KeyMaker* sorter,
			  Xapian::valueno collapse_key,
			  Xapian::doccount collapse_max,
			  int percent_threshold,
			  double weight_threshold,
			  Xapian::Enquire::docid_order order,
			  Xapian::valueno sort_key,
			  Xapian::Enquire::Internal::sort_setting sort_by,
			  bool sort_val_reverse,
			  double time_limit,
			  const std::vector<opt_ptr_spy>& matchspies);
};

#endif // XAPIAN_INCLUDED_MATCHER_H
