#include <string.h>

#define KINO_USE_SHORT_NAMES
#define CHY_USE_SHORT_NAMES

#define KINO_WANT_VARRAY_VTABLE
#include "KinoSearch/Util/VArray.r"

#include "KinoSearch/Util/Carp.h"
#include "KinoSearch/Util/MemManager.h"

#define MAYBE_GROW(_self, _new_size) \
    do { \
        if ((_self)->cap < _new_size) \
            VA_grow(_self, _new_size); \
    } while (0)


VArray*
VA_new(u32_t capacity) 
{
    CREATE(self, VArray, VARRAY);

    /* init */
    self->size = 0;

    /* assign */
    self->cap = capacity;

    /* derive */
    self->elems = CALLOCATE(capacity, Obj*);

    return self;
}

void
VA_destroy(VArray *self) 
{
    Obj **elems        = self->elems;
    Obj **const limit  = elems + self->size;

    /* dispose of elements */
    for ( ; elems < limit; elems++) {
        if (*elems != NULL)
            REFCOUNT_DEC(*elems);
    }

    free(self->elems);
    free(self);
}

VArray*
VA_clone(VArray *self)
{
    u32_t i;
    VArray *evil_twin;
    Obj **elems;

    /* block inheritance */
    if (self->_ != &VARRAY)
        CONFESS("Attempt to inherit VA_Clone by %s", self->_->class_name);

    /* dupe, then increment refcounts */
    evil_twin = VA_new(self->size);
    elems = evil_twin->elems;
    memcpy(elems, self->elems, self->size * sizeof(Obj*));
    evil_twin->size = self->size;
    for (i = 0; i < self->size; i++) {
        if (elems[i] != NULL)
            (void)REFCOUNT_INC(elems[i]);
    }

    return evil_twin;
}

void
VA_push(VArray *self, Obj *element) 
{
    MAYBE_GROW(self, self->size + 1);
    self->elems[ self->size ] = REFCOUNT_INC(element);
    self->size++;
}

Obj*
VA_pop(VArray *self) 
{
    if (!self->size) 
        return NULL;
    self->size--;
    return  self->elems[ self->size ];
}

void
VA_unshift(VArray *self, Obj *elem) 
{
    MAYBE_GROW(self, self->size + 1);
    memmove(self->elems + 1, self->elems, self->size * sizeof(Obj*));
    self->elems[0] = REFCOUNT_INC(elem);
    self->size++;
}

Obj*
VA_shift(VArray *self) 
{
    if (!self->size) {
        return NULL;
    }
    else {
        Obj *const return_val = self->elems[0];
        self->size--;
        if (self->size > 0) {
            memmove(self->elems, self->elems + 1, 
                self->size * sizeof(Obj*));
        }
        return return_val;
    }
}

Obj*
VA_fetch(VArray *self, u32_t num) 
{
    if (num >= self->size) 
        return NULL;

    return self->elems[num];
}

void
VA_store(VArray *self, u32_t num, Obj *elem) 
{
    MAYBE_GROW(self, num + 1);
    if (num < self->size) {
        Obj *const old_elem = self->elems[num];
        if (old_elem != NULL)
            REFCOUNT_DEC(old_elem);
    }
    else {
        self->size = num + 1;
    }
    self->elems[num] = REFCOUNT_INC(elem);
}

void
VA_grow(VArray *self, u32_t capacity) 
{
    if (capacity > self->cap) {
        /* add an extra 10% */
        capacity += capacity / 10;
        self->elems = REALLOCATE(self->elems, capacity, Obj*); 
        self->cap   = capacity;
        memset(self->elems + self->size, 0,
            (capacity - self->size) * sizeof(Obj*));
    }
}

Obj*
VA_delete(VArray *self, u32_t num)
{
    Obj *elem = NULL;
    if (num < self->size) {
        elem = self->elems[num];
        self->elems[num] = NULL;
    }
    return elem;
}

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