/*
 * handles.c --
 *
 * adapted from tclXhandles.c tclXutil.c and tclExtend.h
 */
 
/*
 *
 * tclXhandles.c --
 *
 * Tcl handles.  Provides a mechanism for managing expandable tables that are
 * addressed by textual handles.
 *-----------------------------------------------------------------------------
 * Copyright 1991-1994 Karl Lehenbauer and Mark Diekhans.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies.  Karl Lehenbauer and
 * Mark Diekhans make no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *-----------------------------------------------------------------------------
 * $Id: tclXhandles.c,v 4.0 1994/07/16 05:27:04 markd Rel $
 *-----------------------------------------------------------------------------
 */

#include "dghandle.h"

/*
 * Variable set to contain the alignment factor (in bytes) for this machine.
 * It is set on the first table initialization.
 */
static int entryAlignment = 0;

/*=============================================================================
 * LinkInNewEntries --
 *   Build free links through the newly allocated part of a table.
 *   
 * Parameters:
 *   o tblHdrPtr (I) - A pointer to the table header.
 *   o newIdx (I) - Index of the first new entry.
 *   o numEntries (I) - The number of new entries.
 *-----------------------------------------------------------------------------
 */
static void LinkInNewEntries (tblHeader_pt tblHdrPtr, int newIdx, int numEntries)
{
    int            entIdx, lastIdx;
    entryHeader_pt entryPtr;
    
    lastIdx = newIdx + numEntries - 1;

    for (entIdx = newIdx; entIdx < lastIdx; entIdx++) {
        entryPtr = TBL_INDEX (tblHdrPtr, entIdx);
        entryPtr->freeLink = entIdx + 1;
    }
    entryPtr = TBL_INDEX (tblHdrPtr, lastIdx);
    entryPtr->freeLink = tblHdrPtr->freeHeadIdx;
    tblHdrPtr->freeHeadIdx = newIdx;

}

/*=============================================================================
 * ExpandTable --
 *   Expand a handle table, doubling its size.
 * Parameters:
 *   o tblHdrPtr (I) - A pointer to the table header.
 *   o neededIdx (I) - If positive, then the table will be expanded so that
 *     this entry is available.  If -1, then just expand by the number of 
 *     entries specified on table creation.  MUST be smaller than this size.
 *-----------------------------------------------------------------------------
 */
static void ExpandTable (tblHeader_pt tblHdrPtr, int neededIdx)
{
    ubyte_pt oldbodyPtr = tblHdrPtr->bodyPtr;
    int      numNewEntries;
    int      newSize;
    
    if (neededIdx < 0)
        numNewEntries = tblHdrPtr->tableSize;
    else
        numNewEntries = (neededIdx - tblHdrPtr->tableSize) + 1;
    newSize = (tblHdrPtr->tableSize + numNewEntries) * tblHdrPtr->entrySize;

    tblHdrPtr->bodyPtr = (ubyte_pt) ckalloc (newSize);
    memcpy (tblHdrPtr->bodyPtr, oldbodyPtr, 
            (tblHdrPtr->tableSize * tblHdrPtr->entrySize));
    LinkInNewEntries (tblHdrPtr, tblHdrPtr->tableSize, numNewEntries);
    tblHdrPtr->tableSize += numNewEntries;
    ckfree (oldbodyPtr);
    
}

/*=============================================================================
 * dghandleAlloc --
 *   Allocate a table entry, expanding if necessary.
 *
 * Parameters:
 *   o headerPtr (I) - A pointer to the table header.
 *   o entryIdxPtr (O) - The index of the table entry is returned here.
 * Returns:
 *    The a pointer to the entry.
 *-----------------------------------------------------------------------------
 */
entryHeader_pt dghandleAlloc (tblHeader_pt headerPtr, ulong *entryIdxPtr)
{
    tblHeader_pt  tblHdrPtr = (tblHeader_pt)headerPtr;
    entryHeader_pt entryPtr;
    int            entryIdx;

    if (tblHdrPtr->freeHeadIdx == NULL_IDX)
        ExpandTable (tblHdrPtr, -1);

    entryIdx = tblHdrPtr->freeHeadIdx;    
    entryPtr = TBL_INDEX (tblHdrPtr, entryIdx);
    tblHdrPtr->freeHeadIdx = entryPtr->freeLink;
    entryPtr->freeLink = ALLOCATED_IDX;
    
    *entryIdxPtr = entryIdx;
    return USER_AREA (entryPtr);
    
}

/*=============================================================================
 * dghandleInit --
 *   Create and initialize a Tcl dynamic handle table. 
 * Parameters:
 *   o entrySize (I) - The size of an entry, in bytes.
 *   o initEntries (I) - Initial size of the table, in entries.
 * Returns:
 *   A pointer to the table header.  
 *-----------------------------------------------------------------------------
 */
tblHeader_pt dghandleInit (int entrySize, int initEntries)
{
    tblHeader_pt tblHdrPtr;

    /*
     * It its not been calculated yet, determine the entry alignment required
     * for this machine.
     */
    if (entryAlignment == 0) {
        entryAlignment = sizeof (void *);
        if (sizeof (ulong) > entryAlignment)
            entryAlignment = sizeof (ulong);
        if (sizeof (double) > entryAlignment)
            entryAlignment = sizeof (double);
    }

    /*
     * Set up the table entry.
     */
    tblHdrPtr = (tblHeader_pt) ckalloc (sizeof (tblHeader_t) );

    /* 
     * Calculate entry size, including header, rounded up to sizeof (void *). 
     */
    tblHdrPtr->entrySize = ENTRY_HEADER_SIZE + ROUND_ENTRY_SIZE (entrySize);
    tblHdrPtr->freeHeadIdx = NULL_IDX;
    tblHdrPtr->tableSize = initEntries;
    tblHdrPtr->bodyPtr =
        (ubyte_pt) ckalloc (initEntries * tblHdrPtr->entrySize);
    LinkInNewEntries (tblHdrPtr, 0, initEntries);

    return tblHdrPtr;

}

/*=============================================================================
 * dghandleReset --
 *   Reset a Tcl dynamic handle table.  
 * Parameters:
 *   tblHdrPtr - A pointer to the table header.
 *   initEntries - The number of initial entries in the reset table.
 * Returns:
 *   TCL_OK if success
 *   TCL_ERROR if any handles still in use in the old handle table.
 *-----------------------------------------------------------------------------
 */

int dghandleReset (tblHeader_pt tblHdrPtr, int initEntries)
{
    int            entIdx, lastIdx;
    entryHeader_pt entryPtr;
    
    lastIdx = tblHdrPtr->tableSize;
    for (entIdx = 0; entIdx < lastIdx; entIdx++) {
        entryPtr = TBL_INDEX (tblHdrPtr, entIdx);
        if (entryPtr->freeLink == ALLOCATED_IDX) return TCL_ERROR;
    }
    free(tblHdrPtr->bodyPtr);
    tblHdrPtr->freeHeadIdx = NULL_IDX;
    tblHdrPtr->tableSize = initEntries;
    tblHdrPtr->bodyPtr =
        (ubyte_pt) ckalloc (initEntries * tblHdrPtr->entrySize);
    LinkInNewEntries (tblHdrPtr, 0, initEntries);

    return TCL_OK;
}

/*=============================================================================
 * dghandleXlate --
 *   Translate an index to a entry pointer.
 *
 * Parameters:
 *   o headerPtr (I) - A pointer to the table header.
 *   o index (I) - The index of an entry.
 * Returns:
 *   A pointer to the entry, or NULL if an error occured.
 *-----------------------------------------------------------------------------
 */
void * dghandleXlate (void *headerPtr, ulong entryIdx)
{
    tblHeader_pt   tblHdrPtr = (tblHeader_pt)headerPtr;
    entryHeader_pt entryPtr;
    
    entryPtr = TBL_INDEX (tblHdrPtr, entryIdx);

    if ((entryIdx >= tblHdrPtr->tableSize) ||
            (entryPtr->freeLink != ALLOCATED_IDX)) {
        return NULL;
    }     

    return USER_AREA (entryPtr);
}

/*============================================================================
 * dghandleFree --
 *   Frees a handle table entry by index.
 *
 * Parameters:
 *   o headerPtr (I) - A pointer to the table header.
 *   o index (I) - The index of an entry.
 *----------------------------------------------------------------------------
 * Returns:
 *   TRUE if success, or FALSE if an error occured.
 *----------------------------------------------------------------------------
 */
int dghandleFree (void *headerPtr, ulong entryIdx)
{
    tblHeader_pt   tblHdrPtr = (tblHeader_pt)headerPtr;
    entryHeader_pt entryPtr, freeentryPtr;
     
    entryPtr = TBL_INDEX (tblHdrPtr, entryIdx);

    if ((entryIdx >= tblHdrPtr->tableSize) ||
            (entryPtr->freeLink != ALLOCATED_IDX)) {
        return FALSE;
    }     

    entryPtr = USER_AREA (entryPtr);
    freeentryPtr = HEADER_AREA (entryPtr);
    freeentryPtr->freeLink = tblHdrPtr->freeHeadIdx;
    tblHdrPtr->freeHeadIdx = (((ubyte_pt) entryPtr) - tblHdrPtr->bodyPtr) /
                           tblHdrPtr->entrySize;
      
    return TRUE;
}
