/* Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */


#ifndef NDB_HASHMAP2_HPP
#define NDB_HASHMAP2_HPP

#include <ndb_global.h>


/* Basic HashTable implementation
 * The HashTable stores elements of type KV.
 * The storage for the elements is managed outside the
 * HashTable implementation.
 * The HashTable uses element chaining in each bucket to
 * deal with collisions.
 * The HashTable can optionally enforce uniqueness
 * The HashTable can be resized when it is empty.
 */

/**
 * KVOPStaticAdapter template
 *
 * Used with HashMap2
 * Creates a class with static methods calling members of
 * an object passed to them, for the case where the HashMap
 * contains types ptrs with the relevant methods defined
 * (objects).
 *
 * Implements the KVOP Api, and requires the following API
 * from the KV type :
 *
 * Useful for supplying OP type.
 * Required KV Api:
 *   Uint32 hashValue() const;
 *   bool equal(const KV* other) const;
 *   void setNext(KV* next);
 *   KV* getNext() const;
 */
template<typename KV>
class KVOPStaticAdapter
{
public:
  static Uint32 hashValue(const KV* obj)
  {
    return obj->hashValue();
  };

  static bool equal(const KV* objA, const KV* objB)
  {
    return objA->equal(objB);
  };

  static void setNext(KV* from, KV* to)
  {
    return from->setNext(to);
  };

  static KV* getNext(const KV* from)
  {
    return from->getNext();
  }
};

// TODO :
//    Pass allocator context rather than allocator
//    Support re-alloc?
//    Use calloc?

/**
 * StandardAllocator - used in HashMap2 when no allocator supplied
 * Uses standard malloc/free.
 */
struct StandardAllocator
{
  static void* alloc(void* ignore, size_t bytes)
  {
    return ::malloc(bytes);
  };

  static void* calloc(void* ignore, size_t nelem, size_t bytes)
  {
    return ::calloc(nelem, bytes);
  }

  static void free(void* ignore, void* mem)
  {
    ::free(mem);
  };
};

/**
 * Template parameters
 *
 * Classes with static methods are used to avoid the necessity
 * of using OO wrapper objects for C data.
 * Objects can be used by defining static methods which call
 * normal methods.
 * A default StaticWrapper class exists to 'automate' this if
 * necessary.
 *
 * class KV - Key Value pair.
 *   The HashTable stores pointers to these.  No interface is
 *   assumed - they are manipulated via KVOP below, so can be
 *   chunks of memory or C structs etc.
 *
 * bool unique
 *   True if all keys in a hash table instance must be
 *   unique.
 *   False otherwise.
 *
 * class A - Allocator
 *   Used for hash bucket allocation on setSize() call.
 *   NOT used for element allocation, which is the responsibility
 *   of the user.
 *
 *   Must support static methods :
 *   - static void* calloc(void* context, size_t nelem, size_t bytes)
 *   - static void free(void* context, void*)
 *
 * class KVOP - Operations on Key Value pair
 *   KV instances are stored based on the hash returned
 *   by the KVOP::hashValue() method, with identity based on the
 *   KVOP::equal() method.
 *   KV instances must be linkable using KVOP::getNext() and
 *   KVOP::setNext() methods.
 *
 *   KVOP allows the static methods on the KV pair to be separate
 *   from the data itself.  If they are in the same class, use
 *   KVOP=KV.  If the methods are not static, and are on the KV class,
 *   use KVOP=KVOPStaticAdapter<KV>, or equivalent.
 *
 *   KVOP must support the following static methods :
 *   - static bool equal(const class KV* a, const class KV* b);
 *     Return true if two elements are equal.
 *
 *   - static Uint32 hashValue(const class KV*) const;
 *     Return a 32-bit stable hashvalue for the KV.
 *     equal(a,b) implies hashValue(a) == hashValue(b)
 *
 *   - static void setNext(KV* from, KV* to)
 *
 *   - static KV* getNext(const KV* from) const
 *
 *
 * TODO :
 *   - collision count?
 *   - release option?
 */
template<typename KV,
         bool unique = true,
         typename A = StandardAllocator,
         typename KVOP = KVOPStaticAdapter<KV> >
class HashMap2
{
public:
  /**
   * HashMap2 constructor
   * Pass an Allocator pointer if the templated allocator
   * requires some context info.
   * setSize() must be called before the HashMap is used.
   */
  HashMap2(void* _allocatorContext = NULL)
    : tableSize(0),
      elementCount(0),
      allocatorContext(_allocatorContext),
      table(NULL)
  {
  };

  ~HashMap2()
  {
    if (table)
      A::free(allocatorContext, table);
  }

  /**
   * setSize
   *
   * Set the number of buckets.
   *  Can only be set when the hash table is empty.
   *  The Allocator is used to allocate/release bucket
   *  storage.
   */
  bool
  setSize(Uint32 hashBuckets)
  {
    if (elementCount)
    {
      /* Can't set size while we have contents */
      return false;
    }

    if (hashBuckets == 0)
    {
      return false;
    }

    if (table)
    {
      A::free(allocatorContext, table);
      table = NULL;
    }

    /* TODO : Consider using only power-of-2 + bitmask instead of mod */
    tableSize = hashBuckets;

    table = (KV**) A::calloc(allocatorContext, hashBuckets, sizeof(KV*));

    if (!table)
    {
      return false;
    }

    for (Uint32 i=0; i < tableSize; i++)
      table[i] = NULL;

    return true;
  };

  /**
   * add
   *
   * Add a KV element to the hash table
   * The next value must be null
   * If the hash table requires uniqueness, and the
   * element is not unique, false will be returned
   */
  bool
  add(KV* keyVal)
  {
    assert(table);

    Uint32 hashVal = rehash(KVOP::hashValue(keyVal));
    Uint32 bucketIdx = hashVal % tableSize;

    KV* bucket = table[bucketIdx];

    if (bucket != NULL)
    {
      if (unique)
      {
        /* Need to check element is not already there, in this
         * chain
         */
        const KV* chainElement = bucket;
        while (chainElement)
        {
          if (KVOP::equal(keyVal, chainElement))
          {
            /* Found duplicate */
            return false;
          }
          chainElement= KVOP::getNext(chainElement);
        }
      }

      /* Can insert at head of list, as either no uniqueness
       * guarantee, or uniqueness checked.
       */
      assert(KVOP::getNext(keyVal) == NULL);
      KVOP::setNext(keyVal, bucket);
      table[bucketIdx] = keyVal;
    }
    else
    {
      /* First element in bucket */
      assert(KVOP::getNext(keyVal) == NULL);
      KVOP::setNext(keyVal, NULL);
      table[bucketIdx] = keyVal;
    }

    elementCount++;
    return true;
  }

  KV*
  remove(KV* key)
  {
    assert(table);

    Uint32 hashVal = rehash(KVOP::hashValue(key));
    Uint32 bucketIdx = hashVal % tableSize;

    KV* bucket = table[bucketIdx];

    if (bucket != NULL)
    {
      KV* chainElement = bucket;
      KV* prev = NULL;
      while (chainElement)
      {
        if (KVOP::equal(key, chainElement))
        {
          /* Found, repair bucket chain
           * Get next, might be NULL
           */
          KV* n = KVOP::getNext(chainElement);
          if (prev)
          {
            /* Link prev to next */
            KVOP::setNext(prev, n);
          }
          else
          {
            /* Put next as first in bucket */
            table[bucketIdx] = n;
          }

          KVOP::setNext(chainElement, NULL);
          elementCount--;

          return chainElement;
        }
        prev = chainElement;
        chainElement = KVOP::getNext(chainElement);
      }
    }

    return NULL;
  }

  /**
   * get
   *
   * Get a ptr to a KV element in the hash table
   * with the same key as the element passed.
   * Returns NULL if none exists.
   *
   * For non unique hash tables, a ptr to the
   * first element with the given key is returned.
   * Further elements must be found by iteration
   * (and key comparison), until NULL is returned.
   */
  KV*
  get(const KV* key) const
  {
    assert(table);

    Uint32 hashVal = rehash(KVOP::hashValue(key));
    Uint32 bucketIdx = hashVal % tableSize;

    KV* chainElement = table[bucketIdx];

    while(chainElement)
    {
      if (KVOP::equal(key, chainElement))
      {
        break;
      }
      chainElement = KVOP::getNext(chainElement);
    }

    return chainElement;
  };

  /**
   * reset
   *
   * Resets the hash table to have no entries.
   * KV elements added are not released in any
   * way.  This must be handled outside the
   * HashTable implementation, perhaps by
   * iterating and removing the elements?
   * Storage for the hash table itself is
   * preserved
   */
  void
  reset()
  {
    /* Zap the hashtable ptrs, without freeing the 'elements'
     * Keep the storage allocated for the ptrs
     */
    if (elementCount)
    {
      assert(table);
      for (Uint32 i=0; i < tableSize; i++)
      {
        table[i] = NULL;
      }

      elementCount = 0;
    }
  }

  /**
   * getElementCount
   *
   * Returns the number of elements currently
   * stored in this hash table.
   */
  Uint32
  getElementCount() const
  {
      return elementCount;
  }

  /**
   * getTableSize
   *
   * Returns the number of hashBuckets in this hash table
   */
  Uint32
  getTableSize() const
  {
    return tableSize;
  }

  class Iterator
  {
  public:
    Iterator(HashMap2& hashMap)
      : m_hash(&hashMap),
        curr_element(NULL),
        curr_bucket(0)
    {}

    /**
      Return the current element and reposition the iterator to the next
      element.
    */
    KV* next()
    {
      while (curr_bucket < m_hash->tableSize)
      {
        if (curr_element == NULL)
        {
          /* First this bucket */
          curr_element = m_hash->table[ curr_bucket ];
        }
        else
        {
          /* Next this bucket */
          curr_element = KVOP::getNext(curr_element);
        }

        if (curr_element)
        {
          return curr_element;
        }
        curr_bucket++;
      }

      return NULL;
    }
    void reset()
    {
      curr_element = NULL;
      curr_bucket = 0;
    }
  private:
    HashMap2* m_hash;
    KV* curr_element;
    Uint32 curr_bucket;
  };

private:
  static Uint32 rehash(Uint32 userHash)
  {
    /* We rehash the supplied hash value in case
     * it's low quality, mixing some higher order
     * bits in with the lower bits
     */
    userHash ^= ((userHash >> 20) ^ (userHash >> 12));
    return userHash ^ (userHash >> 7) ^ (userHash >> 4);
  }

  Uint32 tableSize;
  Uint32 elementCount;
  void* allocatorContext;

  KV** table;

}; // class HashMap2()

#endif
