/** @file
 * @brief Subclass of HoneyTable which holds postlists.
 */
/* 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 "honey_postlisttable.h"

#include "honey_alldocspostlist.h"
#include "honey_cursor.h"
#include "honey_database.h"
#include "honey_defs.h"
#include "honey_postlist.h"
#include "honey_postlist_encodings.h"

#include <memory>
#include <string_view>

using namespace Honey;
using namespace std;

HoneyPostList*
HoneyPostListTable::open_post_list(const HoneyDatabase* db,
                                   std::string_view term,
                                   bool need_read_pos) const
{
    Assert(!term.empty());
    // Try to position cursor first so we avoid creating HoneyPostList objects
    // for terms which don't exist.
    unique_ptr<HoneyCursor> cursor(cursor_get());
    if (!cursor->find_exact(Honey::make_postingchunk_key(term))) {
        return nullptr;
    }

    if (need_read_pos)
        return new HoneyPosPostList(db, term, cursor.release());
    return new HoneyPostList(db, term, cursor.release());
}

void
HoneyPostListTable::get_freqs(std::string_view term,
                              Xapian::doccount* termfreq_ptr,
                              Xapian::termcount* collfreq_ptr) const
{
    string chunk;
    if (!get_exact_entry(Honey::make_postingchunk_key(term), chunk)) {
        if (termfreq_ptr) *termfreq_ptr = 0;
        if (collfreq_ptr) *collfreq_ptr = 0;
        return;
    }

    const char* p = chunk.data();
    const char* pend = p + chunk.size();
    Xapian::doccount tf;
    Xapian::termcount cf;
    if (!decode_initial_chunk_header_freqs(&p, pend, tf, cf))
        throw Xapian::DatabaseCorruptError("Postlist initial chunk header");
    if (termfreq_ptr) *termfreq_ptr = tf;
    if (collfreq_ptr) *collfreq_ptr = cf;
}

void
HoneyPostListTable::get_used_docid_range(Xapian::doccount doccount,
                                         Xapian::docid& first,
                                         Xapian::docid& last) const
{
    unique_ptr<HoneyCursor> cursor(cursor_get());
    Assert(cursor);

    static const char doclen_key_prefix[2] = {
        0, char(Honey::KEY_DOCLEN_CHUNK)
    };
    if (cursor->find_entry_ge(string(doclen_key_prefix, 2))) {
        first = 1;
    } else {
        // doccount == 0 should be handled by our caller.
        Assert(!cursor->after_end());
        Xapian::docid last_in_first_chunk = docid_from_key(cursor->current_key);
        if (last_in_first_chunk == 0) {
            // Note that our caller checks for doccount == 0 and handles that.
            throw Xapian::DatabaseCorruptError("Bad first doclen chunk key");
        }
        cursor->read_tag();
        unsigned width = cursor->current_tag[0] / 8;
        first = last_in_first_chunk - (cursor->current_tag.size() - 2) / width;
    }

    // We know the last docid is at least first - 1 + doccount, so seek
    // to there and then scan forwards.  If we match exactly, then that
    // is exactly the last docid (our caller handles this case when
    // first == 1, but not otherwise).
    last = first - 1 + doccount;
    if (cursor->find_entry_ge(make_doclenchunk_key(last)))
        return;

    if (cursor->after_end())
        throw Xapian::DatabaseCorruptError("Missing doclen chunk");

    do {
        Xapian::docid new_last = docid_from_key(cursor->current_key);
        if (new_last == 0) {
            // We've hit a non-doclen item.
            return;
        }
        last = new_last;
    } while (cursor->next());

    // We've reached the end of the table (only possible if there are no terms
    // at all!)
}

Xapian::termcount
HoneyPostListTable::get_wdf_upper_bound(std::string_view term) const
{
    string chunk;
    if (!get_exact_entry(Honey::make_postingchunk_key(term), chunk)) {
        // Term not present.
        return 0;
    }

    const char* p = chunk.data();
    const char* pend = p + chunk.size();
    Xapian::doccount tf;
    Xapian::termcount cf;
    Xapian::docid first;
    Xapian::docid last;
    Xapian::docid chunk_last;
    Xapian::termcount first_wdf;
    Xapian::termcount wdf_max;
    if (!decode_initial_chunk_header(&p, pend, tf, cf, first, last, chunk_last,
                                     first_wdf, wdf_max))
        throw Xapian::DatabaseCorruptError("Postlist initial chunk header");
    return wdf_max;
}
