#define KINO_USE_SHORT_NAMES
#include "KinoSearch/Util/ToolSet.h"

#define KINO_WANT_SEGPOSTINGLIST_VTABLE
#include "KinoSearch/Index/SegPostingList.r"

#include "KinoSearch/Posting.r"
#include "KinoSearch/Schema.r"
#include "KinoSearch/FieldSpec.r"
#include "KinoSearch/Index/DelDocs.r"
#include "KinoSearch/Index/SegInfo.r"
#include "KinoSearch/Index/SkipStepper.r"
#include "KinoSearch/Index/Term.r"
#include "KinoSearch/Index/TermInfo.r"
#include "KinoSearch/Index/SegLexicon.r"
#include "KinoSearch/Index/LexReader.r"
#include "KinoSearch/Store/InStream.r"
#include "KinoSearch/Store/Folder.r"

/* Low level seek call. 
 */
static void
seek_tinfo(SegPostingList *self, TermInfo *tinfo);

SegPostingList*
SegPList_new(Schema *schema, Folder *folder, SegInfo *seg_info, 
             const ByteBuf *field, LexReader *lex_reader, 
             DelDocs *deldocs, u32_t skip_interval) 
{
    ByteBuf *post_filename;
    CREATE(self, SegPostingList, SEGPOSTINGLIST);

    /* init */
    self->doc_freq        = DOC_NUM_SENTINEL;
    self->post_stream     = NULL;
    self->count           = 0;
    self->doc_base        = 0;

    /* init skipping vars */
    self->skip_stepper    = SkipStepper_new();
    self->skip_count      = 0;
    self->num_skips       = 0;
    self->skip_stream     = NULL;

    /* assign */
    self->schema          = REFCOUNT_INC(schema);
    self->folder          = REFCOUNT_INC(folder);
    self->seg_info        = REFCOUNT_INC(seg_info);
    self->field           = BB_CLONE(field);
    self->deldocs         = deldocs == NULL ? NULL : REFCOUNT_INC(deldocs);
    self->lex_reader      = REFCOUNT_INC(lex_reader);
    self->skip_interval   = skip_interval;
    
    /* derive */
    self->posting   = Schema_Fetch_Posting(schema, field);
    self->field_num = SegInfo_Field_Num(seg_info, field);

    /* build the filename of the postings file */
    post_filename = BB_CLONE(seg_info->seg_name);
    BB_Cat_Str(post_filename, ".p", 2);
    BB_Cat_I64(post_filename, (i64_t)self->field_num);

    /* open both a main stream and a skip stream if the field exists */
    if (Folder_File_Exists(self->folder, post_filename)) {
        ByteBuf *skip_filename = BB_CLONE(seg_info->seg_name);
        
        /* skip_stream */
        BB_Cat_Str(skip_filename, ".skip", 5);
        self->skip_stream = Folder_Open_InStream(self->folder, skip_filename);
        REFCOUNT_DEC(skip_filename);

        /* main stream */
        self->post_stream = Folder_Open_InStream(self->folder, post_filename);
    }
    else {
        /*  empty, so don't bother with these */
        self->post_stream = NULL;
        self->skip_stream = NULL;
    }

    /* clean up */
    REFCOUNT_DEC(post_filename);

    return self;
}

void 
SegPList_destroy(SegPostingList *self)
{
    REFCOUNT_DEC(self->schema);
    REFCOUNT_DEC(self->folder);
    REFCOUNT_DEC(self->seg_info);
    REFCOUNT_DEC(self->posting);
    REFCOUNT_DEC(self->skip_stepper);
    REFCOUNT_DEC(self->field);
    REFCOUNT_DEC(self->deldocs);
    REFCOUNT_DEC(self->lex_reader);
    
    if (self->post_stream != NULL) {
        InStream_SClose(self->post_stream);
        InStream_SClose(self->skip_stream);
        REFCOUNT_DEC(self->post_stream);
        REFCOUNT_DEC(self->skip_stream);
    }

    free(self);
}

Posting*
SegPList_get_posting(SegPostingList *self) 
{
    return self->posting;
}

u32_t
SegPList_get_doc_freq(SegPostingList *self) 
{
    return self->doc_freq;
}

u32_t
SegPList_get_doc_num(SegPostingList *self) 
{
    return self->posting->doc_num;
}

/* TODO: This is unsafe to call except right after constructor. */
void
SegPList_set_doc_base(SegPostingList *self, u32_t doc_base)
{
    self->doc_base = doc_base;
}

bool_t
SegPList_next(SegPostingList *self) 
{
    InStream *const post_stream = self->post_stream;
    DelDocs  *const deldocs     = self->deldocs;
    Posting  *const posting     = self->posting;

    while (1) {

        /* bail if we're out of docs */
        if (self->count >= self->doc_freq) {
            Post_Reset(posting, self->doc_base);
            return false;
        }
        self->count++;

        Post_Read_Record(posting, post_stream);

        /* if the doc isn't deleted... success! */
        if (deldocs == NULL) {
            break;
        }
        else {
            const u32_t doc_minus_base = posting->doc_num - self->doc_base;
            if ( !DelDocs_Get(deldocs, doc_minus_base) ) {
                break;
            }
        }
    }

    return true;
}

bool_t
SegPList_skip_to(SegPostingList *self, u32_t target)
{
    Posting *posting = self->posting;

    if (self->doc_freq >= self->skip_interval) {
        InStream *post_stream           = self->post_stream;
        InStream *skip_stream           = self->skip_stream;
        SkipStepper *const skip_stepper = self->skip_stepper;
        u32_t new_doc_num               = skip_stepper->doc_num;
        u64_t new_filepos               = InStream_STell(post_stream);

        /* Assuming the default skip_interval of 16...
         * 
         * Say we're currently on the 5th doc matching this term, and we get a
         * request to skip to the 18th doc matching it.  We won't have skipped
         * yet, but we'll have already gone past 5 of the 16 skip docs --
         * ergo, the modulus in the following formula.
         */
        i32_t num_skipped = 0 - (self->count % self->skip_interval);

        /* see if there's anything to skip */
        while (target > skip_stepper->doc_num) {
            new_doc_num   = skip_stepper->doc_num;
            new_filepos   = skip_stepper->filepos;

            if (   skip_stepper->doc_num != self->doc_base 
                && skip_stepper->doc_num >= posting->doc_num
            ) {
                num_skipped += self->skip_interval;
            }

            if (self->skip_count >= self->num_skips)
                break;

            SkipStepper_Read_Record(skip_stepper, skip_stream);
            self->skip_count++;
        }

        /* if we found something to skip, skip it */
        if (new_filepos > InStream_STell(post_stream)) {

            /* move the postings filepointer up */
            InStream_SSeek(post_stream, new_filepos);

            /* jump to the new doc num */
            posting->doc_num = new_doc_num;

            /* increase count by the number of docs we skipped over */
            self->count += num_skipped;
        }
    }

    /* done skipping, so scan */
    do {
        if (!SegPList_Next(self)) {
            return false;
        }
    } while (target > posting->doc_num);
    return true;
}

void
SegPList_seek(SegPostingList *self, Term *target)
{
    TermInfo *tinfo = LexReader_Fetch_Term_Info(self->lex_reader, target);
    seek_tinfo(self, tinfo);
}

void
SegPList_seek_lex(SegPostingList *self, Lexicon *lexicon)
{
    /* maybe true, maybe not */
    SegLexicon *const seg_lexicon = (SegLexicon*)lexicon;

    /* optimized case */
    if (   OBJ_IS_A(lexicon, SEGLEXICON)
        && seg_lexicon->seg_info == self->seg_info /* i.e. same segment */
    ) {
        seek_tinfo(self, SegLex_Get_Term_Info(seg_lexicon));
    }
    /* punt case */
    else {
        SegPList_Seek(self, Lex_Get_Term(lexicon));
    }
}

static void
seek_tinfo(SegPostingList *self, TermInfo *tinfo) 
{
    self->count = 0;

    if (tinfo == NULL) {
        /* Next will return false; other methods invalid now */
        self->doc_freq = 0;
    }
    else {
        /* transfer doc_freq, seek main stream */
        self->doc_freq     = tinfo->doc_freq;
        InStream_SSeek(self->post_stream, tinfo->post_filepos);

        /* prepare posting */
        Post_Reset(self->posting, self->doc_base);

        /* prepare to skip */
        self->skip_count       = 0;
        self->num_skips        = tinfo->doc_freq / self->skip_interval;
        SkipStepper_Reset(self->skip_stepper, self->doc_base,
            (u64_t)tinfo->post_filepos);
        InStream_SSeek(self->skip_stream, tinfo->skip_filepos);
    }
}

struct kino_Scorer*
SegPList_make_scorer(SegPostingList *self, struct kino_Similarity *sim, 
                     void *weight, float weight_val)
{
    return Post_Make_Scorer(self->posting, sim, (PostingList*)self, 
        weight, weight_val);
}

/* Copyright 2006-2007 Marvin Humphrey
 *
 * This program is free software; you can redistribute it and/or modify
 * under the same terms as Perl itself.
 */