/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
** prcountr.c -- NSPR Instrumentation Counters
**
** Implement the interface defined in prcountr.h
**
** Design Notes:
**
** The Counter Facility (CF) has a single anchor: qNameList.
** The anchor is a PRCList. qNameList is a list of links in QName
** structures. From qNameList any QName structure and its
** associated RName structure can be located.
**
** For each QName, a list of RName structures is anchored at
** rnLink in the QName structure.
**
** The counter itself is embedded in the RName structure.
**
** For manipulating the counter database, single lock is used to
** protect the entire list: counterLock.
**
** A PRCounterHandle, defined in prcountr.h, is really a pointer
** to a RName structure. References by PRCounterHandle are
** dead-reconed to the RName structure. The PRCounterHandle is
** "overloaded" for traversing the QName structures; only the
** function PR_FindNextQnameHandle() uses this overloading.
**
**
** ToDo (lth): decide on how to lock or atomically update
** individual counters. Candidates are: the global lock; a lock
** per RName structure; Atomic operations (Note that there are
** not adaquate atomic operations (yet) to achieve this goal). At
** this writing (6/19/98) , the update of the counter variable in
** a QName structure is unprotected.
**
*/

#include "prcountr.h"
#include "prclist.h"
#include "prlock.h"
#include "prlog.h"
#include "prmem.h"
#include <string.h>

/*
**
*/
typedef struct QName
{
    PRCList link;
    PRCList rNameList;
    char    name[PRCOUNTER_NAME_MAX+1];
} QName;

/*
**
*/
typedef struct RName
{
    PRCList link;
    QName   *qName;
    PRLock  *lock;
    volatile PRUint32   counter;
    char    name[PRCOUNTER_NAME_MAX+1];
    char    desc[PRCOUNTER_DESC_MAX+1];
} RName;


/*
** Define the Counter Facility database
*/
static PRLock  *counterLock;
static PRCList qNameList;
static PRLogModuleInfo *lm;

/*
** _PR_CounterInitialize() -- Initialize the Counter Facility
**
*/
static void _PR_CounterInitialize( void )
{
    /*
    ** This function should be called only once
    */
    PR_ASSERT( counterLock == NULL );

    counterLock = PR_NewLock();
    PR_INIT_CLIST( &qNameList );
    lm = PR_NewLogModule("counters");
    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: Initialization complete"));

    return;
} /* end _PR_CounterInitialize() */

/*
** PR_CreateCounter() -- Create a counter
**
**  ValidateArguments
**  Lock
**  if (qName not already in database)
**      NewQname
**  if (rName already in database )
**      Assert
**  else NewRname
**  NewCounter
**  link 'em up
**  Unlock
**
*/
PR_IMPLEMENT(PRCounterHandle)
PR_CreateCounter(
    const char *qName,
    const char *rName,
    const char *description
)
{
    QName   *qnp;
    RName   *rnp;
    PRBool  matchQname = PR_FALSE;

    /* Self initialize, if necessary */
    if ( counterLock == NULL ) {
        _PR_CounterInitialize();
    }

    /* Validate input arguments */
    PR_ASSERT( strlen(qName) <= PRCOUNTER_NAME_MAX );
    PR_ASSERT( strlen(rName) <= PRCOUNTER_NAME_MAX );
    PR_ASSERT( strlen(description) <= PRCOUNTER_DESC_MAX );

    /* Lock the Facility */
    PR_Lock( counterLock );

    /* Do we already have a matching QName? */
    if (!PR_CLIST_IS_EMPTY( &qNameList ))
    {
        qnp = (QName *) PR_LIST_HEAD( &qNameList );
        do {
            if ( strcmp(qnp->name, qName) == 0)
            {
                matchQname = PR_TRUE;
                break;
            }
            qnp = (QName *)PR_NEXT_LINK( &qnp->link );
        } while( qnp != (QName *)&qNameList );
    }
    /*
    ** If we did not find a matching QName,
    **    allocate one and initialize it.
    **    link it onto the qNameList.
    **
    */
    if ( matchQname != PR_TRUE )
    {
        qnp = PR_NEWZAP( QName );
        PR_ASSERT( qnp != NULL );
        PR_INIT_CLIST( &qnp->link );
        PR_INIT_CLIST( &qnp->rNameList );
        strcpy( qnp->name, qName );
        PR_APPEND_LINK( &qnp->link, &qNameList );
    }

    /* Do we already have a matching RName? */
    if (!PR_CLIST_IS_EMPTY( &qnp->rNameList ))
    {
        rnp = (RName *) PR_LIST_HEAD( &qnp->rNameList );
        do {
            /*
            ** No duplicate RNames are allowed within a QName
            **
            */
            PR_ASSERT( strcmp(rnp->name, rName));
            rnp = (RName *)PR_NEXT_LINK( &rnp->link );
        } while( rnp != (RName *)&qnp->rNameList );
    }

    /* Get a new RName structure; initialize its members */
    rnp = PR_NEWZAP( RName );
    PR_ASSERT( rnp != NULL );
    PR_INIT_CLIST( &rnp->link );
    strcpy( rnp->name, rName );
    strcpy( rnp->desc, description );
    rnp->lock = PR_NewLock();
    if ( rnp->lock == NULL )
    {
        PR_ASSERT(0);
    }

    PR_APPEND_LINK( &rnp->link, &qnp->rNameList ); /* add RName to QName's rnList */
    rnp->qName = qnp;                       /* point the RName to the QName */

    /* Unlock the Facility */
    PR_Unlock( counterLock );
    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: Create: QName: %s %p, RName: %s %p\n\t",
                               qName, qnp, rName, rnp ));

    return((PRCounterHandle)rnp);
} /*  end PR_CreateCounter() */


/*
**
*/
PR_IMPLEMENT(void)
PR_DestroyCounter(
    PRCounterHandle handle
)
{
    RName   *rnp = (RName *)handle;
    QName   *qnp = rnp->qName;

    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: Deleting: QName: %s, RName: %s",
                               qnp->name, rnp->name));

    /* Lock the Facility */
    PR_Lock( counterLock );

    /*
    ** Remove RName from the list of RNames in QName
    ** and free RName
    */
    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: Deleting RName: %s, %p",
                               rnp->name, rnp));
    PR_REMOVE_LINK( &rnp->link );
    PR_Free( rnp->lock );
    PR_DELETE( rnp );

    /*
    ** If this is the last RName within QName
    **   remove QName from the qNameList and free it
    */
    if ( PR_CLIST_IS_EMPTY( &qnp->rNameList ) )
    {
        PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: Deleting unused QName: %s, %p",
                                   qnp->name, qnp));
        PR_REMOVE_LINK( &qnp->link );
        PR_DELETE( qnp );
    }

    /* Unlock the Facility */
    PR_Unlock( counterLock );
    return;
} /*  end PR_DestroyCounter() */

/*
**
*/
PR_IMPLEMENT(PRCounterHandle)
PR_GetCounterHandleFromName(
    const char *qName,
    const char *rName
)
{
    const char    *qn, *rn, *desc;
    PRCounterHandle     qh, rh = NULL;
    RName   *rnp = NULL;

    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: GetCounterHandleFromName:\n\t"
                               "QName: %s, RName: %s", qName, rName ));

    qh = PR_FindNextCounterQname( NULL );
    while (qh != NULL)
    {
        rh = PR_FindNextCounterRname( NULL, qh );
        while ( rh != NULL )
        {
            PR_GetCounterNameFromHandle( rh, &qn, &rn, &desc );
            if ( (strcmp( qName, qn ) == 0)
                 && (strcmp( rName, rn ) == 0 ))
            {
                rnp = (RName *)rh;
                goto foundIt;
            }
            rh = PR_FindNextCounterRname( rh, qh );
        }
        qh = PR_FindNextCounterQname( NULL );
    }

foundIt:
    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: GetConterHandleFromName: %p", rnp ));
    return(rh);
} /*  end PR_GetCounterHandleFromName() */

/*
**
*/
PR_IMPLEMENT(void)
PR_GetCounterNameFromHandle(
    PRCounterHandle handle,
    const char **qName,
    const char **rName,
    const char **description
)
{
    RName   *rnp = (RName *)handle;
    QName   *qnp = rnp->qName;

    *qName = qnp->name;
    *rName = rnp->name;
    *description = rnp->desc;

    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: GetConterNameFromHandle: "
                               "QNp: %p, RNp: %p,\n\tQName: %s, RName: %s, Desc: %s",
                               qnp, rnp, qnp->name, rnp->name, rnp->desc ));

    return;
} /*  end PR_GetCounterNameFromHandle() */


/*
**
*/
PR_IMPLEMENT(void)
PR_IncrementCounter(
    PRCounterHandle handle
)
{
    PR_Lock(((RName *)handle)->lock);
    ((RName *)handle)->counter++;
    PR_Unlock(((RName *)handle)->lock);

    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: Increment: %p, %ld",
                               handle, ((RName *)handle)->counter ));

    return;
} /*  end PR_IncrementCounter() */



/*
**
*/
PR_IMPLEMENT(void)
PR_DecrementCounter(
    PRCounterHandle handle
)
{
    PR_Lock(((RName *)handle)->lock);
    ((RName *)handle)->counter--;
    PR_Unlock(((RName *)handle)->lock);

    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: Decrement: %p, %ld",
                               handle, ((RName *)handle)->counter ));

    return;
} /*  end PR_DecrementCounter()  */


/*
**
*/
PR_IMPLEMENT(void)
PR_AddToCounter(
    PRCounterHandle handle,
    PRUint32 value
)
{
    PR_Lock(((RName *)handle)->lock);
    ((RName *)handle)->counter += value;
    PR_Unlock(((RName *)handle)->lock);

    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: AddToCounter: %p, %ld",
                               handle, ((RName *)handle)->counter ));

    return;
} /*  end PR_AddToCounter() */


/*
**
*/
PR_IMPLEMENT(void)
PR_SubtractFromCounter(
    PRCounterHandle handle,
    PRUint32 value
)
{
    PR_Lock(((RName *)handle)->lock);
    ((RName *)handle)->counter -= value;
    PR_Unlock(((RName *)handle)->lock);

    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: SubtractFromCounter: %p, %ld",
                               handle, ((RName *)handle)->counter ));

    return;
} /*  end  PR_SubtractFromCounter() */

/*
**
*/
PR_IMPLEMENT(PRUint32)
PR_GetCounter(
    PRCounterHandle handle
)
{
    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: GetCounter: %p, %ld",
                               handle, ((RName *)handle)->counter ));

    return(((RName *)handle)->counter);
} /*  end  PR_GetCounter() */

/*
**
*/
PR_IMPLEMENT(void)
PR_SetCounter(
    PRCounterHandle handle,
    PRUint32 value
)
{
    ((RName *)handle)->counter = value;

    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: SetCounter: %p, %ld",
                               handle, ((RName *)handle)->counter ));

    return;
} /*  end  PR_SetCounter() */

/*
**
*/
PR_IMPLEMENT(PRCounterHandle)
PR_FindNextCounterQname(
    PRCounterHandle handle
)
{
    QName *qnp = (QName *)handle;

    if ( PR_CLIST_IS_EMPTY( &qNameList )) {
        qnp = NULL;
    }
    else if ( qnp == NULL ) {
        qnp = (QName *)PR_LIST_HEAD( &qNameList );
    }
    else if ( PR_NEXT_LINK( &qnp->link ) ==  &qNameList ) {
        qnp = NULL;
    }
    else {
        qnp = (QName *)PR_NEXT_LINK( &qnp->link );
    }

    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: FindNextQname: Handle: %p, Returns: %p",
                               handle, qnp ));

    return((PRCounterHandle)qnp);
} /*  end  PR_FindNextCounterQname() */


/*
**
*/
PR_IMPLEMENT(PRCounterHandle)
PR_FindNextCounterRname(
    PRCounterHandle rhandle,
    PRCounterHandle qhandle
)
{
    RName *rnp = (RName *)rhandle;
    QName *qnp = (QName *)qhandle;


    if ( PR_CLIST_IS_EMPTY( &qnp->rNameList )) {
        rnp = NULL;
    }
    else if ( rnp == NULL ) {
        rnp = (RName *)PR_LIST_HEAD( &qnp->rNameList );
    }
    else if ( PR_NEXT_LINK( &rnp->link ) ==  &qnp->rNameList ) {
        rnp = NULL;
    }
    else {
        rnp = (RName *)PR_NEXT_LINK( &rnp->link );
    }

    PR_LOG( lm, PR_LOG_DEBUG, ("PR_Counter: FindNextRname: Rhandle: %p, QHandle: %p, Returns: %p",
                               rhandle, qhandle, rnp ));

    return((PRCounterHandle)rnp);
} /*  end PR_FindNextCounterRname() */
