// $Id: Dict.cc,v 1.23 2003/04/09 06:56:57 vern Exp $
//
// Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003
//	The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif

#include "Dict.h"

// If the mean bucket length exceeds the following then Insert() will
// increase the size of the hash table.
#define DEFAULT_DENSITY_THRESH 3.0

// Threshold above which we do not try to ensure that the hash size
// is prime.
#define PRIME_THRESH 1000

class DictEntry {
public:
	DictEntry(void* k, int l, hash_t h, void* val)
		{ key = k; len = l; hash = h; value = val; }

	~DictEntry()
		{
		delete [] (char *)key;
		}

	void* key;
	int len;
	hash_t hash;
	void* value;
};

// The value of an iteration cookie is the bucket and offset within the
// bucket at which to start looking for the next value to return.
class IterCookie {
public:
	IterCookie(int b, int o)
		{
		bucket = b;
		offset = o;
		ttbl = 0;
		num_buckets_p = 0;
		}

	int bucket, offset;
	PList(DictEntry)** ttbl;
	const int* num_buckets_p;
};

Dictionary::Dictionary(dict_order ordering, int initial_size)
	{
	Init(initial_size);
	tbl2 = 0;

	if ( ordering == ORDERED )
		order = new PList(DictEntry);
	else
		order = 0;

	SetDensityThresh(DEFAULT_DENSITY_THRESH);

	delete_func = 0;
	}

Dictionary::~Dictionary()
	{
	for ( int i = 0; i < num_buckets; ++i )
		if ( tbl[i] )
			{
			PList(DictEntry)* chain = tbl[i];
			loop_over_list(*chain, j)
				{
				DictEntry* e = (*chain)[j];
				if ( delete_func )
					delete_func(e->value);
				delete e;
				}

			delete chain;
			}

	delete [] tbl;
	delete order;

	if ( tbl2 == 0 )
		return;

	for ( int i = 0; i < num_buckets2; ++i )
		if ( tbl2[i] )
			{
			PList(DictEntry)* chain = tbl2[i];
			loop_over_list(*chain, j)
				{
				DictEntry* e = (*chain)[j];
				if ( delete_func )
					delete_func(e->value);
				delete e;
				}

			delete chain;
			}
	delete [] tbl2;
	}

void* Dictionary::Lookup(const void* key, int key_size, hash_t hash) const
	{
	hash_t h;
	PList(DictEntry)* chain;

	// Figure out which hash table to look in.
	h = hash % num_buckets;
	if ( ! tbl2 || h >= tbl_next_ind )
		chain = tbl[h];
	else
		chain = tbl2[hash % num_buckets2];

	if ( chain )
		{
		for ( int i = 0; i < chain->length(); ++i )
			{
			DictEntry* entry = (*chain)[i];

			if ( entry->hash == hash && entry->len == key_size &&
			     ! memcmp(key, entry->key, key_size) )
				return entry->value;
			}
		}

	return 0;
	}

void* Dictionary::Insert(void* key, int key_size, hash_t hash, void* val,
				int copy_key)
	{
	DictEntry* new_entry = new DictEntry(key, key_size, hash, val);
	void* old_val = Insert(new_entry, copy_key);

	if ( old_val )
		{
		// We didn't need the new DictEntry, the key was already
		// present.
		delete new_entry;
		}
	else if ( order )
		order->append(new_entry);

	// Resize logic.
	if ( tbl2 )
		MoveChains();
	else if ( num_entries >= thresh_entries )
		StartChangeSize(num_buckets * 2 + 1);

	return old_val;
	}

void* Dictionary::Remove(const void* key, int key_size, hash_t hash)
	{
	hash_t h;
	PList(DictEntry)* chain;
	int* num_entries_ptr;

	// Figure out which hash table to look in
	h = hash % num_buckets;
	if ( ! tbl2 || h >= tbl_next_ind )
		{
		chain = tbl[h];
		num_entries_ptr = &num_entries;
		}
	else
		{
		chain = tbl2[hash % num_buckets2];
		num_entries_ptr = &num_entries2;
		}

	if ( chain )
		{
		for ( int i = 0; i < chain->length(); ++i )
			{
			DictEntry* entry = (*chain)[i];

			if ( entry->hash == hash && entry->len == key_size &&
			     ! memcmp(key, entry->key, key_size) )
				{
				void* entry_value = entry->value;

				chain->remove_nth(i);
				if ( order )
					order->remove(entry);

				delete entry;
				--*num_entries_ptr;
				return entry_value;
				}
			}
		}

	return 0;
	}

void* Dictionary::NthEntry(int n, const void*& key, int& key_len) const
	{
	if ( order || n < 0 || n >= Length() )
		return 0;

	DictEntry* entry = (*order)[n];
	key = entry->key;
	key_len = entry->len;
	return entry->value;
	}

IterCookie* Dictionary::InitForIteration() const
	{
	return new IterCookie(0, 0);
	}

void* Dictionary::NextEntry(HashKey*& h, IterCookie*& cookie, int return_hash) const
	{
	int b = cookie->bucket;
	int o = cookie->offset;
	PList(DictEntry)** ttbl;
	const int* num_buckets_p;
	DictEntry* entry;

	if ( ! cookie->ttbl )
		{
		// XXX maybe we could update cookie->b from tbl_next_ind here?
		cookie->ttbl = tbl;
		cookie->num_buckets_p = &num_buckets;
		}

	ttbl = cookie->ttbl;
	num_buckets_p = cookie->num_buckets_p;

	if ( ttbl[b] && ttbl[b]->length() > o )
		{
		entry = (*ttbl[b])[o];
		++cookie->offset;
		if ( return_hash )
			h = new HashKey(entry->key, entry->len, entry->hash);
		return entry->value;
		}

	++b;	// Move on to next non-empty bucket.
	while ( b < *num_buckets_p && (! ttbl[b] || ttbl[b]->length() == 0) )
		++b;

	if ( b >= *num_buckets_p )
		{
		// If we're resizing, we need to search the 2nd table too.
		if ( ttbl == tbl && tbl2 )
			{
			cookie->ttbl = tbl2;
			cookie->num_buckets_p = &num_buckets2;
			cookie->bucket = 0;
			cookie->offset = 0;
			return Dictionary::NextEntry(h, cookie, return_hash);
			}

		// All done.
		delete cookie;
		cookie = 0;
		return 0;
		}

	entry = (*ttbl[b])[0];
	if ( return_hash )
		h = new HashKey(entry->key, entry->len, entry->hash);

	cookie->bucket = b;
	cookie->offset = 1;

	return entry->value;
	}

void Dictionary::Init(int size)
	{
	num_buckets = NextPrime(size);
	tbl = new PList(DictEntry)*[num_buckets];

	for ( int i = 0; i < num_buckets; ++i )
		tbl[i] = 0;

	max_num_entries = num_entries = 0;
	}

void Dictionary::Init2(int size)
	{
	num_buckets2 = NextPrime(size);
	tbl2 = new PList(DictEntry)*[num_buckets2];

	for ( int i = 0; i < num_buckets2; ++i )
		tbl2[i] = 0;

	max_num_entries2 = num_entries2 = 0;
	}

// private
void* Dictionary::Insert(DictEntry* new_entry, int copy_key)
	{
	PList(DictEntry)** ttbl;
	int* num_entries_ptr;
	int* max_num_entries_ptr;
	hash_t h = new_entry->hash % num_buckets;

	// We must be careful when we are in the middle of resizing.
	// If the new entry hashes to a bucket in the old table we
	// haven't moved yet, we need to put it in the old table. If
	// we didn't do it this way, we would sometimes have to
	// search both tables which is probably more expensive.

	if ( ! tbl2 || h >= tbl_next_ind )
		{
		ttbl = tbl;
		num_entries_ptr = &num_entries;
		max_num_entries_ptr = &max_num_entries;
		}
	else
		{
		ttbl = tbl2;
		h = new_entry->hash % num_buckets2;
		num_entries_ptr = &num_entries2;
		max_num_entries_ptr = &max_num_entries2;
		}

	PList(DictEntry)* chain = ttbl[h];

	int n = new_entry->len;

	if ( chain )
		{
		for ( int i = 0; i < chain->length(); ++i )
			{
			DictEntry* entry = (*chain)[i];

			if ( entry->hash == new_entry->hash &&
			     entry->len == n &&
			     ! memcmp(entry->key, new_entry->key, n) )
				{
				void* old_value = entry->value;
				entry->value = new_entry->value;
				return old_value;
				}
			}
		}
	else
		// Create new chain.
		chain = ttbl[h] = new PList(DictEntry);

	// If we got this far, then we couldn't use an existing copy
	// of the key, so make a new one if necessary.
	if ( copy_key )
		{
		void* old_key = new_entry->key;
		new_entry->key = (void*) new char[n];
		memcpy(new_entry->key, old_key, n);
		}

	// We happen to know (:-() that appending is more efficient
	// on lists than prepending.
	chain->append(new_entry);

	if ( *max_num_entries_ptr < ++*num_entries_ptr )
		*max_num_entries_ptr = *num_entries_ptr;

	return 0;
	}

int Dictionary::NextPrime(int n) const
	{
	if ( (n & 0x1) == 0 )
		// Even.
		++n;

	if ( n > PRIME_THRESH )
		// Too expensive to test for primality, just stick with it.
		return n;

	while ( ! IsPrime(n) )
		n += 2;

	return n;
	}

int Dictionary::IsPrime(int n) const
	{
	for ( int j = 3; j * j <= n; ++j )
		if ( n % j == 0 )
			return 0;

	return 1;
	}

void Dictionary::StartChangeSize(int new_size)
	{

	if ( tbl2 )
		internal_error("Dictionary::StartChangeSize() tbl2 not NULL");

	Init2(new_size);

	tbl_next_ind = 0;

	// Preserve threshold density
	SetDensityThresh2(DensityThresh());
	}

void Dictionary::MoveChains()
	{
	// Attempt to move this many entries (must do at least 2)
	int num = 8;

	do
		{
		PList(DictEntry)* chain = tbl[tbl_next_ind++];

		if ( ! chain )
			continue;

		tbl[tbl_next_ind - 1] = 0;

		for ( int j = 0; j < chain->length(); ++j )
			{
			Insert((*chain)[j], 0);
			--num_entries;
			--num;
			}

		delete chain;
		}
	while ( num > 0 && int(tbl_next_ind) < num_buckets );

	if ( int(tbl_next_ind) >= num_buckets )
		FinishChangeSize();
	}

void Dictionary::FinishChangeSize()
	{
	// Cheap safety check.
	if ( num_entries != 0 )
		internal_error(
		    "Dictionary::FinishChangeSize: num_entries is %d\n",
		    num_entries);

	delete [] tbl;
	tbl = tbl2;
	tbl2 = 0;

	num_buckets = num_buckets2;
	num_entries = num_entries2;
	max_num_entries = max_num_entries2;
	den_thresh = den_thresh2;
	thresh_entries = thresh_entries2;

	num_buckets2 = 0;
	num_entries2 = 0;
	max_num_entries2 = 0;
	den_thresh2 = 0;
	thresh_entries2 = 0;
	}

void generic_delete_func(void* v)
	{
	free(v);
	}
