/** @file
 * @brief Merge two TermList objects using an OR operation.
 */
/* Copyright (C) 2007-2024 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 "ortermlist.h"

#include "debuglog.h"
#include "omassert.h"

#include <xapian/positioniterator.h>

using namespace std;

void
OrTermList::check_started() const
{
    Assert(!left->get_termname().empty());
    Assert(!right->get_termname().empty());
}

OrTermList::~OrTermList()
{
    delete left;
    delete right;
}

Xapian::termcount
OrTermList::get_approx_size() const
{
    LOGCALL(EXPAND, Xapian::termcount, "OrTermList::get_approx_size", NO_ARGS);
    // This is actually the upper bound, but we only use this to order the
    // binary tree of OrTermList objects so it's probably not worth trying
    // to be more sophisticated.
    RETURN(left->get_approx_size() + right->get_approx_size());
}

void
OrTermList::accumulate_stats(Xapian::Internal::ExpandStats & stats) const
{
    LOGCALL_VOID(EXPAND, "OrTermList::accumulate_stats", stats);
    check_started();
    if (cmp <= 0)
	left->accumulate_stats(stats);
    if (cmp >= 0)
	right->accumulate_stats(stats);
}

Xapian::termcount
OrTermList::get_wdf() const
{
    LOGCALL(EXPAND, Xapian::termcount, "OrTermList::get_wdf", NO_ARGS);
    check_started();
    if (cmp < 0) RETURN(left->get_wdf());
    if (cmp > 0) RETURN(right->get_wdf());
    RETURN(left->get_wdf() + right->get_wdf());
}

Xapian::doccount
OrTermList::get_termfreq() const
{
    LOGCALL(EXPAND, Xapian::doccount, "OrTermList::get_termfreq", NO_ARGS);
    check_started();
    if (cmp < 0)
	RETURN(left->get_termfreq());
    Assert(cmp > 0 || left->get_termfreq() == right->get_termfreq());
    RETURN(right->get_termfreq());
}

TermList *
OrTermList::next()
{
    LOGCALL(EXPAND, TermList *, "OrTermList::next", NO_ARGS);
    // If we've not started yet, cmp will be zero so we'll take the third case
    // below which is what we want to do to get started.
    if (cmp < 0) {
	TermList* lret = left->next();
	if (lret == left) {
	    TermList *ret = right;
	    right = NULL;
	    // Prune.
	    RETURN(ret);
	}
	if (lret) {
	    delete left;
	    left = lret;
	}
    } else if (cmp > 0) {
	TermList* rret = right->next();
	if (rret == right) {
	    TermList *ret = left;
	    left = NULL;
	    // Prune.
	    RETURN(ret);
	}
	if (rret) {
	    delete right;
	    right = rret;
	}
    } else {
	TermList* lret = left->next();
	if (lret && lret != left) {
	    delete left;
	    left = lret;
	    lret = NULL;
	}
	TermList* rret = right->next();
	if (rret && rret != right) {
	    delete right;
	    right = rret;
	    rret = NULL;
	}
	if (lret) {
	    if (rret)
		return this;
	    TermList *ret = right;
	    right = NULL;
	    // Prune.
	    RETURN(ret);
	}
	if (rret) {
	    TermList *ret = left;
	    left = NULL;
	    // Prune.
	    RETURN(ret);
	}
    }
    cmp = left->get_termname().compare(right->get_termname());
    current_term = cmp < 0 ? left->get_termname() : right->get_termname();
    RETURN(NULL);
}

TermList*
OrTermList::skip_to(string_view term)
{
    LOGCALL(EXPAND, TermList *, "OrTermList::skip_to", term);
    TermList* lret = left->skip_to(term);
    if (lret && lret != left) {
	delete left;
	left = lret;
	lret = NULL;
    }
    TermList* rret = right->skip_to(term);
    if (rret && rret != right) {
	delete right;
	right = rret;
	rret = NULL;
    }
    if (lret) {
	// Left at end.
	if (rret) {
	    // Both at end.
	    RETURN(this);
	}
	TermList *ret = right;
	right = NULL;
	// Prune.
	RETURN(ret);
    }
    if (rret) {
	// Right at end.
	TermList *ret = left;
	left = NULL;
	// Prune.
	RETURN(ret);
    }
    cmp = left->get_termname().compare(right->get_termname());
    current_term = cmp < 0 ? left->get_termname() : right->get_termname();
    RETURN(NULL);
}

Xapian::termcount
OrTermList::positionlist_count() const
{
    Assert(false);
    return 0;
}

PositionList*
OrTermList::positionlist_begin() const
{
    Assert(false);
    return NULL;
}


Xapian::doccount
FreqAdderOrTermList::get_termfreq() const
{
    LOGCALL(EXPAND, Xapian::doccount, "FreqAdderOrTermList::get_termfreq", NO_ARGS);
    check_started();
    if (cmp < 0) RETURN(left->get_termfreq());
    if (cmp > 0) RETURN(right->get_termfreq());
    RETURN(left->get_termfreq() + right->get_termfreq());
}
