#include "KinoSearch/Util/ToolSet.h"

#define KINO_WANT_LEXWRITER_VTABLE
#include "KinoSearch/Index/LexWriter.r"

#include "KinoSearch/Schema.r"
#include "KinoSearch/InvIndex.r"
#include "KinoSearch/Index/IndexFileNames.h"
#include "KinoSearch/Index/SegInfo.r"
#include "KinoSearch/Index/Term.r"
#include "KinoSearch/Index/TermStepper.r"
#include "KinoSearch/Index/TermInfo.r"
#include "KinoSearch/Store/Folder.r"
#include "KinoSearch/Store/OutStream.r"

LexWriter*
LexWriter_new(InvIndex *invindex, SegInfo *seg_info, i32_t is_index) 
{
    ByteBuf empty = BYTEBUF_BLANK;
    CREATE(self, LexWriter, LEXWRITER);

    /* assign */
    self->invindex       = REFCOUNT_INC(invindex);
    self->seg_info       = REFCOUNT_INC(seg_info);
    self->is_index       = is_index;
    self->index_interval = invindex->schema->index_interval;
    self->skip_interval  = invindex->schema->skip_interval;

    /* init */
    self->outstream          = NULL;
    self->other              = NULL;
    self->last_lex_filepos       = 0;
    self->count              = 0;
    self->last_tinfo         = TInfo_new(0,0,0,0);
    self->last_text          = BB_new(40);
    self->filename           = BB_new(30);
    self->counts             = Hash_new(0);
    self->stepper            = TermStepper_new(&empty, self->skip_interval,
                                    is_index);
    self->temp_mode          = false;

    /* create the doppelganger */
    if (!is_index) {
        self->other = LexWriter_new(invindex, seg_info, true);
        self->other->other = self;
    }

    return self;
}

void
LexWriter_destroy(LexWriter *self) 
{
    REFCOUNT_DEC(self->invindex);
    REFCOUNT_DEC(self->seg_info);
    REFCOUNT_DEC(self->stepper);
    REFCOUNT_DEC(self->filename);
    REFCOUNT_DEC(self->counts);
    REFCOUNT_DEC(self->outstream);
    if (!self->is_index) {
        REFCOUNT_DEC(self->other);
    }
    REFCOUNT_DEC(self->last_text);
    REFCOUNT_DEC(self->last_tinfo);
    free(self);
}

void 
LexWriter_add(LexWriter* self, ByteBuf* term_text, TermInfo* tinfo) 
{
    /* the .lexx index file gets a pointer to the location of the primary */
    const u64_t lex_filepos = self->is_index
        ? OutStream_STell(self->other->outstream)
        : 0;

    /* write a subset of the entries to the .lexx index */
    if (    (self->count % self->index_interval == 0)
         && !self->is_index
         && !self->temp_mode
    ) {
        LexWriter_add(self->other, self->last_text, 
            self->last_tinfo);
    }

    /* write the record; track number of terms */
    TermStepper_Write_Record(self->stepper, self->outstream, 
        term_text->ptr, term_text->len, 
        self->last_text->ptr, self->last_text->len, 
        tinfo, self->last_tinfo, 
        lex_filepos, self->last_lex_filepos
    );
    self->count++;

    /* remember for delta encoding */
    BB_Copy_BB(self->last_text, term_text);
    TInfo_copy(self->last_tinfo, tinfo);
    self->last_lex_filepos = lex_filepos;
}

void
LexWriter_start_field(LexWriter *self, i32_t field_num)
{
    ByteBuf *filename = self->filename;

    /* build filename */
    BB_Copy_BB(filename, self->seg_info->seg_name);
    if (self->is_index)
        BB_Cat_Str(filename, ".lexx", 5);
    else
        BB_Cat_Str(filename, ".lex", 4);
    BB_Cat_I64(filename, (i64_t)field_num);

    /* open outstream */
    self->outstream = Folder_Open_OutStream(self->invindex->folder, filename);

    /* initialize count, TermInfo and last term text */
    self->count = 0;
    TInfo_Reset(self->last_tinfo);
    self->last_text->len = 0;
    self->last_lex_filepos = 0;

    /* switch over other */
    if (!self->is_index)
        LexWriter_Start_Field(self->other, field_num);
}

void
LexWriter_finish_field(LexWriter *self, i32_t field_num)
{
    ByteBuf *field_name = SegInfo_Field_Name(self->seg_info, field_num);
    OutStream_SClose(self->outstream);
    REFCOUNT_DEC(self->outstream);
    self->outstream = NULL;
    Hash_Store_I64(self->counts,field_name->ptr, field_name->len,
        (i64_t)self->count);

    /* finish other */
    if (!self->is_index)
        LexWriter_Finish_Field(self->other, field_num);
}

void
LexWriter_enter_temp_mode(LexWriter *self, OutStream *temp_outstream)
{
    /* assign outstream */
    if (self->outstream != NULL)
        CONFESS("Can't enter temp mode (filename: %s) ", self->filename->ptr);
    self->outstream = REFCOUNT_INC(temp_outstream);

    /* initialize count, TermInfo and last term text */
    self->count = 0;
    TInfo_Reset(self->last_tinfo);
    self->last_text->len = 0;
    self->last_lex_filepos = 0;

    /* remember that we're in temp mode */
    self->temp_mode = true;
}

void
LexWriter_leave_temp_mode(LexWriter *self)
{
    REFCOUNT_DEC(self->outstream);
    self->outstream = NULL;
    self->temp_mode = false;
}

void
LexWriter_finish(LexWriter *self)
{
    Hash *metadata = Hash_new(0);

    /* close down */
    if (self->outstream != NULL)
        CONFESS("File '%s' never closed", self->filename->ptr);

    if (self->counts->size == 0) {
        /* placeholder */
        Hash_Store_I64(self->counts, "none", 4, 0);
    }

    /* generate metadata */
    Hash_Store_I64(metadata, "format", 6, (i64_t)LEXICON_FORMAT);
    Hash_Store(metadata, "counts", 6, (Obj*)self->counts);

    /* cue the doppelganger's exit and store metadata */
    if (self->is_index) {
        SegInfo_Add_Metadata(self->seg_info, "lexicon_index", 13, 
            (Obj*)metadata);
    }
    else {
        LexWriter_Finish(self->other);
        /* store term infos metadata in the segment's metadata hash */
        SegInfo_Add_Metadata(self->seg_info, "lexicon", 7, (Obj*)metadata);
    }

    /* clean up, now that seg_info owns a refcount */
    REFCOUNT_DEC(metadata);
}

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