/* 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 <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <wchar.h>
#include <assert.h>

#include "error.h"
#include "utils/nt_std_t.h"
#include "utils/nt_mutex.h"
#include "utils/text.h"

#define NT_MUTEX_CHK_SUM (778428)

struct mutex_ctx_t {
	pthread_mutex_t mutex;
	nt_link_tp mutex_list;
};
static struct mutex_ctx_t g_mutext_ctx;

typedef struct _nt_mutex_t *nt_mutex_tp;
typedef struct _nt_mutex_t {
	nt_mutex_handle_t handle;
	pthread_mutex_t mutex;
	nt_link_tp moniker;
}nt_mutex_t;

static BOOL hit_test(nt_link_tp stock_moniker,
			nt_link_tp test_moniker);
static nt_mutex_tp get_match_moniker(nt_link_tp moniker);

static void nt_mutex_free(void *ptr)
{
	assert(ptr);
	nt_mutex_tp mutexp = (nt_mutex_tp)ptr;
	if(mutexp->moniker){
		nt_all_link_free(mutexp->moniker, free);
	}
	pthread_mutex_destroy(&(mutexp->mutex));
	free(mutexp);
}
void nt_mutex_lib_init()
{
	g_mutext_ctx.mutex_list = NULL;
	pthread_mutex_init(&(g_mutext_ctx.mutex), NULL);
}

void nt_mutex_lib_finish()
{
	pthread_mutex_lock(&(g_mutext_ctx.mutex));
	if(g_mutext_ctx.mutex_list){
		nt_all_link_free(g_mutext_ctx.mutex_list, nt_mutex_free);
	}
	pthread_mutex_unlock(&(g_mutext_ctx.mutex));
	pthread_mutex_destroy(&(g_mutext_ctx.mutex));
}

nt_mutex_handle nt_mutex_get_one_time_handle(const wchar_t *moniker)
{
	nt_mutex_tp mutexp;
	wchar_t *wc;
	nt_link_tp linkp, clinkp;
	int err;
	
	wc = nt_w_str_clone(moniker);
	if(!wc)
		return NULL;
	
	err = pthread_mutex_lock(&(g_mutext_ctx.mutex));
	if(err != 0)
		goto ERROR_TRAP;
	
	linkp = g_mutext_ctx.mutex_list;
	if(linkp){
		do{
			mutexp = linkp->data;
			assert(mutexp);
			if(!mutexp->moniker){
				clinkp = nt_link_add_data(NULL, wc);
				if(!clinkp){
					pthread_mutex_unlock(&(g_mutext_ctx.mutex));
					goto ERROR_TRAP;
				}
				mutexp->moniker = clinkp;
				g_mutext_ctx.mutex_list = 
						nt_link_remove2(g_mutext_ctx.mutex_list, linkp);
				pthread_mutex_unlock(&(g_mutext_ctx.mutex));
				free(linkp);
				return (nt_mutex_handle)mutexp;
			}
			linkp = linkp->next;
		}while(linkp != g_mutext_ctx.mutex_list);
	}
	err = pthread_mutex_unlock(&(g_mutext_ctx.mutex));
	if(err != 0)
		goto ERROR_TRAP;
	
	mutexp = malloc(sizeof(nt_mutex_t));
	if(!mutexp)
		goto ERROR_TRAP;
	mutexp->handle.chk_sum = NT_MUTEX_CHK_SUM;
	mutexp->moniker = nt_link_add_data(NULL, wc);
	if(!mutexp->moniker){
		free(mutexp);
		goto ERROR_TRAP;
	}
	pthread_mutex_init(&mutexp->mutex, NULL);
	return (nt_mutex_handle)mutexp;
ERROR_TRAP:
	free(wc);
	return NULL;
}

BOOL nt_mutex_add_moniker(nt_mutex_handle handle, const wchar_t *moniker)
{
	nt_mutex_tp mutexp;
	nt_link_tp linkp;
	wchar_t *wc;
	
	assert(handle);
	mutexp = (nt_mutex_tp)handle;
	assert(mutexp->handle.chk_sum == NT_MUTEX_CHK_SUM);
	assert(mutexp->moniker);
	
	wc = nt_w_str_clone(moniker);
	if(!wc)
		return FALSE;
	
	linkp = nt_link_add_data(mutexp->moniker, wc);
	if(!linkp){
		free(wc);
		return FALSE;
	}
	return TRUE;
}

BOOL nt_mutex_add_mbs_moniker(nt_mutex_handle handle, const char *moniker)
{
	nt_mutex_tp mutexp;
	nt_link_tp linkp;
	wchar_t *wc;
	int len;
	
	assert(handle);
	mutexp = (nt_mutex_tp)handle;
	assert(mutexp->handle.chk_sum == NT_MUTEX_CHK_SUM);
	assert(mutexp->moniker);
	
	len = strlen(moniker);
	if(len == 0)
		return FALSE;
	
	wc = malloc(sizeof(wchar_t)*(len+2));
	if(!wc)
		return FALSE;
	
	if(0 >= mbstowcs(wc, moniker, len+2)){
		free(wc);
		return FALSE;
	}
	
	linkp = nt_link_add_data(mutexp->moniker, wc);
	if(!linkp){
		free(wc);
		return FALSE;
	}
	return TRUE;
}

BOOL nt_mutex_lock(nt_mutex_handle handle)
{
	nt_mutex_tp mutexp;
	nt_mutex_tp stock_mutexp;
	nt_link_tp linkp;
	int err;
	
	assert(handle);
	mutexp = (nt_mutex_tp)handle;
	assert(mutexp->handle.chk_sum == NT_MUTEX_CHK_SUM);
	
	while(1){
		err = pthread_mutex_lock(&(g_mutext_ctx.mutex));
		if(err != 0)
			return FALSE;
		
		stock_mutexp = get_match_moniker(mutexp->moniker);
		if(!stock_mutexp){
			linkp = nt_link_add_data(g_mutext_ctx.mutex_list, mutexp);
			if(!linkp){
				err = pthread_mutex_unlock(&(g_mutext_ctx.mutex));
				return FALSE;
			}
			if(!g_mutext_ctx.mutex_list)
				g_mutext_ctx.mutex_list = linkp;
		}
		err = pthread_mutex_unlock(&(g_mutext_ctx.mutex));
		if(err != 0)
			return FALSE;
		
		if(!stock_mutexp)
			break;
		
		err = pthread_mutex_lock(&(stock_mutexp->mutex));
		if(err != 0)
			return FALSE;
		err = pthread_mutex_unlock(&(stock_mutexp->mutex));
		if(err != 0)
			return FALSE;
	}
	
	err = pthread_mutex_lock(&mutexp->mutex);
	if(err != 0)
		return FALSE;
	return TRUE;
}

BOOL nt_mutex_unlock(nt_mutex_handle handle)
{
	nt_mutex_tp mutexp;
	int err;
	
	assert(handle);
	assert(handle->chk_sum == NT_MUTEX_CHK_SUM);
	
	mutexp = (nt_mutex_tp)handle;
	err = pthread_mutex_unlock(&mutexp->mutex);
	if(err != 0)
		return FALSE;
	
	err = pthread_mutex_lock(&(g_mutext_ctx.mutex));
	if(err != 0)
		return FALSE;
	assert(mutexp->moniker);
	nt_all_link_free(mutexp->moniker, free);
	mutexp->moniker = NULL;
	
	err = pthread_mutex_unlock(&(g_mutext_ctx.mutex));
	if(err != 0)
		return FALSE;
	
	return TRUE;
}

static BOOL hit_test(nt_link_tp stock_moniker,
			nt_link_tp test_moniker)
{
	wchar_t *wc1, *wc2;
	nt_link_tp slinkp, tlinkp;
	
	assert(test_moniker);
	
	if(!stock_moniker)
		return FALSE;
	
	slinkp = stock_moniker;
	tlinkp = test_moniker;
	do{
		wc1 = slinkp->data;
		wc2 = tlinkp->data;
		assert(wc1 && wc2);
		if(0 != wcscmp(wc1, wc2)){
			return FALSE;
		}
		slinkp = slinkp->next;
		tlinkp = tlinkp->next;
	}while((tlinkp != test_moniker)
		&& (slinkp != stock_moniker));
	
	return TRUE;
}

static nt_mutex_tp get_match_moniker(nt_link_tp moniker)
{
	nt_link_tp linkp;
	nt_mutex_tp stock_mutexp;
	
	assert(moniker);
	
	linkp = g_mutext_ctx.mutex_list;
	if(linkp){
		do{
			stock_mutexp = linkp->data;
			assert(stock_mutexp);
			if(hit_test(stock_mutexp->moniker, moniker))
				return stock_mutexp;
			linkp = linkp->next;
		}while(linkp != g_mutext_ctx.mutex_list);
	}
	return NULL;
}

