The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define CFISH_USE_SHORT_NAMES
#define LUCY_USE_SHORT_NAMES

#define C_LUCY_SIMPLE
#include "Lucy/Simple.h"

#include "Clownfish/Err.h"
#include "Clownfish/Hash.h"
#include "Clownfish/HashIterator.h"
#include "Clownfish/String.h"
#include "Clownfish/Vector.h"
#include "Lucy/Analysis/EasyAnalyzer.h"
#include "Lucy/Document/Doc.h"
#include "Lucy/Document/HitDoc.h"
#include "Lucy/Index/Indexer.h"
#include "Lucy/Index/PolyReader.h"
#include "Lucy/Plan/FullTextType.h"
#include "Lucy/Plan/Schema.h"
#include "Lucy/Search/Hits.h"
#include "Lucy/Search/IndexSearcher.h"
#include "Lucy/Search/SortSpec.h"

Simple*
Simple_new(Obj *index, String *language) {
    Simple *self = (Simple*)Class_Make_Obj(SIMPLE);
    return Simple_init(self, index, language);
}

Simple*
Simple_init(Simple *self, Obj *index, String *language) {
    SimpleIVARS *const ivars = Simple_IVARS(self);
    ivars->index    = INCREF(index);
    ivars->language = Str_Clone(language);
    return self;
}

void
Simple_Destroy_IMP(Simple *self) {
    SimpleIVARS *const ivars = Simple_IVARS(self);

    Simple_Finish_Indexing(self);

    DECREF(ivars->index);
    DECREF(ivars->language);
    DECREF(ivars->schema);
    DECREF(ivars->type);
    DECREF(ivars->indexer);
    DECREF(ivars->searcher);
    DECREF(ivars->hits);

    SUPER_DESTROY(self, SIMPLE);
}

static void
S_create_indexer(Simple *self) {
    SimpleIVARS *const ivars = Simple_IVARS(self);

    // Trigger searcher refresh.
    DECREF(ivars->searcher);
    DECREF(ivars->hits);
    ivars->searcher = NULL;
    ivars->hits     = NULL;

    // Get type and schema
    Schema     *schema      = NULL;
    FieldType  *type        = NULL;
    PolyReader *reader      = PolyReader_open(ivars->index, NULL, NULL);
    Vector     *seg_readers = PolyReader_Get_Seg_Readers(reader);

    if (Vec_Get_Size(seg_readers) == 0) {
        // Index is empty, create new schema and type.
        schema = Schema_new();
        EasyAnalyzer *analyzer = EasyAnalyzer_new(ivars->language);
        type = (FieldType*)FullTextType_new((Analyzer*)analyzer);
        DECREF(analyzer);
    }
    else {
        // Get schema from reader.
        schema = (Schema*)INCREF(PolyReader_Get_Schema(reader));
        Vector *fields = Schema_All_Fields(schema);
        String *field  = (String*)CERTIFY(Vec_Fetch(fields, 0), STRING);
        type = (FieldType*)INCREF(Schema_Fetch_Type(schema, field));
        DECREF(fields);
    }

    ivars->indexer = Indexer_new(schema, ivars->index, NULL, 0);
    ivars->schema  = schema;
    ivars->type    = type;

    DECREF(reader);
}

void
Simple_Add_Doc_IMP(Simple *self, Doc *doc) {
    SimpleIVARS *const ivars = Simple_IVARS(self);

    if (!ivars->indexer) {
        S_create_indexer(self);
    }

    Vector *field_names = Doc_Field_Names(doc);

    for (size_t i = 0, max = Vec_Get_Size(field_names); i < max; i++) {
        String *field = (String*)Vec_Fetch(field_names, i);
        Schema_Spec_Field(ivars->schema, field, ivars->type);
    }

    Indexer_Add_Doc(ivars->indexer, doc, 1.0);

    DECREF(field_names);
}

uint32_t
Simple_Search_IMP(Simple *self, String *query, uint32_t offset,
                  uint32_t num_wanted, SortSpec *sort_spec) {
    SimpleIVARS *const ivars = Simple_IVARS(self);

    // Flush recent adds; lazily create searcher.
    Simple_Finish_Indexing(self);
    if (!ivars->searcher) {
        ivars->searcher = IxSearcher_new(ivars->index);
    }

    DECREF(ivars->hits);
    ivars->hits = IxSearcher_Hits(ivars->searcher, (Obj*)query, offset,
                                  num_wanted, sort_spec);

    return Hits_Total_Hits(ivars->hits);
}

HitDoc*
Simple_Next_IMP(Simple *self) {
    SimpleIVARS *const ivars = Simple_IVARS(self);

    if (!ivars->hits) { return NULL; }

    // Get the hit, bail if hits are exhausted.
    HitDoc *doc = Hits_Next(ivars->hits);
    if (!doc) {
        DECREF(ivars->hits);
        ivars->hits = NULL;
    }

    return doc;
}

Indexer*
Simple_Get_Indexer_IMP(Simple *self) {
    SimpleIVARS *const ivars = Simple_IVARS(self);

    if (!ivars->indexer) {
        S_create_indexer(self);
    }

    return ivars->indexer;
}

Hits*
Simple_Get_Hits_IMP(Simple *self) {
    return Simple_IVARS(self)->hits;
}

void
Simple_Finish_Indexing_IMP(Simple *self) {
    SimpleIVARS *const ivars = Simple_IVARS(self);

    // Don't bother to throw an error if index not modified.
    if (ivars->indexer) {
        Indexer_Commit(ivars->indexer);

        // Trigger searcher and indexer refresh.
        DECREF(ivars->schema);
        DECREF(ivars->type);
        DECREF(ivars->indexer);
        ivars->schema   = NULL;
        ivars->type     = NULL;
        ivars->indexer  = NULL;
    }
}