The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define C_KINO_VTABLE
#define C_KINO_OBJ
#define KINO_USE_SHORT_NAMES
#define CHY_USE_SHORT_NAMES

#include <string.h>
#include <ctype.h>

#include "KinoSearch/Object/VTable.h"
#include "KinoSearch/Object/CharBuf.h"
#include "KinoSearch/Object/Err.h"
#include "KinoSearch/Object/Hash.h"
#include "KinoSearch/Object/LockFreeRegistry.h"
#include "KinoSearch/Object/VArray.h"
#include "KinoSearch/Util/Atomic.h"
#include "KinoSearch/Util/Memory.h"

size_t VTable_offset_of_parent = offsetof(VTable, parent);

// Remove spaces and underscores, convert to lower case. 
static void
S_scrunch_charbuf(CharBuf *source, CharBuf *target);

LockFreeRegistry *VTable_registry = NULL;

void
VTable_destroy(VTable *self)
{
    THROW(ERR, "Insane attempt to destroy VTable for class '%o'", self->name);
}

VTable*
VTable_clone(VTable *self)
{
    VTable *evil_twin 
        = (VTable*)Memory_wrapped_calloc(self->vt_alloc_size, 1);

    memcpy(evil_twin, self, self->vt_alloc_size);
    evil_twin->name = CB_Clone(self->name);
    evil_twin->ref.count = 1; 

    return evil_twin;
}

Obj*
VTable_inc_refcount(VTable *self)
{
    return (Obj*)self;
}

uint32_t
VTable_dec_refcount(VTable *self)
{
    UNUSED_VAR(self);
    return 1;
}

uint32_t
VTable_get_refcount(VTable *self)
{
    UNUSED_VAR(self);
    /* VTable_Get_RefCount() lies to other KinoSearch code about the refcount
     * because we don't want to have to synchronize access to the cached host
     * object to which we have delegated responsibility for keeping refcounts.
     * It always returns 1 because 1 is a positive number, and thus other KinoSearch 
     * code will be fooled into believing it never needs to take action such
     * as initiating a destructor.
     * 
     * It's possible that the host has in fact increased the refcount of the
     * cached host object if there are multiple refs to it on the other side
     * of the KinoSearch/host border, but returning 1 is good enough to fool KinoSearch 
     * code.
     */
    return 1;
}

void
VTable_override(VTable *self, kino_method_t method, size_t offset) 
{
    union { char *char_ptr; kino_method_t *func_ptr; } pointer;
    pointer.char_ptr = ((char*)self) + offset;
    pointer.func_ptr[0] = method;
}

CharBuf*
VTable_get_name(VTable *self)   { return self->name; }
VTable*
VTable_get_parent(VTable *self) { return self->parent; }
size_t
VTable_get_obj_alloc_size(VTable *self) { return self->obj_alloc_size; }

void
VTable_init_registry()
{
    LockFreeRegistry *reg = LFReg_new(256);
    if (Atomic_cas_ptr((void*volatile*)&VTable_registry, NULL, reg)) {
        return;
    }
    else {
        DECREF(reg);
    }
}

VTable*
VTable_singleton(const CharBuf *subclass_name, VTable *parent)
{
    if (VTable_registry == NULL) { 
        VTable_init_registry(); 
    }

    VTable *singleton = (VTable*)LFReg_Fetch(VTable_registry, (Obj*)subclass_name);
    if (singleton == NULL) {
        VArray *novel_host_methods;
        uint32_t num_novel;

        if (parent == NULL) {
            CharBuf *parent_class = VTable_find_parent_class(subclass_name);
            if (parent_class == NULL) {
                THROW(ERR, "Class '%o' doesn't descend from %o", subclass_name,
                    OBJ->name);
            }
            else {
                parent = VTable_singleton(parent_class, NULL);
                DECREF(parent_class);
            }
        }

        // Copy source vtable. 
        singleton = VTable_Clone(parent);

        // Turn clone into child. 
        singleton->parent = parent; 
        DECREF(singleton->name);
        singleton->name = CB_Clone(subclass_name);
        
        // Allow host methods to override. 
        novel_host_methods = VTable_novel_host_methods(subclass_name);
        num_novel = VA_Get_Size(novel_host_methods);
        if (num_novel) {
            Hash *meths = Hash_new(num_novel);
            uint32_t i;
            CharBuf *scrunched = CB_new(0);
            ZombieCharBuf *callback_name = ZCB_BLANK();
            for (i = 0; i < num_novel; i++) {
                CharBuf *meth = (CharBuf*)VA_fetch(novel_host_methods, i);
                S_scrunch_charbuf(meth, scrunched);
                Hash_Store(meths, (Obj*)scrunched, INCREF(&EMPTY));
            }
            cfish_Callback **callbacks
                = (cfish_Callback**)singleton->callbacks;
            for (i = 0; callbacks[i] != NULL; i++) {
                cfish_Callback *const callback = callbacks[i];
                ZCB_Assign_Str(callback_name, callback->name,
                    callback->name_len);
                S_scrunch_charbuf((CharBuf*)callback_name, scrunched);
                if (Hash_Fetch(meths, (Obj*)scrunched)) {
                    VTable_Override(singleton, callback->func, 
                        callback->offset);
                }
            }
            DECREF(scrunched);
            DECREF(meths);
        }
        DECREF(novel_host_methods);

        // Register the new class, both locally and with host. 
        if (VTable_add_to_registry(singleton)) {
            // Doing this after registering is racy, but hard to fix. :( 
            VTable_register_with_host(singleton, parent);
        }
        else {
            DECREF(singleton);
            singleton = (VTable*)LFReg_Fetch(VTable_registry, (Obj*)subclass_name);
            if (!singleton) {
                THROW(ERR, "Failed to either insert or fetch VTable for '%o'",
                    subclass_name);
            }
        }
    }
    
    return singleton;
}

Obj*
VTable_make_obj(VTable *self)
{
    Obj *obj = (Obj*)Memory_wrapped_calloc(self->obj_alloc_size, 1);
    obj->vtable = self;
    obj->ref.count = 1;
    return obj;
}

Obj*
VTable_init_obj(VTable *self, void *allocation)
{
    Obj *obj = (Obj*)allocation;
    obj->vtable = self;
    obj->ref.count = 1;
    return obj;
}

Obj*
VTable_load_obj(VTable *self, Obj *dump)
{
    Obj_load_t load = (Obj_load_t)METHOD(self, Obj, Load);
    if (load == Obj_load) {
        THROW(ERR, "Abstract method Load() not defined for %o", self->name);
    }
    return load(NULL, dump);
}

static void
S_scrunch_charbuf(CharBuf *source, CharBuf *target)
{
    ZombieCharBuf *iterator = ZCB_WRAP(source);
    CB_Set_Size(target, 0);
    while (ZCB_Get_Size(iterator)) {
        uint32_t code_point = ZCB_Nip_One(iterator);
        if (code_point > 127) {
            THROW(ERR, "Can't fold case for %o", source);
        }
        else if (code_point != '_') {
            CB_Cat_Char(target, tolower(code_point));
        }
    }
}

bool_t
VTable_add_to_registry(VTable *vtable)
{
    if (VTable_registry == NULL) {
        VTable_init_registry();
    }
    if (LFReg_Fetch(VTable_registry, (Obj*)vtable->name)) {
        return false;
    }
    else {
        CharBuf *klass = CB_Clone(vtable->name);
        bool_t retval 
            = LFReg_Register(VTable_registry, (Obj*)klass, (Obj*)vtable);
        DECREF(klass);
        return retval;
    }
}

VTable*
VTable_fetch_vtable(const CharBuf *class_name)
{
    VTable *vtable = NULL;
    if (VTable_registry != NULL) {
        vtable = (VTable*)LFReg_Fetch(VTable_registry, (Obj*)class_name);
    }
    return vtable;
}

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