/** @file
 * @brief Class for merging PostList objects from subdatabases.
 */
/* Copyright (C) 2007,2008,2009,2011,2017,2018,2020 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
 *
 * 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 "multi_postlist.h"

#include <xapian/database.h>

#include "backends/multi.h"
#include "backends/postlist.h"
#include "heap.h"
#include "omassert.h"

#include <algorithm>
#include <functional>

using namespace std;

MultiPostList::~MultiPostList()
{
    while (n_shards)
        delete postlists[--n_shards];
    delete [] postlists;
    delete [] docids;
}

Xapian::docid
MultiPostList::get_docid() const
{
    return docids[0];
}

Xapian::termcount
MultiPostList::get_wdf() const
{
    return postlists[shard_number(docids[0], n_shards)]->get_wdf();
}

double
MultiPostList::get_weight(Xapian::termcount,
                          Xapian::termcount,
                          Xapian::termcount) const
{
    // MultiPostList is only used by PostingIterator which should never call
    // this method.
    Assert(false);
    return 0;
}

bool
MultiPostList::at_end() const
{
    return docids_size == 0;
}

double
MultiPostList::recalc_maxweight()
{
    // MultiPostList is only used by PostingIterator which should never call
    // this method.
    Assert(false);
    return 0;
}

PositionList*
MultiPostList::open_position_list() const
{
    return postlists[shard_number(docids[0], n_shards)]->open_position_list();
}

PostList*
MultiPostList::next(double w_min)
{
    if (docids_size == 0) {
        // Make a heap of the mapped docids so that the smallest is at the top
        // of the heap.
        Xapian::doccount j = 0;
        for (Xapian::doccount i = 0; i != n_shards; ++i) {
            PostList* pl = postlists[i];
            if (!pl) continue;
            pl->next(w_min);
            if (pl->at_end()) {
                delete pl;
                postlists[i] = NULL;
            } else {
                docids[j++] = unshard(pl->get_docid(), i, n_shards);
            }
        }
        docids_size = j;
        Heap::make(docids, docids + docids_size,
                   std::greater<Xapian::docid>());
    } else {
        Xapian::docid old_did = docids[0];
        Xapian::doccount shard = shard_number(old_did, n_shards);
        PostList* pl = postlists[shard];
        pl->next(w_min);
        if (pl->at_end()) {
            Heap::pop(docids, docids + docids_size,
                      std::greater<Xapian::docid>());
            delete pl;
            postlists[shard] = NULL;
            --docids_size;
        } else {
            docids[0] = unshard(pl->get_docid(), shard, n_shards);
            Heap::replace(docids, docids + docids_size,
                          std::greater<Xapian::docid>());
        }
    }

    return NULL;
}

PostList*
MultiPostList::skip_to(Xapian::docid did, double w_min)
{
    Xapian::doccount j = 0;
    if (docids_size == 0) {
        // Make a heap of the mapped docids so that the smallest is at the top
        // of the heap.
        Xapian::docid shard_did = shard_docid(did, n_shards);
        Xapian::doccount shard = shard_number(did, n_shards);
        for (Xapian::doccount i = 0; i != n_shards; ++i) {
            PostList* pl = postlists[i];
            if (!pl) continue;
            pl->skip_to(shard_did + (i < shard), w_min);
            if (pl->at_end()) {
                delete pl;
                postlists[i] = NULL;
            } else {
                docids[j++] = unshard(pl->get_docid(), i, n_shards);
            }
        }
    } else {
        if (did <= docids[0])
            return NULL;
        // For a skip by < n_shards docids, pop/push may be more efficient than
        // rebuilding the heap.  For now, always just rebuild the heap unless
        // we're just skipping the next docid, in which case do next() instead.
        if (did == docids[0] + 1)
            return MultiPostList::next(w_min);
        Xapian::docid shard_did = shard_docid(did, n_shards);
        Xapian::doccount shard = shard_number(did, n_shards);
        for (Xapian::doccount i = 0; i != docids_size; ++i) {
            Xapian::docid old_did = docids[i];
            if (old_did < did) {
                Xapian::doccount old_shard = shard_number(old_did, n_shards);
                PostList* pl = postlists[old_shard];
                pl->skip_to(shard_did + (old_shard < shard), w_min);
                if (pl->at_end()) {
                    delete pl;
                    postlists[old_shard] = NULL;
                } else {
                    docids[j++] = unshard(pl->get_docid(), old_shard, n_shards);
                }
            } else {
                docids[j++] = old_did;
            }
        }
    }
    docids_size = j;
    Heap::make(docids, docids + docids_size, std::greater<Xapian::docid>());

    return NULL;
}

std::string
MultiPostList::get_description() const
{
    string desc = "MultiPostList(";
    for (Xapian::doccount i = 0; i != n_shards; ++i) {
        if (postlists[i]) {
            desc += postlists[i]->get_description();
            desc += ',';
        } else {
            desc += "NULL,";
        }
    }
    desc.back() = ')';
    return desc;
}
