/* Hash table for checking keyword links.  Implemented using double hashing.
   Copyright (C) 1989-1998, 2000, 2002-2003 Free Software Foundation, Inc.
   Written by Douglas C. Schmidt <schmidt@ics.uci.edu>
   and Bruno Haible <bruno@clisp.org>.

   This file is part of GNU GPERF.

   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; either version 3 of the License, or
   (at your option) any later version.

   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, see <https://www.gnu.org/licenses/>.  */

/* Specification. */
#include "hash-table.h"

#include <stdio.h>
#include <string.h> /* declares memset(), strcmp() */
#include <hash.h>
#include "options.h"

/* We use a hash table with double hashing.  This is the simplest kind of
   hash table, given that we always only insert and never remove entries
   from the hash table.  */

/* To make double hashing efficient, there need to be enough spare entries.  */
static const int size_factor = 10;

/* We make the size of the hash table a power of 2.  This allows for two
   optimizations: It eliminates the modulo instruction, and allows for an
   easy secondary hashing function.  */

/* Constructor.  */
Hash_Table::Hash_Table (unsigned int size, bool ignore_length)
  : _ignore_length (ignore_length),
    _collisions (0)
{
  /* There need to be enough spare entries.  */
  size = size * size_factor;

  /* Find smallest power of 2 that is >= size.  */
  unsigned int shift = 0;
  if ((size >> 16) > 0)
    {
      size = size >> 16;
      shift += 16;
    }
  if ((size >> 8) > 0)
    {
      size = size >> 8;
      shift += 8;
    }
  if ((size >> 4) > 0)
    {
      size = size >> 4;
      shift += 4;
    }
  if ((size >> 2) > 0)
    {
      size = size >> 2;
      shift += 2;
    }
  if ((size >> 1) > 0)
    {
      size = size >> 1;
      shift += 1;
    }
  _log_size = shift;
  _size = 1 << shift;

  /* Allocate table.  */
  _table = new KeywordExt*[_size];
  memset (_table, 0, _size * sizeof (*_table));
}

/* Destructor.  */
Hash_Table::~Hash_Table ()
{
  delete[] _table;
}

/* Print the table's contents.  */
void
Hash_Table::dump () const
{
  int field_width;

  field_width = 0;
  {
    for (int i = _size - 1; i >= 0; i--)
      if (_table[i])
        if (field_width < _table[i]->_selchars_length)
          field_width = _table[i]->_selchars_length;
  }

  fprintf (stderr,
           "\ndumping the hash table\n"
           "total available table slots = %d, total bytes = %d, total collisions = %d\n"
           "location, %*s, keyword\n",
           _size, _size * static_cast<unsigned int>(sizeof (*_table)),
           _collisions, field_width, "keysig");

  for (int i = _size - 1; i >= 0; i--)
    if (_table[i])
      {
        fprintf (stderr, "%8d, ", i);
        if (field_width > _table[i]->_selchars_length)
          fprintf (stderr, "%*s", field_width - _table[i]->_selchars_length, "");
        for (int j = 0; j < _table[i]->_selchars_length; j++)
          putc (_table[i]->_selchars[j], stderr);
        fprintf (stderr, ", %.*s\n",
                 _table[i]->_allchars_length, _table[i]->_allchars);
      }

  fprintf (stderr, "\nend dumping hash table\n\n");
}

/* Compares two items.  */
inline bool
Hash_Table::equal (KeywordExt *item1, KeywordExt *item2) const
{
  return item1->_selchars_length == item2->_selchars_length
         && memcmp (item1->_selchars, item2->_selchars,
                    item2->_selchars_length * sizeof (unsigned int))
            == 0
         && (_ignore_length
             || item1->_allchars_length == item2->_allchars_length);
}

/* Attempts to insert ITEM in the table.  If there is already an equal
   entry in it, returns it.  Otherwise inserts ITEM and returns NULL.  */
KeywordExt *
Hash_Table::insert (KeywordExt *item)
{
  unsigned hash_val =
    hashpjw (reinterpret_cast<const unsigned char *>(item->_selchars),
             item->_selchars_length * sizeof (unsigned int));
  unsigned int probe = hash_val & (_size - 1);
  unsigned int increment =
    (((hash_val >> _log_size)
      ^ (_ignore_length ? 0 : item->_allchars_length))
     << 1) + 1;
  /* Note that because _size is a power of 2 and increment is odd,
     we have gcd(increment,_size) = 1, which guarantees that we'll find
     an empty entry during the loop.  */

  while (_table[probe] != NULL)
    {
      if (equal (_table[probe], item))
        return _table[probe];

      _collisions++;
      probe = (probe + increment) & (_size - 1);
    }

  _table[probe] = item;
  return NULL;
}
