/* gdbmhash.pmc
 *  Copyright (C) 2005-2009 The Perl Foundation.
 *  SVN Info
 *     $Id: /mirror/trunk/src/dynpmc/gdbmhash.pmc 35702 2009-01-17T10:43:56.666163Z bernhard  $
 *  Overview:
 *     These are the vtable functions for the GDBMHash PMC
 *  Data Structure and Algorithms:
 *  History:
 *  Notes:
 *     Please remove unneeded entries.
 *  References:

=head1 NAME

src/dynpmc/gdbmhash.pmc - GDBM PMC

=head1 DESCRIPTION

This is an interface to the GNU dbm library.

=head1 keys

On inserted and fetch, keys are converted to STRINGs.

=head2 store

PMC insert values are converted to STRINGs.

=head2 fetch

get_integer_keyed() and get_number_keyed() probably don't make any sense,
as integers usually can't be converted.

When a PMC is requested, a String PMC is returned.


=head2 Functions

=over 4

=cut

 */

#include <gdbm.h>
#include "parrot/extend.h"

/*

=item C<static STRING* make_hash_key(PARROT_INTERP, PMC *key)>

Returns a Parrot string for C<*key>.

=cut

*/

static STRING* make_hash_key(PARROT_INTERP, NOTNULL(PMC * key))
{
    if (key)
        return VTABLE_get_string(interp, key);

    Parrot_ex_throw_from_c_args(interp, NULL, EXCEPTION_OUT_OF_BOUNDS,
        "Hash: Cannot use NULL key");

}


pmclass GDBMHash provides hash dynpmc lib gdbm {

/*

=item C<void class_init()>

Class initialization. GDBMHash is a dynamic PMC, meaning that a dynamically
loadable module is created. On Unix-like systems this is a shared library.
When it is available, the shared library has linked in the library 'gdbm'.

On WIN32 the relevant library seems to be called 'gdbm3'.
So we do a bit of cheating here, and load it during class initialization.

=cut

*/

    void class_init() {
        if (pass) {
#ifdef WIN32
            /* RT #46393: What if libgdbm.so cannot be loaded */
            /* Parrot_load_lib(interp, slib, NULL); */
            STRING *slib = CONST_STRING(interp, "gdbm3");
#endif
        }
    }

    VTABLE void init() {
        PMC_struct_val(SELF) = NULL;
    }

    void* get_pointer() {
        return PMC_struct_val(SELF);
    }

    void set_pointer(void* p) {
        PMC_struct_val(SELF) = p;
    }

/*

=item C<VOID set_string_native(STRING* value)>

Open a or create a new dbm file.

=cut

*/

    void set_string_native(STRING* value) {
        char * const c_db_name = string_to_cstring(interp, value);
        GDBM_FILE dbf          = gdbm_open(c_db_name, 0, GDBM_NEWDB, 0666, 0);

        string_cstring_free(c_db_name);

        PMC_struct_val(SELF) = dbf;
    }

/*

=item C<INTVAL get_integer()>

Returns the number of pairs in the hash.
A uninitialized GDBMHash returns 0.

=cut

RT #46395: This can be optimized by keeping track of number of elements

*/

    VTABLE INTVAL get_integer() {
        GDBM_FILE dbf = (GDBM_FILE)PMC_struct_val(SELF);
        if (! dbf) {
            return 0;
        }
        else {
            INTVAL cnt = 0;
            datum key, nextkey;
            for (key = gdbm_firstkey(dbf); key.dptr; key = nextkey) {
                cnt++;
                nextkey = gdbm_nextkey(dbf, key);
                free(key.dptr);
            }

            return cnt;
        }
    }

/*

=item C<INTVAL get_bool()>

Returns true if the hash size is not zero.

=cut

*/

    VTABLE INTVAL get_bool() {
        GDBM_FILE dbf = (GDBM_FILE)PMC_struct_val(SELF);
        if (! dbf) {
            return 0;
        }
        else {
            datum key = gdbm_firstkey(dbf);
            if (key.dptr) {
                return 1;
            }
            else {
                return 0;
            }
        }
    }

/*

=item C<void set_string_keyed(PMC *key, STRING *value)>

=cut

*/

    void set_string_keyed(PMC* key, STRING* value) {
        STRING* keystr;
        GDBM_FILE dbf = (GDBM_FILE)PMC_struct_val(SELF);
        datum key_gdbm, val_gdbm;

        if (!key) return;
        if (!dbf) return;
        keystr = make_hash_key(interp, key);

        key_gdbm.dsize = keystr->strlen;
        key_gdbm.dptr  = keystr->strstart;
        val_gdbm.dsize = value->strlen;
        val_gdbm.dptr  = value->strstart;

        gdbm_store(dbf, key_gdbm, val_gdbm, GDBM_REPLACE);

        return;
    }

/*

=item C<STRING *get_string_keyed(PMC *key)>

Returns the string value for the element at C<*key>.

=cut

*/

    STRING* get_string_keyed(PMC *key) {
        STRING *keystr, *val;
        GDBM_FILE dbf = (GDBM_FILE)PMC_struct_val(SELF);
        datum key_gdbm, val_gdbm;

        if (!key)
            return string_from_literal(INTERP, "");

        if (!dbf)
            return string_from_literal(INTERP, "");

        keystr         = make_hash_key(interp, key);

        key_gdbm.dsize = keystr->strlen;
        key_gdbm.dptr  = keystr->strstart;
        val_gdbm       = gdbm_fetch(dbf, key_gdbm);

        val = string_from_cstring(interp, val_gdbm.dptr, val_gdbm.dsize);
        free(val_gdbm.dptr);

        return val;
    }


/*

=item C<void set_pmc_keyed(PMC* key, PMC *value)>

Convert C<value> to a string and set the string for the C<key>.

=cut

*/

    void set_pmc_keyed(PMC* key, PMC* value) {
        STRING *temp;

        temp = VTABLE_get_string(interp, value);
        SELF.set_string_keyed(key, temp);
    }

/*

=item C<PMC *get_pmc_keyed(PMC *key)>

Returns the PMC value for the element at C<*key>.

=cut

*/

    PMC* get_pmc_keyed(PMC* key) {
        STRING * ret_string = SELF.get_string_keyed(key);
        PMC *ret_pmc = pmc_new(interp, enum_class_String);

        VTABLE_set_string_native(interp, ret_pmc, ret_string);

        return ret_pmc;
    }

/*

=item C<void set_integer_keyed(PMC* key, INTVAL value)>

Convert C<value> to a string and set the string for the C<key>.

=cut

*/

    void set_integer_keyed(PMC* key, INTVAL value) {
        PMC *temp;

        temp = pmc_new(interp, enum_class_String);
        VTABLE_set_integer_native(interp, temp, value);
        SELF.set_pmc_keyed(key, temp);
    }


/*

=item C<void set_number_keyed(PMC* key, FLOATVAL value)>

Convert C<value> to a string and set the string for the C<key>.

=cut

*/

    void set_number_keyed(PMC* key, FLOATVAL value) {
        PMC *temp;

        temp = pmc_new(interp, enum_class_String);
        VTABLE_set_number_native(interp, temp, value);
        SELF.set_pmc_keyed(key, temp);
    }

/*

=item C<INTVAL exists_keyed(PMC *key)>

Returns whether a key C<*key> exists in the hash.

=cut

*/

    INTVAL exists_keyed(PMC* key) {
        STRING* keystr;
        GDBM_FILE dbf = (GDBM_FILE)PMC_struct_val(SELF);
        datum key_gdbm;

        if (!key) return 0;
        if (!dbf) return 0;
        keystr = make_hash_key(interp, key);

        key_gdbm.dsize = keystr->strlen;
        key_gdbm.dptr  = keystr->strstart;

        return gdbm_exists(dbf, key_gdbm);
    }

/*

=item C<void delete_keyed(PMC *key)>

Deletes the element associated with C<*key>.

=cut

*/

    void delete_keyed(PMC* key) {
        STRING* keystr;
        GDBM_FILE dbf = (GDBM_FILE)PMC_struct_val(SELF);
        datum key_gdbm;

        if (!key) return;
        if (!dbf) return;
        keystr = make_hash_key(interp, key);

        key_gdbm.dsize = keystr->strlen;
        key_gdbm.dptr  = keystr->strstart;

        if (gdbm_exists(dbf, key_gdbm)) {
           gdbm_delete(dbf, key_gdbm);
        }

        return;
    }
}

/*

=back

=head1 SEE ALSO

F<docs/pdds/pdd08_keys.pod>, L<http://gdbm.gnu.org>

=cut

*/

/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4:
 */
