/* 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.
 */

#include <ctype.h>

#define C_LUCY_SEGMENT
#include "Lucy/Util/ToolSet.h"

#include "Lucy/Index/Segment.h"
#include "Clownfish/Num.h"
#include "Lucy/Store/Folder.h"
#include "Lucy/Util/Json.h"
#include "Lucy/Util/StringHelper.h"
#include "Lucy/Util/IndexFileNames.h"

Segment*
Seg_new(int64_t number) {
    Segment *self = (Segment*)Class_Make_Obj(SEGMENT);
    return Seg_init(self, number);
}

Segment*
Seg_init(Segment *self, int64_t number) {
    SegmentIVARS *const ivars = Seg_IVARS(self);

    // Validate.
    if (number < 0) { THROW(ERR, "Segment number %i64 less than 0", number); }

    // Init.
    ivars->metadata  = Hash_new(0);
    ivars->count     = 0;
    ivars->by_num    = Vec_new(2);
    ivars->by_name   = Hash_new(0);

    // Start field numbers at 1, not 0.
    Vec_Push(ivars->by_num, (Obj*)Str_newf(""));

    // Assign.
    ivars->number = number;

    // Derive.
    ivars->name = Seg_num_to_name(number);

    return self;
}

String*
Seg_num_to_name(int64_t number) {
    char base36[StrHelp_MAX_BASE36_BYTES];
    if (number < 0) {
        THROW(ERR, "Arg can't be negative: %i64", number);
    }
    StrHelp_to_base36((uint64_t)number, &base36);
    return Str_newf("seg_%s", &base36);
}

bool
Seg_valid_seg_name(String *name) {
    if (Str_Starts_With_Utf8(name, "seg_", 4)) {
        StringIterator *iter = Str_Top(name);
        StrIter_Advance(iter, 4);
        int32_t code_point;
        while (STR_OOB != (code_point = StrIter_Next(iter))) {
            if (!isalnum(code_point)) {
                DECREF(iter);
                return false;
            }
        }
        DECREF(iter);
        return true; // Success!
    }
    return false;
}

void
Seg_Destroy_IMP(Segment *self) {
    SegmentIVARS *const ivars = Seg_IVARS(self);
    DECREF(ivars->name);
    DECREF(ivars->metadata);
    DECREF(ivars->by_name);
    DECREF(ivars->by_num);
    SUPER_DESTROY(self, SEGMENT);
}

bool
Seg_Read_File_IMP(Segment *self, Folder *folder) {
    SegmentIVARS *const ivars = Seg_IVARS(self);
    String *filename = Str_newf("%o/segmeta.json", ivars->name);
    Hash   *metadata = (Hash*)Json_slurp_json(folder, filename);
    Hash   *my_metadata;

    // Bail unless the segmeta file was read successfully.
    DECREF(filename);
    if (!metadata) { return false; }
    CERTIFY(metadata, HASH);

    // Grab metadata for the Segment object itself.
    DECREF(ivars->metadata);
    ivars->metadata = metadata;
    my_metadata
        = (Hash*)CERTIFY(Hash_Fetch_Utf8(ivars->metadata, "segmeta", 7), HASH);

    // Assign.
    Obj *count = Hash_Fetch_Utf8(my_metadata, "count", 5);
    if (!count) { count = Hash_Fetch_Utf8(my_metadata, "doc_count", 9); }
    if (!count) { THROW(ERR, "Missing 'count'"); }
    else { ivars->count = Json_obj_to_i64(count); }

    // Get list of field nums.
    Vector *source_by_num = (Vector*)Hash_Fetch_Utf8(my_metadata,
                                                     "field_names", 11);
    size_t num_fields = source_by_num ? Vec_Get_Size(source_by_num) : 0;
    if (source_by_num == NULL) {
        THROW(ERR, "Failed to extract 'field_names' from metadata");
    }

    // Init.
    DECREF(ivars->by_num);
    DECREF(ivars->by_name);
    ivars->by_num  = Vec_new(num_fields);
    ivars->by_name = Hash_new(num_fields);

    // Copy the list of fields from the source.
    for (size_t i = 0; i < num_fields; i++) {
        String *name = (String*)Vec_Fetch(source_by_num, i);
        Seg_Add_Field(self, name);
    }

    return true;
}

void
Seg_Write_File_IMP(Segment *self, Folder *folder) {
    SegmentIVARS *const ivars = Seg_IVARS(self);
    Hash *my_metadata = Hash_new(16);

    // Store metadata specific to this Segment object.
    Hash_Store_Utf8(my_metadata, "count", 5,
                    (Obj*)Str_newf("%i64", ivars->count));
    Hash_Store_Utf8(my_metadata, "name", 4, (Obj*)Str_Clone(ivars->name));
    Hash_Store_Utf8(my_metadata, "field_names", 11, INCREF(ivars->by_num));
    Hash_Store_Utf8(my_metadata, "format", 6, (Obj*)Str_newf("%i32", 1));
    Hash_Store_Utf8(ivars->metadata, "segmeta", 7, (Obj*)my_metadata);

    String *filename = Str_newf("%o/segmeta.json", ivars->name);
    bool result = Json_spew_json((Obj*)ivars->metadata, folder, filename);
    DECREF(filename);
    if (!result) { RETHROW(INCREF(Err_get_error())); }
}

int32_t
Seg_Add_Field_IMP(Segment *self, String *field) {
    SegmentIVARS *const ivars = Seg_IVARS(self);
    Integer *num = (Integer*)Hash_Fetch(ivars->by_name, field);
    if (num) {
        return (int32_t)Int_Get_Value(num);
    }
    else {
        int32_t field_num = (int32_t)Vec_Get_Size(ivars->by_num);
        Hash_Store(ivars->by_name, field, (Obj*)Int_new(field_num));
        Vec_Push(ivars->by_num, (Obj*)Str_Clone(field));
        return field_num;
    }
}

String*
Seg_Get_Name_IMP(Segment *self) {
    return Seg_IVARS(self)->name;
}

int64_t
Seg_Get_Number_IMP(Segment *self) {
    return Seg_IVARS(self)->number;
}

void
Seg_Set_Count_IMP(Segment *self, int64_t count) {
    Seg_IVARS(self)->count = count;
}

int64_t
Seg_Get_Count_IMP(Segment *self) {
    return Seg_IVARS(self)->count;
}

int64_t
Seg_Increment_Count_IMP(Segment *self, int64_t increment) {
    SegmentIVARS *const ivars = Seg_IVARS(self);
    ivars->count += increment;
    return ivars->count;
}

void
Seg_Store_Metadata_IMP(Segment *self, String *key, Obj *value) {
    SegmentIVARS *const ivars = Seg_IVARS(self);
    if (Hash_Fetch(ivars->metadata, key)) {
        THROW(ERR, "Metadata key '%o' already registered", key);
    }
    Hash_Store(ivars->metadata, key, value);
}

void
Seg_Store_Metadata_Utf8_IMP(Segment *self, const char *key, size_t key_len,
                            Obj *value) {
    String *k = SSTR_WRAP_UTF8((char*)key, key_len);
    Seg_Store_Metadata(self, k, value);
}

Obj*
Seg_Fetch_Metadata_IMP(Segment *self, String *key) {
    SegmentIVARS *const ivars = Seg_IVARS(self);
    return Hash_Fetch(ivars->metadata, key);
}

Obj*
Seg_Fetch_Metadata_Utf8_IMP(Segment *self, const char *key, size_t len) {
    SegmentIVARS *const ivars = Seg_IVARS(self);
    return Hash_Fetch_Utf8(ivars->metadata, key, len);
}

Hash*
Seg_Get_Metadata_IMP(Segment *self) {
    return Seg_IVARS(self)->metadata;
}

int32_t
Seg_Compare_To_IMP(Segment *self, Obj *other) {
    Segment *other_seg = (Segment*)CERTIFY(other, SEGMENT);
    SegmentIVARS *const ivars = Seg_IVARS(self);
    SegmentIVARS *const ovars = Seg_IVARS(other_seg);
    if (ivars->number < ovars->number)       { return -1; }
    else if (ivars->number == ovars->number) { return 0;  }
    else                                     { return 1;  }
}

String*
Seg_Field_Name_IMP(Segment *self, int32_t field_num) {
    SegmentIVARS *const ivars = Seg_IVARS(self);
    return field_num
           ? (String*)Vec_Fetch(ivars->by_num, (size_t)field_num)
           : NULL;
}

int32_t
Seg_Field_Num_IMP(Segment *self, String *field) {
    if (field == NULL) {
        return 0;
    }
    else {
        SegmentIVARS *const ivars = Seg_IVARS(self);
        Integer *num = (Integer*)Hash_Fetch(ivars->by_name, field);
        return num ? (int32_t)Int_Get_Value(num) : 0;
    }
}