/*******************************************************************************
*                                                                              *
* refString.c -- Nirvana editor string handling                                *
*                                                                              *
* Copyright (C) 200 Scott Tringali                                             *
*                                                                              *
* This is free software; you can redistribute it and/or modify it under the    *
* terms of the GNU General Public License as published by the Free Software    *
* Foundation; either version 2 of the License, or (at your option) any later   *
* version. In addition, you may distribute versions of this program linked to  *
* Motif or Open Motif. See README for details.                                 *
*                                                                              *
* This software is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or        *
* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License        *
* for more details.                                                            *
*                                                                              *
* You should have received a copy of the GNU General Public License along with *
* software; if not, write to the Free Software Foundation, Inc., 59 Temple     *
* Place, Suite 330, Boston, MA  02111-1307 USA                                 *
*                                                                              *
* Nirvana Text Editor                                                          *
* July, 1993                                                                   *
*                                                                              *
* Written by Mark Edel                                                         *
*                                                                              *
*******************************************************************************/

#include "refString.h"
#include "nedit_malloc.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define RCS_SIZE 0x10000

struct rcs;

struct rcs_stats
{
    int talloc, tshar, tgiveup, tbytes, tbyteshared;
};

struct rcs
{
    struct rcs *next;
    char       *string;
    int         usage;
};

static struct rcs       *Rcs[RCS_SIZE];
static struct rcs_stats  RcsStats;

static unsigned const DJB2_SEED = 5381u;
/* djb2s hash for null-terminated string, seeded version */
static unsigned djb2s(char const* key, unsigned hash)
{
    char c;
    while (!!(c = *key++))
        hash = ((hash << 5) + hash) ^ c;
    return hash;
}

/* Compute hash address from a string key */
unsigned StringHashAddr(const char *key)
{
    return djb2s(key, DJB2_SEED);
}

/* Compute hash address from a null-termintated list of strings */
unsigned StringsHashAddr(const char** keys)
{
    unsigned hash = DJB2_SEED;
    char const* key;
    while (!!(key = *keys++))
        hash = djb2s(key, hash);
    return hash;
}

/*
** Take a normal string, create a shared string from it if need be,
** and return pointer to that shared string.
**
** Returned strings are const because they are shared.  Do not modify them!
*/

const char *RefStringDup(const char *str)
{
    unsigned bucket;
    size_t len;
    struct rcs *rp;
    char *newstr;

    if (str == NULL)
        return NULL;

    len = strlen(str);

    RcsStats.talloc++;

    /* Find it in hash */
    bucket = StringHashAddr(str) % RCS_SIZE;
    rp = Rcs[bucket];
    for (; rp; rp = rp->next)
        if (!strcmp(str, rp->string)) break;

    newstr = NULL;
    if (rp)  /* It exists, return it and bump ref ct */
    {
        rp->usage++;
        newstr = rp->string;

        RcsStats.tshar++;
        RcsStats.tbyteshared += len;
    }
    else     /* Doesn't exist, conjure up a new one. */
    {
        rp = NEditNew(struct rcs);
        rp->usage = 1;
        rp->next = Rcs[bucket];
        Rcs[bucket] = rp;

        rp->string = (char*) NEditMalloc(len + 1);
        memcpy(rp->string, str, len + 1);

        newstr = rp->string;
    }

    RcsStats.tbytes += len;
    return newstr;
}

/*
** Decrease the reference count on a shared string.  When the reference
** count reaches zero, free the master string.
*/

void RefStringFree(const char *rcs_str)
{
    int bucket;
    struct rcs *rp;
    struct rcs *prev = NULL;

    if (rcs_str == NULL)
        return;
        
    bucket = StringHashAddr(rcs_str) % RCS_SIZE;

    /* find it in hash */
    for (rp = Rcs[bucket]; rp; rp = rp->next)
    {
        if (rcs_str == rp->string)
            break;
        prev = rp;
    }

    if (rp)  /* It's a shared string, decrease ref count */
    {
        rp->usage--;
        
        if (rp->usage < 0) /* D'OH! */
        {
            fprintf(stderr, "XNEdit: internal error deallocating shared string.");
            return;
        }

        if (rp->usage == 0)  /* Last one- free the storage */
        {
            NEditFree(rp->string);
            if (prev)
                prev->next = rp->next;
            else
                Rcs[bucket] = rp->next;
            NEditFree(rp);
        }
    }
    else    /* Doesn't appear to be a shared string */
    {
        fprintf(stderr, "XNEdit: attempt to free a non-shared string.");
        return;
    }
}

