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

#include "usr/favorite_t.h"
#include "usr/usr_db_t.h"
#include "utils/text.h"
#include "utils/nt_conv_char.h" 

#define NT_FAVORITE_CHK_SUM (678428)
#define NT_FAVORITE_BOARD_CHK_SUM (678429)
#define NT_FAVORITE_GRP_CHK_SUM (678430)
#define NT_FAVORITE_THREAD_CHK_SUM (678431)

#define NT_FAVORITE_BOARD_MUTEX_KEY (L"boards")
#define NT_FAVORITE_GROUP_MUTEX_KEY (L"gruops")

typedef struct tag_nt_favorite_t *nt_favorite_tp;
typedef struct tag_nt_favorite_t {
	nt_favorite_handle_t handle;
	int ref_count;
	wchar_t *key;
	nt_link_tp favorite_board_list;
	nt_link_tp favorite_grp_list;
} nt_favorite_t;


typedef struct tag_nt_favorite_board_t *nt_favorite_board_tp;
typedef struct tag_nt_favorite_board_t {
	nt_favorite_board_handle_t handle;
	int ref_count;
	nt_favorite_tp parent;
	wchar_t *board_name;
} nt_favorite_board_t;

typedef struct tag_nt_favorite_grp_t *nt_favorite_grp_tp;
typedef struct tag_nt_favorite_grp_t {
	nt_favorite_grp_handle_t handle;
	int ref_count;
	nt_favorite_tp parent;
	wchar_t *grp_name;
	int num_content;
	int flags;
	nt_link_tp contents;
} nt_favorite_grp_t;

typedef struct tag_nt_favorite_thread_t *nt_favorite_thread_tp;
typedef struct tag_nt_favorite_thread_t {
	nt_favorite_thread_handle_t handle;
	int ref_count;
	nt_favorite_grp_tp parent;
	wchar_t *dat_name;
	wchar_t *board_name;
	wchar_t *title;
	int num_read;
	int num_res;
} nt_favorite_thread_t;

static int local_wcscmp(void *lhs, void *rhs)
{
	return wcscmp((const wchar_t *)lhs, (const wchar_t *)rhs);
}
static void free_favorite_board(void *ptr);
static void free_favorite_grp(void *ptr);
static void free_grp_content(void *ptr);
static nt_favorite_board_tp get_board_by_name(
		nt_favorite_tp favoritep, const wchar_t *board_name);
static nt_favorite_thread_tp get_thread(
		nt_favorite_grp_tp grpp,
		const wchar_t *board_name,
		const wchar_t *dat_name);
static BOOL nt_favorite_retrieve_thread(
	nt_favorite_thread_handle h_thread, nt_link_tp *thread_list);
static nt_favorite_thread_tp parse_favorite_thread(const wchar_t *source);

const wchar_t* nt_favorite_board_get_name(
		nt_favorite_board_handle handle)
{
	nt_favorite_board_tp boardp;
	boardp = (nt_favorite_board_tp)handle;
	assert(boardp);
	assert(boardp->handle.chk_sum ==
		NT_FAVORITE_BOARD_CHK_SUM);
	return boardp->board_name;
}

const wchar_t* nt_favorite_grp_get_name(
		nt_favorite_grp_handle handle)
{
	nt_favorite_grp_tp grpp;
	grpp = (nt_favorite_grp_tp)handle;
	assert(grpp);
	assert(grpp->handle.chk_sum ==
		NT_FAVORITE_GRP_CHK_SUM);
	return grpp->grp_name;
}


const wchar_t* nt_favorite_thread_get_dat_name(
		nt_favorite_thread_handle handle)
{
	nt_favorite_thread_tp threadp;
	threadp = (nt_favorite_thread_tp)handle;
	assert(threadp);
	assert(threadp->handle.chk_sum ==
		NT_FAVORITE_THREAD_CHK_SUM);
	return threadp->dat_name;
}

const wchar_t* nt_favorite_thread_get_board_name(
		nt_favorite_thread_handle handle)
{
	nt_favorite_thread_tp threadp;
	threadp = (nt_favorite_thread_tp)handle;
	assert(threadp);
	assert(threadp->handle.chk_sum ==
		NT_FAVORITE_THREAD_CHK_SUM);
	return threadp->board_name;
}

const wchar_t* nt_favorite_thread_get_title(
		nt_favorite_thread_handle handle)
{
	nt_favorite_thread_tp threadp;
	threadp = (nt_favorite_thread_tp)handle;
	assert(threadp);
	assert(threadp->handle.chk_sum ==
		NT_FAVORITE_THREAD_CHK_SUM);
	return threadp->title;
}

int nt_favorite_thread_get_read_count(
		nt_favorite_thread_handle handle)
{
	nt_favorite_thread_tp threadp;
	threadp = (nt_favorite_thread_tp)handle;
	assert(threadp);
	assert(threadp->handle.chk_sum ==
		NT_FAVORITE_THREAD_CHK_SUM);
	return threadp->num_read;
}

int nt_favorite_thread_get_estimate_res_count(
		nt_favorite_thread_handle handle)
{
	nt_favorite_thread_tp threadp;
	threadp = (nt_favorite_thread_tp)handle;
	assert(threadp);
	assert(threadp->handle.chk_sum ==
		NT_FAVORITE_THREAD_CHK_SUM);
	return threadp->num_res;
}
void nt_favorite_thread_set_read_count(
		nt_favorite_thread_handle handle, int read_count)
{
	nt_favorite_thread_tp threadp;
	threadp = (nt_favorite_thread_tp)handle;
	assert(threadp);
	assert(threadp->handle.chk_sum ==
		NT_FAVORITE_THREAD_CHK_SUM);
	threadp->num_read = read_count;
}

void nt_favorite_thread_set_estimate_res_count(
		nt_favorite_thread_handle handle, int res_count)
{
	nt_favorite_thread_tp threadp;
	threadp = (nt_favorite_thread_tp)handle;
	assert(threadp);
	assert(threadp->handle.chk_sum ==
		NT_FAVORITE_THREAD_CHK_SUM);
	threadp->num_res = res_count;
}

BOOL nt_favorite_grp_get_flag(
		nt_favorite_grp_handle handle, int flag)
{
	nt_favorite_grp_tp grpp;
	grpp = (nt_favorite_grp_tp)handle;
	assert(grpp);
	assert(grpp->handle.chk_sum ==
		NT_FAVORITE_GRP_CHK_SUM);
	return (grpp->flags & flag);
}
void nt_favorite_grp_set_flag(
		nt_favorite_grp_handle handle, int flag)
{
	nt_favorite_grp_tp grpp;
	grpp = (nt_favorite_grp_tp)handle;
	assert(grpp);
	assert(grpp->handle.chk_sum ==
		NT_FAVORITE_GRP_CHK_SUM);
	grpp->flags |= flag;
}
void nt_favorite_grp_unset_flag(
		nt_favorite_grp_handle handle, int flag)
{
	nt_favorite_grp_tp grpp;
	grpp = (nt_favorite_grp_tp)handle;
	assert(grpp);
	assert(grpp->handle.chk_sum ==
		NT_FAVORITE_GRP_CHK_SUM);
	grpp->flags &= (~flag);
}

nt_enum_handle nt_favorite_acquire_board_enum(nt_favorite_handle handle)
{
	nt_favorite_tp favoritep;
	
	assert(handle);
	favoritep = (nt_favorite_tp)handle;
	assert(favoritep->handle.chk_sum == NT_FAVORITE_CHK_SUM);
	return nt_enum_set(favoritep->favorite_board_list);
}
nt_enum_handle nt_favorite_acquire_grp_enum(nt_favorite_handle handle)
{
	nt_favorite_tp favoritep;
	
	assert(handle);
	favoritep = (nt_favorite_tp)handle;
	assert(favoritep->handle.chk_sum == NT_FAVORITE_CHK_SUM);
	return nt_enum_set(favoritep->favorite_grp_list);
}
nt_enum_handle nt_favorite_acquire_thread_enum(nt_favorite_grp_handle handle)
{
	nt_favorite_grp_tp grpp;
	
	assert(handle);
	grpp = (nt_favorite_grp_tp)handle;
	assert(grpp->handle.chk_sum == NT_FAVORITE_GRP_CHK_SUM);
	return nt_enum_set(grpp->contents);
}


nt_favorite_handle nt_favorite_alloc(const wchar_t *unique_key)
{
	 nt_favorite_tp favoritep =
		malloc(sizeof(nt_favorite_t));
	if(!favoritep)
		return NULL;
	favoritep->key = nt_w_str_clone(unique_key);
	if(!favoritep->key){
		free(favoritep);
		return NULL;
	}
	favoritep->handle.chk_sum = NT_FAVORITE_CHK_SUM;
	favoritep->favorite_board_list = NULL;
	favoritep->favorite_grp_list = NULL;
	favoritep->ref_count = 1;
	return (nt_favorite_handle)&favoritep->handle;
}

nt_favorite_board_handle nt_favorite_board_alloc(
	nt_favorite_handle favorite_handle, const wchar_t *board_name)
{
	nt_favorite_tp favoritep;
	nt_favorite_board_tp boardp;
	nt_link_tp linkp;
	nt_mutex_handle h_mutex;
	
	assert(favorite_handle);
	assert(board_name);
	favoritep = (nt_favorite_tp)favorite_handle;
	assert(favoritep->handle.chk_sum == NT_FAVORITE_CHK_SUM);
	
	boardp = get_board_by_name(favoritep, board_name);
	if(boardp){
		boardp->ref_count++;
		return (nt_favorite_board_handle)&boardp->handle;
	}
	boardp = malloc(sizeof(nt_favorite_board_t));
	if(!boardp)
		return NULL;
	boardp->handle.chk_sum = NT_FAVORITE_BOARD_CHK_SUM;
	boardp->board_name = nt_w_trim(board_name);
	boardp->parent = favoritep;
	boardp->ref_count = 2;
	
	h_mutex = nt_favorite_board_get_mutex(
			(nt_favorite_board_handle)&boardp->handle);
	if(!h_mutex){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_lock(h_mutex)){
		goto ERROR_TRAP;
	}
	linkp = nt_link_add_data(favoritep->favorite_board_list, boardp);
	if(!linkp){
		nt_mutex_unlock(h_mutex);
		goto ERROR_TRAP;
	}
	
	if(!favoritep->favorite_board_list)
		favoritep->favorite_board_list = linkp;
	
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	return (nt_favorite_board_handle)&boardp->handle;
ERROR_TRAP:
	free(boardp->board_name);
	free(boardp);
	return NULL;
}

BOOL nt_favorite_board_remove(nt_favorite_board_handle handle)
{
	nt_favorite_tp favoritep;
	nt_favorite_board_tp boardp;
	nt_link_tp linkp, find_linkp;
	nt_mutex_handle h_mutex;
	BOOL result;
	
	assert(handle);
	
	result = FALSE;
	
	nt_favorite_board_add_ref(handle);
	
	boardp = (nt_favorite_board_tp)handle;
	favoritep = boardp->parent;
	if(!favoritep){
		goto ERROR_TRAP;
	}
	h_mutex = nt_favorite_get_mutex(&favoritep->handle);
	if(!h_mutex){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_lock(h_mutex)){
		goto ERROR_TRAP;
	}
	find_linkp = NULL;
	if(favoritep->favorite_board_list){
		linkp = favoritep->favorite_board_list;
		do{
			boardp = linkp->data;
			if(handle == &boardp->handle){
				find_linkp = linkp;
				break;
			}
			linkp = linkp->next;
		}while(linkp != favoritep->favorite_board_list);
	}
	if(find_linkp){
		favoritep->favorite_board_list =
			nt_link_remove2(favoritep->favorite_board_list, find_linkp);
		free(find_linkp);
		boardp->parent = NULL;
	}
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	result = (find_linkp != NULL) ? TRUE : FALSE;
ERROR_TRAP:
	nt_favorite_board_release_ref(handle);
	return result;
}


BOOL nt_favorite_thread_remove(nt_favorite_thread_handle handle)
{
	nt_favorite_grp_tp grpp;
	nt_favorite_thread_tp threadp;
	nt_link_tp linkp, find_linkp;
	nt_mutex_handle h_mutex;
	BOOL result;
	
	assert(handle);
	
	result = FALSE;
	
	nt_favorite_thread_add_ref(handle);
	
	threadp = (nt_favorite_thread_tp)handle;
	grpp = threadp->parent;
	if(!grpp){
		goto ERROR_TRAP;
	}
	h_mutex = nt_favorite_grp_get_mutex(&grpp->handle);
	if(!h_mutex){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_lock(h_mutex)){
		goto ERROR_TRAP;
	}
	find_linkp = NULL;
	if(grpp->contents){
		linkp = grpp->contents;
		do{
			threadp = linkp->data;
			if(handle == &threadp->handle){
				find_linkp = linkp;
				break;
			}
			linkp = linkp->next;
		}while(linkp != grpp->contents);
	}
	if(find_linkp){
		grpp->contents =
			nt_link_remove2(grpp->contents, find_linkp);
		free(find_linkp);
		threadp->parent = NULL;
		grpp->num_content--;
	}
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	result = (find_linkp != NULL) ? TRUE : FALSE;
ERROR_TRAP:
	nt_favorite_thread_release_ref(handle);
	return result;
}


nt_favorite_grp_handle nt_favorite_grp_alloc(
	nt_favorite_handle favorite_handle, const wchar_t *grp_name, int flags)
{
	nt_favorite_tp favoritep;
	nt_favorite_grp_tp grpp;
	nt_link_tp linkp;
	nt_mutex_handle h_mutex;
	
	assert(favorite_handle);
	assert(grp_name);
	favoritep = (nt_favorite_tp)favorite_handle;
	assert(favoritep->handle.chk_sum == NT_FAVORITE_CHK_SUM);
	
	grpp = malloc(sizeof(nt_favorite_grp_t));
	if(!grpp)
		return NULL;
	grpp->handle.chk_sum = NT_FAVORITE_GRP_CHK_SUM;
	grpp->grp_name = nt_w_str_clone(grp_name);
	grpp->parent = favoritep;
	grpp->ref_count = 2;
	grpp->num_content = 0;
	grpp->flags = flags;
	grpp->contents = NULL;
	
	h_mutex = nt_favorite_grp_get_mutex(
			(nt_favorite_grp_handle)&grpp->handle);
	if(!h_mutex){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_lock(h_mutex)){
		goto ERROR_TRAP;
	}
	linkp = nt_link_add_data(favoritep->favorite_grp_list, grpp);
	if(!linkp){
		nt_mutex_unlock(h_mutex);
		goto ERROR_TRAP;
	}
	
	if(!favoritep->favorite_grp_list)
		favoritep->favorite_grp_list = linkp;
	
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	return (nt_favorite_grp_handle)&grpp->handle;
ERROR_TRAP:
	free(grpp->grp_name);
	free(grpp);
	return NULL;
}

nt_favorite_thread_handle nt_favorite_thread_alloc(
	nt_favorite_grp_handle grp_handle, 
	const wchar_t *dat_name,
	const wchar_t *board_name,
	const wchar_t *title)
{
	nt_favorite_grp_tp grpp;
	nt_favorite_thread_tp threadp;
	nt_link_tp linkp;
	nt_mutex_handle h_mutex;
	
	assert(grp_handle);
	assert(dat_name && board_name && title);
	
	grpp = (nt_favorite_grp_tp)grp_handle;
	assert(grpp->handle.chk_sum == NT_FAVORITE_GRP_CHK_SUM);
	if(!grpp->parent)
		return NULL;
	
	threadp = get_thread(grpp, board_name, dat_name);
	if(threadp){
		return NULL;
	}
	
	threadp = malloc(sizeof(nt_favorite_thread_t));
	if(!threadp)
		return NULL;
	threadp->handle.chk_sum = NT_FAVORITE_THREAD_CHK_SUM;
	threadp->dat_name = nt_w_str_clone(dat_name);
	threadp->board_name = nt_w_str_clone(board_name);
	threadp->title = nt_w_str_clone(title);
	threadp->parent = grpp;
	threadp->ref_count = 2;
	threadp->num_read = 0;
	threadp->num_res = 0;
	
	h_mutex = nt_favorite_thread_get_mutex(
			(nt_favorite_thread_handle)&threadp->handle);
	if(!h_mutex){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_lock(h_mutex)){
		goto ERROR_TRAP;
	}
	linkp = nt_link_add_data(grpp->contents, threadp);
	if(!linkp){
		nt_mutex_unlock(h_mutex);
		goto ERROR_TRAP;
	}
	
	if(!grpp->contents)
		grpp->contents = linkp;
		
	grpp->num_content++;
	
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	return (nt_favorite_thread_handle)&threadp->handle;
ERROR_TRAP:
	free(threadp->dat_name);
	free(threadp->board_name);
	free(threadp->title);
	free(threadp);
	return NULL;
}

nt_mutex_handle nt_favorite_get_mutex(nt_favorite_handle handle)
{
	nt_mutex_handle h_mutex;
	nt_favorite_tp favoritep;
	
	assert(handle);
	nt_favorite_add_ref(handle);
	
	favoritep = (nt_favorite_tp)handle;
	h_mutex = nt_mutex_get_one_time_handle(favoritep->key);
	
	nt_favorite_release_ref(handle);
	if(!h_mutex){
		return NULL;
	}
	return h_mutex;
}

nt_mutex_handle nt_favorite_board_list_get_mutex(nt_favorite_handle handle)
{
	nt_mutex_handle h_mutex;
	nt_favorite_tp favoritep;
	
	assert(handle);
	nt_favorite_add_ref(handle);
	
	favoritep = (nt_favorite_tp)handle;
	h_mutex = nt_mutex_get_one_time_handle(favoritep->key);
	if(!h_mutex){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_add_moniker(h_mutex, NT_FAVORITE_BOARD_MUTEX_KEY)){
		goto ERROR_TRAP;
	}
	nt_favorite_release_ref(handle);
	return h_mutex;
ERROR_TRAP:
	nt_favorite_release_ref(handle);
	return NULL;
}

nt_mutex_handle nt_favorite_grp_list_get_mutex(nt_favorite_handle handle)
{
	nt_mutex_handle h_mutex;
	nt_favorite_tp favoritep;
	
	assert(handle);
	nt_favorite_add_ref(handle);
	
	favoritep = (nt_favorite_tp)handle;
	h_mutex = nt_mutex_get_one_time_handle(favoritep->key);
	if(!h_mutex){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_add_moniker(h_mutex, NT_FAVORITE_GROUP_MUTEX_KEY)){
		goto ERROR_TRAP;
	}
	nt_favorite_release_ref(handle);
	return h_mutex;
ERROR_TRAP:
	nt_favorite_release_ref(handle);
	return NULL;
}

nt_mutex_handle nt_favorite_board_get_mutex(nt_favorite_board_handle handle)
{
	nt_mutex_handle h_mutex;
	nt_favorite_tp favoritep;
	nt_favorite_board_tp boardp;
	
	assert(handle);
	nt_favorite_board_add_ref(handle);
	
	boardp = (nt_favorite_board_tp)handle;
	favoritep = boardp->parent;
	if(!favoritep){
		goto ERROR_TRAP;
	}
	h_mutex = nt_mutex_get_one_time_handle(favoritep->key);
	if(!h_mutex){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_add_moniker(h_mutex, NT_FAVORITE_BOARD_MUTEX_KEY)){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_add_moniker(h_mutex, boardp->board_name)){
		goto ERROR_TRAP;
	}
	nt_favorite_board_release_ref(handle);
	return h_mutex;
ERROR_TRAP:
	nt_favorite_board_release_ref(handle);
	return NULL;
}

nt_mutex_handle nt_favorite_grp_get_mutex(nt_favorite_grp_handle handle)
{
	nt_mutex_handle h_mutex;
	nt_favorite_tp favoritep;
	nt_favorite_grp_tp grpp;
	
	assert(handle);
	nt_favorite_grp_add_ref(handle);
	
	grpp = (nt_favorite_grp_tp)handle;
	favoritep = grpp->parent;
	if(!favoritep)
		goto ERROR_TRAP;
	h_mutex = nt_mutex_get_one_time_handle(favoritep->key);
	if(!h_mutex){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_add_moniker(h_mutex, NT_FAVORITE_GROUP_MUTEX_KEY)){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_add_moniker(h_mutex, grpp->grp_name)){
		goto ERROR_TRAP;
	}
	nt_favorite_grp_release_ref(handle);
	return h_mutex;
ERROR_TRAP:
	nt_favorite_grp_release_ref(handle);
	return NULL;
}

nt_mutex_handle nt_favorite_thread_get_mutex(nt_favorite_thread_handle handle)
{
	nt_mutex_handle h_mutex;
	nt_favorite_tp favoritep;
	nt_favorite_grp_tp grpp;
	nt_favorite_thread_tp threadp;
	
	assert(handle);
	nt_favorite_thread_add_ref(handle);
	
	threadp = (nt_favorite_thread_tp)handle;
	grpp = threadp->parent;
	if(!grpp)
		goto ERROR_TRAP;
	favoritep = grpp->parent;
	if(!favoritep)
		goto ERROR_TRAP;
	h_mutex = nt_mutex_get_one_time_handle(favoritep->key);
	if(!h_mutex){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_add_moniker(h_mutex, NT_FAVORITE_GROUP_MUTEX_KEY)){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_add_moniker(h_mutex, grpp->grp_name)){
		goto ERROR_TRAP;
	}
	if(!nt_mutex_add_moniker(h_mutex, threadp->dat_name)){
		goto ERROR_TRAP;
	}
	nt_favorite_thread_release_ref(handle);
	return h_mutex;
ERROR_TRAP:
	nt_favorite_thread_release_ref(handle);
	return NULL;
}


int nt_favorite_add_ref(nt_favorite_handle handle)
{
	assert(handle);
	nt_favorite_tp favoritep;
	favoritep = (nt_favorite_tp)handle;
	assert(favoritep->handle.chk_sum == NT_FAVORITE_CHK_SUM);
	assert(favoritep->ref_count > 0);
	return ++favoritep->ref_count;
}


int nt_favorite_release_ref(nt_favorite_handle handle)
{
	assert(handle);
	nt_favorite_tp favoritep;
	favoritep = (nt_favorite_tp)handle;
	assert(favoritep->handle.chk_sum == NT_FAVORITE_CHK_SUM);
	assert(favoritep->ref_count > 0);
	favoritep->ref_count--;
	if(0 != favoritep->ref_count)
		return favoritep->ref_count;

	
	if(favoritep->favorite_board_list){
		nt_all_link_free(favoritep->favorite_board_list, 
				free_favorite_board);
	}
	if(favoritep->favorite_grp_list){
		nt_all_link_free(favoritep->favorite_grp_list, 
				free_favorite_grp);
	}
	free(favoritep->key);
	free(favoritep);
	return 0;
}

int nt_favorite_board_add_ref(nt_favorite_board_handle handle)
{
	assert(handle);
	nt_favorite_board_tp boardp;
	boardp = (nt_favorite_board_tp)handle;
	assert(boardp->handle.chk_sum == NT_FAVORITE_BOARD_CHK_SUM);
	assert(boardp->ref_count > 0);
	return ++boardp->ref_count;
}

int nt_favorite_board_release_ref(nt_favorite_board_handle handle)
{
	assert(handle);
	nt_favorite_board_tp boardp;
	boardp = (nt_favorite_board_tp)handle;
	assert(boardp->handle.chk_sum == NT_FAVORITE_BOARD_CHK_SUM);
	assert(boardp->ref_count > 0);
	boardp->ref_count--;
	if(0 != boardp->ref_count)
		return boardp->ref_count;
	free(boardp->board_name);
	free(boardp);
	return 0;
}

int nt_favorite_grp_add_ref(nt_favorite_grp_handle handle)
{
	assert(handle);
	nt_favorite_grp_tp grpp;
	grpp = (nt_favorite_grp_tp)handle;
	assert(grpp->handle.chk_sum == NT_FAVORITE_GRP_CHK_SUM);
	assert(grpp->ref_count > 0);
	return ++grpp->ref_count;
}


int nt_favorite_grp_release_ref(nt_favorite_grp_handle handle)
{
	assert(handle);
	nt_favorite_grp_tp grpp;
	grpp = (nt_favorite_grp_tp)handle;
	assert(grpp->handle.chk_sum == NT_FAVORITE_GRP_CHK_SUM);
	assert(grpp->ref_count > 0);
	grpp->ref_count--;
	if(0 != grpp->ref_count)
		return grpp->ref_count;
	assert((grpp->num_content == 0 && !grpp->contents) ||
			(grpp->num_content > 0 && grpp->contents));
	if(grpp->contents){
		nt_all_link_free(grpp->contents, free_grp_content);
	}
	free(grpp->grp_name);
	free(grpp);
	return 0;
}

int nt_favorite_thread_add_ref(nt_favorite_thread_handle handle)
{
	assert(handle);
	nt_favorite_thread_tp threadp;
	threadp = (nt_favorite_thread_tp)handle;
	assert(threadp->handle.chk_sum == NT_FAVORITE_THREAD_CHK_SUM);
	assert(threadp->ref_count > 0);
	return ++threadp->ref_count;
}

int nt_favorite_thread_release_ref(nt_favorite_thread_handle handle)
{
	assert(handle);
	nt_favorite_thread_tp threadp;
	threadp = (nt_favorite_thread_tp)handle;
	assert(threadp->handle.chk_sum == NT_FAVORITE_THREAD_CHK_SUM);
	assert(threadp->ref_count > 0);
	threadp->ref_count--;
	if(0 != threadp->ref_count)
		return threadp->ref_count;
	free(threadp->dat_name);
	free(threadp->board_name);
	free(threadp->title);
	free(threadp);
	return 0;
}

static void free_favorite_board(void *ptr)
{
	nt_favorite_board_handle handle
		= (nt_favorite_board_handle)ptr;
	((nt_favorite_board_tp)handle)->parent = NULL;
	nt_favorite_board_release_ref(handle);
}
static void free_favorite_grp(void *ptr)
{
	nt_favorite_grp_handle handle
		= (nt_favorite_grp_handle)ptr;
	((nt_favorite_grp_tp)handle)->parent = NULL;
	nt_favorite_grp_release_ref(handle);
}
static void free_grp_content(void *ptr)
{
	nt_favorite_thread_handle handle
		= (nt_favorite_thread_handle)ptr;
	((nt_favorite_thread_tp)handle)->parent = NULL;
	nt_favorite_thread_release_ref(handle);
}

static nt_favorite_board_tp get_board_by_name(
		nt_favorite_tp favoritep,
		const wchar_t *board_name)
{
	nt_favorite_board_tp boardp;
	nt_link_tp linkp;
	if(favoritep->favorite_board_list){
		linkp = favoritep->favorite_board_list;
		do{
			boardp = linkp->data;
			if(0 == wcscmp(board_name,
				boardp->board_name)){
				return boardp;
			}
			linkp = linkp->next;
		}while(linkp != favoritep->favorite_board_list);
	}
	return NULL;
}

static nt_favorite_thread_tp get_thread(
		nt_favorite_grp_tp grpp,
		const wchar_t *board_name,
		const wchar_t *dat_name)
{
	nt_favorite_thread_tp threadp;
	nt_link_tp linkp;
	
	if(grpp->contents){
		linkp = grpp->contents;
		do{
			threadp = linkp->data;
			if(0 == wcscmp(board_name, threadp->board_name) &&
				0 == wcscmp(dat_name, threadp->dat_name)){
				return threadp;
			}
			linkp = linkp->next;
		}while(linkp != grpp->contents);
	}
	return NULL;
}

BOOL nt_favorite_load_boards(nt_favorite_handle handle, 
		nt_link_tp board_list)
{
	nt_favorite_tp favoritep;
	nt_favorite_board_tp boardp;
	nt_link_tp linkp, blinkp;
	nt_mutex_handle h_mutex;
	wchar_t *cptr;
	wchar_t *board_name;
	assert(board_list);
	
	h_mutex = nt_favorite_board_list_get_mutex(handle);
	if(!h_mutex)
		return FALSE;
	if(!nt_mutex_lock(h_mutex)){
		return FALSE;
	}
	favoritep = (nt_favorite_tp)handle;
	linkp = board_list;
	do{
		cptr = linkp->data;
		board_name = nt_w_trim(cptr);
		if(!board_name){
		}else if(0 == wcslen(board_name)){
			free(board_name);
		}else{
			boardp = get_board_by_name(favoritep, board_name);
			if(boardp){
				free(board_name);
			}else{
				boardp = malloc(sizeof(nt_favorite_board_t));
				if(!boardp){
					free(board_name);
				}else{
					boardp->handle.chk_sum = NT_FAVORITE_BOARD_CHK_SUM;
					boardp->board_name = board_name;
					boardp->parent = favoritep;
					boardp->ref_count = 1;
					blinkp = nt_link_add_data(favoritep->favorite_board_list, boardp);
					if(!blinkp){
						free(board_name);
						free(boardp);
					}else if(!favoritep->favorite_board_list){
						favoritep->favorite_board_list = blinkp;
					}
				}
			}
		}
		linkp = linkp->next;
	}while(linkp != board_list);
	
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	return TRUE;
}

nt_link_tp nt_favorite_retrieve_boards(nt_favorite_handle handle)
{
	nt_favorite_board_handle h_board;
	nt_mutex_handle h_mutex;
	nt_enum_handle h_enum;
	const wchar_t *cptr;
	wchar_t *board_name;
	nt_link_tp linkp, wrkp;
	
	h_mutex = nt_favorite_board_list_get_mutex(handle);
	if(!h_mutex)
		return NULL;
	if(!nt_mutex_lock(h_mutex)){
		return NULL;
	}
	
	linkp = NULL;
	
	h_enum = nt_favorite_acquire_board_enum(handle);
	if(h_enum){
		while(NULL != (h_board =
				(nt_favorite_board_handle)nt_enum_fetch(h_enum))){
			cptr = nt_favorite_board_get_name(h_board);
			if(!cptr)
				continue;
			board_name = nt_w_str_clone(cptr);
			wrkp = nt_link_add_data(linkp, board_name);
			if(wrkp && !linkp)
				linkp = wrkp;
		}
		nt_enum_unset(h_enum);
	}
	
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	return linkp;
}
BOOL nt_favorite_load_threads(nt_favorite_handle handle, 
		nt_favorite_grp_handle h_grp, 
		nt_link_tp thread_list)
{
	nt_favorite_grp_tp grpp;
	nt_favorite_thread_tp threadp;
	nt_link_tp linkp, tlinkp;
	nt_mutex_handle h_mutex;
	wchar_t *cptr;
	assert(thread_list);
	
	h_mutex = nt_favorite_grp_list_get_mutex(handle);
	if(!h_mutex)
		return FALSE;
	if(!nt_mutex_lock(h_mutex)){
		return FALSE;
	}
	grpp = (nt_favorite_grp_tp)h_grp;
	assert(grpp->handle.chk_sum == NT_FAVORITE_GRP_CHK_SUM);
	linkp = thread_list;
	do{
		
		cptr = linkp->data;
		threadp = parse_favorite_thread(cptr);
		if(threadp){
			threadp->parent = grpp;
			threadp->ref_count = 1;
			tlinkp = nt_link_add_data(grpp->contents, threadp);
			if(!tlinkp){
				free(threadp->dat_name);
				free(threadp->board_name);
				free(threadp->title);
				free(threadp);
			}else{
				grpp->num_content++;
				if(!grpp->contents)
					grpp->contents = tlinkp;
			}
		}
		linkp = linkp->next;
	}while(linkp != thread_list);
	
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	return TRUE;
}

static void read_count_data_free(void *ptr)
{
	nt_link_tp linkp;
	assert(ptr);
	linkp = (nt_link_tp)ptr;
	nt_all_link_free(linkp, nt_usr_db_thread_data_free);
}

BOOL nt_favorite_set_num_res(
		nt_2ch_model_handle h_model, nt_favorite_handle h_favorite)
{
	nt_favorite_grp_handle h_grp;
	nt_favorite_thread_handle h_thread;
	nt_thread_handle h_mthread;
	nt_mutex_handle h_f_mutex, h_m_mutex;
	nt_enum_handle h_enum_grp;
	nt_enum_handle h_enum_thread;
	const wchar_t *board_name, *dat_name;
	int num_res;
	
	h_f_mutex = nt_favorite_get_mutex(h_favorite);
	if(!h_f_mutex)
		return FALSE;
	h_m_mutex = nt_2ch_model_get_mutex(h_model);
	if(!h_m_mutex)
		return FALSE;
	if(!nt_mutex_lock(h_f_mutex)){
		return FALSE;
	}
	if(!nt_mutex_lock(h_m_mutex)){
		nt_mutex_unlock(h_f_mutex);
		return FALSE;
	}
	
	h_enum_grp = nt_favorite_acquire_grp_enum(h_favorite);
	if(h_enum_grp){
		while(NULL != (h_grp =
				(nt_favorite_grp_handle)nt_enum_fetch(h_enum_grp))){
			h_enum_thread = nt_favorite_acquire_thread_enum(h_grp);
			if(!h_enum_thread)
				continue;
			while(NULL != (h_thread =
					(nt_favorite_thread_handle)nt_enum_fetch(h_enum_thread))){
				board_name = nt_favorite_thread_get_board_name(h_thread);
				if(!board_name)
					continue;
				dat_name = nt_favorite_thread_get_dat_name(h_thread);
				if(!dat_name)
					continue;
    			h_mthread = nt_get_thread_by_board_and_dat_name(
    						h_model, board_name, dat_name);
    			if(!h_mthread)
    				continue;
				num_res = nt_thread_get_res_count(h_mthread);
				nt_thread_release_ref(h_mthread);
    			if(num_res == -1)
    				continue;
    			nt_favorite_thread_set_estimate_res_count(h_thread, num_res);
			}
			nt_enum_unset(h_enum_thread);
		}
		nt_enum_unset(h_enum_grp);
	}
	
	if(!nt_mutex_unlock(h_m_mutex)){
		assert(0);
	}
	if(!nt_mutex_unlock(h_f_mutex)){
		assert(0);
	}
	return TRUE;
}


BOOL nt_favorite_set_read_count(nt_favorite_handle h_favorite, nt_usr_db_handle h_usr_db)
{
	nt_favorite_grp_handle h_grp;
	nt_favorite_thread_handle h_thread;
	nt_mutex_handle h_mutex;
	nt_enum_handle h_enum_grp;
	nt_enum_handle h_enum_thread;
	nt_map_handle h_map;
	const wchar_t *board_name, *dat_name;
	nt_link_tp linkp;
	void *ptr;
	int read_count;
	
	h_mutex = nt_favorite_get_mutex(h_favorite);
	if(!h_mutex)
		return FALSE;
	if(!nt_mutex_lock(h_mutex)){
		return FALSE;
	}
	h_map = nt_map_alloc();
	if(!h_map){
		nt_mutex_unlock(h_mutex);
		return FALSE;
	}
	
	h_enum_grp = nt_favorite_acquire_grp_enum(h_favorite);
	if(h_enum_grp){
		while(NULL != (h_grp =
				(nt_favorite_grp_handle)nt_enum_fetch(h_enum_grp))){
			h_enum_thread = nt_favorite_acquire_thread_enum(h_grp);
			if(!h_enum_thread)
				continue;
			while(NULL != (h_thread =
					(nt_favorite_thread_handle)nt_enum_fetch(h_enum_thread))){
				board_name = nt_favorite_thread_get_board_name(h_thread);
				if(!board_name)
					continue;
				dat_name = nt_favorite_thread_get_dat_name(h_thread);
				if(!dat_name)
					continue;
				ptr = nt_map_find(h_map, board_name);
				if(ptr){
					linkp = (nt_link_tp)ptr;
				}else{
					linkp = nt_usr_db_query_read_count_list(
							h_usr_db, board_name);
					if(!linkp)
						continue;
					if(!nt_map_add_pair(h_map, board_name, linkp))
						continue;
				}
				read_count = nt_usr_db_get_read_count_by_dat_name(
    					linkp, dat_name);
    			if(read_count == -1)
    				continue;
    			
    			nt_favorite_thread_set_read_count(h_thread, read_count);
			}
			nt_enum_unset(h_enum_thread);
		}
		nt_enum_unset(h_enum_grp);
	}
	
	nt_map_free(h_map, read_count_data_free);
	
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	return TRUE;
}



BOOL nt_favorite_retrieve_threads(nt_favorite_handle handle,
		nt_link_tp *grp_list, nt_link_tp *thread_list)
{
	nt_favorite_grp_handle h_grp;
	nt_favorite_thread_handle h_thread;
	nt_mutex_handle h_mutex;
	nt_enum_handle h_enum;
	nt_enum_handle h_enum_thread;
	nt_link_tp wrkp;
	int num;
	BOOL open;
	wchar_t wc[128];
	const wchar_t *cptr;
	wchar_t *ptr;
	
	*grp_list = NULL;
	*thread_list = NULL;
	
	h_mutex = nt_favorite_board_list_get_mutex(handle);
	if(!h_mutex)
		return FALSE;
	if(!nt_mutex_lock(h_mutex)){
		return FALSE;
	}
	
	h_enum = nt_favorite_acquire_grp_enum(handle);
	if(h_enum){
		while(NULL != (h_grp =
				(nt_favorite_grp_handle)nt_enum_fetch(h_enum))){
			cptr = nt_favorite_grp_get_name(h_grp);
			if(!cptr)
				continue;
			h_enum_thread = nt_favorite_acquire_thread_enum(h_grp);
			if(!h_enum_thread)
				continue;
			num = nt_enum_get_count(h_enum_thread);
			assert(num >= 0);
			open = nt_favorite_grp_get_flag(h_grp, NT_FAVORITE_GRP_FLAG_FOLDER_OPEN);
			if(-1 == swprintf(wc, sizeof(wc)/sizeof(wchar_t)-1, 
					L"%ls,%d,%ls", cptr, num,  open ? L"open" : L" ")){
				nt_enum_unset(h_enum_thread);
				continue;
			}
			ptr = nt_w_str_clone(wc);
			if(!ptr){
				nt_enum_unset(h_enum_thread);
				continue;
			}
			wrkp = nt_link_add_data(*grp_list, ptr);
			if(wrkp && (NULL == *grp_list))
				*grp_list = wrkp;
			while(NULL != (h_thread =
					(nt_favorite_thread_handle)nt_enum_fetch(h_enum_thread))){
				if(!nt_favorite_retrieve_thread(h_thread, thread_list)){
					assert(0);
				}
			}
			nt_enum_unset(h_enum_thread);
		}
		nt_enum_unset(h_enum);
	}
	
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	return TRUE;
}

static nt_favorite_thread_tp parse_favorite_thread(const wchar_t *source)
{
	nt_favorite_thread_tp threadp;
	const wchar_t *start, *end;
	wchar_t *board_name, *title, *dat_name;
	int len;
	
	if(!nt_w_strtok(source, L'\\', &start, &end)){
		return NULL;
	}
	len = end - start;
	dat_name = nt_w_substr(start, 0, len);
	if(!nt_w_strtok(end, L'\\', &start, &end)){
		free(dat_name);
		return NULL;
	}
	len = end - start;
	board_name = nt_w_substr(start, 0, len);
	if(!nt_w_strtok(end, L'\\', &start, &end)){
		free(dat_name);
		free(board_name);
		return NULL;
	}
	len = end - start;
	title = nt_w_substr(start, 0, len);

	threadp = malloc(sizeof(nt_favorite_thread_t));
	if(!threadp){
		free(dat_name);
		free(board_name);
		free(title);
		return NULL;
	}
	threadp->handle.chk_sum = NT_FAVORITE_THREAD_CHK_SUM;
	threadp->dat_name = dat_name;
	threadp->board_name = board_name;
	threadp->title = title;
	threadp->parent = NULL;
	threadp->ref_count = 1;
	threadp->num_read = 0;
	threadp->num_res = 0;
	return threadp;
}

static BOOL nt_favorite_retrieve_thread(
	nt_favorite_thread_handle h_thread, nt_link_tp *thread_list)
{
	const wchar_t *dat_name, *board_name, *title;
	wchar_t *cptr;
	int len;
	nt_link_tp linkp;
	
	dat_name = nt_favorite_thread_get_dat_name(h_thread);
	board_name = nt_favorite_thread_get_board_name(h_thread);
	title = nt_favorite_thread_get_title(h_thread);
	assert(dat_name && board_name && title);
	
	len = wcslen(dat_name);
	len += wcslen(board_name);
	len += wcslen(title);
	len += 3;
	
	cptr = malloc(len * sizeof(wchar_t));
	if(!cptr)
		return FALSE;
	
	if(-1 == swprintf(cptr, len, 
			L"%ls\\%ls\\%ls", dat_name, board_name, title)){
		return FALSE;
	}
	
	linkp = nt_link_add_data(*thread_list, cptr);
	if(!linkp){
		free(cptr);
		return FALSE;
	}
	if(NULL == *thread_list){
		*thread_list = linkp;
	}
	return TRUE;
}

nt_link_tp nt_favorite_get_update_board_list(
		nt_2ch_model_handle h_model, nt_favorite_handle h_favorite)
{
	nt_enum_handle h_enum_grp, h_enum_thread;
	nt_favorite_grp_handle h_grp;
	nt_favorite_thread_handle h_fthread;
	nt_2ch_selected_item_handle h_select;
	nt_category_handle h_category;
	nt_board_handle h_board;
	nt_mutex_handle h_mutex;
	nt_link_tp res_linkp, tmp_linkp, board_name_linkp;
	const wchar_t *board_name, *cptr;
	
	h_mutex = nt_favorite_get_mutex(h_favorite);
	if(!h_mutex){
		return NULL;
	}
	if(!nt_mutex_lock(h_mutex)){
		return NULL;
	}
	h_enum_grp = nt_favorite_acquire_grp_enum(h_favorite);
	if(!h_enum_grp){
		nt_mutex_unlock(h_mutex);
		return NULL;
	}
	res_linkp = NULL;
	board_name_linkp = NULL;
	while(NULL != (h_grp =
			(nt_favorite_grp_handle)nt_enum_fetch(h_enum_grp))){
		h_enum_thread = nt_favorite_acquire_thread_enum(h_grp);
		if(!h_enum_thread)
			continue;
		while(NULL != (h_fthread =
				(nt_favorite_thread_handle)nt_enum_fetch(h_enum_thread))){
			board_name = nt_favorite_thread_get_board_name(h_fthread);
			if(!board_name)
				continue;
			if(board_name_linkp){
				cptr = (const wchar_t*)nt_link_find(
						board_name_linkp, (void*)board_name, local_wcscmp);
				if(cptr)
					continue;
			}
			//fwprintf(stderr, L"%ls\n", board_name);
			h_board = nt_get_board_by_name(h_model, board_name, &h_category);
			if(h_board && h_category){
				h_select = nt_2ch_selected_item_alloc();
				if(h_select){
					nt_set_selected_board(h_select, h_category, h_board);
					tmp_linkp = nt_link_add_data(res_linkp, h_select);
					if(tmp_linkp && !res_linkp)
						res_linkp = tmp_linkp;
					tmp_linkp = nt_link_add_data(board_name_linkp, (void*)board_name);
					if(tmp_linkp && !board_name_linkp)
						board_name_linkp = tmp_linkp;
					
				}
				nt_category_release_ref(h_category);
				nt_board_release_ref(h_board);
			}
		}
		nt_enum_unset(h_enum_thread);
	}
	if(board_name_linkp)
		nt_all_link_free(board_name_linkp, NULL);
	nt_enum_unset(h_enum_grp);
	if(!nt_mutex_unlock(h_mutex)){
		assert(0);
	}
	return res_linkp;
}

void favorite_dump(nt_favorite_handle handle)
{
	nt_favorite_tp favoritep;
	nt_favorite_board_tp boardp;
	nt_favorite_grp_tp grpp;
	nt_favorite_thread_tp threadp;
	nt_link_tp linkp, link2p;
	int num;
	wchar_t wc[256];
	char c[256];
	iconv_t icd;
	nt_mutex_handle h_mutex;
	
	assert(handle);
	favoritep = (nt_favorite_tp)handle;
	assert(favoritep->handle.chk_sum == NT_FAVORITE_CHK_SUM);
	
	icd = iconv_open("UTF-8","wchar_t");
	if(icd == (iconv_t)-1){
		fputs("iconv error\n", stderr);
		return;
	}
	
	h_mutex = nt_mutex_get_one_time_handle(favoritep->key);
	if(!h_mutex){
		fputs("mutex error1\n", stderr);
		return;
	}
	if(!nt_mutex_lock(h_mutex)){
		fputs("mutex error2\n", stderr);
		return;
	}
		
	fprintf(stderr, "--- Dump favorite ref_cnt: %d ---\n\n", favoritep->ref_count);
	if(favoritep->favorite_board_list){
		fprintf(stderr, "\t--- Start enum boards ---\n");
		linkp = favoritep->favorite_board_list;
		num = 1;
		do{
			boardp = linkp->data;
			swprintf(wc, sizeof(wc)/sizeof(wchar_t), 
				L"\t(%d) board name \"%ls\"\n", num, boardp->board_name);
			nt_conv_wc2local(icd, wc, c, sizeof(c));
			fputs(c, stderr);
			num++;
			linkp = linkp->next;
		}while(linkp != favoritep->favorite_board_list);
		fprintf(stderr, "\t--- End enum boards ---\n\n");
	}else{
		fprintf(stderr, "\t--- No boards found --- \n\n");
	}
	
	if(favoritep->favorite_grp_list){
		fprintf(stderr, "\t--- Start enum threads ---\n");
		linkp = favoritep->favorite_grp_list;
		num = 1;
		do{
			grpp = linkp->data;
			if(grpp && grpp->contents){
				link2p = grpp->contents;
				do{
					threadp = link2p->data;
					swprintf(wc, sizeof(wc)/sizeof(wchar_t),
						L"\t(%d) %ls[%ls] -- %ls\n", 
						num, threadp->title, threadp->board_name, threadp->dat_name);
					nt_conv_wc2local(icd, wc, c, sizeof(c));
					fputs(c, stderr);
					num++;
					link2p = link2p->next;
				}while(link2p != grpp->contents);
			}
			linkp = linkp->next;
		}while(linkp != favoritep->favorite_grp_list);
		fprintf(stderr, "\t--- End enum threads ---\n\n");
	}else{
		fprintf(stderr, "\t--- No threads found --- \n\n");
	}
	if(!nt_mutex_unlock(h_mutex)){
		fputs("mutex error2\n", stderr);
	}
	fprintf(stderr, "--- favorite dump end ---\n");
	iconv_close(icd);
}

