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

#define KINO_WANT_ORSCORER_VTABLE
#include "KinoSearch/Search/ORScorer.r"

#include "KinoSearch/Search/ScorerDocQueue.r"
#include "KinoSearch/Search/Similarity.r"
#include "KinoSearch/Search/Tally.r"

/* Move all scorers ahead so that they are at least as 
 */
static bool_t
advance_after_current(ORScorer *self);

ORScorer*
ORScorer_new(Similarity *sim, VArray *subscorers) 
{
    u32_t i;
    CREATE(self, ORScorer, ORSCORER);

    /* assign */
    self->sim              = REFCOUNT_INC(sim);
    self->subscorers       = REFCOUNT_INC(subscorers);

    /* init */
    self->doc_num          = DOC_NUM_SENTINEL;  /* really ought to be -1 */
    self->tally            = Tally_new();
    self->q                = NULL;

    /* derive */
    self->num_subs         = subscorers->size;
    self->scores           = MALLOCATE(self->num_subs, float);

    /* initialize ScorerDocQueue */
    self->q        = ScorerDocQ_new(subscorers->size);
    for (i = 0; i < subscorers->size; i++) {
        Scorer *scorer = (Scorer*)VA_Fetch(subscorers, i);
        if (Scorer_Next(scorer)) {
            ScorerDocQ_Put(self->q, scorer);
        }
    }

    return self;
}

void
ORScorer_destroy(ORScorer *self) 
{
    REFCOUNT_DEC(self->sim);
    REFCOUNT_DEC(self->tally);
    REFCOUNT_DEC(self->q);
    REFCOUNT_DEC(self->subscorers);
    free(self->scores);
    free(self);
}

bool_t
ORScorer_next(ORScorer *self)
{
    return advance_after_current(self);
}

static bool_t
advance_after_current(ORScorer *self) {
    ScorerDocQueue *const q      = self->q;
    float *const          scores = self->scores;
    Tally *const          tally  = self->tally;
    Tally *subtally;

    /* if no scorers, we're done */
    if ( SCORERDOCQ_SIZE(q) == 0)
        return false;

    /* the top scorer will already be at the correct doc, so start with it */
    self->doc_num       = SCORERDOCQ_PEEK_DOC(q);
    subtally            = SCORERDOCQ_PEEK_TALLY(q);
    scores[0]           = subtally->score;
    
    tally->num_matchers = 1;

    do {
        /* attempt to advance past current doc */
        if ( !Kino_ScorerDocQ_Top_Next(q) ) {
            if (!SCORERDOCQ_SIZE(q))
                break; /* bail, no more to advance */
        }

        if (SCORERDOCQ_PEEK_DOC(q) != self->doc_num) {
            /* bail, least doc in queue is now past the one we're scoring */
            break;
        }
        else {
            /* accumulate score */
            subtally = SCORERDOCQ_PEEK_TALLY(q); 
            scores[ tally->num_matchers ] = subtally->score;
            tally->num_matchers++; 
        }
    } while (true);

    return true;
}

bool_t
ORScorer_skip_to(ORScorer *self, u32_t target)
{
    ScorerDocQueue *const q      = self->q;

    if (SCORERDOCQ_SIZE(q) == 0)
        return false;

    /* succeed if we're already past and still on a valid doc */
    if (target <= self->doc_num) {
        if (self->doc_num == DOC_NUM_SENTINEL)
            self->doc_num = 0;
        else
            return true;
    }

    do {
        /* if all scorers are caught up, accumulate score and return */
        if (SCORERDOCQ_PEEK_DOC(q) >= target) {
            return advance_after_current(self);
        }

        /* not caught up yet, so keep skipping scorers */
        if ( !ScorerDocQ_Top_Skip_To(q, target) ) {
            if (SCORERDOCQ_SIZE(q) == 0)
                return false;
        }
    } while (true);
}

chy_u32_t
ORScorer_doc(ORScorer *self)
{
    return self->doc_num;
}

Tally*
ORScorer_tally(ORScorer *self)
{
    u32_t i;
    Tally *const tally = self->tally;
    float *const scores = self->scores;
    
    /* accumulate score */
    tally->score = 0.0;
    for (i = 0; i < tally->num_matchers; i++) {
        tally->score += scores[i];
    }

    return self->tally;
}

u32_t
ORScorer_max_matchers(ORScorer *self)
{
    return self->num_subs;
}

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