#include "KinoSearch/Util/ToolSet.h"

#define KINO_WANT_ANDNOTSCORER_VTABLE
#include "KinoSearch/Search/ANDNOTScorer.r"

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

/* A version of Skip_To without the checks.  When called...
 *    - and_scorer is not NULL
 *    - not_scorer is not NULL
 *    - and_scorer has been advanced to a valid doc
 */
static bool_t
to_non_excluded(ANDNOTScorer *self);

ANDNOTScorer*
ANDNOTScorer_new(Similarity *sim, Scorer *and_scorer, Scorer *not_scorer) 
{
    CREATE(self, ANDNOTScorer, ANDNOTSCORER);

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

    /* init */
    self->first_time       = true;

    return self;
}

void
ANDNOTScorer_destroy(ANDNOTScorer *self) 
{
    REFCOUNT_DEC(self->sim);
    REFCOUNT_DEC(self->and_scorer);
    REFCOUNT_DEC(self->not_scorer);

    free(self);
}

bool_t
ANDNOTScorer_next(ANDNOTScorer *self)
{
    if (self->first_time) {
        return Scorer_Skip_To(self, 0);
    }
    else if (self->and_scorer != NULL) {
        const u32_t target = Scorer_Doc(self->and_scorer) + 1;
        return Scorer_Skip_To(self, target);
    }
    else {
        return false;
    }
}

bool_t
ANDNOTScorer_skip_to(ANDNOTScorer *self, u32_t target)
{
    if (self->first_time) {
        self->first_time = false;

        /* see if the negated scorer has any docs at all */
        if ( !Scorer_Skip_To(self->not_scorer, 0) ) {
            REFCOUNT_DEC(self->not_scorer);
            self->not_scorer = NULL;
        }
    }

    /* if the required scorer is empty, we're done */
    if (self->and_scorer == NULL)
        return false;

    /* if no more exclusions, return anything from the required scorer */
    if (self->not_scorer == NULL)
        return Scorer_Skip_To(self->and_scorer, target);

    if ( Scorer_Skip_To(self->and_scorer, target) ) {
        return to_non_excluded(self);
    }
    else {
        /* bail if required score exausted */
        REFCOUNT_DEC(self->and_scorer);
        self->and_scorer = NULL;
        return false;
    }
}

static bool_t
to_non_excluded(ANDNOTScorer *self)
{
    Scorer *const and_scorer = self->and_scorer;
    Scorer *const not_scorer = self->not_scorer;
    u32_t required_doc       = Scorer_Doc(and_scorer);
    u32_t negated_doc        = Scorer_Doc(not_scorer);

    while (1) {
        if (required_doc < negated_doc) {
            /* success -- required doc not negated */
            return true;
        }
        else if (negated_doc < required_doc) {
            if ( Scorer_Skip_To(not_scorer, required_doc) ) {
                negated_doc = Scorer_Doc(not_scorer);
                if (required_doc < negated_doc) {
                    /* success -- required doc not negated */
                    return true;
                }
            }
            else {
                /* success -- no more exclusions */
                REFCOUNT_DEC(not_scorer);
                self->not_scorer = NULL;
                return true;
            }
        }

        /* current required doc is negated, so advance */
        if ( Scorer_Next(and_scorer) ) 
            required_doc = Scorer_Doc(and_scorer);
        else 
            break;
    }

    /* if we've made it this far, we're out of required docs */
    REFCOUNT_DEC(self->and_scorer);
    self->and_scorer = NULL;
    return false;
}

u32_t
ANDNOTScorer_doc(ANDNOTScorer *self)
{
    return Scorer_Doc(self->and_scorer);
}

Tally*
ANDNOTScorer_tally(ANDNOTScorer *self)
{
    return Scorer_Tally(self->and_scorer);
}

u32_t
ANDNOTScorer_max_matchers(ANDNOTScorer *self)
{
    return Scorer_Max_Matchers(self->and_scorer);
}

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