/*
 * This file implements a family of commands for sharing variables
 * between threads.
 *
 * Initial code is taken from nsd/tclvar.c found in AOLserver 3.+
 * distribution and modified to support Tcl 8.0+ command object interface
 * and internal storage in private shared Tcl objects.
 *
 * Copyright (c) 2002 by Zoran Vasiljevic.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * ----------------------------------------------------------------------------
 */

#include "tclThreadInt.h"
#include "threadSvCmd.h"

#include "threadSvListCmd.h"    /* Shared variants of list commands */
#include "threadSvKeylistCmd.h" /* Shared variants of list commands */
#include "psGdbm.h"             /* The gdbm persistent store implementation */
#include "psLmdb.h"             /* The lmdb persistent store implementation */

#define SV_FINALIZE

/*
 * Number of buckets to spread shared arrays into. Each bucket is
 * associated with one mutex so locking a bucket locks all arrays
 * in that bucket as well. The number of buckets should be a prime.
 */

#define NUMBUCKETS 31

/*
 * Number of object containers
 * to allocate in one shot.
 */

#define OBJS_TO_ALLOC_EACH_TIME 100

/*
 * Reference to Tcl object types used in object-copy code.
 * Those are referenced read-only, thus no mutex protection.
 */

static const Tcl_ObjType* booleanObjTypePtr = 0;
static const Tcl_ObjType* byteArrayObjTypePtr = 0;
static const Tcl_ObjType* doubleObjTypePtr = 0;
static const Tcl_ObjType* intObjTypePtr = 0;
static const Tcl_ObjType* wideIntObjTypePtr = 0;
static const Tcl_ObjType* stringObjTypePtr = 0;

/*
 * In order to be fully stub enabled, a small
 * hack is needed to query the tclEmptyStringRep
 * global symbol defined by Tcl. See SvInit.
 */

static char *Sv_tclEmptyStringRep = NULL;

/*
 * Global variables used within this file.
 */

#ifdef SV_FINALIZE
static size_t     nofThreads;      /* Number of initialized threads */
static Tcl_Mutex  nofThreadsMutex; /* Protects the nofThreads variable */
#endif /* SV_FINALIZE */

static Bucket*    buckets;      /* Array of buckets. */
static Tcl_Mutex  bucketsMutex; /* Protects the array of buckets */

static SvCmdInfo* svCmdInfo;    /* Linked list of registered commands */
static RegType*   regType;      /* Linked list of registered obj types */
static PsStore*   psStore;      /* Linked list of registered pers. stores */

static Tcl_Mutex  svMutex;      /* Protects inserts into above lists */
static Tcl_Mutex  initMutex;    /* Serializes initialization issues */

/*
 * The standard commands found in NaviServer/AOLserver nsv_* interface.
 * For sharp-eye readers: the implementation of the "lappend" command
 * is moved to new list-command package, since it really belongs there.
 */

static Tcl_ObjCmdProc SvObjObjCmd;
static Tcl_ObjCmdProc SvAppendObjCmd;
static Tcl_ObjCmdProc SvIncrObjCmd;
static Tcl_ObjCmdProc SvSetObjCmd;
static Tcl_ObjCmdProc SvExistsObjCmd;
static Tcl_ObjCmdProc SvGetObjCmd;
static Tcl_ObjCmdProc SvArrayObjCmd;
static Tcl_ObjCmdProc SvUnsetObjCmd;
static Tcl_ObjCmdProc SvNamesObjCmd;
static Tcl_ObjCmdProc SvHandlersObjCmd;

/*
 * New commands added to
 * standard set of nsv_*
 */

static Tcl_ObjCmdProc SvPopObjCmd;
static Tcl_ObjCmdProc SvMoveObjCmd;
static Tcl_ObjCmdProc SvLockObjCmd;

/*
 * Forward declarations for functions to
 * manage buckets, arrays and shared objects.
 */

static Container* CreateContainer(Array*, Tcl_HashEntry*, Tcl_Obj*);
static Container* AcquireContainer(Array*, const char*, int);

static Array* CreateArray(Bucket*, const char*);
static Array* LockArray(Tcl_Interp*, const char*, int);

static int ReleaseContainer(Tcl_Interp*, Container*, int);
static int DeleteContainer(Container*);
static int FlushArray(Array*);
static int DeleteArray(Tcl_Interp *, Array*);

static void SvAllocateContainers(Bucket*);
static void SvRegisterStdCommands(void);

#ifdef SV_FINALIZE
static void SvFinalizeContainers(Bucket*);
static void SvFinalize(ClientData);
#endif /* SV_FINALIZE */

static PsStore* GetPsStore(const char *handle);

static int SvObjDispatchObjCmd(ClientData arg,
            Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);

/*
 *-----------------------------------------------------------------------------
 *
 * Sv_RegisterCommand --
 *
 *      Utility to register commands to be loaded at module start.
 *
 * Results:
 *      None.
 *
 * Side effects;
 *      New command will be added to a linked list of registered commands.
 *
 *-----------------------------------------------------------------------------
 */

void
Sv_RegisterCommand(
                   const char *cmdName,                /* Name of command to register */
                   Tcl_ObjCmdProc *objProc,            /* Object-based command procedure */
                   Tcl_CmdDeleteProc *delProc,         /* Command delete procedure */
                   int aolSpecial)
{
    size_t len = strlen(cmdName) + strlen(TSV_CMD_PREFIX) + 1;
    size_t len2 = strlen(cmdName) + strlen(TSV_CMD2_PREFIX) + 1;
    SvCmdInfo *newCmd = (SvCmdInfo*)ckalloc(sizeof(SvCmdInfo) + len + len2);

    /*
     * Setup new command structure
     */

    newCmd->cmdName = (char*)((char*)newCmd + sizeof(SvCmdInfo));
    newCmd->cmdName2 = newCmd->cmdName + len;
    newCmd->aolSpecial = aolSpecial;

    newCmd->objProcPtr = objProc;
    newCmd->delProcPtr = delProc;

    /*
     * Rewrite command name. This is needed so we can
     * easily turn-on the compatiblity with NaviServer/AOLserver
     * command names.
     */

    strcpy(newCmd->cmdName, TSV_CMD_PREFIX);
    strcat(newCmd->cmdName, cmdName);
    newCmd->name = newCmd->cmdName + strlen(TSV_CMD_PREFIX);
    strcpy(newCmd->cmdName2, TSV_CMD2_PREFIX);
    strcat(newCmd->cmdName2, cmdName);

    /*
     * Plug-in in shared list of commands.
     */

    Tcl_MutexLock(&svMutex);
    if (svCmdInfo == NULL) {
        svCmdInfo = newCmd;
        newCmd->nextPtr = NULL;
    } else {
        newCmd->nextPtr = svCmdInfo;
        svCmdInfo = newCmd;
    }
    Tcl_MutexUnlock(&svMutex);

    return;
}

/*
 *-----------------------------------------------------------------------------
 *
 * Sv_RegisterObjType --
 *
 *      Registers custom object duplicator function for a specific
 *      object type. Registered function will be called by the
 *      private object creation routine every time an object is
 *      plugged out or in the shared array. This way we assure that
 *      Tcl objects do not get shared per-reference between threads.
 *
 * Results:
 *      None.
 *
 * Side effects;
 *      Memory gets allocated.
 *
 *-----------------------------------------------------------------------------
 */

void
Sv_RegisterObjType(
                   const Tcl_ObjType *typePtr,               /* Type of object to register */
                   Tcl_DupInternalRepProc *dupProc)    /* Custom object duplicator */
{
    RegType *newType = (RegType*)ckalloc(sizeof(RegType));

    /*
     * Setup new type structure
     */

    newType->typePtr = typePtr;
    newType->dupIntRepProc = dupProc;

    /*
     * Plug-in in shared list
     */

    Tcl_MutexLock(&svMutex);
    newType->nextPtr = regType;
    regType = newType;
    Tcl_MutexUnlock(&svMutex);
}

/*
 *-----------------------------------------------------------------------------
 *
 * Sv_RegisterPsStore --
 *
 *      Registers a handler to the persistent storage.
 *
 * Results:
 *      None.
 *
 * Side effects;
 *      Memory gets allocated.
 *
 *-----------------------------------------------------------------------------
 */

void
Sv_RegisterPsStore(const PsStore *psStorePtr)
{

    PsStore *psPtr = (PsStore*)ckalloc(sizeof(PsStore));

    *psPtr = *psStorePtr;

    /*
     * Plug-in in shared list
     */

    Tcl_MutexLock(&svMutex);
    if (psStore == NULL) {
        psStore = psPtr;
        psStore->nextPtr = NULL;
    } else {
        psPtr->nextPtr = psStore;
        psStore = psPtr;
    }
    Tcl_MutexUnlock(&svMutex);
}

/*
 *-----------------------------------------------------------------------------
 *
 * Sv_GetContainer --
 *
 *      This is the workhorse of the module. It returns the container
 *      with the shared Tcl object. It also locks the container, so
 *      when finished with operation on the Tcl object, one has to
 *      unlock the container by calling the Sv_PutContainer().
 *      If instructed, this command might also create new container
 *      with empty Tcl object.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      New container might be created.
 *
 *-----------------------------------------------------------------------------
 */

int
Sv_GetContainer(
                Tcl_Interp *interp,                 /* Current interpreter. */
                int objc,                           /* Number of arguments */
                Tcl_Obj *const objv[],              /* Argument objects. */
                Container **retObj,                 /* OUT: shared object container */
                int *offset,                        /* Shift in argument list */
                int flags)                          /* Options for locking shared array */
{
    const char *array, *key;

    if (*retObj == NULL) {
        Array *arrayPtr = NULL;

        /*
         * Parse mandatory arguments: <cmd> array key
         */

        if (objc < 3) {
            Tcl_WrongNumArgs(interp, 1, objv, "array key ?args?");
            return TCL_ERROR;
        }

        array = Tcl_GetString(objv[1]);
        key   = Tcl_GetString(objv[2]);

        *offset = 3; /* Consumed three arguments: cmd, array, key */

        /*
         * Lock the shared array and locate the shared object
         */

        arrayPtr = LockArray(interp, array, flags);
        if (arrayPtr == NULL) {
            return TCL_BREAK;
        }
        *retObj = AcquireContainer(arrayPtr, Tcl_GetString(objv[2]), flags);
        if (*retObj == NULL) {
            UnlockArray(arrayPtr);
            Tcl_AppendResult(interp, "no key ", array, "(", key, ")", NULL);
            return TCL_BREAK;
        }
    } else {
        Tcl_HashTable *handles = &((*retObj)->bucketPtr->handles);
        LOCK_CONTAINER(*retObj);
        if (Tcl_FindHashEntry(handles, (char*)(*retObj)) == NULL) {
            UNLOCK_CONTAINER(*retObj);
            Tcl_SetObjResult(interp, Tcl_NewStringObj("key has been deleted", -1));
            return TCL_BREAK;
        }
        *offset = 2; /* Consumed two arguments: object, cmd */
    }

    return TCL_OK;
}

/*
 *-----------------------------------------------------------------------------
 *
 * Sv_PutContainer --
 *
 *      Releases the container obtained by the Sv_GetContainer.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      For bound arrays, update the underlying persistent storage.
 *
 *-----------------------------------------------------------------------------
 */

int
Sv_PutContainer(
                Tcl_Interp *interp,               /* For error reporting; might be NULL */
                Container *svObj,                 /* Shared object container */
                int mode)                         /* One of SV_XXX modes */
{
    int ret;

    ret = ReleaseContainer(interp, svObj, mode);
    UnlockArray(svObj->arrayPtr);

    return ret;
}

/*
 *-----------------------------------------------------------------------------
 *
 * GetPsStore --
 *
 *      Performs a lookup in the list of registered persistent storage
 *      handlers. If the match is found, duplicates the persistent
 *      storage record and passes the copy to the caller.
 *
 * Results:
 *      Pointer to the newly allocated persistent storage handler. Caller
 *      must free this block when done with it. If none found, returns NULL,
 *
 * Side effects;
 *      Memory gets allocated. Caller should free the return value of this
 *      function using ckfree().
 *
 *-----------------------------------------------------------------------------
 */

static PsStore*
GetPsStore(const char *handle)
{
    int i;
    const char *type = handle;
    char *addr, *delimiter = (char *)strchr(handle, ':');
    PsStore *tmpPtr, *psPtr = NULL;

    /*
     * Expect the handle in the following format: <type>:<address>
     * where "type" must match one of the registered presistent store
     * types (gdbm, tcl, whatever) and <address> is what is passed to
     * the open procedure of the registered store.
     *
     * Example: gdbm:/path/to/gdbm/file
     */

    /*
     * Try to see wether some array is already bound to the
     * same persistent storage address.
     */

    for (i = 0; i < NUMBUCKETS; i++) {
        Tcl_HashSearch search;
        Tcl_HashEntry *hPtr;
        Bucket *bucketPtr = &buckets[i];
        LOCK_BUCKET(bucketPtr);
        hPtr = Tcl_FirstHashEntry(&bucketPtr->arrays, &search);
        while (hPtr) {
            Array *arrayPtr = (Array*)Tcl_GetHashValue(hPtr);
            if (arrayPtr->bindAddr && arrayPtr->psPtr) {
                if (strcmp(arrayPtr->bindAddr, handle) == 0) {
                    UNLOCK_BUCKET(bucketPtr);
                    return NULL; /* Array already bound */
                }
            }
            hPtr = Tcl_NextHashEntry(&search);
        }
        UNLOCK_BUCKET(bucketPtr);
    }

    /*
     * Split the address and storage handler
     */

    if (delimiter == NULL) {
        addr = NULL;
    } else {
        *delimiter = 0;
        addr = delimiter + 1;
    }

    /*
     * No array was bound to the same persistent storage.
     * Lookup the persistent storage to bind to.
     */

    Tcl_MutexLock(&svMutex);
    for (tmpPtr = psStore; tmpPtr; tmpPtr = tmpPtr->nextPtr) {
        if (strcmp(tmpPtr->type, type) == 0) {
            tmpPtr->psHandle = tmpPtr->psOpen(addr);
            if (tmpPtr->psHandle) {
                psPtr = (PsStore*)ckalloc(sizeof(PsStore));
                *psPtr = *tmpPtr;
                psPtr->nextPtr = NULL;
            }
            break;
        }
    }
    Tcl_MutexUnlock(&svMutex);

    if (delimiter) {
        *delimiter = ':';
    }

    return psPtr;
}

/*
 *-----------------------------------------------------------------------------
 *
 * AcquireContainer --
 *
 *      Finds a variable within an array and returns it's container.
 *
 * Results:
 *      Pointer to variable object.
 *
 * Side effects;
 *      New variable may be created. For bound arrays, try to locate
 *      the key in the persistent storage as well.
 *
 *-----------------------------------------------------------------------------
 */

static Container *
AcquireContainer(
                 Array *arrayPtr,
                 const char *key,
                 int flags)
{
    int isNew;
    Tcl_Obj *tclObj = NULL;
    Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&arrayPtr->vars, key);

    if (hPtr == NULL) {
        PsStore *psPtr = arrayPtr->psPtr;
        if (psPtr) {
            char *val = NULL;
            size_t len = 0;
            if (psPtr->psGet(psPtr->psHandle, key, &val, &len) == 0) {
                tclObj = Tcl_NewStringObj(val, len);
                psPtr->psFree(psPtr->psHandle, val);
            }
        }
        if (!(flags & FLAGS_CREATEVAR) && tclObj == NULL) {
            return NULL;
        }
        if (tclObj == NULL) {
            tclObj = Tcl_NewObj();
        }
        hPtr = Tcl_CreateHashEntry(&arrayPtr->vars, key, &isNew);
        Tcl_SetHashValue(hPtr, CreateContainer(arrayPtr, hPtr, tclObj));
    }

    return (Container*)Tcl_GetHashValue(hPtr);
}

/*
 *-----------------------------------------------------------------------------
 *
 * ReleaseContainer --
 *
 *      Does some post-processing on the used container. This is mostly
 *      needed when the container has been modified and needs to be
 *      saved in the bound persistent storage.
 *
 * Results:
 *      A standard Tcl result
 *
 * Side effects:
 *      Persistent storage, if bound, might be modified.
 *
 *-----------------------------------------------------------------------------
 */

static int
ReleaseContainer(
                 Tcl_Interp *interp,
                 Container *svObj,
                 int mode)
{
    const PsStore *psPtr = svObj->arrayPtr->psPtr;
    size_t len;
    char *key, *val;

    switch (mode) {
    case SV_UNCHANGED: return TCL_OK;
    case SV_ERROR:     return TCL_ERROR;
    case SV_CHANGED:
        if (psPtr) {
            key = (char *)Tcl_GetHashKey(&svObj->arrayPtr->vars, svObj->entryPtr);
            val = Tcl_GetString(svObj->tclObj);
            len = svObj->tclObj->length;
            if (psPtr->psPut(psPtr->psHandle, key, val, len) == -1) {
                const char *err = psPtr->psError(psPtr->psHandle);
                Tcl_SetObjResult(interp, Tcl_NewStringObj(err, -1));
                return TCL_ERROR;
            }
        }
        return TCL_OK;
    }

    return TCL_ERROR; /* Should never be reached */
}

/*
 *-----------------------------------------------------------------------------
 *
 * CreateContainer --
 *
 *      Creates new shared container holding Tcl object to be stored
 *      in the shared array
 *
 * Results:
 *      The container pointer.
 *
 * Side effects:
 *      Memory gets allocated.
 *
 *-----------------------------------------------------------------------------
 */

static Container *
CreateContainer(
                Array *arrayPtr,
                Tcl_HashEntry *entryPtr,
                Tcl_Obj *tclObj)
{
    Container *svObj;

    if (arrayPtr->bucketPtr->freeCt == NULL) {
        SvAllocateContainers(arrayPtr->bucketPtr);
    }

    svObj = arrayPtr->bucketPtr->freeCt;
    arrayPtr->bucketPtr->freeCt = svObj->nextPtr;

    svObj->arrayPtr  = arrayPtr;
    svObj->bucketPtr = arrayPtr->bucketPtr;
    svObj->tclObj    = tclObj;
    svObj->entryPtr  = entryPtr;
    svObj->handlePtr = NULL;

    if (svObj->tclObj) {
        Tcl_IncrRefCount(svObj->tclObj);
    }

    return svObj;
}

/*
 *-----------------------------------------------------------------------------
 *
 * DeleteContainer --
 *
 *      Destroys the container and the Tcl object within it. For bound
 *      shared arrays, the underlying persistent store is updated as well.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Memory gets reclaimed. If the shared array was bound to persistent
 *      storage, it removes the corresponding record.
 *
 *-----------------------------------------------------------------------------
 */

static int
DeleteContainer(
                Container *svObj)
{
    if (svObj->tclObj) {
        Tcl_DecrRefCount(svObj->tclObj);
    }
    if (svObj->handlePtr) {
        Tcl_DeleteHashEntry(svObj->handlePtr);
    }
    if (svObj->entryPtr) {
        PsStore *psPtr = svObj->arrayPtr->psPtr;
        if (psPtr) {
            char *key = (char *)Tcl_GetHashKey(&svObj->arrayPtr->vars,svObj->entryPtr);
            if (psPtr->psDelete(psPtr->psHandle, key) == -1) {
                return TCL_ERROR;
            }
        }
        Tcl_DeleteHashEntry(svObj->entryPtr);
    }

    svObj->arrayPtr  = NULL;
    svObj->entryPtr  = NULL;
    svObj->handlePtr = NULL;
    svObj->tclObj    = NULL;

    svObj->nextPtr = svObj->bucketPtr->freeCt;
    svObj->bucketPtr->freeCt = svObj;

    return TCL_OK;
}

/*
 *-----------------------------------------------------------------------------
 *
 * LockArray --
 *
 *      Find (or create) the array structure for shared array and lock it.
 *      Array structure must be later unlocked with UnlockArray.
 *
 * Results:
 *      TCL_OK or TCL_ERROR if no such array.
 *
 * Side effects:
 *      Sets *arrayPtrPtr with Array pointer or leave error in given interp.
 *
 *-----------------------------------------------------------------------------
 */

static Array *
LockArray(
          Tcl_Interp *interp,                 /* Interpreter to leave result. */
          const char *array,                  /* Name of array to lock */
          int flags)                          /* FLAGS_CREATEARRAY/FLAGS_NOERRMSG*/
{
    const char *p;
    unsigned int result;
    int i;
    Bucket *bucketPtr;
    Array *arrayPtr;

    /*
     * Compute a hash to map an array to a bucket.
     */

    p = array;
    result = 0;
    while (*p++) {
        i = *p;
        result += (result << 3) + i;
    }
    i = result % NUMBUCKETS;
    bucketPtr = &buckets[i];

    /*
     * Lock the bucket and find the array, or create a new one.
     * The bucket will be left locked on success.
     */

    LOCK_BUCKET(bucketPtr); /* Note: no matching unlock below ! */
    if (flags & FLAGS_CREATEARRAY) {
        arrayPtr = CreateArray(bucketPtr, array);
    } else {
        Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&bucketPtr->arrays, array);
        if (hPtr == NULL) {
            UNLOCK_BUCKET(bucketPtr);
            if (!(flags & FLAGS_NOERRMSG)) {
                Tcl_AppendResult(interp, "\"", array,
                                 "\" is not a thread shared array", NULL);
            }
            return NULL;
        }
        arrayPtr = (Array*)Tcl_GetHashValue(hPtr);
    }

    return arrayPtr;
}
/*
 *-----------------------------------------------------------------------------
 *
 * FlushArray --
 *
 *      Unset all keys in an array.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Array is cleaned but it's variable hash-hable still lives.
 *      For bound arrays, the persistent store is updated accordingly.
 *
 *-----------------------------------------------------------------------------
 */

static int
FlushArray(Array *arrayPtr)                    /* Name of array to flush */
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;

    for (hPtr = Tcl_FirstHashEntry(&arrayPtr->vars, &search); hPtr;
         hPtr = Tcl_NextHashEntry(&search)) {
        if (DeleteContainer((Container*)Tcl_GetHashValue(hPtr)) != TCL_OK) {
            return TCL_ERROR;
        }
    }

    return TCL_OK;
}

/*
 *-----------------------------------------------------------------------------
 *
 * CreateArray --
 *
 *      Creates new shared array instance.
 *
 * Results:
 *      Pointer to the newly created array
 *
 * Side effects:
 *      Memory gets allocated
 *
 *-----------------------------------------------------------------------------
 */

static Array *
CreateArray(
            Bucket *bucketPtr,
            const char *arrayName)
{
    int isNew;
    Array *arrayPtr;
    Tcl_HashEntry *hPtr;

    hPtr = Tcl_CreateHashEntry(&bucketPtr->arrays, arrayName, &isNew);
    if (!isNew) {
        return (Array*)Tcl_GetHashValue(hPtr);
    }

    arrayPtr = (Array*)ckalloc(sizeof(Array));
    arrayPtr->bucketPtr = bucketPtr;
    arrayPtr->entryPtr  = hPtr;
    arrayPtr->psPtr     = NULL;
    arrayPtr->bindAddr  = NULL;

    Tcl_InitHashTable(&arrayPtr->vars, TCL_STRING_KEYS);
    Tcl_SetHashValue(hPtr, arrayPtr);

    return arrayPtr;
}

/*
 *-----------------------------------------------------------------------------
 *
 * DeleteArray --
 *
 *      Deletes the shared array.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      Memory gets reclaimed.
 *
 *-----------------------------------------------------------------------------
 */

static int
UnbindArray(Tcl_Interp *interp, Array *arrayPtr)
{
    PsStore *psPtr = arrayPtr->psPtr;
    if (arrayPtr->bindAddr) {
        ckfree(arrayPtr->bindAddr);
        arrayPtr->bindAddr = NULL;
    }
    if (psPtr) {
        if (psPtr->psClose(psPtr->psHandle) == -1) {
            if (interp) {
                const char *err = psPtr->psError(psPtr->psHandle);
                Tcl_SetObjResult(interp, Tcl_NewStringObj(err, -1));
            }
            return TCL_ERROR;
        }
        ckfree((char*)arrayPtr->psPtr), arrayPtr->psPtr = NULL;
        arrayPtr->psPtr = NULL;
    }
    return TCL_OK;
}

static int
DeleteArray(Tcl_Interp *interp, Array *arrayPtr)
{
    if (FlushArray(arrayPtr) == -1) {
        return TCL_ERROR;
    }
    if (arrayPtr->psPtr) {
        if (UnbindArray(interp, arrayPtr) != TCL_OK) {
            return TCL_ERROR;
        };
    }
    if (arrayPtr->entryPtr) {
        Tcl_DeleteHashEntry(arrayPtr->entryPtr);
    }

    Tcl_DeleteHashTable(&arrayPtr->vars);
    ckfree((char*)arrayPtr);

    return TCL_OK;
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvAllocateContainers --
 *
 *      Any similarity with the Tcl AllocateFreeObj function is purely
 *      coincidental... Just joking; this is (almost) 100% copy of it! :-)
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Allocates memory for many containers at the same time
 *
 *-----------------------------------------------------------------------------
 */

static void
SvAllocateContainers(Bucket *bucketPtr)
{
    Container tmp[2];
    size_t objSizePlusPadding = (size_t)(((char*)(tmp+1))-(char*)tmp);
    size_t bytesToAlloc = (OBJS_TO_ALLOC_EACH_TIME * objSizePlusPadding);
    char *basePtr;
    Container *prevPtr = NULL, *objPtr = NULL;
    int i;

    basePtr = (char*)ckalloc(bytesToAlloc);
    memset(basePtr, 0, bytesToAlloc);

    objPtr = (Container*)basePtr;
    objPtr->chunkAddr = basePtr; /* Mark chunk address for reclaim */

    for (i = 0; i < OBJS_TO_ALLOC_EACH_TIME; i++) {
        objPtr->nextPtr = prevPtr;
        prevPtr = objPtr;
        objPtr = (Container*)(((char*)objPtr) + objSizePlusPadding);
    }
    bucketPtr->freeCt = prevPtr;
}

#ifdef SV_FINALIZE
/*
 *-----------------------------------------------------------------------------
 *
 * SvFinalizeContainers --
 *
 *    Reclaim memory for free object containers per bucket.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Memory gets reclaimed
 *
 *-----------------------------------------------------------------------------
 */

static void
SvFinalizeContainers(Bucket *bucketPtr)
{
   Container *tmpPtr, *objPtr = bucketPtr->freeCt;

    while (objPtr) {
        if (objPtr->chunkAddr == (char*)objPtr) {
            tmpPtr = objPtr->nextPtr;
            ckfree((char*)objPtr);
            objPtr = tmpPtr;
        } else {
            objPtr = objPtr->nextPtr;
        }
    }
}
#endif /* SV_FINALIZE */

/*
 *-----------------------------------------------------------------------------
 *
 * Sv_DuplicateObj --
 *
 *  Create and return a new object that is (mostly) a duplicate of the
 *  argument object. We take care that the duplicate object is either
 *  a proper object copy, i.e. w/o hidden references to original object
 *  elements or a plain string object, i.e one w/o internal representation.
 *
 *  Decision about whether to produce a real duplicate or a string object
 *  is done as follows:
 *
 *     1) Scalar Tcl object types are properly copied by default;
 *        these include: boolean, int double, string and byteArray types.
 *     2) Object registered with Sv_RegisterObjType are duplicated
 *        using custom duplicator function which is guaranteed to
 *        produce a proper deep copy of the object in question.
 *     3) All other object types are stringified; these include
 *        miscelaneous Tcl objects (cmdName, nsName, bytecode, etc, etc)
 *        and all user-defined objects.
 *
 * Results:
 *      The return value is a pointer to a newly created Tcl_Obj. This
 *      object has reference count 0 and the same type, if any, as the
 *      source object objPtr. Also:
 *
 *        1) If the source object has a valid string rep, we copy it;
 *           otherwise, the new string rep is marked invalid.
 *        2) If the source object has an internal representation (i.e. its
 *           typePtr is non-NULL), the new object's internal rep is set to
 *           a copy; otherwise the new internal rep is marked invalid.
 *
 * Side effects:
 *  Some object may, when copied, loose their type, i.e. will become
 *  just plain string objects.
 *
 *-----------------------------------------------------------------------------
 */

Tcl_Obj *
Sv_DuplicateObj(
    Tcl_Obj *objPtr        /* The object to duplicate. */
) {
    Tcl_Obj *dupPtr = Tcl_NewObj();

    /*
     * Handle the internal rep
     */

    if (objPtr->typePtr != NULL) {
        if (objPtr->typePtr->dupIntRepProc == NULL) {
            dupPtr->internalRep = objPtr->internalRep;
            dupPtr->typePtr = objPtr->typePtr;
            Tcl_InvalidateStringRep(dupPtr);
        } else {
            if (   objPtr->typePtr == booleanObjTypePtr    \
                || objPtr->typePtr == byteArrayObjTypePtr  \
                || objPtr->typePtr == doubleObjTypePtr     \
                || objPtr->typePtr == intObjTypePtr        \
                || objPtr->typePtr == wideIntObjTypePtr    \
                || objPtr->typePtr == stringObjTypePtr) {
               /*
                * Cover all "safe" obj types (see header comment)
                */
              (*objPtr->typePtr->dupIntRepProc)(objPtr, dupPtr);
              if (dupPtr->typePtr != NULL) {
                  Tcl_InvalidateStringRep(dupPtr);
              }
            } else {
                int found = 0;
                RegType *regPtr;
               /*
                * Cover special registered types. Assume not
                * very many of those, so this sequential walk
                * should be fast enough.
                */
                for (regPtr = regType; regPtr; regPtr = regPtr->nextPtr) {
                    if (objPtr->typePtr == regPtr->typePtr) {
                        (*regPtr->dupIntRepProc)(objPtr, dupPtr);
                        if (dupPtr->typePtr != NULL) {
                            Tcl_InvalidateStringRep(dupPtr);
                        }
                        found = 1;
                        break;
                    }
                }
               /*
                * Assure at least string rep of the source
                * is present, which will be copied below.
                */
                if (found == 0 && objPtr->bytes == NULL
                    && objPtr->typePtr->updateStringProc != NULL) {
                    (*objPtr->typePtr->updateStringProc)(objPtr);
                }
            }
        }
    }

    /*
     * Handle the string rep
     */

    if (objPtr->bytes == NULL) {
	if (dupPtr->bytes != Sv_tclEmptyStringRep) {
	    dupPtr->bytes = NULL;
	}
    } else if (objPtr->bytes != Sv_tclEmptyStringRep) {
	/* A copy of TclInitStringRep macro */
	dupPtr->bytes = (char*)ckalloc((unsigned)objPtr->length + 1);
	if (objPtr->length > 0) {
	    memcpy(dupPtr->bytes, objPtr->bytes,
		    objPtr->length);
	}
	dupPtr->length = objPtr->length;
	dupPtr->bytes[objPtr->length] = '\0';
    }

    return dupPtr;
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvObjDispatchObjCmd --
 *
 *      The method command for dispatching sub-commands of the shared
 *      object.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      Depends on the dispatched command
 *
 *-----------------------------------------------------------------------------
 */

static int
SvObjDispatchObjCmd(
                    ClientData arg,                     /* Pointer to object container. */
                    Tcl_Interp *interp,                 /* Current interpreter. */
                    int objc,                           /* Number of arguments. */
                    Tcl_Obj *const objv[])              /* Argument objects. */
{
    const char *cmdName;
    SvCmdInfo *cmdPtr;

    if (objc < 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "args");
        return TCL_ERROR;
    }

    cmdName = Tcl_GetString(objv[1]);

    /*
     * Do simple linear search. We may later replace this list
     * with the hash table to gain speed. Currently, the list
     * of registered commands is so small, so this will work
     * fast enough.
     */

    for (cmdPtr = svCmdInfo; cmdPtr; cmdPtr = cmdPtr->nextPtr) {
        if (!strcmp(cmdPtr->name, cmdName)) {
            return (*cmdPtr->objProcPtr)(arg, interp, objc, objv);
        }
    }

    Tcl_AppendResult(interp, "invalid command name \"", cmdName, "\"", NULL);
    return TCL_ERROR;
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvObjObjCmd --
 *
 *      Creates the object command for a shared array.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      New Tcl command gets created.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvObjObjCmd(
            ClientData arg,                     /* != NULL if aolSpecial */
            Tcl_Interp *interp,                 /* Current interpreter. */
            int objc,                           /* Number of arguments. */
            Tcl_Obj *const objv[])              /* Argument objects. */
{
    int isNew, off, ret, flg;
    char buf[128];
    Tcl_Obj *val = NULL;
    Container *svObj = NULL;

    /*
     * Syntax: sv::object array key ?var?
     */

    ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, 0);
    switch (ret) {
    case TCL_BREAK: /* Shared array was not found */
        if ((objc - off)) {
            val = objv[off];
        }
        Tcl_ResetResult(interp);
        flg = FLAGS_CREATEARRAY | FLAGS_CREATEVAR;
        ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, flg);
        if (ret != TCL_OK) {
            return TCL_ERROR;
        }
        Tcl_DecrRefCount(svObj->tclObj);
        svObj->tclObj = Sv_DuplicateObj(val ? val : Tcl_NewObj());
        Tcl_IncrRefCount(svObj->tclObj);
        break;
    case TCL_ERROR:
        return TCL_ERROR;
    }

    if (svObj->handlePtr == NULL) {
        Tcl_HashTable *handles = &svObj->arrayPtr->bucketPtr->handles;
        svObj->handlePtr = Tcl_CreateHashEntry(handles, (char*)svObj, &isNew);
    }

    /*
     * Format the command name
     */

    snprintf(buf, sizeof(buf), "::%p", (int*)svObj);
    svObj->aolSpecial = (arg != NULL);
    Tcl_CreateObjCommand(interp, buf, SvObjDispatchObjCmd, svObj, NULL);
    Tcl_ResetResult(interp);
    Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, -1));

    return Sv_PutContainer(interp, svObj, SV_UNCHANGED);
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvArrayObjCmd --
 *
 *      This procedure is invoked to process the "tsv::array" command.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvArrayObjCmd(
              ClientData arg,                     /* Pointer to object container. */
              Tcl_Interp *interp,                 /* Current interpreter. */
              int objc,                           /* Number of arguments. */
              Tcl_Obj *const objv[])              /* Argument objects. */
{
    int i, argx = 0, lobjc = 0, index, ret = TCL_OK;
    const char *arrayName = NULL;
    Array *arrayPtr = NULL;
    Tcl_Obj **lobjv = NULL;
    Container *svObj, *elObj = NULL;

    static const char *opts[] = {
        "set",  "reset", "get", "names", "size", "exists", "isbound",
        "bind", "unbind", NULL
    };
    enum options {
        ASET,   ARESET,  AGET,  ANAMES,  ASIZE,  AEXISTS, AISBOUND,
        ABIND,  AUNBIND
    };

    svObj = (Container*)arg;

    if (objc < 3) {
        Tcl_WrongNumArgs(interp, 1, objv, "option array");
        return TCL_ERROR;
    }

    arrayName = Tcl_GetString(objv[2]);
    arrayPtr  = LockArray(interp, arrayName, FLAGS_NOERRMSG);

    if (objc > 3) {
        argx = 3;
    }

    Tcl_ResetResult(interp);

    if (Tcl_GetIndexFromObjStruct(interp,objv[1],opts, sizeof(char *),"option",0,&index) != TCL_OK) {
        ret = TCL_ERROR;

    } else if (index == AEXISTS) {
        Tcl_SetIntObj(Tcl_GetObjResult(interp), arrayPtr!=0);

    } else if (index == AISBOUND) {
        if (arrayPtr == NULL) {
            Tcl_SetIntObj(Tcl_GetObjResult(interp), 0);
        } else {
            Tcl_SetIntObj(Tcl_GetObjResult(interp), arrayPtr->psPtr!=0);
        }

    } else if (index == ASIZE) {
        if (arrayPtr == NULL) {
            Tcl_SetIntObj(Tcl_GetObjResult(interp), 0);
        } else {
            Tcl_SetWideIntObj(Tcl_GetObjResult(interp),arrayPtr->vars.numEntries);
        }

    } else if (index == ASET || index == ARESET) {
        if (argx == (objc - 1)) {
            if (argx && Tcl_ListObjGetElements(interp, objv[argx], &lobjc,
                    &lobjv) != TCL_OK) {
                ret = TCL_ERROR;
                goto cmdExit;
            }
        } else {
            lobjc = objc - 3;
            lobjv = (Tcl_Obj**)objv + 3;
        }
        if (lobjc & 1) {
            Tcl_AppendResult(interp, "list must have an even number"
                    " of elements", NULL);
            ret = TCL_ERROR;
            goto cmdExit;
        }
        if (arrayPtr == NULL) {
            arrayPtr = LockArray(interp, arrayName, FLAGS_CREATEARRAY);
        }
        if (index == ARESET) {
            ret = FlushArray(arrayPtr);
            if (ret != TCL_OK) {
                if (arrayPtr->psPtr) {
                    PsStore *psPtr = arrayPtr->psPtr;
                    const char *err = psPtr->psError(psPtr->psHandle);
                    Tcl_SetObjResult(interp, Tcl_NewStringObj(err, -1));
                }
                goto cmdExit;
            }
        }
        for (i = 0; i < lobjc; i += 2) {
            const char *key = Tcl_GetString(lobjv[i]);
            elObj = AcquireContainer(arrayPtr, key, FLAGS_CREATEVAR);
            Tcl_DecrRefCount(elObj->tclObj);
            elObj->tclObj = Sv_DuplicateObj(lobjv[i+1]);
            Tcl_IncrRefCount(elObj->tclObj);
            if (ReleaseContainer(interp, elObj, SV_CHANGED) != TCL_OK) {
                ret = TCL_ERROR;
                goto cmdExit;
            }
        }

    } else if (index == AGET || index == ANAMES) {
        if (arrayPtr) {
            Tcl_HashSearch search;
            Tcl_Obj *resObj = Tcl_NewListObj(0, NULL);
            const char *pattern = (argx == 0) ? NULL : Tcl_GetString(objv[argx]);
            Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(&arrayPtr->vars,&search);
            while (hPtr) {
                char *key = (char *)Tcl_GetHashKey(&arrayPtr->vars, hPtr);
                if (pattern == NULL || Tcl_StringCaseMatch(key, pattern, 0)) {
                    Tcl_ListObjAppendElement(interp, resObj,
                            Tcl_NewStringObj(key, -1));
                    if (index == AGET) {
                        elObj = (Container*)Tcl_GetHashValue(hPtr);
                        Tcl_ListObjAppendElement(interp, resObj,
                                Sv_DuplicateObj(elObj->tclObj));
                    }
                }
                hPtr = Tcl_NextHashEntry(&search);
            }
            Tcl_SetObjResult(interp, resObj);
        }

    } else if (index == ABIND) {

        /*
         * This is more complex operation, requiring some clarification.
         *
         * When binding an already existing array, we walk the array
         * first and store all key/value pairs found there in the
         * persistent storage. Then we proceed with the below.
         *
         * When binding an non-existent array, we open the persistent
         * storage and cache all key/value pairs found there into tne
         * newly created shared array.
         */

        PsStore *psPtr;
        Tcl_HashEntry *hPtr;
        size_t len;
        int isNew;
        char *psurl, *key = NULL, *val = NULL;

        if (objc < 4) {
            Tcl_WrongNumArgs(interp, 2, objv, "array handle");
            ret = TCL_ERROR;
            goto cmdExit;
        }

        if (arrayPtr && arrayPtr->psPtr) {
            Tcl_AppendResult(interp, "array is already bound", NULL);
            ret = TCL_ERROR;
            goto cmdExit;
        }

        psurl = Tcl_GetString(objv[3]);
        len = objv[3]->length;
        psPtr = GetPsStore(psurl);

        if (psPtr == NULL) {
            Tcl_AppendResult(interp, "can't open persistent storage on \"",
                             psurl, "\"", NULL);
            ret = TCL_ERROR;
            goto cmdExit;
        }
        if (arrayPtr) {
            Tcl_HashSearch search;
            hPtr = Tcl_FirstHashEntry(&arrayPtr->vars,&search);
            arrayPtr->psPtr = psPtr;
            arrayPtr->bindAddr = strcpy((char *)ckalloc(len+1), psurl);
            while (hPtr) {
                svObj = (Container *)Tcl_GetHashValue(hPtr);
                if (ReleaseContainer(interp, svObj, SV_CHANGED) != TCL_OK) {
                    ret = TCL_ERROR;
                    goto cmdExit;
                }
                hPtr = Tcl_NextHashEntry(&search);
            }
        } else {
            arrayPtr = LockArray(interp, arrayName, FLAGS_CREATEARRAY);
            arrayPtr->psPtr = psPtr;
            arrayPtr->bindAddr = strcpy((char *)ckalloc(len+1), psurl);
        }
        if (!psPtr->psFirst(psPtr->psHandle, &key, &val, &len)) {
            do {
                Tcl_Obj * tclObj = Tcl_NewStringObj(val, len);
                hPtr = Tcl_CreateHashEntry(&arrayPtr->vars, key, &isNew);
                Tcl_SetHashValue(hPtr, CreateContainer(arrayPtr, hPtr, tclObj));
                psPtr->psFree(psPtr->psHandle, val);
            } while (!psPtr->psNext(psPtr->psHandle, &key, &val, &len));
        }

    } else if (index == AUNBIND) {
        if (!arrayPtr || !arrayPtr->psPtr) {
            Tcl_AppendResult(interp, "shared variable is not bound", NULL);
            ret = TCL_ERROR;
            goto cmdExit;
        }
        if (UnbindArray(interp, arrayPtr) != TCL_OK) {
            ret = TCL_ERROR;
            goto cmdExit;
        }
    }

 cmdExit:
    if (arrayPtr) {
        UnlockArray(arrayPtr);
    }

    return ret;
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvUnsetObjCmd --
 *
 *      This procedure is invoked to process the "tsv::unset" command.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvUnsetObjCmd(
              ClientData dummy,                   /* Not used. */
              Tcl_Interp *interp,                 /* Current interpreter. */
              int objc,                           /* Number of arguments. */
              Tcl_Obj *const objv[])              /* Argument objects. */
{
    int ii;
    const char *arrayName;
    Array *arrayPtr;
    (void)dummy;

    if (objc < 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "array ?key ...?");
        return TCL_ERROR;
    }

    arrayName = Tcl_GetString(objv[1]);
    arrayPtr  = LockArray(interp, arrayName, 0);

    if (arrayPtr == NULL) {
        return TCL_ERROR;
    }
    if (objc == 2) {
        UnlockArray(arrayPtr);
        if (DeleteArray(interp, arrayPtr) != TCL_OK) {
            return TCL_ERROR;
        }
    } else {
        for (ii = 2; ii < objc; ii++) {
            const char *key = Tcl_GetString(objv[ii]);
            Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&arrayPtr->vars, key);
            if (hPtr) {
                if (DeleteContainer((Container*)Tcl_GetHashValue(hPtr))
                    != TCL_OK) {
                    UnlockArray(arrayPtr);
                    return TCL_ERROR;
                }
            } else {
                UnlockArray(arrayPtr);
                Tcl_AppendResult(interp,"no key ",arrayName,"(",key,")",NULL);
                return TCL_ERROR;
            }
        }
        UnlockArray(arrayPtr);
    }

    return TCL_OK;
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvNamesObjCmd --
 *
 *      This procedure is invoked to process the "tsv::names" command.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvNamesObjCmd(
              ClientData arg,                     /* != NULL if aolSpecial */
              Tcl_Interp *interp,                 /* Current interpreter. */
              int objc,                           /* Number of arguments. */
              Tcl_Obj *const objv[])              /* Argument objects. */
{
    int i;
    const char *pattern = NULL;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;
    Tcl_Obj *resObj;

    if (objc > 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "?pattern?");
        return TCL_ERROR;
    }
    if (objc == 2) {
        pattern = Tcl_GetString(objv[1]);
    }

    resObj = Tcl_NewListObj(0, NULL);

    for (i = 0; i < NUMBUCKETS; i++) {
        Bucket *bucketPtr = &buckets[i];
        LOCK_BUCKET(bucketPtr);
        hPtr = Tcl_FirstHashEntry(&bucketPtr->arrays, &search);
        while (hPtr) {
            char *key = (char *)Tcl_GetHashKey(&bucketPtr->arrays, hPtr);
            if ((arg==NULL || (*key != '.')) /* Hide .<name> arrays for AOL*/ &&
                (pattern == NULL || Tcl_StringCaseMatch(key, pattern, 0))) {
                Tcl_ListObjAppendElement(interp, resObj,
                        Tcl_NewStringObj(key, -1));
            }
            hPtr = Tcl_NextHashEntry(&search);
        }
        UNLOCK_BUCKET(bucketPtr);
    }

    Tcl_SetObjResult(interp, resObj);

    return TCL_OK;
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvGetObjCmd --
 *
 *      This procedure is invoked to process "tsv::get" command.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvGetObjCmd(
            ClientData arg,                     /* Pointer to object container. */
            Tcl_Interp *interp,                 /* Current interpreter. */
            int objc,                           /* Number of arguments. */
            Tcl_Obj *const objv[])              /* Argument objects. */
{
    int off, ret;
    Tcl_Obj *res;
    Container *svObj = (Container*)arg;

    /*
     * Syntax:
     *          tsv::get array key ?var?
     *          $object get ?var?
     */

    ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, 0);
    switch (ret) {
    case TCL_BREAK:
        if ((objc - off) == 0) {
            return TCL_ERROR;
        } else {
            Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
            return TCL_OK;
        }
    case TCL_ERROR:
        return TCL_ERROR;
    }

    res = Sv_DuplicateObj(svObj->tclObj);

    if ((objc - off) == 0) {
        Tcl_SetObjResult(interp, res);
    } else {
        if (Tcl_ObjSetVar2(interp, objv[off], NULL, res, 0) == NULL) {
            Tcl_DecrRefCount(res);
            goto cmd_err;
        }
        Tcl_SetObjResult(interp, Tcl_NewIntObj(1));
    }

    return Sv_PutContainer(interp, svObj, SV_UNCHANGED);

 cmd_err:
    return Sv_PutContainer(interp, svObj, SV_ERROR);
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvExistsObjCmd --
 *
 *      This procedure is invoked to process "tsv::exists" command.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvExistsObjCmd(
               ClientData arg,                     /* Pointer to object container. */
               Tcl_Interp *interp,                 /* Current interpreter. */
               int objc,                           /* Number of arguments. */
               Tcl_Obj *const objv[])              /* Argument objects. */
{
    int off, ret;
    Container *svObj = (Container*)arg;

    /*
     * Syntax:
     *          tsv::exists array key
     *          $object exists
     */

    ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, 0);
    switch (ret) {
    case TCL_BREAK: /* Array/key not found */
        Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
        return TCL_OK;
    case TCL_ERROR:
        return TCL_ERROR;
    }

    Tcl_SetObjResult(interp, Tcl_NewIntObj(1));

    return Sv_PutContainer(interp, svObj, SV_UNCHANGED);
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvSetObjCmd --
 *
 *      This procedure is invoked to process the "tsv::set" command.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvSetObjCmd(
            ClientData arg,                     /* Pointer to object container */
            Tcl_Interp *interp,                 /* Current interpreter. */
            int objc,                           /* Number of arguments. */
            Tcl_Obj *const objv[])              /* Argument objects. */
{
    int ret, off, flg, mode;
    Tcl_Obj *val;
    Container *svObj = (Container*)arg;

    /*
     * Syntax:
     *          tsv::set array key ?value?
     *          $object set ?value?
     */

    ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, 0);
    switch (ret) {
    case TCL_BREAK:
        if ((objc - off) == 0) {
            return TCL_ERROR;
        } else {
            Tcl_ResetResult(interp);
            flg = FLAGS_CREATEARRAY | FLAGS_CREATEVAR;
            ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, flg);
            if (ret != TCL_OK) {
                return TCL_ERROR;
            }
        }
        break;
    case TCL_ERROR:
        return TCL_ERROR;
    }
    if ((objc - off)) {
        val = objv[off];
        Tcl_DecrRefCount(svObj->tclObj);
        svObj->tclObj = Sv_DuplicateObj(val);
        Tcl_IncrRefCount(svObj->tclObj);
        mode = SV_CHANGED;
    } else {
        val = Sv_DuplicateObj(svObj->tclObj);
        mode = SV_UNCHANGED;
    }

    Tcl_SetObjResult(interp, val);

    return Sv_PutContainer(interp, svObj, mode);
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvIncrObjCmd --
 *
 *      This procedure is invoked to process the "tsv::incr" command.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvIncrObjCmd(
             ClientData arg,                     /* Pointer to object container */
             Tcl_Interp *interp,                 /* Current interpreter. */
             int objc,                           /* Number of arguments. */
             Tcl_Obj *const objv[])              /* Argument objects. */
{
    int off, ret, flg, isNew = 0;
    Tcl_WideInt incrValue = 1, currValue = 0;
    Container *svObj = (Container*)arg;

    /*
     * Syntax:
     *          tsv::incr array key ?increment?
     *          $object incr ?increment?
     */

    ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, 0);
    if (ret != TCL_OK) {
        if (ret != TCL_BREAK) {
            return TCL_ERROR;
        }
        flg = FLAGS_CREATEARRAY | FLAGS_CREATEVAR;
        Tcl_ResetResult(interp);
        ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, flg);
        if (ret != TCL_OK) {
            return TCL_ERROR;
        }
        isNew = 1;
    }
    if ((objc - off)) {
        ret = Tcl_GetWideIntFromObj(interp, objv[off], &incrValue);
        if (ret != TCL_OK) {
            goto cmd_err;
        }
    }
    if (isNew) {
        currValue = 0;
    } else {
        ret = Tcl_GetWideIntFromObj(interp, svObj->tclObj, &currValue);
        if (ret != TCL_OK) {
            goto cmd_err;
        }
    }

    incrValue += currValue;
    Tcl_SetWideIntObj(svObj->tclObj, incrValue);
    Tcl_ResetResult(interp);
    Tcl_SetWideIntObj(Tcl_GetObjResult(interp), incrValue);

    return Sv_PutContainer(interp, svObj, SV_CHANGED);

 cmd_err:
    return Sv_PutContainer(interp, svObj, SV_ERROR);
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvAppendObjCmd --
 *
 *      This procedure is invoked to process the "tsv::append" command.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvAppendObjCmd(
               ClientData arg,                     /* Pointer to object container */
               Tcl_Interp *interp,                 /* Current interpreter. */
               int objc,                           /* Number of arguments. */
               Tcl_Obj *const objv[])              /* Argument objects. */
{
    int i, off, flg, ret;
    Container *svObj = (Container*)arg;

    /*
     * Syntax:
     *          tsv::append array key value ?value ...?
     *          $object append value ?value ...?
     */

    flg = FLAGS_CREATEARRAY | FLAGS_CREATEVAR;
    ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, flg);
    if (ret != TCL_OK) {
        return TCL_ERROR;
    }
    if ((objc - off) < 1) {
        Tcl_WrongNumArgs(interp, off, objv, "value ?value ...?");
        goto cmd_err;
    }
    for (i = off; i < objc; ++i) {
        Tcl_AppendObjToObj(svObj->tclObj, Sv_DuplicateObj(objv[i]));
    }

    Tcl_SetObjResult(interp, Sv_DuplicateObj(svObj->tclObj));

    return Sv_PutContainer(interp, svObj, SV_CHANGED);

 cmd_err:
    return Sv_PutContainer(interp, svObj, SV_ERROR);
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvPopObjCmd --
 *
 *      This procedure is invoked to process "tsv::pop" command.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvPopObjCmd(
            ClientData arg,                     /* Pointer to object container */
            Tcl_Interp *interp,                 /* Current interpreter. */
            int objc,                           /* Number of arguments. */
            Tcl_Obj *const objv[])              /* Argument objects. */
{
    int ret, off;
    Tcl_Obj *retObj;
    Array *arrayPtr = NULL;
    Container *svObj = (Container*)arg;

    /*
     * Syntax:
     *          tsv::pop array key ?var?
     *          $object pop ?var?
     *
     * Note: the object command will run into error next time !
     */

    ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, 0);
    switch (ret) {
    case TCL_BREAK:
        if ((objc - off) == 0) {
            return TCL_ERROR;
        } else {
            Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
            return TCL_OK;
        }
    case TCL_ERROR:
        return TCL_ERROR;
    }

    arrayPtr = svObj->arrayPtr;

    retObj = svObj->tclObj;
    svObj->tclObj = NULL;

    if (DeleteContainer(svObj) != TCL_OK) {
        if (svObj->arrayPtr->psPtr) {
            PsStore *psPtr = svObj->arrayPtr->psPtr;
            const char *err = psPtr->psError(psPtr->psHandle);
            Tcl_SetObjResult(interp, Tcl_NewStringObj(err,-1));
        }
        ret = TCL_ERROR;
        goto cmd_exit;
    }

    if ((objc - off) == 0) {
        Tcl_SetObjResult(interp, retObj);
    } else {
        if (Tcl_ObjSetVar2(interp, objv[off], NULL, retObj, 0) == NULL) {
            ret = TCL_ERROR;
            goto cmd_exit;
        }
        Tcl_SetObjResult(interp, Tcl_NewIntObj(1));
    }

  cmd_exit:
    Tcl_DecrRefCount(retObj);
    UnlockArray(arrayPtr);

    return ret;
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvMoveObjCmd --
 *
 *      This procedure is invoked to process the "tsv::move" command.
 *      See the user documentation for details on what it does.
 *
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

static int
SvMoveObjCmd(
             ClientData arg,                     /* Pointer to object container. */
             Tcl_Interp *interp,                 /* Current interpreter. */
             int objc,                           /* Number of arguments. */
             Tcl_Obj *const objv[])              /* Argument objects. */
{
    int ret, off, isNew;
    const char *toKey;
    Tcl_HashEntry *hPtr;
    Container *svObj = (Container*)arg;

    /*
     * Syntax:
     *          tsv::move array key to
     *          $object move to
     */

    ret = Sv_GetContainer(interp, objc, objv, &svObj, &off, 0);
    if (ret != TCL_OK) {
        return TCL_ERROR;
    }

    toKey = Tcl_GetString(objv[off]);
    hPtr = Tcl_CreateHashEntry(&svObj->arrayPtr->vars, toKey, &isNew);

    if (!isNew) {
        Tcl_AppendResult(interp, "key \"", toKey, "\" exists", NULL);
        goto cmd_err;
    }
    if (svObj->entryPtr) {
        char *key = (char *)Tcl_GetHashKey(&svObj->arrayPtr->vars, svObj->entryPtr);
        if (svObj->arrayPtr->psPtr) {
            PsStore *psPtr = svObj->arrayPtr->psPtr;
            if (psPtr->psDelete(psPtr->psHandle, key) == -1) {
                const char *err = psPtr->psError(psPtr->psHandle);
                Tcl_SetObjResult(interp, Tcl_NewStringObj(err, -1));
                return TCL_ERROR;
            }
        }
        Tcl_DeleteHashEntry(svObj->entryPtr);
    }

    svObj->entryPtr = hPtr;
    Tcl_SetHashValue(hPtr, svObj);

    return Sv_PutContainer(interp, svObj, SV_CHANGED);

 cmd_err:
    return Sv_PutContainer(interp, svObj, SV_ERROR);

}

/*
 *----------------------------------------------------------------------
 *
 * SvLockObjCmd --
 *
 *    This procedure is invoked to process "tsv::lock" Tcl command.
 *    See the user documentation for details on what it does.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    See the user documentation.
 *
 *----------------------------------------------------------------------
 */

static int
SvLockObjCmd(
             ClientData dummy,                   /* Not used. */
             Tcl_Interp *interp,                 /* Current interpreter. */
             int objc,                           /* Number of arguments. */
             Tcl_Obj *const objv[])              /* Argument objects. */
{
    int ret;
    Tcl_Obj *scriptObj;
    Bucket *bucketPtr;
    Array *arrayPtr = NULL;
    (void)dummy;

    /*
     * Syntax:
     *
     *     tsv::lock array arg ?arg ...?
     */

    if (objc < 3) {
        Tcl_WrongNumArgs(interp, 1, objv, "array arg ?arg...?");
        return TCL_ERROR;
    }

    arrayPtr  = LockArray(interp, Tcl_GetString(objv[1]), FLAGS_CREATEARRAY);
    bucketPtr = arrayPtr->bucketPtr;

    /*
     * Evaluate passed arguments as Tcl script. Note that
     * Tcl_EvalObjEx throws away the passed object by
     * doing an decrement reference count on it. This also
     * means we need not build object bytecode rep.
     */

    if (objc == 3) {
        scriptObj = Tcl_DuplicateObj(objv[2]);
    } else {
        scriptObj = Tcl_ConcatObj(objc-2, objv + 2);
    }

    Tcl_AllowExceptions(interp);
    ret = Tcl_EvalObjEx(interp, scriptObj, TCL_EVAL_DIRECT);

    if (ret == TCL_ERROR) {
        char msg[32 + TCL_INTEGER_SPACE];
        /* Next line generates a Deprecation warning when compiled with Tcl 8.6.
         * See Tcl bug #3562640 */
        snprintf(msg, sizeof(msg), "\n    (\"eval\" body line %d)", Tcl_GetErrorLine(interp));
        Tcl_AddErrorInfo(interp, msg);
    }

    /*
     * We unlock the bucket directly, w/o going to Sv_Unlock()
     * since it needs the array which may be unset by the script.
     */

    UNLOCK_BUCKET(bucketPtr);

    return ret;
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvHandlersObjCmd --
 *
 *    This procedure is invoked to process "tsv::handlers" Tcl command.
 *    See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      None.
 *
 *-----------------------------------------------------------------------------
 */
static int
SvHandlersObjCmd(
             ClientData dummy,                     /* Not used. */
             Tcl_Interp *interp,                 /* Current interpreter. */
             int objc,                           /* Number of arguments. */
             Tcl_Obj *const objv[])              /* Argument objects. */
{
    PsStore *tmpPtr = NULL;
    (void)dummy;

    /*
     * Syntax:
     *
     *     tsv::handlers
     */

    if (objc != 1) {
        Tcl_WrongNumArgs(interp, 1, objv, NULL);
        return TCL_ERROR;
    }

    Tcl_ResetResult(interp);
    Tcl_MutexLock(&svMutex);
    for (tmpPtr = psStore; tmpPtr; tmpPtr = tmpPtr->nextPtr) {
        Tcl_AppendElement(interp, tmpPtr->type);
    }
    Tcl_MutexUnlock(&svMutex);

    return TCL_OK;
}


/*
 *-----------------------------------------------------------------------------
 *
 * Sv_RegisterStdCommands --
 *
 *      Register standard shared variable commands
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      Memory gets allocated
 *
 *-----------------------------------------------------------------------------
 */

static void
SvRegisterStdCommands(void)
{
    static int initialized = 0;

    if (initialized == 0) {
        Tcl_MutexLock(&initMutex);
        if (initialized == 0) {
            Sv_RegisterCommand("var",      SvObjObjCmd,      NULL, 1);
            Sv_RegisterCommand("object",   SvObjObjCmd,      NULL, 1);
            Sv_RegisterCommand("set",      SvSetObjCmd,      NULL, 0);
            Sv_RegisterCommand("unset",    SvUnsetObjCmd,    NULL, 0);
            Sv_RegisterCommand("get",      SvGetObjCmd,      NULL, 0);
            Sv_RegisterCommand("incr",     SvIncrObjCmd,     NULL, 0);
            Sv_RegisterCommand("exists",   SvExistsObjCmd,   NULL, 0);
            Sv_RegisterCommand("append",   SvAppendObjCmd,   NULL, 0);
            Sv_RegisterCommand("array",    SvArrayObjCmd,    NULL, 0);
            Sv_RegisterCommand("names",    SvNamesObjCmd,    NULL, 0);
            Sv_RegisterCommand("pop",      SvPopObjCmd,      NULL, 0);
            Sv_RegisterCommand("move",     SvMoveObjCmd,     NULL, 0);
            Sv_RegisterCommand("lock",     SvLockObjCmd,     NULL, 0);
            Sv_RegisterCommand("handlers", SvHandlersObjCmd, NULL, 0);
            initialized = 1;
        }
        Tcl_MutexUnlock(&initMutex);
    }
}

/*
 *-----------------------------------------------------------------------------
 *
 * SvInit --
 *
 *    Creates commands in current interpreter.
 *
 * Results:
 *    NULL
 *
 * Side effects
 *    Many new command created in current interpreter. Global data
 *    structures used by them initialized as well.
 *
 *-----------------------------------------------------------------------------
 */
const char *
SvInit (
    Tcl_Interp *interp
) {
    int i;
    Bucket *bucketPtr;
    SvCmdInfo *cmdPtr;
    Tcl_Obj *obj;

#ifdef SV_FINALIZE
    /*
     * Create exit handler for this thread
     */
    Tcl_CreateThreadExitHandler(SvFinalize, NULL);

    /*
     * Increment number of threads
     */
    Tcl_MutexLock(&nofThreadsMutex);
    ++nofThreads;
    Tcl_MutexUnlock(&nofThreadsMutex);
#endif /* SV_FINALIZE */

    /*
     * Add keyed-list datatype
     */

    TclX_KeyedListInit(interp);
    Sv_RegisterKeylistCommands();

    /*
     * Register standard (nsv_* compatible) and our
     * own extensive set of list manipulating commands
     */

    SvRegisterStdCommands();
    Sv_RegisterListCommands();

    /*
     * Get Tcl object types. These are used
     * in custom object duplicator function.
     */

    obj = Tcl_NewStringObj("no", -1);
    Tcl_GetBooleanFromObj(NULL, obj, &i);
    booleanObjTypePtr   = obj->typePtr;

#ifdef USE_TCL_STUBS
    if (Tcl_GetUnicodeFromObj)
#endif
    {
	Tcl_GetUnicodeFromObj(obj, &i);
	stringObjTypePtr = obj->typePtr;
    }
    Tcl_GetByteArrayFromObj(obj, &i);
    byteArrayObjTypePtr = obj->typePtr;
    Tcl_DecrRefCount(obj);

    obj = Tcl_NewDoubleObj(0.0);
    doubleObjTypePtr    = obj->typePtr;
    Tcl_DecrRefCount(obj);

    obj = Tcl_NewIntObj(0);
    intObjTypePtr       = obj->typePtr;
    Tcl_DecrRefCount(obj);

    obj = Tcl_NewWideIntObj(((Tcl_WideInt)1)<<35);
    wideIntObjTypePtr       = obj->typePtr;
    Tcl_DecrRefCount(obj);

    /*
     * Plug-in registered commands in current interpreter
     */

    for (cmdPtr = svCmdInfo; cmdPtr; cmdPtr = cmdPtr->nextPtr) {
        Tcl_CreateObjCommand(interp, cmdPtr->cmdName, cmdPtr->objProcPtr,
                NULL, (Tcl_CmdDeleteProc*)0);
#ifdef NS_AOLSERVER
        Tcl_CreateObjCommand(interp, cmdPtr->cmdName2, cmdPtr->objProcPtr,
                (ClientData)(size_t)cmdPtr->aolSpecial, (Tcl_CmdDeleteProc*)0);
#endif
    }

    /*
     * Create array of buckets and initialize each bucket
     */

    if (buckets == NULL) {
        Tcl_MutexLock(&bucketsMutex);
        if (buckets == NULL) {
            buckets = (Bucket *)ckalloc(sizeof(Bucket) * NUMBUCKETS);

            for (i = 0; i < NUMBUCKETS; ++i) {
                bucketPtr = &buckets[i];
                memset(bucketPtr, 0, sizeof(Bucket));
                Tcl_InitHashTable(&bucketPtr->arrays, TCL_STRING_KEYS);
                Tcl_InitHashTable(&bucketPtr->handles, TCL_ONE_WORD_KEYS);
            }

            /*
             * There is no other way to get Sv_tclEmptyStringRep
             * pointer value w/o this trick.
             */

            {
                Tcl_Obj *dummy = Tcl_NewObj();
                Sv_tclEmptyStringRep = dummy->bytes;
                Tcl_DecrRefCount(dummy);
            }

            /*
             * Register persistent store handlers
             */
#ifdef HAVE_GDBM
            Sv_RegisterGdbmStore();
#endif
#ifdef HAVE_LMDB
            Sv_RegisterLmdbStore();
#endif
        }
        Tcl_MutexUnlock(&bucketsMutex);
    }

    return NULL;
}

#ifdef SV_FINALIZE
/*
 * Left for reference, but unused since multithreaded finalization is
 * unsolvable in the general case. Brave souls can revive this by
 * installing a late exit handler on Thread's behalf, bringing the
 * function back onto the Tcl_Finalize (but not Tcl_Exit) path.
 */

/*
 *-----------------------------------------------------------------------------
 *
 * SvFinalize --
 *
 *    Unset all arrays and reclaim all buckets.
 *
 * Results:
 *    None.
 *
 * Side effects
 *    Memory gets reclaimed.
 *
 *-----------------------------------------------------------------------------
 */

static void
SvFinalize (ClientData dummy)
{
    int i;
    SvCmdInfo *cmdPtr;
    RegType *regPtr;

    Tcl_HashEntry *hashPtr;
    Tcl_HashSearch search;
    (void)dummy;

    /*
     * Decrement number of threads. Proceed only if I was the last one. The
     * mutex is unlocked at the end of this function, so new threads that might
     * want to register in the meanwhile will find a clean environment when
     * they eventually succeed acquiring nofThreadsMutex.
     */
    Tcl_MutexLock(&nofThreadsMutex);
    if (nofThreads > 1)
    {
        goto done;
    }

    /*
     * Reclaim memory for shared arrays
     */

    if (buckets != NULL) {
        Tcl_MutexLock(&bucketsMutex);
        if (buckets != NULL) {
            for (i = 0; i < NUMBUCKETS; ++i) {
                Bucket *bucketPtr = &buckets[i];
                hashPtr = Tcl_FirstHashEntry(&bucketPtr->arrays, &search);
                while (hashPtr != NULL) {
                    Array *arrayPtr = (Array*)Tcl_GetHashValue(hashPtr);
                    UnlockArray(arrayPtr);
                    /* unbind array before delete (avoid flush of persistent storage) */
                    UnbindArray(NULL, arrayPtr);
                    /* flush, delete etc. */
                    DeleteArray(NULL, arrayPtr);
                    hashPtr = Tcl_NextHashEntry(&search);
                }
                if (bucketPtr->lock) {
                    Sp_RecursiveMutexFinalize(&bucketPtr->lock);
                }
                SvFinalizeContainers(bucketPtr);
                Tcl_DeleteHashTable(&bucketPtr->handles);
                Tcl_DeleteHashTable(&bucketPtr->arrays);
            }
            ckfree((char *)buckets), buckets = NULL;
        }
        buckets = NULL;
        Tcl_MutexUnlock(&bucketsMutex);
    }

    Tcl_MutexLock(&svMutex);

    /*
     * Reclaim memory for registered commands
     */

    if (svCmdInfo != NULL) {
        cmdPtr = svCmdInfo;
        while (cmdPtr) {
            SvCmdInfo *tmpPtr = cmdPtr->nextPtr;
            ckfree((char*)cmdPtr);
            cmdPtr = tmpPtr;
        }
        svCmdInfo = NULL;
    }

    /*
     * Reclaim memory for registered object types
     */

    if (regType != NULL) {
        regPtr = regType;
        while (regPtr) {
            RegType *tmpPtr = regPtr->nextPtr;
            ckfree((char*)regPtr);
            regPtr = tmpPtr;
        }
        regType = NULL;
    }

    Tcl_MutexUnlock(&svMutex);

done:
    --nofThreads;
    Tcl_MutexUnlock(&nofThreadsMutex);
}
#endif /* SV_FINALIZE */

/* EOF $RCSfile: threadSvCmd.c,v $ */

/* Emacs Setup Variables */
/* Local Variables:      */
/* mode: C               */
/* indent-tabs-mode: nil */
/* c-basic-offset: 4     */
/* End:                  */

