#include "KinoSearch/Util/ToolSet.h"

#define KINO_WANT_SEGINFO_VTABLE
#include "KinoSearch/Index/SegInfo.r"

#include "KinoSearch/Index/IndexFileNames.h"
#include "KinoSearch/Util/Int.r"
#include "KinoSearch/Util/IntMap.r"

SegInfo*
SegInfo_new(const ByteBuf *seg_name, Hash *fspecs, Hash *source_metadata)
{
    Hash   *seg_info_metadata = NULL;
    CREATE(self, SegInfo, SEGINFO);

    /* either assign or create metadata hash */
    if (source_metadata != NULL) {
        seg_info_metadata = (Hash*)Hash_Fetch(source_metadata, "seg_info", 8);
        REFCOUNT_INC(source_metadata);
        self->metadata = source_metadata;
    }
    else {
        self->metadata = Hash_new(0);
    }

    /* assign */
    self->seg_name  = BB_CLONE(seg_name);
    self->doc_count = seg_info_metadata == NULL
        ? 0
        : Hash_Fetch_I64(seg_info_metadata, "doc_count", 9);

    /* get list of field nums, either from metadata or fspecs hash */
    if (seg_info_metadata != NULL) {
        u32_t i;
        VArray *source_by_num = (VArray*)Hash_Fetch(seg_info_metadata, 
            "field_names", 11);
        if (source_by_num == NULL)
            CONFESS("Failed to extract 'field_names' from metadata");

        /* init */
        self->by_num  = VA_new(source_by_num->size);
        self->by_name = Hash_new(source_by_num->size);

        /* copy the list of fields from the source */
        for (i = 0; i < source_by_num->size; i++) {
            ByteBuf *name = (ByteBuf*)VA_Fetch(source_by_num, i);
            SegInfo_Add_Field(self, name);
        }
    }
    else if (fspecs != NULL) {
        ByteBuf *field_name;
        Obj *ignore;

        /* init */
        self->by_num  = VA_new(fspecs->size);
        self->by_name = Hash_new(fspecs->size);

        /* add all fields in the fspecs */
        Hash_Iter_Init(fspecs);
        while (Hash_Iter_Next(fspecs, &field_name, &ignore)) {
            SegInfo_Add_Field(self, field_name);
        }
    }
    else {
        CONFESS("Either metadata or fspecs must be non-NULL");
    }

    return self;
}

void
SegInfo_add_field(SegInfo *self, const ByteBuf *field_name)
{
    if ( (Hash_Fetch_BB(self->by_name, field_name)) == NULL) {
        Int *num = Int_new(self->by_num->size);
        ByteBuf *name_copy = BB_CLONE(field_name);
        VA_Push(self->by_num, (Obj*)name_copy);
        Hash_Store_BB(self->by_name, name_copy, (Obj*)num);
        REFCOUNT_DEC(name_copy);
        REFCOUNT_DEC(num);
    }
}

void
SegInfo_destroy(SegInfo *self)
{
    REFCOUNT_DEC(self->seg_name);
    REFCOUNT_DEC(self->metadata);
    REFCOUNT_DEC(self->by_name);
    REFCOUNT_DEC(self->by_num);
    free(self);
}

void
SegInfo_add_metadata(SegInfo *self, const char *key, size_t key_len,
                     Obj *value)
{
    Hash_Store(self->metadata, key, key_len, value);
}

Obj*
SegInfo_extract_metadata(SegInfo *self, const char *key, size_t key_len)
{
    Obj *retval = Hash_Fetch(self->metadata, key, key_len);
    if (retval == NULL)
        CONFESS("Failed to extract metadata for %s in segment %s", key,
            self->seg_name->ptr);
    return retval;
}

Hash*
SegInfo_get_metadata(SegInfo *self)
{
    Hash *seg_info_metadata = Hash_new(16);
    ByteBuf *seg_name_copy = BB_CLONE(self->seg_name);
    
    /* store metadata specific to this SegInfo object */
    Hash_Store_I64(seg_info_metadata, "doc_count", 9, (i64_t)self->doc_count);
    Hash_Store(seg_info_metadata, "seg_name", 8, (Obj*)seg_name_copy);
    Hash_Store(seg_info_metadata, "field_names", 11, (Obj*)self->by_num);
    Hash_Store(self->metadata, "seg_info", 8, (Obj*)seg_info_metadata);
    REFCOUNT_DEC(seg_name_copy);
    REFCOUNT_DEC(seg_info_metadata);

    return self->metadata;
}

ByteBuf empty_string = BYTEBUF_BLANK;

ByteBuf*
SegInfo_field_name(SegInfo *self, i32_t field_num)
{
    if (field_num == -1) {
        return &empty_string;
    }
    else {
         ByteBuf * field_name = (ByteBuf*)VA_Fetch(self->by_num, field_num);
         return field_name == NULL ? &empty_string : field_name;
    }
}

i32_t
SegInfo_field_num(SegInfo *self, const ByteBuf *field_name)
{
    if (field_name == NULL) {
        return -1;
    }
    else {
        Int *num = (Int*)Hash_Fetch_BB(self->by_name, field_name);
        if (num == NULL)
            return -1;
        return num->value;
    }
}

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