/* Copyright 2013 Akira Ohta (akohta001@gmail.com)
    This file is part of ntch.

    The ntch 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.

    The ntch 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 ntch.  If not, see <http://www.gnu.org/licenses/>.
    
*/
#include <stdlib.h>
#include <wchar.h>
#include <memory.h>
#include <assert.h>
#include "utils/nt_std_t.h"
#include "utils/text.h"

#define NT_ENUM_CHK_SUM (878428)
#define NT_MAP_CHK_SUM (878429)
#define NT_QUEUE_CHK_SUM (878430)

typedef struct tag_nt_enum_t *nt_enum_tp;
typedef struct tag_nt_enum_t {
	nt_enum_handle_t handle;
	nt_link_tp source;
	nt_link_tp current;
	
	nt_memfree_fn free_func;
}nt_enum_t;

typedef struct tag_nt_map_t *nt_map_tp;
typedef struct tag_nt_map_t {
	nt_map_handle_t handle;
	nt_link_tp key_value_list;
}nt_map_t;

typedef struct tag_nt_queue_t *nt_queue_tp;
typedef struct tag_nt_queue_t{
	nt_queue_handle_t handle;
	nt_link_tp linkp;
} nt_queue_t;


nt_map_handle nt_map_alloc()
{
	nt_map_tp mapp;
	mapp = malloc(sizeof(nt_map_t));
	if(!mapp)
		return NULL;
	
	mapp->handle.chk_sum = NT_MAP_CHK_SUM;
	mapp->key_value_list = NULL;
	return &mapp->handle;
}

void nt_map_free(nt_map_handle handle, nt_memfree_fn value_free_func)
{
	nt_map_tp mapp;
	nt_link_tp linkp, nextp;
	nt_w_key_value_tp kvp;
	assert(handle);
	assert(handle->chk_sum == NT_MAP_CHK_SUM);
	mapp = (nt_map_tp)handle;
	if(mapp->key_value_list){
		linkp = mapp->key_value_list;
		do{
			nextp = linkp->next;
			kvp = (nt_w_key_value_tp)linkp->data;
			if(kvp->value)
				if(value_free_func){
					(value_free_func)(kvp->value);
			}
			free(kvp->key);
			free(kvp);
			free(linkp);
			linkp = nextp;
		}while(linkp != mapp->key_value_list);
	}
	free(mapp);
}

BOOL nt_map_add_pair(nt_map_handle handle, const wchar_t *key, void *value)
{
	nt_map_tp mapp;
	nt_link_tp linkp;
	nt_w_key_value_tp kvp;
	assert(handle);
	assert(handle->chk_sum == NT_MAP_CHK_SUM);
	mapp = (nt_map_tp)handle;
	kvp = nt_map_find(handle, key);
	assert(!kvp);
	kvp = nt_w_key_value_alloc(key, value);
	if(!kvp)
		return FALSE;
	linkp = nt_link_add_data(mapp->key_value_list, kvp);
	if(!linkp){
		free(kvp->key);
		free(kvp);
		return FALSE;
	}
	if(!mapp->key_value_list)
		mapp->key_value_list = linkp;
	return TRUE;
}

void* nt_map_find(nt_map_handle handle, const wchar_t *key)
{
	nt_map_tp mapp;
	nt_link_tp linkp;
	nt_w_key_value_tp kvp;
	assert(handle);
	assert(handle->chk_sum == NT_MAP_CHK_SUM);
	mapp = (nt_map_tp)handle;
	if(mapp->key_value_list){
		linkp = mapp->key_value_list;
		do{
			kvp = (nt_w_key_value_tp)linkp->data;
			if(0 == wcscmp(key, kvp->key))
				return kvp->value;
			
			linkp = linkp->next;
		}while(linkp != mapp->key_value_list);
	}
	return NULL;
}

void* nt_map_remove(nt_map_handle handle, const wchar_t *key)
{
	nt_map_tp mapp;
	nt_link_tp linkp;
	nt_w_key_value_tp kvp;
	void *data;
	assert(handle);
	assert(handle->chk_sum == NT_MAP_CHK_SUM);
	mapp = (nt_map_tp)handle;
	if(mapp->key_value_list){
		linkp = mapp->key_value_list;
		do{
			kvp = (nt_w_key_value_tp)linkp->data;
			if(0 == wcscmp(key, kvp->key)){
				data = kvp->value;
				mapp->key_value_list = nt_link_remove2(
							mapp->key_value_list, linkp);
				free(linkp);
				free(kvp->key);
				free(kvp);
				return data;
			}
			linkp = linkp->next;
		}while(linkp != mapp->key_value_list);
	}
	return NULL;
}

int nt_map_get_count(nt_enum_handle handle)
{
	nt_map_tp mapp;
	assert(handle);
	assert(handle->chk_sum == NT_MAP_CHK_SUM);
	mapp = (nt_map_tp)handle;
	if(!mapp->key_value_list)
		return 0;
	return nt_link_num(mapp->key_value_list);
}


nt_enum_handle nt_enum_set(nt_link_tp linkp)
{
	nt_enum_tp enump;
	
	enump = malloc(sizeof(nt_enum_t));
	if(!enump)
		return NULL;
	enump->handle.chk_sum = NT_ENUM_CHK_SUM;
	
	enump->source = linkp;
	enump->current = NULL;
	enump->free_func = NULL;
	return (nt_enum_handle)enump;
}

void nt_enum_set_free_func(nt_enum_handle handle, nt_memfree_fn free_func)
{
	nt_enum_tp enump;
	enump = (nt_enum_tp)handle;
	assert(enump);
	assert(enump->handle.chk_sum == NT_ENUM_CHK_SUM);
	enump->free_func = 	free_func;
}


void nt_enum_reset(nt_enum_handle handle)
{
	nt_enum_tp enump;
	enump = (nt_enum_tp)handle;
	assert(enump);
	assert(enump->handle.chk_sum == NT_ENUM_CHK_SUM);
	enump->current = NULL;
}

void nt_enum_unset(nt_enum_handle handle)
{
	nt_enum_tp enump;
	enump = (nt_enum_tp)handle;
	assert(enump);
	assert(enump->handle.chk_sum == NT_ENUM_CHK_SUM);
	if(enump->source && enump->free_func){
		nt_all_link_free(enump->source, enump->free_func);
	}
	free(enump);
}

void* nt_enum_fetch(nt_enum_handle handle)
{
	nt_enum_tp enump;
	void *data;
	
	enump = (nt_enum_tp)handle;
	assert(enump);
	assert(enump->handle.chk_sum == NT_ENUM_CHK_SUM);
	
	if(!enump->source)
		return NULL;
	
	if(!enump->current){
		data = enump->source->data;
		enump->current = enump->source->next;
		return data;
	}
	
	if(enump->source == enump->current){
		return NULL;
	}
	
	data = enump->current->data;
	enump->current = enump->current->next;
	return data;
}

int nt_enum_get_count(nt_enum_handle handle)
{
	nt_enum_tp enump;
	enump = (nt_enum_tp)handle;
	assert(enump);
	assert(enump->handle.chk_sum == NT_ENUM_CHK_SUM);
	if(!enump->source)
		return 0;
	return nt_link_num(enump->source);
}

nt_link_tp nt_link_insert_before(nt_link_tp top_linkp,
		nt_link_tp dest, nt_link_tp src)
{
	if(top_linkp == dest){
		src->next = top_linkp;
		src->prev = top_linkp->prev;
		src->prev->next = src;
		src->next->prev = src;
		return src;
	}

	src->next = dest;
	src->prev = dest->prev;
	src->prev->next = src;
	src->next->prev = src;

	return top_linkp;
}

nt_link_tp nt_link_add_last(nt_link_tp top_linkp, nt_link_tp src)
{
	nt_link_tp old_last;
	
	if(!top_linkp)
		return src;
	
	old_last = top_linkp->prev;
	old_last->next = src;
	top_linkp->prev = src->prev;
	src->prev->next = top_linkp;
	src->prev = old_last;
	return top_linkp;
}

void nt_link_sort(nt_link_tp *top_linkp, nt_compare_fn comp_func)
{
	nt_link_tp cur_linkp, next_linkp, work_linkp;

	cur_linkp = *top_linkp;
	do{
		next_linkp = cur_linkp->next;
		work_linkp = *top_linkp;

		while(work_linkp != cur_linkp){
			if(0 < (comp_func)(cur_linkp->data, work_linkp->data)){
				*top_linkp = nt_link_remove2(*top_linkp, cur_linkp);
				*top_linkp = nt_link_insert_before(
						*top_linkp, work_linkp, cur_linkp);
				break;
			}
			work_linkp = work_linkp->next;
		}
		cur_linkp = next_linkp;
	}while(cur_linkp != *top_linkp);

}

void nt_link_n_sort(nt_link_tp *top_linkp, nt_compare_n_fn comp_func)
{
	nt_link_tp cur_linkp, next_linkp, work_linkp;

	cur_linkp = *top_linkp;
	do{
		next_linkp = cur_linkp->next;
		work_linkp = *top_linkp;

		while(work_linkp != cur_linkp){
			if(0 < (comp_func)(cur_linkp->n_data, work_linkp->n_data)){
				*top_linkp = nt_link_remove2(*top_linkp, cur_linkp);
				*top_linkp = nt_link_insert_before(
						*top_linkp, work_linkp, cur_linkp);
				break;
			}
			work_linkp = work_linkp->next;
		}
		cur_linkp = next_linkp;
	}while(cur_linkp != *top_linkp);

}

int nt_link_num(nt_link_tp linkp){
	nt_link_tp curp;
	int num = 1;
	assert(linkp);
	assert(linkp->next);
	curp= linkp->next;
	while(curp != linkp){
		num++;
		curp = curp->next;
	}
	return num;
}

void* nt_link_get_by_index(nt_link_tp linkp, int index){
	nt_link_tp curp;
	int i = 0;
	assert(linkp);
	assert(linkp->next);
	if(index == 0)
		return linkp->data;
	curp= linkp->next;
	while(curp != linkp){
		i++;
		if(i == index)
			return curp->data;
		curp = curp->next;
	}
	return NULL;
}

nt_link_tp nt_link_add_data(nt_link_tp link, void *data)
{
	nt_link_tp ptr = malloc(sizeof(nt_link_t));
	if(ptr == NULL)
		return NULL;

	ptr->data = data;
	
	if(link != NULL){
		ptr->prev = link->prev;
		ptr->prev->next = ptr;
		link->prev = ptr;
		ptr->next = link;
	}else{
		ptr->prev = ptr;
		ptr->next = ptr;
	}
	return ptr;
}

nt_link_tp nt_link_add_n_data(nt_link_tp link, int n_data)
{
	nt_link_tp ptr = calloc(1,sizeof(nt_link_t));
	if(ptr == NULL)
		return NULL;

	ptr->n_data = n_data;
	
	if(link != NULL){
		ptr->prev = link->prev;
		ptr->prev->next = ptr;
		link->prev = ptr;
		ptr->next = link;
	}else{
		ptr->prev = ptr;
		ptr->next = ptr;
	}
	return ptr;
}

nt_link_tp nt_link_remove(nt_link_tp src, nt_link_tp target)
{
	while(src != target){
		if(src->next == target){
			src->next = target->next;
			target->next->prev = src;
			return target;
		}
		src = src->next;
	}
	return target;
}

nt_link_tp nt_link_remove2(nt_link_tp top_linkp, nt_link_tp target)
{
	nt_link_tp new_top_linkp, prev, next;

	if(top_linkp == target){
		if(top_linkp == top_linkp->next)
			return NULL;
		new_top_linkp = top_linkp->next;
	}else{
		new_top_linkp = top_linkp;
	}
	prev = target->prev;
	next = target->next;
	prev->next = next;
	next->prev = prev;

	return new_top_linkp;
}

nt_link_tp nt_link_remove_by_data(nt_link_tp link, void *data)
{
	nt_link_tp ptr = link;
	do{
		if(ptr->data  == data){
			ptr->prev->next = ptr->next;
			ptr->next->prev = ptr->prev;
			return ptr;
		}
		ptr++;
	}while(ptr != link);

	return NULL;
}

void* nt_link_get(nt_link_tp link)
{
	return link->data;
}

nt_link_tp nt_link_find(nt_link_tp link, void *data, 
					nt_compare_fn comp_func)
{
	nt_link_tp ptr = link;
	do{
		if(0 == (comp_func)(ptr->data, data)){
			return ptr;
		}
		ptr = ptr->next;
	}while(ptr != link);

	return NULL;

}

nt_link_tp nt_link_next(nt_link_tp link)
{
	return link->next;
}

nt_link_tp nt_link_copy(nt_link_tp src_linkp)
{
	nt_link_tp srcp, resultp, workp;
	assert(src_linkp);
	
	srcp = src_linkp;
	resultp = NULL;
	do{
		workp = nt_link_add_data(resultp, srcp->data);
		if(!workp){
			if(resultp)
				nt_all_link_free(resultp, NULL);
			return NULL;
		}
		if(!resultp)
			resultp = workp;
		srcp = srcp->next;
	}while(srcp != src_linkp);
	return resultp;
}

void nt_all_link_free(nt_link_tp ptr, nt_memfree_fn free_func)
{
	nt_link_tp linkp;
	nt_link_tp tmp_linkp;
	assert(ptr);

	linkp = ptr;
	do{
		if(linkp->data && free_func)
			(free_func)(linkp->data);
		tmp_linkp = linkp;	
		linkp = linkp->next;
		free(tmp_linkp);
	}while(ptr != linkp);
}

nt_key_value_tp nt_key_value_alloc(char *key, char *value)
{
	nt_key_value_tp ptr = malloc(sizeof(nt_key_value_t));
	if(ptr == NULL)
		return NULL;
	ptr->key = key;
	ptr->value = value;
	return ptr;
}

nt_w_key_value_tp nt_w_key_value_alloc(const wchar_t *key, void *value)
{
	nt_w_key_value_tp ptr = malloc(sizeof(nt_w_key_value_t));
	if(ptr == NULL)
		return NULL;
	ptr->key = nt_w_str_clone(key);
	if(!ptr->key){
		free(ptr);
		return NULL;
	}
	ptr->value = value;
	return ptr;
}

void* nt_stack_add_last(nt_stack_tp stackp, void* data)
{
	int cursor;
	nt_link_tp linkp;

	assert(stackp);
	if(!stackp->linkp){
		return NULL;
	}
	linkp = stackp->linkp;
	cursor = 0;
	do{
		/* If there are data after the cursor, 
		 * then do nothing. 
		 */
		if(cursor > stackp->cursor){
			return NULL;
		}
		linkp = linkp->next;
		cursor++;
	}while(linkp != stackp->linkp);

	linkp = nt_link_add_data(stackp->linkp, data);
	if(!linkp)
		return NULL;
	/* This special case we do not increment cursor */
	/* stackp->cursor++;*/
	return data;
}

void* nt_stack_push(nt_stack_tp stackp, void* data)
{
	int cursor;
	nt_link_tp saved_linkp, linkp;

	assert(stackp);
	if(!stackp->linkp){
		linkp = nt_link_add_data(stackp->linkp, data);
		if(!linkp)
			return NULL;
		stackp->linkp = linkp;
		stackp->cursor = 0;
		return data;
	}
	linkp = stackp->linkp;
	cursor = 0;
	do{
		/* Remove the links after the current corsor. */
		if(cursor > stackp->cursor){
			saved_linkp = stackp->linkp->prev;
			stackp->linkp->prev = linkp->prev;
			linkp->prev->next = stackp->linkp;

			linkp->prev = saved_linkp;
			saved_linkp->next = linkp;

			/* Now the linkp is dangling */
			if(!stackp->unlinkedp){
				stackp->unlinkedp = linkp;
			}else{
				saved_linkp = stackp->unlinkedp->prev;
				stackp->unlinkedp->prev = linkp->prev;
				saved_linkp->next = linkp;
				linkp->prev = saved_linkp;
			}
			break;
		}
		linkp = linkp->next;
		cursor++;
	}while(linkp != stackp->linkp);

	linkp = nt_link_add_data(stackp->linkp, data);
	if(!linkp)
		return NULL;
	stackp->cursor++;
	return data;
}

void* nt_stack_pop(nt_stack_tp stackp)
{
	int cursor;
	nt_link_tp linkp;
	assert(stackp);
	if(!stackp->linkp)
		return NULL;
	linkp = stackp->linkp;
	cursor = 0;
	do{
		if(cursor == stackp->cursor){
			stackp->cursor--;
			if(stackp->cursor < 0){
				stackp->cursor++;
			}
			return linkp->data;
		}
		linkp = linkp->next;
		cursor++;
	}while(linkp != stackp->linkp);
	return NULL;
}

void* nt_stack_peek(nt_stack_tp stackp)
{
	int cursor;
	nt_link_tp linkp;
	assert(stackp);
	if(!stackp->linkp)
		return NULL;
	linkp = stackp->linkp;
	cursor = 0;
	do{
		if(cursor == stackp->cursor){
			return linkp->data;
		}
		linkp = linkp->next;
		cursor++;
	}while(linkp != stackp->linkp);
	return NULL;
}
int nt_stack_get_position(nt_stack_tp stackp)
{
	return stackp->cursor;
}

BOOL nt_stack_is_empty(nt_stack_tp stackp)
{
	return (!stackp->linkp);
}

void* nt_stack_cursor_next(nt_stack_tp stackp)
{
	int cursor;
	nt_link_tp linkp;
	assert(stackp);
	if(!stackp->linkp)
		return NULL;
	linkp = stackp->linkp;
	cursor = 0;
	do{
		if(cursor > (stackp->cursor+1)){
			stackp->cursor++;
			return linkp->data;
		}
		linkp = linkp->next;
		cursor++;
	}while(linkp != stackp->linkp);
	return NULL;
}

nt_stack_tp nt_stack_alloc()
{
	nt_stack_tp stackp = malloc(sizeof(nt_stack_t));
	if(!stackp)
		return NULL;

	stackp->cursor = -1;
	stackp->linkp = NULL;
	stackp->unlinkedp = NULL;
	
	return stackp;
}


void nt_stack_free(nt_stack_tp ptr, nt_memfree_fn free_func)
{
	assert(ptr);

	if(!free_func){
		free(ptr);
		return;
	}
	if(ptr->linkp){
		nt_all_link_free(ptr->linkp, free_func);
	}
	if(ptr->unlinkedp){
		nt_all_link_free(ptr->unlinkedp, free_func);
	}
	free(ptr);	
}

nt_queue_handle nt_queue_alloc()
{
	nt_queue_tp ptr;
	ptr = malloc(sizeof(nt_queue_t));
	if(!ptr)
		return NULL;
	ptr->linkp = NULL;
	ptr->handle.chk_sum = NT_QUEUE_CHK_SUM;
	return &ptr->handle;
}
void nt_queue_free(nt_queue_handle handle, nt_memfree_fn free_func)
{
	nt_queue_tp queuep;
	
	assert(handle);
	assert(handle->chk_sum == NT_QUEUE_CHK_SUM);
	queuep = (nt_queue_tp)handle;
	if(queuep->linkp){
		nt_all_link_free(queuep->linkp, free_func);
	}
	free(queuep);
}

BOOL nt_queue_push(nt_queue_handle handle, void* data)
{
	nt_link_tp linkp;
	nt_queue_tp queuep;
	
	assert(handle);
	assert(handle->chk_sum == NT_QUEUE_CHK_SUM);
	queuep = (nt_queue_tp)handle;
	
	linkp = nt_link_add_data(queuep->linkp, data);
	if(!linkp)
		return FALSE;
	
	if(!queuep->linkp)
		queuep->linkp = linkp;
	return TRUE;
}
void* nt_queue_shift(nt_queue_handle handle)
{
	nt_link_tp linkp, prev, next;
	nt_queue_tp queuep;
	void *data;
	
	assert(handle);
	assert(handle->chk_sum == NT_QUEUE_CHK_SUM);
	queuep = (nt_queue_tp)handle;
	
	if(!queuep->linkp)
		return NULL;
	
	linkp = queuep->linkp;
	if(linkp == linkp->next){
		data = linkp->data;
		free(linkp);
		queuep->linkp = NULL;
		return data;
	}
	
	prev = linkp->prev;
	next = linkp->next;
	
	prev->next = next;
	next->prev = prev;
	queuep->linkp = next;
	data = linkp->data;
	free(linkp);
	return data;
}

int nt_queue_get_count(nt_queue_handle handle)
{
	nt_queue_tp queuep;
	
	assert(handle);
	assert(handle->chk_sum == NT_QUEUE_CHK_SUM);
	queuep = (nt_queue_tp)handle;
	
	if(!queuep->linkp)
		return 0;
	return nt_link_num(queuep->linkp);
}


