using System;
using System.Diagnostics;

using u32 = System.UInt32;

namespace Community.CsharpSqlite
{
	public partial class Sqlite3
	{
		/*
		** 2001 September 22
		**
		** The author disclaims copyright to this source code.  In place of
		** a legal notice, here is a blessing:
		**
		**    May you do good and not evil.
		**    May you find forgiveness for yourself and forgive others.
		**    May you share freely, never taking more than you give.
		**
		*************************************************************************
		** This is the implementation of generic hash-tables
		** used in SQLite.
		*************************************************************************
		**  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
		**  C#-SQLite is an independent reimplementation of the SQLite software library
		**
		**  SQLITE_SOURCE_ID: 2010-08-23 18:52:01 42537b60566f288167f1b5864a5435986838e3a3
		**
		*************************************************************************
		*/
		//#include "sqliteInt.h"
		//#include <assert.h>

		/* Turn bulk memory into a hash table object by initializing the
		** fields of the Hash structure.
		**
		** "pNew" is a pointer to the hash table that is to be initialized.
		*/

		private static void sqlite3HashInit(Hash pNew)
		{
			Debug.Assert(pNew != null);
			pNew.first = null;
			pNew.count = 0;
			pNew.htsize = 0;
			pNew.ht = null;
		}

		/* Remove all entries from a hash table.  Reclaim all memory.
		** Call this routine to delete a hash table or to reset a hash table
		** to the empty state.
		*/

		private static void sqlite3HashClear(Hash pH)
		{
			HashElem elem;         /* For looping over all elements of the table */

			Debug.Assert(pH != null);
			elem = pH.first;
			pH.first = null;
			//sqlite3_free( ref pH.ht );
			pH.ht = null;
			pH.htsize = 0;
			while (elem != null)
			{
				HashElem next_elem = elem.next;
				////sqlite3_free(ref  elem );
				elem = next_elem;
			}
			pH.count = 0;
		}

		/*
		** The hashing function.
		*/

		private static u32 strHash(string z, int nKey)
		{
			int h = 0;
			Debug.Assert(nKey >= 0);
			int _z = 0;
			while (nKey > 0)
			{
				h = (h << 3) ^ h ^ ((_z < z.Length) ? (int)sqlite3UpperToLower[(byte)z[_z++]] : 0);
				nKey--;
			}
			return (u32)h;
		}

		/* Link pNew element into the hash table pH.  If pEntry!=0 then also
		** insert pNew into the pEntry hash bucket.
		*/

		private static void insertElement(
		Hash pH,              /* The complete hash table */
		_ht pEntry,           /* The entry into which pNew is inserted */
		HashElem pNew         /* The element to be inserted */
		)
		{
			HashElem pHead;       /* First element already in pEntry */
			if (pEntry != null)
			{
				pHead = pEntry.count != 0 ? pEntry.chain : null;
				pEntry.count++;
				pEntry.chain = pNew;
			}
			else
			{
				pHead = null;
			}
			if (pHead != null)
			{
				pNew.next = pHead;
				pNew.prev = pHead.prev;
				if (pHead.prev != null)
				{
					pHead.prev.next = pNew;
				}
				else
				{
					pH.first = pNew;
				}
				pHead.prev = pNew;
			}
			else
			{
				pNew.next = pH.first;
				if (pH.first != null)
				{
					pH.first.prev = pNew;
				}
				pNew.prev = null;
				pH.first = pNew;
			}
		}

		/* Resize the hash table so that it cantains "new_size" buckets.
		**
		** The hash table might fail to resize if sqlite3_malloc() fails or
		** if the new size is the same as the prior size.
		** Return TRUE if the resize occurs and false if not.
		*/

		private static bool rehash(ref Hash pH, u32 new_size)
		{
			_ht[] new_ht;            /* The new hash table */
			HashElem elem;
			HashElem next_elem;    /* For looping over existing elements */

#if SQLITE_MALLOC_SOFT_LIMIT
if( new_size*sizeof(struct _ht)>SQLITE_MALLOC_SOFT_LIMIT ){
new_size = SQLITE_MALLOC_SOFT_LIMIT/sizeof(struct _ht);
}
if( new_size==pH->htsize ) return false;
#endif

			/* There is a call to sqlite3Malloc() inside rehash(). If there is
** already an allocation at pH.ht, then if this malloc() fails it
** is benign (since failing to resize a hash table is a performance
** hit only, not a fatal error).
*/
			sqlite3BeginBenignMalloc();
			new_ht = new _ht[new_size]; //(struct _ht )sqlite3Malloc( new_size*sizeof(struct _ht) );
			for (int i = 0; i < new_size; i++)
				new_ht[i] = new _ht();
			sqlite3EndBenignMalloc();

			if (new_ht == null)
				return false;
			//sqlite3_free( ref pH.ht );
			pH.ht = new_ht;
			// pH.htsize = new_size = sqlite3MallocSize(new_ht)/sizeof(struct _ht);
			//memset(new_ht, 0, new_size*sizeof(struct _ht));
			pH.htsize = new_size;

			for (elem = pH.first, pH.first = null; elem != null; elem = next_elem)
			{
				u32 h = strHash(elem.pKey, elem.nKey) % new_size;
				next_elem = elem.next;
				insertElement(pH, new_ht[h], elem);
			}
			return true;
		}

		/* This function (for internal use only) locates an element in an
		** hash table that matches the given key.  The hash for this key has
		** already been computed and is passed as the 4th parameter.
		*/

		private static HashElem findElementGivenHash(
		Hash pH,       /* The pH to be searched */
		string pKey,   /* The key we are searching for */
		int nKey,      /* Bytes in key (not counting zero terminator) */
		u32 h         /* The hash for this key. */
		)
		{
			HashElem elem;                /* Used to loop thru the element list */
			int count;                    /* Number of elements left to test */

			if (pH.ht != null && pH.ht[h] != null)
			{
				_ht pEntry = pH.ht[h];
				elem = pEntry.chain;
				count = (int)pEntry.count;
			}
			else
			{
				elem = pH.first;
				count = (int)pH.count;
			}
			while (count-- > 0 && ALWAYS(elem))
			{
				if (elem.nKey == nKey && elem.pKey.Equals(pKey, StringComparison.OrdinalIgnoreCase))
				{
					return elem;
				}
				elem = elem.next;
			}
			return null;
		}

		/* Remove a single entry from the hash table given a pointer to that
		** element and a hash on the element's key.
		*/

		private static void removeElementGivenHash(
		Hash pH,            /* The pH containing "elem" */
		ref HashElem elem,  /* The element to be removed from the pH */
		u32 h               /* Hash value for the element */
		)
		{
			_ht pEntry;
			if (elem.prev != null)
			{
				elem.prev.next = elem.next;
			}
			else
			{
				pH.first = elem.next;
			}
			if (elem.next != null)
			{
				elem.next.prev = elem.prev;
			}
			if (pH.ht != null && pH.ht[h] != null)
			{
				pEntry = pH.ht[h];
				if (pEntry.chain == elem)
				{
					pEntry.chain = elem.next;
				}
				pEntry.count--;
				Debug.Assert(pEntry.count >= 0);
			}
			//sqlite3_free( ref elem );
			pH.count--;
			if (pH.count <= 0)
			{
				Debug.Assert(pH.first == null);
				Debug.Assert(pH.count == 0);
				sqlite3HashClear(pH);
			}
		}

		/* Attempt to locate an element of the hash table pH with a key
		** that matches pKey,nKey.  Return the data for this element if it is
		** found, or NULL if there is no match.
		*/

		private static T sqlite3HashFind<T>(Hash pH, string pKey, int nKey, T nullType) where T : class
		{
			HashElem elem;  /* The element that matches key */
			u32 h;          /* A hash on key */

			Debug.Assert(pH != null);
			Debug.Assert(pKey != null);
			Debug.Assert(nKey >= 0);
			if (pH.ht != null)
			{
				h = strHash(pKey, nKey) % pH.htsize;
			}
			else
			{
				h = 0;
			}
			elem = findElementGivenHash(pH, pKey, nKey, h);
			return elem != null ? (T)elem.data : nullType;
		}

		/* Insert an element into the hash table pH.  The key is pKey,nKey
		** and the data is "data".
		**
		** If no element exists with a matching key, then a new
		** element is created and NULL is returned.
		**
		** If another element already exists with the same key, then the
		** new data replaces the old data and the old data is returned.
		** The key is not copied in this instance.  If a malloc fails, then
		** the new data is returned and the hash table is unchanged.
		**
		** If the "data" parameter to this function is NULL, then the
		** element corresponding to "key" is removed from the hash table.
		*/

		private static T sqlite3HashInsert<T>(ref Hash pH, string pKey, int nKey, T data) where T : class
		{
			u32 h;       /* the hash of the key modulo hash table size */

			HashElem elem;       /* Used to loop thru the element list */
			HashElem new_elem;   /* New element added to the pH */

			Debug.Assert(pH != null);
			Debug.Assert(pKey != null);
			Debug.Assert(nKey >= 0);

			if (pH.htsize != 0)
			{
				h = strHash(pKey, nKey) % pH.htsize;
			}
			else
			{
				h = 0;
			}
			elem = findElementGivenHash(pH, pKey, nKey, h);
			if (elem != null)
			{
				T old_data = (T)elem.data;
				if (data == null)
				{
					removeElementGivenHash(pH, ref elem, h);
				}
				else
				{
					elem.data = data;
					elem.pKey = pKey;
					Debug.Assert(nKey == elem.nKey);
				}
				return old_data;
			}
			if (data == null)
				return data;
			new_elem = new HashElem();//(HashElem)sqlite3Malloc( sizeof(HashElem) );
			if (new_elem == null)
				return data;
			new_elem.pKey = pKey;
			new_elem.nKey = nKey;
			new_elem.data = data;
			pH.count++;
			if (pH.count >= 10 && pH.count > 2 * pH.htsize)
			{
				if (rehash(ref pH, pH.count * 2))
				{
					Debug.Assert(pH.htsize > 0);
					h = strHash(pKey, nKey) % pH.htsize;
				}
			}
			if (pH.ht != null)
			{
				insertElement(pH, pH.ht[h], new_elem);
			}
			else
			{
				insertElement(pH, null, new_elem);
			}
			return null;
		}
	}
}