#include "KinoSearch/Util/ToolSet.h"

#define KINO_WANT_ANDSCORER_VTABLE
#include "KinoSearch/Search/ANDScorer.r"

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

/* Perform some initialization stuff we can't know until all subscorers have
 * been added.  Return false if we'll never match anything with this scorer.
 */
static bool_t 
delayed_init(ANDScorer *self, u32_t target);

/* Mark this scorer as invalid/finished.
 */
static bool_t
invalidate(ANDScorer *self);

/* Return the highest value for doc() from an array of Scorers.
 */
static u32_t
highest_doc(Scorer **subscorers, u32_t num_subs);

ANDScorer*
ANDScorer_new(Similarity *sim) 
{
    CREATE(self, ANDScorer, ANDSCORER);

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

    /* init */
    self->tally            = Tally_new();
    self->cap              = 10;
    self->subscorers       = MALLOCATE(10, Scorer*);
    self->num_subs         = 0;
    self->raw_prox_bb      = BB_new(0);
    self->first_time       = true;
    self->more             = true;

    return self;
}

void
ANDScorer_destroy(ANDScorer *self) 
{
    u32_t i;
    REFCOUNT_DEC(self->raw_prox_bb);
    REFCOUNT_DEC(self->sim);
    REFCOUNT_DEC(self->tally);

    for (i = 0; i < self->num_subs; i++) {
        REFCOUNT_DEC(self->subscorers[i]);
    }
    free(self->subscorers);

    free(self);
}

void
ANDScorer_add_subscorer(ANDScorer *self, Scorer *subscorer)
{
    if (!self->first_time)
        CONFESS("Can't add scorers after starting iteration");

    if (self->num_subs == self->cap) {
        self->cap += 100;
        self->subscorers = REALLOCATE(self->subscorers, self->cap, Scorer*);
    }
    self->subscorers[ self->num_subs++ ] = REFCOUNT_INC(subscorer);

    /* add to the matcher count; don't bother with subclauses */
    self->tally->num_matchers += 1;
}

static bool_t 
delayed_init(ANDScorer *self, u32_t target)
{
    u32_t i;

    /* once is enough */
    self->first_time = false;

    /* no scorers, no matches! */
    if (!self->num_subs)
        return invalidate(self);

    /* set fixed value for num_matchers */
    self->tally->num_matchers = self->num_subs;

    /* calculate coord multiplier */
    self->coord = Sim_Coord(self->sim, self->num_subs, self->num_subs);

    /* advance all scorers */
    for (i = 0; i < self->num_subs; i++) {
        if (!Scorer_Skip_To(self->subscorers[i], target))
            return invalidate(self);
    }

    return true;
}

bool_t
ANDScorer_next(ANDScorer *self)
{
    if (self->first_time) {
        return Scorer_Skip_To(self, 0);
    }
    else if (self->more) {
        const u32_t target = Scorer_Doc(self->subscorers[0]) + 1;
        return Scorer_Skip_To(self, target);
    }
    else {
        return false;
    }
}

static bool_t
invalidate(ANDScorer *self)
{
    self->more = false;
    return false;
}

static u32_t
highest_doc(Scorer **subscorers, u32_t num_subs)
{
    u32_t highest = 0;
    u32_t i;

    for (i = 0; i < num_subs; i++) {
        u32_t candidate = Scorer_Doc(subscorers[i]); 
        if (candidate > highest)
            highest = candidate;
    }

    return highest;
}

bool_t
ANDScorer_skip_to(ANDScorer *self, u32_t target)
{
    Scorer **const subscorers = self->subscorers;
    const u32_t    num_subs   = self->num_subs;
    u32_t          highest    = 0;

    /* first step: advance */
    if (self->first_time) {
        /* scoot ALL subscorers and find the least doc they might agree on */
        if ( !delayed_init(self, target) )
            return invalidate(self);
        highest = highest_doc(subscorers, num_subs);
    }
    else {
        /* advance first subscorer and use it's doc as a starting point */
        if ( !Scorer_Skip_To(subscorers[0], target) )
            return invalidate(self);
        highest = Scorer_Doc(subscorers[0]);
    }

    /* second step: reconcile */
    while(1) {
        u32_t i;
        bool_t agreement = true;

        /* scoot all scorers up */
        for (i = 0; i < num_subs; i++) {
            Scorer *const subscorer = subscorers[i];
            u32_t candidate = Scorer_Doc(subscorer);

            /* if this subscorer is highest, others will need to catch up */
            if (highest < candidate)
                highest = candidate;

            /* if least doc scorers can agree on exceeds target, raise bar */
            if (target < highest)
                target = highest;

            /* scoot this scorer up if not already at highest */
            if (candidate < target) {
                if ( !Scorer_Skip_To(subscorer, target) )
                    return invalidate(self);

                /* this scorer is definitely the highest right now */
                highest = Scorer_Doc(subscorer);
            }
        }

        /* if scorers don't agree, send back through the loop */
        for (i = 0; i < num_subs; i++) {
            Scorer *const subscorer = subscorers[i];
            const u32_t candidate = Scorer_Doc(subscorer);
            if (candidate != highest) {
                agreement = false;
                break;
            }
        }

        if (!agreement)
            continue;
        if (highest >= target)
            break;
    } 

    return true;
}

u32_t
ANDScorer_doc(ANDScorer *self)
{
    return Scorer_Doc(self->subscorers[0]);
}

Tally*
ANDScorer_tally(ANDScorer *self)
{
    u32_t i;
    Scorer **const subscorers = self->subscorers;
    Tally *const   tally      = self->tally;

    tally->score = 0.0f;
    for (i = 0; i < self->num_subs; i++) {
        Tally *subtally = Scorer_Tally(subscorers[i]);
        tally->score        += subtally->score;
    }

    tally->score *= self->coord;

    return tally;
}

u32_t
ANDScorer_max_matchers(ANDScorer *self)
{
    return self->num_subs;
}

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