/** @file
 * @brief PostList class implementing Query::OP_AND_MAYBE
 */
/* Copyright 2017 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/>.
 */

#include <config.h>

#include "andmaybepostlist.h"

#include "andpostlist.h"

using namespace std;

PostList*
AndMaybePostList::decay_to_and(Xapian::docid did,
			       double w_min,
			       bool* valid_ptr)
{
    pl = new AndPostList(pl, r, pl_max, r_max, pltree, termfreq);
    r = NULL;
    PostList* result;
    if (valid_ptr) {
	result = pl->check(did, w_min, *valid_ptr);
    } else {
	result = pl->skip_to(did, w_min);
    }
    if (!result) {
	result = pl;
	pl = NULL;
    }
    pltree->force_recalc();
    return result;
}

Xapian::docid
AndMaybePostList::get_docid() const
{
    return pl_did;
}

double
AndMaybePostList::get_weight(Xapian::termcount doclen,
			     Xapian::termcount unique_terms,
			     Xapian::termcount wdfdocmax) const
{
    auto res = pl->get_weight(doclen, unique_terms, wdfdocmax);
    if (maybe_matches())
	res += r->get_weight(doclen, unique_terms, wdfdocmax);
    return res;
}

double
AndMaybePostList::recalc_maxweight()
{
    pl_max = pl->recalc_maxweight();
    r_max = r->recalc_maxweight();
    return pl_max + r_max;
}

PostList*
AndMaybePostList::next(double w_min)
{
    if (w_min > pl_max)
	return decay_to_and(max(pl_did, r_did) + 1, w_min);

    PostList* result = pl->next(w_min - r_max);
    if (result) {
	delete pl;
	pl = result;
    }
    if (pl->at_end()) {
	result = pl;
	pl = NULL;
	pltree->force_recalc();
	return result;
    }

    pl_did = pl->get_docid();
    if (pl_did > r_did) {
	bool r_valid;
	result = r->check(pl_did, w_min - pl_max, r_valid);
	if (result) {
	    delete r;
	    r = result;
	}
	if (!r_valid)
	    return NULL;
	if (r->at_end()) {
	    result = pl;
	    pl = NULL;
	    pltree->force_recalc();
	    return result;
	}
	r_did = r->get_docid();
    }
    return NULL;
}

PostList*
AndMaybePostList::skip_to(Xapian::docid did, double w_min)
{
    // skip_to(pl_did) happens after decay from OR
    if (did < pl_did)
	return NULL;

    if (w_min > pl_max) {
	// We dealt with did <= pl_did just above.
	return decay_to_and(max(did, r_did), w_min);
    }

    PostList* result = pl->skip_to(did, w_min - r_max);
    if (result) {
	delete pl;
	pl = result;
    }
    if (pl->at_end()) {
	result = pl;
	pl = NULL;
	pltree->force_recalc();
	return result;
    }
    pl_did = pl->get_docid();
    if (pl_did > r_did) {
	bool r_valid;
	result = r->check(pl_did, 0, r_valid);
	if (result) {
	    delete r;
	    r = result;
	}
	if (!r_valid)
	    return NULL;
	if (r->at_end()) {
	    result = pl;
	    pl = NULL;
	    pltree->force_recalc();
	    return result;
	}
	r_did = r->get_docid();
    }
    return NULL;
}

PostList*
AndMaybePostList::check(Xapian::docid did, double w_min, bool& valid)
{
    if (w_min > pl_max)
	return decay_to_and(max({did, pl_did, r_did}), w_min, &valid);

    PostList* result = pl->check(did, w_min - r_max, valid);
    if (result) {
	delete pl;
	pl = result;
    }
    if (valid) {
	if (pl->at_end()) {
	    result = pl;
	    pl = NULL;
	    pltree->force_recalc();
	    return result;
	}
	pl_did = pl->get_docid();
	if (pl_did > r_did) {
	    bool r_valid;
	    result = r->check(pl_did, 0, r_valid);
	    if (result) {
		delete r;
		r = result;
	    }
	    if (!r_valid)
		return NULL;
	    if (r->at_end()) {
		result = pl;
		pl = NULL;
		pltree->force_recalc();
		return result;
	    }
	    r_did = r->get_docid();
	}
    }
    return NULL;
}

string
AndMaybePostList::get_description() const
{
    string desc = "AndMaybePostList(";
    desc += pl->get_description();
    desc += ", ";
    desc += r->get_description();
    desc += ')';
    return desc;
}

Xapian::termcount
AndMaybePostList::get_wdf() const
{
    auto res = pl->get_wdf();
    if (maybe_matches())
	res += r->get_wdf();
    return res;
}

Xapian::termcount
AndMaybePostList::count_matching_subqs() const
{
    auto res = pl->count_matching_subqs();
    if (maybe_matches())
	res += r->count_matching_subqs();
    return res;
}

void
AndMaybePostList::gather_position_lists(OrPositionList* orposlist)
{
    pl->gather_position_lists(orposlist);
    if (maybe_matches())
	r->gather_position_lists(orposlist);
}
