#define KINO_USE_SHORT_NAMES

#include <string.h>

#define KINO_WANT_DYNVIRTUALTABLE_VTABLE
#include "KinoSearch/Util/DynVirtualTable.r"

#include "KinoSearch/Util/ByteBuf.r"
#include "KinoSearch/Util/Hash.r"
#include "KinoSearch/Util/Carp.h"
#include "KinoSearch/Util/MemManager.h"

Hash *DynVT_registry = NULL;

/* Wrap an objects original destroy method.  Call destroy on the object, then
 * REFCOUNT_DEC the dynamic virtual table.
 */
static void
DynVT_obj_destroy_wrapper(Obj* obj);

/* Constructor.  Returns singletons, keyed by class name.
 */
kino_DynVirtualTable*
DynVT_singleton(const char *subclass_name, KINO_OBJ_VTABLE *parent, 
                size_t parent_size)
{
    DynVirtualTable *self;

    if (DynVT_registry == NULL)
        DynVT_registry = Hash_new(0);

    self = (DynVirtualTable*)Hash_Fetch(DynVT_registry, subclass_name,
        strlen(subclass_name));

    if (self != NULL) {
        REFCOUNT_INC(self);
    }
    else {
        /* copy source vtable */
        self = (DynVirtualTable*)malloc(parent_size);
        memcpy(self, parent, parent_size);

        /* override parts of the source vtable */
        REFCOUNT_INC(parent);
        self->_          = &DYNVIRTUALTABLE; 
        self->refcount   = 1; /* replacing whatever the orig recount was */
        self->parent     = parent;
        self->class_name = strdup(subclass_name);
        
        /* wrap the original vtable's destroy */
        ((KINO_OBJ_VTABLE*)self)->destroy = DynVT_obj_destroy_wrapper;

        /* store the virtual table in the registry */
        Hash_Store(DynVT_registry, subclass_name, strlen(subclass_name),
            (Obj*)self);
    }
    
    return self;
}

void
DynVT_destroy(DynVirtualTable *self)
{
    REFCOUNT_DEC(self->parent);

    /* ignore const */
    free((char*)self->class_name);

    free(self);
}

static void
DynVT_obj_destroy_wrapper(Obj* obj)
{
    DynVirtualTable *self = (DynVirtualTable*)obj->_;
    Obj_destroy_t original_destroy = self->parent->destroy;

    /* First, destroy the object (which depends on self to find its destroy
     * method. */
    original_destroy(obj);

    /* Now that the object is gone, think about destroying self.
     */
    REFCOUNT_DEC(self);
    if (self->refcount == 1) { 
        const size_t name_len = strlen(self->class_name);
        Hash_Delete(DynVT_registry, self->class_name, name_len);
    }

    /* and last, zap the registry when there are no more entries */
    if (DynVT_registry->size == 0) {
        REFCOUNT_DEC(DynVT_registry);
        DynVT_registry = NULL;
    }
}

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