/**
 * @file NCDMethodIndex.c
 * @author Ambroz Bizjak <ambrop7@gmail.com>
 * 
 * @section LICENSE
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <string.h>
#include <stdlib.h>
#include <limits.h>

#include <misc/hashfun.h>
#include <misc/balloc.h>
#include <misc/strdup.h>

#include "NCDMethodIndex.h"

#include "NCDMethodIndex_hash.h"
#include <structure/CHash_impl.h>

#define GROWARRAY_NAME NamesArray
#define GROWARRAY_OBJECT_TYPE NCDMethodIndex
#define GROWARRAY_ARRAY_MEMBER names
#define GROWARRAY_CAPACITY_MEMBER names_capacity
#define GROWARRAY_MAX_CAPACITY INT_MAX
#include <misc/grow_array.h>

#define GROWARRAY_NAME EntriesArray
#define GROWARRAY_OBJECT_TYPE NCDMethodIndex
#define GROWARRAY_ARRAY_MEMBER entries
#define GROWARRAY_CAPACITY_MEMBER entries_capacity
#define GROWARRAY_MAX_CAPACITY INT_MAX
#include <misc/grow_array.h>

#include <generated/blog_channel_ncd.h>

static int find_method_name (NCDMethodIndex *o, const char *method_name, int *out_entry_idx)
{
    ASSERT(method_name)
    
    NCDMethodIndex__HashRef ref = NCDMethodIndex__Hash_Lookup(&o->hash, o->names, method_name);
    if (ref.link == -1) {
        return 0;
    }
    
    ASSERT(ref.link >= 0)
    ASSERT(ref.link < o->num_names)
    
    struct NCDMethodIndex__method_name *name_entry = ref.ptr;
    ASSERT(!strcmp(name_entry->method_name, method_name))
    ASSERT(name_entry->first_entry >= 0)
    ASSERT(name_entry->first_entry < o->num_entries)
    
    if (out_entry_idx) {
        *out_entry_idx = name_entry->first_entry;
    }
    return 1;
}

static int add_method_name (NCDMethodIndex *o, const char *method_name, int *out_entry_idx)
{
    ASSERT(method_name)
    ASSERT(!find_method_name(o, method_name, NULL))
    
    if (o->num_entries == o->entries_capacity && !EntriesArray_DoubleUp(o)) {
        BLog(BLOG_ERROR, "EntriesArray_DoubleUp failed");
        goto fail0;
    }
    
    if (o->num_names == o->names_capacity && !NamesArray_DoubleUp(o)) {
        BLog(BLOG_ERROR, "NamesArray_DoubleUp failed");
        goto fail0;
    }
    
    struct NCDMethodIndex__entry *entry = &o->entries[o->num_entries];
    entry->obj_type = -1;
    entry->next = -1;
    
    struct NCDMethodIndex__method_name *name_entry = &o->names[o->num_names];
    
    if (!(name_entry->method_name = b_strdup(method_name))) {
        BLog(BLOG_ERROR, "b_strdup failed");
        goto fail0;
    }
    
    name_entry->first_entry = o->num_entries;
    
    NCDMethodIndex__HashRef ref = {name_entry, o->num_names};
    int res = NCDMethodIndex__Hash_Insert(&o->hash, o->names, ref, NULL);
    ASSERT_EXECUTE(res)
    
    o->num_entries++;
    o->num_names++;
    
    if (out_entry_idx) {
        *out_entry_idx = name_entry->first_entry;
    }
    return 1;
    
fail0:
    return 0;
}

int NCDMethodIndex_Init (NCDMethodIndex *o, NCDStringIndex *string_index)
{
    ASSERT(string_index)
    
    o->string_index = string_index;
    
    if (!NamesArray_Init(o, NCDMETHODINDEX_NUM_EXPECTED_METHOD_NAMES)) {
        BLog(BLOG_ERROR, "NamesArray_Init failed");
        goto fail0;
    }
    
    if (!EntriesArray_Init(o, NCDMETHODINDEX_NUM_EXPECTED_ENTRIES)) {
        BLog(BLOG_ERROR, "EntriesArray_Init failed");
        goto fail1;
    }
    
    o->num_names = 0;
    o->num_entries = 0;
    
    if (!NCDMethodIndex__Hash_Init(&o->hash, NCDMETHODINDEX_NUM_EXPECTED_METHOD_NAMES)) {
        BLog(BLOG_ERROR, "NCDMethodIndex__Hash_Init failed");
        goto fail2;
    }
    
    return 1;
    
fail2:
    EntriesArray_Free(o);
fail1:
    NamesArray_Free(o);
fail0:
    return 0;
}

void NCDMethodIndex_Free (NCDMethodIndex *o)
{
    for (int i = 0; i < o->num_names; i++) {
        free(o->names[i].method_name);
    }
    
    NCDMethodIndex__Hash_Free(&o->hash);
    EntriesArray_Free(o);
    NamesArray_Free(o);
}

int NCDMethodIndex_AddMethod (NCDMethodIndex *o, const char *obj_type, size_t obj_type_len, const char *method_name, const struct NCDInterpModule *module)
{
    ASSERT(obj_type)
    ASSERT(method_name)
    ASSERT(module)
    
    NCD_string_id_t obj_type_id = NCDStringIndex_GetBin(o->string_index, obj_type, obj_type_len);
    if (obj_type_id < 0) {
        BLog(BLOG_ERROR, "NCDStringIndex_Get failed");
        goto fail0;
    }
    
    int entry_idx;
    int first_entry_idx;
    
    if (!find_method_name(o, method_name, &first_entry_idx)) {
        if (!add_method_name(o, method_name, &entry_idx)) {
            goto fail0;
        }
        
        ASSERT(entry_idx >= 0)
        ASSERT(entry_idx < o->num_entries)
        
        struct NCDMethodIndex__entry *entry = &o->entries[entry_idx];
        
        entry->obj_type = obj_type_id;
        entry->module = module;
    } else {
        ASSERT(first_entry_idx >= 0)
        ASSERT(first_entry_idx < o->num_entries)
        
        if (o->num_entries == o->entries_capacity && !EntriesArray_DoubleUp(o)) {
            BLog(BLOG_ERROR, "EntriesArray_DoubleUp failed");
            goto fail0;
        }
        
        entry_idx = o->num_entries;
        struct NCDMethodIndex__entry *entry = &o->entries[o->num_entries];
        
        entry->obj_type = obj_type_id;
        entry->module = module;
        
        entry->next = o->entries[first_entry_idx].next;
        o->entries[first_entry_idx].next = o->num_entries;
        
        o->num_entries++;
    }
    
    return entry_idx;
    
fail0:
    return -1;
}

void NCDMethodIndex_RemoveMethod (NCDMethodIndex *o, int method_name_id)
{
    ASSERT(method_name_id >= 0)
    ASSERT(method_name_id < o->num_entries)
    ASSERT(o->entries[method_name_id].obj_type >= 0)
    
    o->entries[method_name_id].obj_type = -1;
}

int NCDMethodIndex_GetMethodNameId (NCDMethodIndex *o, const char *method_name)
{
    ASSERT(method_name)
    
    int first_entry_idx;
    
    if (!find_method_name(o, method_name, &first_entry_idx)) {
        if (!add_method_name(o, method_name, &first_entry_idx)) {
            return -1;
        }
    }
    
    ASSERT(first_entry_idx >= 0)
    ASSERT(first_entry_idx < o->num_entries)
    
    return first_entry_idx;
}

const struct NCDInterpModule * NCDMethodIndex_GetMethodModule (NCDMethodIndex *o, NCD_string_id_t obj_type, int method_name_id)
{
    ASSERT(obj_type >= 0)
    ASSERT(method_name_id >= 0)
    ASSERT(method_name_id < o->num_entries)
    
    do {
        struct NCDMethodIndex__entry *entry = &o->entries[method_name_id];
        
        if (entry->obj_type == obj_type) {
            ASSERT(entry->module)
            return entry->module;
        }
        
        method_name_id = entry->next;
        ASSERT(method_name_id >= -1)
        ASSERT(method_name_id < o->num_entries)
    } while (method_name_id >= 0);
    
    return NULL;
}
