#include "mk.h"
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <stdio.h>

static
void mk_extend_hashtable( MK_HASHTABLE *ptable );
static
int mk_find_item_position_string( MK_HASHTABLE *table, const MK_CHAR *key, void **result );
static
int mk_seek_insert_position_string( MK_HASHTABLE *table, const MK_CHAR *key, int *isKey );
static
int mk_find_item_position_ptr( MK_HASHTABLE *table, const INT_PTR key, void **result );
static
int mk_seek_insert_position_ptr( MK_HASHTABLE *table, const INT_PTR key, int *isKey );

MK_HASHTABLE *mk_create_hashtable( unsigned int defaultSize, unsigned int extend )
{
	size_t sizeElem = 
		sizeof( MK_MAP_ITEM ) * defaultSize;
	MK_HASHTABLE *result = 
		malloc( sizeof(MK_HASHTABLE) );

	result->size = defaultSize;
	result->used = 0;
	result->flags = MK_TYPE_HASHTABLE | ( ( extend << 10 ) & MK_TYPE_HASHTABLE_SIZE_EXTEND_MASK );
	result->flags |= MK_TYPE_ATTRIBUTE_HASHKEY_INTPTR;
	result->elems = malloc( sizeElem );
	return result;
}

unsigned int mk_size_item_hashtable( MK_HASHTABLE *ptable )
{
	return ptable->used;
}

MK_HASHTABLE *mk_allocate_vm_managed_hashtable( MK_MANAGED_HASH_TABLE **managedHashTable )
{
	MK_MANAGED_HASH_TABLE *topHashTable = NULL;
	MK_HASHTABLE *pCurrent = NULL;

	topHashTable = *managedHashTable;
	while( topHashTable != NULL )
	{
		if( topHashTable->freeSpace > 0 )
			break;
		topHashTable = topHashTable->previous;
	}
	if( topHashTable == NULL )
	{
		topHashTable = ( MK_MANAGED_HASH_TABLE * )malloc( sizeof( MK_MANAGED_HASH_TABLE ) );
		memset( topHashTable, 0x00, sizeof( MK_MANAGED_HASH_TABLE ) );
		topHashTable->freeSpace = MK_SIZEOF_MANAGED_TABLE;
		topHashTable->nextIndex = 0;
		topHashTable->previous = *managedHashTable;
		*managedHashTable = topHashTable;
	}
	pCurrent = topHashTable->hashTables + topHashTable->nextIndex;
	while( pCurrent < topHashTable->hashTables + MK_SIZEOF_MANAGED_TABLE )
	{
		if( MK_GC_IS_ALLOCATED_OBJECT( pCurrent->flags ) == 0 )
			break;
		pCurrent ++;
	}
	if( pCurrent >= topHashTable->hashTables + MK_SIZEOF_MANAGED_TABLE )
	{
		pCurrent = topHashTable->hashTables;
		while( pCurrent != topHashTable->hashTables + topHashTable->nextIndex )
		{
			if( MK_GC_IS_ALLOCATED_OBJECT( pCurrent->flags ) == 0 )
				break;
			pCurrent ++;
		}
	}

	pCurrent->flags = 
		MK_TYPE_HASHTABLE | ( ( MK_SIZEOF_EXTEND_HASH_DEFAULT << 10 ) & MK_TYPE_HASHTABLE_SIZE_EXTEND_MASK );
	MK_GC_SET_MANAGED_BIT( &pCurrent->flags );
	MK_GC_ALLOCATE_OBJECT( &pCurrent->flags );
	MK_GC_SET_CN_BIT( &pCurrent->flags );
	pCurrent->flags |= MK_TYPE_ATTRIBUTE_HASHKEY_INTPTR;

	if( pCurrent->elems == NULL )
	{
		pCurrent->elems = 
			malloc( sizeof( MK_MAP_ITEM ) * MK_SIZEOF_HASH_DEFAULT );
		
		pCurrent->size = MK_SIZEOF_HASH_DEFAULT;
	}
	pCurrent->used = 0;
	topHashTable->freeSpace --;
	topHashTable->nextIndex = pCurrent - topHashTable->hashTables + 1;
	return pCurrent;
}

unsigned int mk_insert_item_hashtable( MK_HASHTABLE *table, const MK_CHAR *key, void *value )
{
	unsigned int insertIndex = 0;
	int isKey = 0;
	if( table->used > 0 )
	{
		switch( MK_TYPE_ATTRIBUTE_HASHKEY_STYLE( table->flags ) )
		{
		case MK_TYPE_ATTRIBUTE_HASHKEY_STRING:
			insertIndex = mk_seek_insert_position_string( table, key, &isKey );
			break;
		case MK_TYPE_ATTRIBUTE_HASHKEY_INTPTR:
			insertIndex = mk_seek_insert_position_ptr( table, (INT_PTR)key, &isKey );
			break;
		}
	}

	if( isKey != 0 )
	{
		table->elems[insertIndex].value = value;
	}
	else
	{
		if( table->size == table->used )
			mk_extend_hashtable( table );
		if( insertIndex < table->used )
			memmove( &(table->elems[insertIndex+1]), 
				&(table->elems[insertIndex]), 
				sizeof( MK_MAP_ITEM ) * ( table->size - insertIndex - 1 ) );

		switch( MK_TYPE_ATTRIBUTE_HASHKEY_STYLE( table->flags ) )
		{
		case MK_TYPE_ATTRIBUTE_HASHKEY_STRING:
			mk_copy_string( &table->elems[insertIndex].key, key );
			break;
		case MK_TYPE_ATTRIBUTE_HASHKEY_INTPTR:
			table->elems[insertIndex].intPtrKey = (INT_PTR)key;
			break;
		}
		table->elems[insertIndex].value = value;
		table->used ++;
	}
	return insertIndex;
}

int mk_is_key_hashtable( MK_HASHTABLE *table, const MK_CHAR *key )
{
	int ret = 0;
	void *value;

	switch( MK_TYPE_ATTRIBUTE_HASHKEY_STYLE( table->flags ) )
	{
	case MK_TYPE_ATTRIBUTE_HASHKEY_STRING:
		ret = mk_find_item_position_string( table, key, &value );
		break;
	case MK_TYPE_ATTRIBUTE_HASHKEY_INTPTR:
		ret = mk_find_item_position_ptr( table, (const int)key, &value );
		break;
	}
	return ret >= 0;
}

int mk_find_item_hashtable( MK_HASHTABLE *ptable, const MK_CHAR *key, void **value )
{
	int ret = 0;

	switch( MK_TYPE_ATTRIBUTE_HASHKEY_STYLE( ptable->flags ) )
	{
	case MK_TYPE_ATTRIBUTE_HASHKEY_INTPTR:
		ret = mk_find_item_position_ptr( ptable, (const INT_PTR)key, value );
		break;
	case MK_TYPE_ATTRIBUTE_HASHKEY_STRING:
		ret = mk_find_item_position_string( ptable, key, value );
		break;
	}
	if( ret < 0 )
		*value = NULL;
	return ret >= 0;
}

void* mk_enum_item_hashtable_begin( MK_HASHTABLE *table )
{
	if( table->used == 0 )
		return NULL;
	else
		return (void*)0x00000001;
}

void* mk_enum_item_hashtable_next( MK_HASHTABLE *table, void *iterator, const MK_CHAR **key, void **value )
{
	unsigned int index = 
		( unsigned int )iterator - 1;

	if( index >= table->used )
		return NULL;

	*key = table->elems[index].key;
	*value = table->elems[index].value;
	return index + 1 < table->used ? ( void * )( index + 2 ) : NULL;
}

void mk_enum_item_hashtable_end( MK_HASHTABLE *table, void *iterator )
{
}

static
int mk_find_item_position_ptr( MK_HASHTABLE *table, const INT_PTR key, void **result )
{
	int high = table->used - 1, 
		low = 0, 
		mid = 0;
	int ret = 0;
	MK_MAP_ITEM *elems = 
		table->elems;
	while( low <= high )
	{
		int target = 0;
		mid = ( low + high ) / 2;
		target = elems[mid].intPtrKey;
		if( target == key )
		{
			*result = elems[mid].value;
			return mid;
		}
		else if(target < key )
		{
			low = mid + 1;
		}
		else
		{
			high = mid - 1;
		}
	}
	return -1;
}

static
int mk_find_item_position_string( MK_HASHTABLE *table, const MK_CHAR *key, void **result )
{
	int high = table->used - 1, 
		low = 0, 
		mid = 0;
	int ret = 0;
	MK_MAP_ITEM *elems = 
		table->elems;
	while( low <= high )
	{
		int ret = 0;
		mid = ( low + high ) / 2;
		ret = strcmp( elems[mid].key, key );
		if( ret == 0 )
		{
			*result = elems[mid].value;
			return mid;
		}
		else if( ret < 0 )
		{
			low = mid + 1;
		}
		else
		{
			high = mid - 1;
		}
	}
	return -1;
}

static
int mk_seek_insert_position_ptr( MK_HASHTABLE *table, const INT_PTR key, int *isKey )
{
	int high = table->used - 1, 
		low = 0, 
		mid = 0;
	int ret = 0;
	MK_MAP_ITEM *elems = 
		table->elems;
	while( low <= high )
	{
		int target = 0;
		mid = ( low + high ) / 2;
		target = elems[mid].intPtrKey;
		if( target == key )
		{
			*isKey = 1;
			return mid;
		}
		else if(target < key )
		{
			low = mid + 1;
		}
		else
		{
			high = mid - 1;
		}
	}
	*isKey = 0;
	return low;
}

static
int mk_seek_insert_position_string( MK_HASHTABLE *table, const MK_CHAR *key, int *isKey )
{
	int high = table->used - 1, 
		low = 0, 
		mid = 0;
	int ret = 0;
	MK_MAP_ITEM *elems = 
		table->elems;
	while( low <= high )
	{
		int ret = 0;
		mid = ( low + high ) / 2;
		ret = strcmp( elems[mid].key, key );
		if( ret == 0 )
		{
			*isKey = 1;
			return mid;		// key already registerd.
		}
		else if( ret < 0 )
			low = mid + 1;
		else
			high = mid - 1;
	}
	*isKey = 0;
	return low;	// key not found.
}

static
void mk_extend_hashtable( MK_HASHTABLE *ptable )
{
	MK_MAP_ITEM *newMapItem = NULL, *oldMapItem = NULL;
	unsigned int extend = 0;

	if( ptable == NULL )
		return;

	extend = MK_TYPE_HASHTABLE_SIZE_EXTEND(ptable->flags);
	newMapItem = 
		malloc( sizeof(MK_MAP_ITEM) * ( ptable->size + extend ) );
	memcpy( newMapItem,
		ptable->elems,
		sizeof( MK_MAP_ITEM ) * ptable->size );
	oldMapItem = ptable->elems;
	ptable->elems = newMapItem;
	ptable->size += extend;
	free( oldMapItem );
}
