package KinoSearch::Search::Scorer;
use strict;
use warnings;
use KinoSearch::Util::ToolSet;
use base qw( KinoSearch::Util::CClass Exporter );

BEGIN {
    __PACKAGE__->init_instance_vars(
        # constructor params
        similarity => undef,
    );
}
our %instance_vars;

our @EXPORT_OK = qw( %score_batch_args );

sub new {
    my $class = shift;
    verify_args( \%instance_vars, @_ );
    my %args = ( %instance_vars, @_ );

    $class = ref($class) || $class;
    my $self = _construct_parent($class);

    if ( defined $args{similarity} ) {
        $self->set_similarity( $args{similarity} );
    }

    return $self;
}

our %score_batch_args = (
    hit_collector => undef,
    start         => 0,
    end           => 2**31,
);

=begin comment

    my $explanation = $scorer->explain($doc_num);

Provide an Explanation for how this scorer rates a given doc.

=end comment
=cut

sub explain { shift->abstract_death }

1;

__END__

__XS__

MODULE = KinoSearch    PACKAGE = KinoSearch::Search::Scorer 

void
_construct_parent(class)
    char *class;
PREINIT:
    Scorer *scorer;
PPCODE:
    scorer   = Kino_Scorer_new();
    ST(0) = sv_newmortal();
    sv_setref_pv(ST(0), class, (void*)scorer);
    XSRETURN(1);

SV*
_scorer_set_or_get(scorer, ...)
    Scorer *scorer;
ALIAS:
    set_similarity = 1
    get_similarity = 2
CODE:
{
    KINO_START_SET_OR_GET_SWITCH

    case 1:  SvREFCNT_dec(scorer->similarity_sv);
             scorer->similarity_sv = newSVsv( ST(1) );
             Kino_extract_struct( scorer->similarity_sv, scorer->sim, 
                Similarity*, "KinoSearch::Search::Similarity" );
             /* fall through */
    case 2:  RETVAL = newSVsv(scorer->similarity_sv);
             break;

    KINO_END_SET_OR_GET_SWITCH
}
OUTPUT: RETVAL


=begin comment

    my $score = $scorer->score;

Calculate and return a score for the scorer's current document.

=end comment
=cut

float
score(scorer)
    Scorer* scorer;
CODE:
    RETVAL = scorer->score(scorer);
OUTPUT: RETVAL


=begin comment

    my $valid_state = $scorer->next;

Move the internal state of the scorer to the next document.  Return false when
there are no more documents to score.

=end comment
=cut

bool
next(scorer)
    Scorer* scorer;
CODE:
    RETVAL = scorer->next(scorer);
OUTPUT: RETVAL


=begin comment

    $scorer->score_batch( 
        hit_collector => $collector,
        start         => $start,
        end           => $end,
    );

Execute the scoring number crunching, accumulating results via the 
$hit_collector.

TODO: Doesn't actually pay any attention to start/end at present.

=end comment
=cut

void
score_batch(scorer, ...)
    Scorer       *scorer;
PREINIT:
    HV           *args_hash;
    U32           start, end;
    HitCollector *hc;
PPCODE:
    /* process hash-style params */
    Kino_Verify_build_args_hash(args_hash, 
        "KinoSearch::Search::Scorer::score_batch_args", 1);
    Kino_extract_struct_from_hv(args_hash, hc, "hit_collector", 13, 
        HitCollector*, "KinoSearch::Search::HitCollector");
    start = (U32)SvUV( Kino_Verify_extract_arg(args_hash, "start", 5) );
    end   = (U32)SvUV( Kino_Verify_extract_arg(args_hash, "end", 3) );

    /* execute scoring loop */
    while (scorer->next(scorer)) {
        hc->collect( hc, scorer->doc(scorer), scorer->score(scorer) );
    }


=begin comment

Not implemented yet.

=end comment
=cut

bool
skip_to(scorer, target_doc_num)
    Scorer* scorer;
    U32     target_doc_num;
CODE:
    RETVAL = scorer->skip_to(scorer, target_doc_num);
OUTPUT: RETVAL


void
DESTROY(scorer)
    Scorer *scorer;
PPCODE:
    Kino_Scorer_destroy(scorer);

    
__H__

#ifndef H_KINO_SCORER
#define H_KINO_SCORER 1

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "KinoSearchSearchSimilarity.h"
#include "KinoSearchUtilMemManager.h"
#include "KinoSearchUtilCarp.h"

typedef struct scorer {
    void       *child;
    Similarity *sim;
    float     (*score)(struct scorer*);
    bool      (*next)(struct scorer*);
    U32       (*doc)(struct scorer*);
    bool      (*skip_to)(struct scorer*, U32);
    SV         *similarity_sv;
} Scorer;

Scorer* Kino_Scorer_new();
float Kino_Scorer_score_death(Scorer*);
bool  Kino_Scorer_next_death(Scorer*);
U32   Kino_Scorer_doc_death(Scorer*);
bool  Kino_Scorer_skip_to_death(Scorer*, U32);
void  Kino_Scorer_destroy(Scorer*);

#endif /* include guard */

__C__

#include "KinoSearchSearchScorer.h"

Scorer*
Kino_Scorer_new() {
    Scorer* scorer;

    Kino_New(0, scorer, 1, Scorer);
    scorer->child         = NULL;
    scorer->sim           = NULL;
    scorer->next          = Kino_Scorer_next_death;
    scorer->score         = Kino_Scorer_score_death;
    scorer->skip_to       = Kino_Scorer_skip_to_death;
    scorer->similarity_sv = &PL_sv_undef;

    return scorer;
}

float
Kino_Scorer_score_death(Scorer* scorer) {
    Kino_confess("scorer->score must be defined in a subclass");
    return 1.0;
}

bool
Kino_Scorer_next_death(Scorer* scorer) {
    Kino_confess("scorer->next must be defined in a subclass");
    return 1;
}

U32
Kino_Scorer_doc_death(Scorer* scorer) {
    Kino_confess("scorer->doc must be defined in a subclass");
    return 1;
}

bool
Kino_Scorer_skip_to_death(Scorer* scorer, U32 target_doc_num) {
    Kino_confess("scorer->skip_to must be defined in a subclass");
    return 1;
}

void
Kino_Scorer_destroy(Scorer* scorer) {
    SvREFCNT_dec(scorer->similarity_sv);
    Kino_Safefree(scorer);
}


__POD__

=begin devdocs

=head1 NAME

KinoSearch::Search::Scorer - score documents against a Query

=head1 DESCRIPTION 

Abstract base class for scorers.

Scorers iterate through a list of documents, producing score/doc_num pairs for
further processing, typically by a HitCollector.

=head1 COPYRIGHT

Copyright 2005-2006 Marvin Humphrey

=head1 LICENSE, DISCLAIMER, BUGS, etc.

See L<KinoSearch|KinoSearch> version 0.12.

=end devdocs
=cut