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

#include <xapian/error.h>

#include "heap.h"
#include "omassert.h"

#include <algorithm>

using namespace std;
using Xapian::Internal::intrusive_ptr;

/// Comparison functor which orders SubValueList* by ascending docid.
struct CompareSubValueListsByDocId {
    /// Order by ascending docid.
    bool operator()(const SubValueList *a, const SubValueList *b) const {
        Xapian::docid did_a = a->get_docid();
        Xapian::docid did_b = b->get_docid();
        if (did_a > did_b) return true;
        if (did_a < did_b) return false;
        return a->shard > b->shard;
    }
};

MultiValueList::~MultiValueList()
{
    while (count)
        delete valuelists[--count];
    delete [] valuelists;
}

Xapian::docid
MultiValueList::get_docid() const
{
    Assert(!at_end());
    return current_docid;
}

std::string
MultiValueList::get_value() const
{
    Assert(!at_end());
    return valuelists[0]->get_value();
}

Xapian::valueno
MultiValueList::get_valueno() const
{
    return slot;
}

bool
MultiValueList::at_end() const
{
    return count == 0;
}

void
MultiValueList::next()
{
    if (current_docid == 0) {
        // Make valuelists into a heap so that the one with the earliest
        // sorting docid is at the top of the heap.
        Xapian::doccount j = 0;
        for (Xapian::doccount i = 0; i != count; ++i) {
            valuelists[i]->next();
            if (valuelists[i]->at_end()) {
                delete valuelists[i];
                valuelists[i] = 0;
            } else {
                if (i != j)
                    swap(valuelists[i], valuelists[j]);
                ++j;
            }
        }
        count = j;
        if (rare(count == 0))
            return;

        Heap::make(valuelists, valuelists + count,
                   CompareSubValueListsByDocId());
    } else {
        // Advance to the next docid.
        SubValueList * vl = valuelists[0];
        vl->next();
        if (vl->at_end()) {
            Heap::pop(valuelists, valuelists + count,
                      CompareSubValueListsByDocId());
            delete vl;
            if (--count == 0)
                return;
        } else {
            Heap::replace(valuelists, valuelists + count,
                          CompareSubValueListsByDocId());
        }
    }

    current_docid = valuelists[0]->get_merged_docid(n_shards);
}

void
MultiValueList::skip_to(Xapian::docid did)
{
    // Assume the skip is likely to be a long distance, and rebuild the heap
    // from scratch.  FIXME: It would be useful to profile this against an
    // approach more like that next() uses if this ever gets heavy use.
    Xapian::doccount j = 0;
    for (Xapian::doccount i = 0; i != count; ++i) {
        valuelists[i]->skip_to(did, n_shards);
        if (valuelists[i]->at_end()) {
            delete valuelists[i];
            valuelists[i] = 0;
        } else {
            if (i != j)
                swap(valuelists[i], valuelists[j]);
            ++j;
        }
    }
    count = j;
    if (rare(count == 0))
        return;

    Heap::make(valuelists, valuelists + count, CompareSubValueListsByDocId());

    current_docid = valuelists[0]->get_merged_docid(n_shards);
}

bool
MultiValueList::check(Xapian::docid did)
{
    // FIXME: just run check on the subvaluelist which would hold that docid.
    skip_to(did);
    return true;
}

string
MultiValueList::get_description() const
{
    return "MultiValueList()"; // FIXME: improve description...
}
