/******************************************************************************/
/*                                                                            */
/*                        X r d O u c H a s h . i c c                         */
/*                                                                            */
/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University  */
/*   Produced by Andrew Hanushevsky for Stanford University under contract    */
/*              DE-AC02-76-SFO0515 with the Department of Energy              */
/*                                                                            */
/* This file is part of the XRootD software suite.                            */
/*                                                                            */
/* XRootD is free software: you can redistribute it and/or modify it under    */
/* the terms of the GNU Lesser General Public License as published by the     */
/* Free Software Foundation, either version 3 of the License, or (at your     */
/* option) any later version.                                                 */
/*                                                                            */
/* XRootD is distributed in the hope that it will be useful, but WITHOUT      */
/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or      */
/* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public       */
/* License for more details.                                                  */
/*                                                                            */
/* You should have received a copy of the GNU Lesser General Public License   */
/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file  */
/* COPYING (GPL license).  If not, see <http://www.gnu.org/licenses/>.        */
/*                                                                            */
/* The copyright holder's institutional names and contributor's names may not */
/* be used to endorse or promote products derived from this software without  */
/* specific prior written permission of the institution or contributor.       */
/******************************************************************************/

#include <cerrno>
#include <cstring>

/******************************************************************************/
/*                E x t e r n a l   H a s h   F u n c t i o n                 */
/******************************************************************************/
  
extern unsigned long XrdOucHashVal(const char *KeyVal);

/******************************************************************************/
/*                           C o n s t r u c t o r                            */
/******************************************************************************/
  
template<class T>
XrdOucHash<T>::XrdOucHash(int psize, int csize, int load)
{
     prevtablesize = psize;
     hashtablesize = csize;
     hashload      = load;
     hashmax       = (csize * load) / 100;
     hashnum       = 0;
     hashtable     = (XrdOucHash_Item<T> **)
                     malloc( (size_t)(csize*sizeof(XrdOucHash_Item<T> *)) );
     memset((void *)hashtable, 0, (size_t)(csize*sizeof(XrdOucHash_Item<T> *)));
}

/******************************************************************************/
/*                                   A d d                                    */
/******************************************************************************/

template<class T>
T *XrdOucHash<T>::Add(const char *KeyVal, T *KeyData, const int LifeTime,
                     XrdOucHash_Options opt)
{
    unsigned long khash = XrdOucHashVal(KeyVal);
    int hent;
    time_t lifetime, KeyTime=0;
    XrdOucHash_Item<T> *hip, *newhip, *prevhip;

    // Compute the hash index and look up the entry. If found, either
    // return an error or delete it because caller wanted it replaced or
    // it has expired.
    //
    hent = khash % hashtablesize;
    if ((hip = hashtable[hent]) && (hip = Search(hip, khash, KeyVal, &prevhip)))
       {if (opt & Hash_count)
           {hip->Update(hip->Count()+1, 
                       (LifeTime || hip->Time() ? LifeTime + time(0) : 0) );}
        if (!(opt & Hash_replace)
        && ((lifetime=hip->Time())==0||lifetime>=time(0))) return hip->Data();
        Remove(hent, hip, prevhip);
       } else {
       // Check if we should expand the table
       //
       if (hashnum >= hashmax) {Expand(); hent = khash % hashtablesize;}
       }

    // Add the entry
    //
    if (LifeTime) KeyTime = LifeTime + time(0);
    if ( !(newhip = new XrdOucHash_Item<T>(khash, KeyVal, KeyData, KeyTime, 
                             hashtable[hent], opt)) ) throw ENOMEM;
    hashtable[hent] = newhip;
    hashnum++;
    return (T *)0;
}

/******************************************************************************/
/*                                 A p p l y                                  */
/******************************************************************************/
  
template<class T>
T *XrdOucHash<T>::Apply(int (*func)(const char *, T *, void *), void *Arg)
{
     int i, rc;
     time_t lifetime;
     XrdOucHash_Item<T> *hip, *prevhip, *nexthip;

     //Run through all the entries, applying the function to each. Expire
     // dead entries by pretending that the function asked for a deletion.
     //
     for (i = 0; i < hashtablesize; i++)
         {hip = hashtable[i]; prevhip = 0;
          while(hip)
               {nexthip = hip->Next();
                if ((lifetime = hip->Time()) && lifetime < time(0)) rc = -1;
                   else if ( (rc = (*func)(hip->Key(), hip->Data(), Arg)) > 0 )
                        return hip->Data();
                if (rc < 0)
                   {delete hip;
                    if (prevhip) prevhip->SetNext(nexthip);
                       else hashtable[i] = nexthip;
                    hashnum--;
                   }
                   else prevhip = hip;
                hip = nexthip;
               }
         }
     return (T *)0;
}
  
/******************************************************************************/
/*                                   D e l                                    */
/******************************************************************************/
  
template<class T>
int XrdOucHash<T>::Del(const char *KeyVal, XrdOucHash_Options)
{
    unsigned long khash = XrdOucHashVal(KeyVal);
    int hent, cnt;
    XrdOucHash_Item<T> *hip, *phip, *thip;

    // Compute the hash index and look up the entry. If found, return it.
    //
    hent = khash % hashtablesize;
    if (!(thip = hashtable[hent])) return -ENOENT;
    if (!( hip = Search(thip, khash, KeyVal, &phip) ) ) return -ENOENT;

   // Delete the item and return
   //
   if ((cnt = hip->Count()) <= 0) Remove(hent, hip, phip);
      else hip->Update(cnt-1, 0);
   return 0;
}

/******************************************************************************/
/*                                  F i n d                                   */
/******************************************************************************/

template<class T>
T *XrdOucHash<T>::Find(const char *KeyVal, time_t *KeyTime)
{
  unsigned long khash = XrdOucHashVal(KeyVal);
  int kent;
  time_t lifetime = 0;
  XrdOucHash_Item<T> *phip, *hip;

// Compute position of the hash table entry
//
   kent = khash%hashtablesize;

// Find the entry (remove it if expired and return nothing)
//
   if ((hip = hashtable[kent]))
      if ((hip = Search(hip, khash, KeyVal, &phip)))
          if ( (lifetime = hip->Time()) && lifetime < time(0) )
            {Remove(kent, hip, phip);
             if (KeyTime) *KeyTime = (time_t)0;
             return (T *)0;
            }

// Return actual information
//
   if (KeyTime) *KeyTime = lifetime;
   if (hip) return hip->Data();
   return (T *)0;
}

/******************************************************************************/
/*                                 P u r g e                                  */
/******************************************************************************/
  
template<class T>
void XrdOucHash<T>::Purge()
{
     int i;
     XrdOucHash_Item<T> *hip, *nexthip;

     //Run through all the entries, deleting each one
     //
     for (i = 0; i < hashtablesize; i++)
         {hip = hashtable[i]; hashtable[i] = 0;
          while(hip)
               {nexthip = hip->Next();
                delete hip;
                hip = nexthip;
               }
         }
     hashnum = 0;
}
  
/******************************************************************************/
/*                       P r i v a t e   M e t h o d s                        */
/******************************************************************************/
  
/******************************************************************************/
/*                                E x p a n d                                 */
/******************************************************************************/
  
template<class T>
void XrdOucHash<T>::Expand()
{
    int newsize, newent, i;
    size_t memlen;
    XrdOucHash_Item<T> **newtab, *hip, *nexthip;

    // Compute new size for table using a fibonacci series
    //
    newsize = prevtablesize +hashtablesize;

    // Allocate the new table
    //
    memlen = (size_t)(newsize*sizeof(XrdOucHash_Item<T> *));
    if (!(newtab = (XrdOucHash_Item<T> **) malloc(memlen))) throw ENOMEM;
    memset((void *)newtab, 0, memlen);

    // Redistribute all of the current items
    //
    for (i = 0; i < hashtablesize; i++)
        {hip = hashtable[i];
         while(hip)
           {nexthip = hip->Next();
            newent  = (hip->Hash()) % newsize;
            hip->SetNext(newtab[newent]);
            newtab[newent] = hip;
            hip = nexthip;
           }
        }

    // Free the old table and plug in the new table
    //
    free((void *)hashtable);
    hashtable     = newtab;
    prevtablesize = hashtablesize;
    hashtablesize = newsize;

    // Compute new expansion threshold
    //
    hashmax = static_cast<int>((static_cast<long long>(newsize)*hashload)/100);
}

/******************************************************************************/
/*                                R e m o v e                                 */
/******************************************************************************/
  
template<class T>
void XrdOucHash<T>::Remove(int kent, XrdOucHash_Item<T> *hip,
                                    XrdOucHash_Item<T> *phip)
{
     if (phip) phip->SetNext(hip->Next());
        else hashtable[kent] = hip->Next();
     delete hip;
     hashnum--;
}

/******************************************************************************/
/*                                S e a r c h                                 */
/******************************************************************************/
  
template<class T>
XrdOucHash_Item<T> *XrdOucHash<T>::Search(XrdOucHash_Item<T>  *hip,
                                        const unsigned long khash,
                                        const char         *kval,
                                        XrdOucHash_Item<T> **pitem)
{
   XrdOucHash_Item<T> *prevp = 0;

   // Scan through the chain looking for a match
   //
   while(hip && !hip->Same(khash, kval))
        {prevp = hip;
         hip = hip->Next();
        }
   if (pitem) *pitem = prevp;
   return hip;
}
