/*
	SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
	http://www.audiosynth.com

    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 2 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, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/

#pragma once

#include "SC_Types.h"
#include "SC_BoundsMacros.h"
#include "Hash.h"
#include <stddef.h>

template<class T, class Allocator, class KeyType>
class HashTable
{
	Allocator *mPool;
	int32 mNumItems, mMaxItems, mTableSize, mHashMask;
	T** mItems;
	bool mCanResize;

public:

	HashTable(Allocator *inPool, int32 inMaxItems, bool inCanResize = true)
		: mPool(inPool)
	{
		mNumItems = 0;
		mMaxItems = inMaxItems;
		mTableSize = mMaxItems << 1;
		mItems = AllocTable(mTableSize);
		mHashMask = mTableSize - 1;
		mCanResize = inCanResize;
	}

	~HashTable() {
		mPool->Free(mItems);
	}

	int32 TableSize() const { return mTableSize; }
	int32 MaxItems() const { return mMaxItems; }
	int32 NumItems() const { return mNumItems; }

	T** AllocTable(int inTableSize)
	{
		size_t size = inTableSize * sizeof(T*);
		T** items = static_cast<T**>(mPool->Alloc(size));
		for (int i=0; i<inTableSize; ++i) {
			items[i] = 0;
		}
		return items;
	}

	void Resize()
	{
		int32 newSize = sc_max(mTableSize << 1, 32);
		T** oldItems = mItems;
		mItems = AllocTable(newSize);
		for (int i=0; i<mTableSize; ++i) {
			T* item = oldItems[i];
			if (item) Add(item);
		}
		mTableSize = newSize;
		mMaxItems = mTableSize >> 1;
		mHashMask = mTableSize - 1;
		mPool->Free(oldItems);
		//printf("mMaxItems %d   mTableSize %d   newSize %d\n", mMaxItems, mTableSize, newSize);
	}

	bool Add(T* inItem)
	{
		//printf("mNumItems %d\n", mNumItems);
		//printf("mMaxItems %d\n", mMaxItems);
		//printf("mCanResize %d\n", mCanResize);
		if (mNumItems >= mMaxItems) {
			if (!mCanResize) return false;
			Resize();
		}

		//printf("GetHash(inItem) %d\n", GetHash(inItem));
		//printf("GetKey(inItem) %s\n", GetKey(inItem));
		int32 index = IndexFor(GetHash(inItem), GetKey(inItem));
		//printf("index %d\n", index);

		T *item = mItems[index];
		if (item) return item == inItem;

		mItems[index] = inItem;
		mNumItems++;
		return true;
	}

	bool Remove(T* inItem)
	{
		int32 index = IndexFor(GetHash(inItem), GetKey(inItem));
		if (mItems[index] != inItem) return false;
		mItems[index] = 0;

		FixCollisionsFrom(index);
		mNumItems--;
		return true;
	}

	int32 IndexFor(int32 inHashID, KeyType inKey) const
	{
		int index = inHashID & mHashMask;
		for(;;) {
			T *item = mItems[index];
			if (!item) return index;
			if (GetHash(item) == inHashID
				&& strcmp(inKey, GetKey(item)) == 0) return index;
			index = (index + 1) & mHashMask;
		}
	}

	T* Get(KeyType inKey) const
	{
		return Get(Hash(inKey), inKey);
	}

	T* Get(int32 inHashID, KeyType inKey) const
	{
		int32 index = IndexFor(inHashID, inKey);
		return mItems[index];
	}

	bool Includes(T* inItem) const
	{
		return Get(GetHash(inItem), GetKey(inItem)) == inItem;
	}

	T* AtIndex(int32 inIndex) const
	{
		return mItems[inIndex];
	}

private:
	void FixCollisionsFrom(int32 inIndex)
	{
		int oldIndex = inIndex;
		for (;;) {
			oldIndex = (oldIndex + 1) & mHashMask;
			T *oldItem = mItems[oldIndex];
			if (!oldItem) break;
			int newIndex = IndexFor(GetHash(oldItem), GetKey(oldItem));
			if (oldIndex != newIndex) {
				mItems[oldIndex] = mItems[newIndex];
				mItems[newIndex] = oldItem;
			}
		}
	}
};

template<class T, int kMaxItems, class KeyType>
class StaticHashTable
{
	int32 mNumItems, mTableSize, mHashMask;
	T* mItems[kMaxItems*2];

public:

	StaticHashTable()
	{
		mNumItems = 0;
		mTableSize = kMaxItems << 1;
		ClearTable();
		mHashMask = mTableSize - 1;
	}

	~StaticHashTable() {
	}

	int32 TableSize() const { return mTableSize; }
	int32 MaxItems() const { return kMaxItems; }
	int32 NumItems() const { return mNumItems; }

	void ClearTable()
	{
		for (int i=0; i<mTableSize; ++i) {
			mItems[i] = 0;
		}
	}

	bool Add(T* inItem)
	{
		if (mNumItems >= kMaxItems) return false;

		int32 index = IndexFor(GetHash(inItem), GetKey(inItem));

		T *item = mItems[index];
		if (item) return item == inItem;

		mItems[index] = inItem;
		mNumItems++;
		return true;
	}

	bool Remove(T* inItem)
	{
		int32 index = IndexFor(GetHash(inItem), GetKey(inItem));
		if (mItems[index] != inItem) return false;
		mItems[index] = 0;

		FixCollisionsFrom(index);
		mNumItems--;
		return true;
	}

	int32 IndexFor(int32 inHashID, KeyType inKey) const
	{
		int index = inHashID & mHashMask;
		for(;;) {
			T *item = mItems[index];
			if (!item) return index;
			if (GetHash(item) == inHashID
				&& strcmp(inKey, GetKey(item)) == 0) return index;
			index = (index + 1) & mHashMask;
		}
	}

	T* Get(KeyType inKey) const
	{
		return Get(Hash(inKey), inKey);
	}

	T* Get(int32 inHashID, KeyType inKey) const
	{
		int32 index = IndexFor(inHashID, inKey);
		return mItems[index];
	}

	bool Includes(T* inItem) const
	{
		return Get(GetHash(inItem), GetKey(inItem)) == inItem;
	}

	T* AtIndex(int32 inIndex) const
	{
		return mItems[inIndex];
	}

private:
	void FixCollisionsFrom(int32 inIndex)
	{
		int oldIndex = inIndex;
		for (;;) {
			oldIndex = (oldIndex + 1) & mHashMask;
			T *oldItem = mItems[oldIndex];
			if (!oldItem) break;
			int newIndex = IndexFor(GetHash(oldItem), GetKey(oldItem));
			if (oldIndex != newIndex) {
				mItems[oldIndex] = mItems[newIndex];
				mItems[newIndex] = oldItem;
			}
		}
	}
};
