#include "copyright.h"
/*
 ****************************************************************** 
 *
 * clientsclass.c --
 *
 *     Implementation of clients class. Hold all the clients'
 *     characteristics, including the key->keyset & key-order
 *     mappings which allow the generation of a client-ID.
 *
 *     Process incoming Nlv_Field info from parseclass and
 *     find out event number/value, place results in a cache
 *     for Graph and Analysis routines (single ref-counted copy).
 *
 *
 * Created: 9 June 1998
 * $Author: mstoufer $
 * $Source: /home/g2/src/projects/NetLogger/src/nlv/clientsclass.c,v $
 * $RCSfile: clientsclass.c,v $
 * $Revision: 1.1.1.1 $
 * $Locker:  $
 * $Date: 2000/12/05 19:52:32 $
 *
 ****************************************************************** 
 */
static char rcsid[] = "$Id: clientsclass.c,v 1.1.1.1 2000/12/05 19:52:32 mstoufer Exp $";

#include <tcl.h>
#include "clientsclass.h"
#include "graphclass.h"
#include "util.h"
#include "errclass.h"

/*========= DATA =====================================================*/

extern Nlv_err eobj;
extern Nlv_State nlvState;

static char *EMPTY_HOST="unknown";
static char *EMPTY_ID  ="unknown";
static Nlv_Client *NEW_CLIENT = (Nlv_Client *)1;

/** Linked list for each keyset **/

struct Nlv_KeysetNode;
typedef struct Nlv_KeysetNode {
  Nlv_Client *data;
  struct Nlv_KeysetNode *next;
} Nlv_KeysetNode;
typedef struct Nlv_KeysetList {
  Nlv_KeysetNode  *newest;
  Nlv_KeysetNode  *oldest;
  int              length;
} Nlv_KeysetList;

#define T Nlv_cl
struct T {
  Nlv_GData       graphData;
  Tcl_HashTable   clientTbl;
  Nlv_KeysetList *ksListArr;
  int             numClients;
  /*
    Nlv_ca          gcache;
    Nlv_ca          acache;
  */
  Nlv_ky          keyData;
  char           *unmatchedId;
  char           *unmatchedHost;
  Nlv_Client     *newClient;
};

#define FILE_ERR_CODE 500

/*========= FUNCTIONS ================================================*/

char       *Nlv_clBuildId( Nlv_KeySet *ks, Nlv_Fields *f );
char       *Nlv_clBuildHost( Nlv_KeySet *ks, Nlv_Fields *f );
Nlv_Client *Nlv_clMatchId( T, Nlv_KeySet *ks, Nlv_Fields *f );
Nlv_Client *Nlv_clAddClient( T, Nlv_Key *keyPtr );
Nlv_Client *Nlv_clRemoveClient( T, Nlv_Key *keyPtr );
void        Nlv_clFreeClientStruct( Nlv_Client *p );
/* Keyset list functions */
static void addToKeysetList( T, Nlv_Client *clPtr );
static int  oldestFromKeysetList( T, int ksIdx, Nlv_Client **clPtr );

/*
 *----------------------------------------------------------------------
 *
 * New -- Allocate new object.
 *
 * Calls: malloc
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */

T Nlv_clNew( )
{
  T this = (T)malloc( sizeof( struct T ) );
  Tcl_InitHashTable( & (this->clientTbl), TCL_STRING_KEYS);
  this->ksListArr = (Nlv_KeysetList *) calloc( NLV_MAX_KEYSETS, sizeof(Nlv_KeysetList) );
  this->numClients    = 0;
  this->keyData       = NULL;
  this->unmatchedId   = NULL;
  this->unmatchedHost = NULL;
  this->newClient     = NULL;

  return this;
}

/*
 *----------------------------------------------------------------------
 *
 * SetKeyData -- Set the pointer to the proper keyset data class.
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */

void Nlv_clSetKeyData( T this, Nlv_ky data )
{
  if ( this->keyData != NULL ) {
    /*    printf("set key data again\n"); */
  }
  this->keyData = data;
}


/*
 *----------------------------------------------------------------------
 *
 * Process -- Parsed fields ==> internal graph/analysis data.
 *
 * Side effects: Load-lines or points which are on the same Y
 *               coordinate in the graph will be rescaled if the
 *               new value falls outside the current range and
 *               autoRescale is turned on for the keyset.
 *               This also modifies the valMin, valMax for the keyset.
 *
 * Calls: Nlv_grRescale (see above)
 *
 *----------------------------------------------------------------------
 */

Nlv_GData *Nlv_clProcess( T this, 
			  Nlv_Fields *f)
{
  int i, m, fld, pos;
  Nlv_Key *k;
  Nlv_Client *clPtr;
  char tmp[ 512 ];
  char *errstr = NULL;
  double t, val = 0.;
  Nlv_EInfo einfo;


  this->newClient = NULL;

  if ( this->keyData == NULL ) {
    Nlv_errPrintString( eobj, "Nlv_clProcess", "Data for event is NULL" );
    goto error;
  }

  /* Clear old data */
  memset( &this->graphData, 0, sizeof(Nlv_GData));

  /* Get a pointer to the static information for this event */
  if ( NULL == (k = Nlv_clGetKeyPtr( this, f, &m )) ) {
    errstr = Nlv_Fields2String( f );
    NLV_SET_EINFO( einfo, FILE_ERR_CODE+2+NLV_WARN, 10 );
    Nlv_errReport( eobj, &einfo, "Unknown key '%s' in log line \"%s\"",
		   FIELD_VAL(f,m), errstr);
    goto error;
  }
  
  /* Get the time of the event */
  t = Nlv_clGetTime( f );

  /* If the time in the event is less than the maximum allowed
   * out-of-order points, return error
   */
   if ( Nlv_grGraphTime( nlvState.gr, t ) <= NLV_MAX_BACKWARDS )
     goto error;

  /* 
   * Find which client we are looking at. If it is a new one,
   * we had better add it first.
   */
  clPtr = Nlv_clMatchId( this, k->ks, f );
  if ( clPtr == NULL ) {
    errstr = Nlv_Fields2String( f );
    NLV_SET_EINFO( einfo, FILE_ERR_CODE+2, 10 );
    Nlv_errReport( eobj, &einfo,"Cannot make identifier "
		   "for event %d in keyset '%s' in log line \"%s\"",
		   k->index,
		   ( k->ks->name == NULL ? "(none)" : k->ks->name ),
		   errstr );
    goto error;
  }

  if ( clPtr == NEW_CLIENT ) {
    if ( (clPtr = Nlv_clAddClient( this, k )) == NULL ) {
        NLV_SET_EINFO( einfo, FILE_ERR_CODE+3, 0 );
        Nlv_errReport( eobj, &einfo,"Already have %d clients in keyset %s: cannot add new client", 
		       this->numClients, ( k->ks->name == NULL ? "(none)" : k->ks->name ) );
        goto error;
    }
    else {
      this->newClient = clPtr;
    }
  }

  /* 
   * Get eventVal, if the graph type demands it 
   */
  if ( (k->ks->gtype == NLV_LOAD || k->ks->gtype == NLV_POINT) && (k->ks->valField)) 
  {
      double newmin = k->ks->valMin;
      double newmax = k->ks->valMax;

      val = Nlv_clGetEventVal( f, k->ks->valField, k->ks->valMin, k->ks->valMax );
    
      /* 
       * If value falls outside current scale limits.
       *   Auto-scale mode: rescale all other points,
       *   Else: round value to scale limits.
       */
      if ( (val < 0 && k->ks->autoScaleMin ) ||
	   (val > 1 && k->ks->autoScaleMax ) )
      {
	double slopVal = val * ( 1 + NLV_RESCALE_SLOP );

	/* Debugging */
	/* printf("Nlv_clProcess: val %lf will be rescaled\n", val ); */

	if ( val < 0  )
	{
	  newmin = k->ks->valMin + ( k->ks->valMax - k->ks->valMin ) * slopVal ;
	  newmax = k->ks->valMax;
	  Nlv_grRescale( nlvState.gr, clPtr->clIndex, k->ks, slopVal, -1 );
	  /* Put val within new limits */
	  val = NLV_RESCALE_SLOP;
	}
        else if ( val > 1 )
	{
	  newmax = ( k->ks->valMax - k->ks->valMin ) * slopVal;
	  Nlv_grRescale( nlvState.gr, clPtr->clIndex, k->ks, slopVal, 1 );
	  /* Put val within new limits */
	  val = 1 - NLV_RESCALE_SLOP;
	}
	  
	/* Debugging */
	/* printf("Nlv_clProcess: Rescaled from [ %lf .. %lf ] to [ %lf .. %lf ]; new val = %lf\n", 
	       k->ks->valMin, k->ks->valMax, newmin, newmax, val );
	*/
  	  
	/* 
	 * Change the keyset to the new scale for all future points 
	 */
	k->ks->valMin = newmin;
	k->ks->valMax = newmax;
      }
      /*
       * With autoScale turned off, out-of-bounds values are truncated.
       */
      else if ( val < 0 )
	  val = 0.;
      else if ( val > 1 )
	  val = 1;
  }

  /* Fill in the rest of the graph data structure */
  this->graphData.clIndex   = clPtr->clIndex;
  this->graphData.event     = k->index;
  this->graphData.eventSet  = k->ks->index;
  this->graphData.time      = t;
  this->graphData.eventVal  = val;
  this->graphData.graphType = k->ks->gtype;
  this->graphData.filepos   = Nlv_fiGetPrevReadPos( nlvState.fi );
  this->graphData.refct     = 2;

  return &this->graphData;

error:
  if ( errstr )
    free( errstr );
  return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * GetKeyPtr -- Get a pointer to the key information for this
 *              set of fields (NL.EVNT field defines the event).
 *
 * Calls: Nlv_MatchField, Nlv_kyGetKey
 *
 * Algorithm: Find the field NL.EVNT, then return the key which 
 *            is associated with the value of NL.EVNT
 *
 *----------------------------------------------------------------------
 */

Nlv_Key *Nlv_clGetKeyPtr( T this, Nlv_Fields *fldPtr, int *evnt )
{
  Nlv_EInfo einfo;
  char *errstr = NULL;

  *evnt = Nlv_MatchField( fldPtr, "NL.EVNT" );
  if ( *evnt < 0 ) {
    errstr = Nlv_Fields2String( fldPtr );
    NLV_SET_EINFO( einfo, FILE_ERR_CODE+1+NLV_WARN, 10 );
    Nlv_errReport( eobj, &einfo, "Log line is missing the 'NL.EVNT' field: \"%s\"", 
		   errstr );
    if ( errstr )
      free( errstr );
    return NULL;
  }
  return Nlv_kyGetKey( this->keyData, FIELD_VAL(fldPtr,*evnt) );
}


/*
 *----------------------------------------------------------------------
 *
 * GetTime -- Get the time for a log line, in seconds
 *
 * Calls: Nlv_MatchField, strtol
 *
 * Algorithm: Use 'NL.USEC' and 'NL.SEC' fields, the formula is simply:
 *               time = SEC + USEC / 1E6
 *
 *----------------------------------------------------------------------
 */
double Nlv_clGetTime( Nlv_Fields *line )
#if defined OLD_DATE_FORMAT
{
  int usecFld, secFld;
  long usec, sec;

  usecFld = Nlv_MatchField( line, NLV_USEC_FLD );
  secFld  = Nlv_MatchField( line, NLV_SEC_FLD );
  if ( usecFld < 0 || secFld < 0 ) {
    Nlv_errPrintString( eobj, "Nlv_clGetTime", 
			"Could not find seconds and/or microseconds in log line.");
    return -1.;
  }

  usec = strtol( FIELD_VAL(line,usecFld), NULL, 10 );
  sec  = strtol( FIELD_VAL(line, secFld), NULL, 10 );

  return (double)sec + (double)usec / NLV_TM_TO_SEC;
}
#else
{
  int dateFld = Nlv_MatchField(line,ULM_DATE_FLD);
  if ( dateFld < 0 ) {
    char msg[2048];
    sprintf(msg,"No date field '%s' in line:\n%s\n",ULM_DATE_FLD,line->raw);
    Nlv_errPrintString( eobj, "Nlv_clGetTime", msg);
    return -1.;
  }
  return Nlv_pdCalcDate( FIELD_VAL( line, dateFld ) );
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * GetEventVal -- Get the floating point "value" also associated with
 *                this event, and return it as a number scaled from 0
 *                to 1 (inclusive).
 *
 * Calls: Nlv_MatchField, FIELD_VAL, Nlv_Fields2String
 *
 * Algorithm: Use second argument as field to match, then parse the
 *            resulting string as a floating-point number
 *
 *----------------------------------------------------------------------
 */
double Nlv_clGetEventVal( Nlv_Fields *line, 
			  char *valStr,
			  double min,
			  double max )
{
    Nlv_EInfo einfo; /* contains error information; thread-safe if on stack */
    int    valFld;   /* index of field in log line matching valStr */
    double rawVal;   /* value in log line */
    double val;      /* return value */
  
  val = 0.0;

  if ( valStr )
  {
      valFld = Nlv_MatchField( line, valStr );
      if ( valFld >= 0 )
      {
	  sscanf( FIELD_VAL(line, valFld), "%lf", &rawVal);
	  val = ( rawVal - min ) / ( max - min );
      }
      else
      {
          /* so we can print the log line */
	  char *errstr = Nlv_Fields2String( line ); 
	  NLV_SET_EINFO( einfo, FILE_ERR_CODE+3+NLV_WARN, 10 );
	  Nlv_errReport( eobj, &einfo, 
			 "Missing event value field '%s' in log line \"%s\"",
			 valStr, errstr );
	  free( errstr );
      }
  }
  else
  {
      NLV_SET_EINFO( einfo, FILE_ERR_CODE+3+NLV_WARN, 10 );
      Nlv_errReport( eobj, &einfo, "Nlv_clGetEventVal: Value field name is null\n");
  }

  return val;
}

/*
 *----------------------------------------------------------------------
 *
 * BuildId -- Construct a client ID.
 *
 * Calls: malloc, strcat
 *
 * Algorithm: 
 *
 *----------------------------------------------------------------------
 */
char *Nlv_clBuildId( Nlv_KeySet *ks, Nlv_Fields *f )
{
  int i, idFld;
  char *r = (char *)malloc( 512 );

  /*   *r = '\0'; */
  sprintf(r,"%d%s", ks->index, NLV_IDSTR_SEP );
  for (i=0; i < ks->numIdFields; i++) {
    idFld = Nlv_MatchField( f, ks->idFields[i] );
    if ( idFld >= 0 ) {
      strcat( r, FIELD_VAL(f,idFld) );
      if ( i < (ks->numIdFields-1))
	strcat( r, NLV_IDSTR_SEP );
    }
    else {
      free( r );
      return NULL;
    }
  }

  return r;
}

/*
 *----------------------------------------------------------------------
 *
 * BuildHost -- Construct a host name.
 *
 * Calls: malloc, strcat
 *
 * Algorithm: 
 *
 *----------------------------------------------------------------------
 */
char *Nlv_clBuildHost( Nlv_KeySet *ks, Nlv_Fields *f )
{
  int i, hostFld;
  char *r = (char *)malloc( 512 );

  *r = '\0';

  for (i=0; i < ks->numHostFields; i++) {
    hostFld = Nlv_MatchField( f, ks->hostFields[i] );
    if ( hostFld >= 0 ) {
      strcat( r, FIELD_VAL(f,hostFld) );
      if ( i < (ks->numHostFields-1))
	strcat( r, NLV_IDSTR_SEP );
    }
    else {
      free( r );
      r = NULL;
      return NULL;
    }

  }

  return r;
}

/*
 *----------------------------------------------------------------------
 *
 * MatchId -- Match ID to client structure.
 *
 * Calls: Tcl_FindHashEntry, Tcl_GetHashValue, free
 *
 * Algorithm: 
 *
 *----------------------------------------------------------------------
 */
Nlv_Client *Nlv_clMatchId( T this, Nlv_KeySet *ks, Nlv_Fields *fld_p )
{
  char *id, *host;
  Tcl_HashEntry *entryPtr;
  Nlv_EInfo einfo;

  NLV_SET_EINFO( einfo, FILE_ERR_CODE+4, 5 );

  /* Build identification strings */
  if ( (id = Nlv_clBuildId( ks, fld_p )) == NULL ) {
    Nlv_errReport( eobj, &einfo, "Unable to build id for event in "
		   "keyset '%s'", ks->name );
    this->unmatchedId   = EMPTY_ID;
    this->unmatchedHost = EMPTY_HOST;
    return NULL;
  }

  /* Find client with matching ID string */
  entryPtr = Tcl_FindHashEntry( &(this->clientTbl), id );
  if ( entryPtr == NULL ) {
    this->unmatchedId = id;
    host              = Nlv_clBuildHost( ks, fld_p );
    if ( host == NULL ) {
      Nlv_errReport( eobj, &einfo, "Unable to build host-id for event"
	      " in keyset '%s'", ks->name );
      host = EMPTY_HOST; 
      return NULL;
    }     
    this->unmatchedHost = host;
    return NEW_CLIENT;
  }
  free(id);
  return (Nlv_Client *)Tcl_GetHashValue( entryPtr );
}

/*
 *----------------------------------------------------------------------
 *
 * AddClient -- Add a new client to the hash table and the linked
 *              list of pointers stored by-keyset.
 *
 * Calls:
 *
 * Algorithm: The unsuccessful last attempt to find a client id
 *            was stored in 'unmatchedId'. Add a new client with this
 *            id and then set this variable to NULL.
 *
 *----------------------------------------------------------------------
 */

Nlv_Client *Nlv_clAddClient( T this, Nlv_Key *keyPtr )
{
  Nlv_Client *clPtr = NULL;
  Tcl_HashEntry *entryPtr;
  int newEntry, m;

  /* fprintf(stderr,"<dbg>add client %d @ event=%s ID=%s HOST=%s\n",
   * this->numClients, keyPtr->name, this->unmatchedId, this->unmatchedHost);
   */

  if ( this->ksListArr[keyPtr->ks->index].length > NLV_MAX_KS_CLIENTS ) {
    if ( ! keyPtr->ks->rotateClients ) 
      return NULL;
    clPtr = Nlv_clRemoveClient( this, keyPtr );
    if ( clPtr == NULL )
      return NULL;
    else
      Nlv_clFreeClientStruct( clPtr );
  }


  /* Allocate and fill in client structure, increment numClients */
  clPtr            = (Nlv_Client *) malloc( sizeof(struct Nlv_Client) );
  clPtr->clIndex   = this->numClients;
  clPtr->name      = this->unmatchedId;
  clPtr->host      = this->unmatchedHost;
  clPtr->hostName  = Nlv_IpStr2HostName( this->unmatchedHost );
  clPtr->keyPtr    = keyPtr;

  /* Add to hash table */
  entryPtr = Tcl_CreateHashEntry( &(this->clientTbl), this->unmatchedId, &newEntry );
  this->unmatchedId   = NULL;
  this->unmatchedHost = NULL;
  if ( newEntry == 0 ) {
    Nlv_errPrintString( eobj, "Nlv_clAddClient", "Attempt to add duplicate client" );
    return NULL;
  }
  Tcl_SetHashValue( entryPtr, clPtr );

  /* Add to keyset list */
  addToKeysetList( this, clPtr );

  this->numClients++;

  return clPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Remove client -- Remove one client from list.
 *
 * Calls:
 *
 * Algorithm: Find lowest-numbered client in the same
 *            keyset as us, remove it, and renumber all
 *            subsequent clients. Return a pointer to the
 *            client.
 *
 * Return: Pointer to removed client, or NULL.
 *
 *----------------------------------------------------------------------
 */

Nlv_Client *Nlv_clRemoveClient( T this, Nlv_Key *keyPtr )
{
  Tcl_HashEntry *entryPtr;
  Tcl_HashEntry *removeEntryPtr;
  Tcl_HashSearch search;
  Nlv_Client    *clientPtr;
  Nlv_Client    *removeClientPtr;
 
  /* Remove "oldest" client from per-keyset list */
  if ( 0 == oldestFromKeysetList( this, keyPtr->ks->index, &removeClientPtr ) )
    return NULL;
  if ( NULL == (removeEntryPtr = Tcl_FindHashEntry( &(this->clientTbl), removeClientPtr->name )) )
    return NULL;

  /* printf( "<dbg>remove client %s\n", removeClientPtr->name ); */

  Tcl_DeleteHashEntry( removeEntryPtr );

  /* Update indices of all clients AFTER this one */ 
  entryPtr    = Tcl_FirstHashEntry( &(this->clientTbl), &search );
  while( entryPtr ) {
    clientPtr = (Nlv_Client *)Tcl_GetHashValue( entryPtr );
    if ( clientPtr->clIndex > removeClientPtr->clIndex ) {
       clientPtr->clIndex--;
    }
    entryPtr = Tcl_NextHashEntry( &search );
  }

  /* Also move graph data indices, of a range of indices */
  Nlv_grShiftClientData( nlvState.gr,
                         removeClientPtr->clIndex,
                         this->numClients - removeClientPtr->clIndex - 1 );

  /* Update the number of clients in the table */
  this->numClients--;

  return removeClientPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Nlv_clFreeClientStruct -- Free fields of Nlv_Client structure.
 *
 *----------------------------------------------------------------------
 *
 */
void Nlv_clFreeClientStruct( Nlv_Client *p )
{
  free( p->name );
  free( p->host );
  free( p->hostName );
  free( p );
}

/*
 *----------------------------------------------------------------------
 *
 * NewClient -- Get new client if any.
 *
 * Calls:
 *
 * Algorithm: Accessor to class var.
 *
 *----------------------------------------------------------------------
 */

Nlv_Client *Nlv_clNewClient( T this )
{
  return this->newClient;
}

/*
 *----------------------------------------------------------------------
 *
 * GetNumClients -- How many clients, bub?
 *
 * Calls:
 *
 * Algorithm: Accessor to class var.
 *
 *----------------------------------------------------------------------
 */
int Nlv_clGetNumClients( T this )
{
  return this->numClients;
}

/*
 *----------------------------------------------------------------------
 *
 * GetClientRecord -- Find a client record from its client-index.
 *                    Return a pointer to an area of memory that
 *                    should be copied if it will be modified.
 *
 * Calls    : Tcl_FirstHashEntry, Tcl_NextHashEntry, Tcl_GetHashValue
 *
 * Algorithm: Linear search through the hashtable, which is of course
 *            not very fast, so cache this info if needed often.
 *
 *----------------------------------------------------------------------
 */
Nlv_Client *Nlv_clGetClientRecord( T this, int idx )
{
  Tcl_HashEntry *entryPtr;
  Tcl_HashSearch search;
  Nlv_Client *clientPtr;
  
  entryPtr = Tcl_FirstHashEntry( &(this->clientTbl), &search );
  while ( entryPtr ) {
    if ((clientPtr = (Nlv_Client *)Tcl_GetHashValue( entryPtr ))->clIndex == idx)
      return clientPtr;
    entryPtr = Tcl_NextHashEntry( &search );
  }
  return NULL;
}

char *Nlv_clGetClientName( Nlv_Client *clientPtr )
{
  if ( clientPtr )
    return (clientPtr->name);
  else
    return (NULL);
}

Nlv_KeySet *Nlv_clGetKeySet( Nlv_Client *clientPtr )
{
  if ( clientPtr )
    return (clientPtr->keyPtr->ks);
  else
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * addToKeysetList -- Add a client to keyset list.
 *
 * PRE : We have space for client, client is initialized.
 * IN  : Pointer to client.
 * OUT : -nil-
 * RTRN: -nil-
 * POST: Client is inserted as the "newest" element in the list.
 *       Length of list is incremented by 1.
 *
 *----------------------------------------------------------------------
 */
void addToKeysetList( T this, Nlv_Client *clPtr )
{
  Nlv_KeysetList *listPtr;
  Nlv_KeysetNode *newNode;

  newNode = (Nlv_KeysetNode *) malloc( sizeof(Nlv_KeysetNode) );
  newNode->data = clPtr;
  newNode->next = NULL;
  listPtr = &( this->ksListArr[clPtr->keyPtr->ks->index] );
  if ( NULL == listPtr->newest ) {
    listPtr->newest = listPtr->oldest = newNode;
  }
  else {
    Nlv_KeysetNode *p = listPtr->newest;
    listPtr->newest = newNode;
    newNode->next = p;
  }
  listPtr->length++;
}

/*
 *----------------------------------------------------------------------
 *
 * oldestFromKeysetList -- Remove the "oldest" client from a keyset list.
 *
 * PRE : Keyset list is initialized, but possibly empty.
 *       Input keyset index is in the list.
 * IN  : (this pointer), keyset index
 * OUT : Pointer to removed client
 * RTRN: 1=success, 0=empty list
 * POST: Client is removed from "oldest" spot, new oldest is
 *       chosen.
 *       Length of list is decremented by 1.
 *
 *----------------------------------------------------------------------
 */
int oldestFromKeysetList( T this, int ksIdx, Nlv_Client **clPtr )
{
  Nlv_KeysetList *listPtr;
  Nlv_KeysetNode *p;
  
  listPtr = &this->ksListArr[ksIdx];
  p       = listPtr->oldest;

  if ( p == NULL )
    return 0;

  (*clPtr) = p->data;

  if ( listPtr->newest == listPtr->oldest )
    listPtr->newest = listPtr->oldest = NULL;
  else {
    Nlv_KeysetNode *cur;
    for ( cur = listPtr->newest; cur->next != p; cur = cur->next );
    cur->next = NULL;
    listPtr->oldest = cur;
  }

  free( p );
  listPtr->length--;

  return 1;
}

#undef T
#undef FILE_ERR_CODE
