/* 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 <stdio.h>
#include <string.h>
#include <assert.h>
#include <wchar.h>
#include <ncursesw/ncurses.h>
#include <regex.h>

#include "env.h"
#include "error.h"
#include "nt_string.h"
#include "utils/nt_std_t.h"
#include "usr/usr_db_t.h"
#include "_2ch/_2ch.h"
#include "_2ch/model_2ch.h"
#include "_2ch/parse_2ch.h"
#include "utils/text.h"
#include "ui/disp.h"
#include "ui/disp_string.h"

typedef struct tag_ctx_threadlist_t *ctx_threadlist_tp;
typedef struct tag_ctx_threadlist_t
{
	int prev_state;
	int thread_num;
	int tracked_thread_num;
	int cursor_pos;
	int scroll_pos;

	regex_t regex;
	BOOL regex_init;
	int sel_thread_no;

	nt_link_tp thread_data_list;

	nt_link_tp thread_disp_list;

	int sort_type;

} ctx_threadlist_t;

typedef struct tag_thread_disp_data_t{
	int seq_no;
	int num_read;
	int num_res;
	wchar_t *dat_name;
	wchar_t *title;
}thread_disp_data_t, *thread_disp_data_tp;

static thread_disp_data_tp disp_data_alloc(
		int seq_no, int num_res, const wchar_t *title, const wchar_t *dat_name);
static void disp_data_free(void *ptr);
static BOOL get_thread_data_by_dat_name(const wchar_t *dat_name, nt_link_tp disp_list);


#define NT_CMD_ERR -1
#define NT_CMD_NONE 0
#define NT_CMD_SORT_NUMBER 1
#define NT_CMD_SORT_READ 2
#define NT_CMD_SORT_UNREAD 3
#define NT_CMD_DEL_THREAD_LOG 4
#define NT_CMD_SEARCH_THREAD 5
#define NT_CMD_FAVORITE 6

static int parse_cmd1(const char *param);
static thread_disp_data_tp get_thread_by_seq_no(nt_link_tp linkp, int seq_no);
static int get_thread_index_by_seqno(nt_link_tp linkp, int seq_no);
static BOOL search_line_asc(regex_t *regexp, 
				nt_link_tp threadlistp, int *sel_thread_no);
static BOOL search_line_desc(regex_t *regexp, 
				nt_link_tp threadlistp, int *sel_thread_no);
static ctx_threadlist_tp init_context(nt_2ch_selected_item_handle h_select);
static int merge_untracked_thread_log(int init_seq_no, nt_board_handle h_board,
		nt_link_tp disp_thread_listpp, nt_link_tp log_list);


static int sort_by_number(void *lhs, void *rhs);
static int sort_by_unread(void *lhs, void *rhs);
static int sort_by_read(void *lhs, void *rhs);
static ctx_threadlist_tp g_ctxp;

static void sort_thread_title(int sort_type, ctx_threadlist_tp ctxp)
{
	g_ctxp = ctxp;
	switch(sort_type){
	case NT_THREAD_SORT_BY_NUMBER:
		nt_link_sort(&ctxp->thread_disp_list, sort_by_number);
		break;
	case NT_THREAD_SORT_BY_UNREAD:
		nt_link_sort(&ctxp->thread_disp_list, sort_by_unread);
		break;
	case NT_THREAD_SORT_BY_READ:
		nt_link_sort(&ctxp->thread_disp_list, sort_by_read);
		break;
	}
}

int disp_threadlist(nt_window_tp wp, int prev_state, nt_2ch_selected_item_handle h_select,
				 nt_usr_db_handle db_handle)
{
	ctx_threadlist_tp ctxp;
	nt_board_handle h_board;
	nt_thread_handle h_thread;
	thread_disp_data_tp threadp;
	const wchar_t *board_name;
	nt_link_tp clistp, linkp;
	int i, rows, nwrite, len, ch;
	int btm_cur, num, wlines;
	wchar_t buf[16];
	attr_t attr;
	BOOL search_asc;
	int read_count, column;
	int cmd;
	BOOL sort;
	const char *start, *end;
	char *cptr;
	int result_state;

	result_state = DISP_STATE_THREADTITLE;
	sort = FALSE;

	ctxp = (ctx_threadlist_tp)wp->data;
	if(!ctxp){
		ctxp = init_context(h_select);
		if(!ctxp)
			return DISP_STATE_ERROR;
		wp->data = ctxp;
		sort = TRUE;
	}
	if(prev_state != DISP_STATE_THREADTITLE)
		ctxp->prev_state = prev_state;

	h_board = nt_get_selected_board(h_select);
	assert(h_board);
	board_name = nt_board_get_name(h_board);

	if(!ctxp->thread_data_list){
		ctxp->thread_data_list =
			nt_usr_db_query_read_count_list(
					db_handle, board_name);
		if(ctxp->thread_data_list){
			ctxp->thread_num = 
					merge_untracked_thread_log(
							ctxp->tracked_thread_num+1,
							h_board,
							ctxp->thread_disp_list,
							ctxp->thread_data_list);
		}
	}

	if(sort){
		sort_thread_title(THREAD_SORT_TYPE, ctxp);
	}

	ch = wp->key;

	switch(ch){
	case NT_KEY_REFRESH: 
		result_state = DISP_STATE_REFRESH;
		goto ERROR_TRAP;
	case NT_KEY_CLOSE:
	case KEY_LEFT:
		result_state = ctxp->prev_state;
		goto ERROR_TRAP;
	case NT_KEY_BOTTOM:
	case KEY_END:
		ctxp->cursor_pos = ctxp->thread_num - 1;

		wlines = ctxp->thread_num * 2;
		wlines -= wp->lines;
		if(wlines < 0){
			ctxp->scroll_pos = 0;
			break;
		}
		ctxp->scroll_pos = wlines;
		//ctxp->scroll_pos += wlines%2;
		break;
	case NT_KEY_COMMAND2:
	case NT_KEY_COMMAND3:
		search_asc = (ch == NT_KEY_COMMAND2);
		if(wp->cmd_param && wp->cmd_param[0] != '\0'){
			if(0 != regcomp(&(ctxp->regex),
					wp->cmd_param, REG_EXTENDED)){
				if(ctxp->regex_init){
					regfree(&(ctxp->regex));
					ctxp->regex_init = FALSE;
					break;
				}
			}
			if(!ctxp->regex_init)
			    ctxp->regex_init = TRUE;
		}
		if(!ctxp->regex_init)
		    break;
		if(search_asc){
		    if(!search_line_asc(&(ctxp->regex), ctxp->thread_disp_list,
			        &ctxp->sel_thread_no)){
				wp->status_msg = NT_ERR_MSG_SEARCH_NOT_FOUND;
				break;
			}
		}else{
		    if(!search_line_desc(&(ctxp->regex), ctxp->thread_disp_list,
			        &ctxp->sel_thread_no)){
				wp->status_msg = NT_ERR_MSG_SEARCH_NOT_FOUND;
				break;
			}
		}
		num = ctxp->sel_thread_no;
		//fall through
	case NT_KEY_COMMAND1:
		if(ch == NT_KEY_COMMAND1){
			assert(wp->cmd_param);
			cmd = parse_cmd1(wp->cmd_param);
			switch(cmd){
			case NT_CMD_SORT_NUMBER:
				THREAD_SORT_TYPE = NT_THREAD_SORT_BY_NUMBER;
				sort_thread_title(THREAD_SORT_TYPE, ctxp);
				result_state = DISP_STATE_REFRESH;
				goto ERROR_TRAP;
			case NT_CMD_SORT_READ:
				THREAD_SORT_TYPE = NT_THREAD_SORT_BY_READ;
				sort_thread_title(THREAD_SORT_TYPE, ctxp);
				result_state = DISP_STATE_REFRESH;
				goto ERROR_TRAP;
			case NT_CMD_SORT_UNREAD:
				THREAD_SORT_TYPE = NT_THREAD_SORT_BY_UNREAD;
				sort_thread_title(THREAD_SORT_TYPE, ctxp);
				result_state = DISP_STATE_REFRESH;
				goto ERROR_TRAP;
			case NT_CMD_DEL_THREAD_LOG: 
				if(!nt_strtok(wp->cmd_param,' ', &start, &end))
					break;
				cptr = nt_trim(end);
				if(!cptr)
					break;
				if(0 == strcmp("*", cptr)){
					free(cptr);
					if(!nt_usr_db_delete_board_log(db_handle,
							board_name))
						break;
					result_state = DISP_STATE_REFRESH;
					goto ERROR_TRAP;
				}
				linkp = nt_parse_number_list(cptr, NULL);
				free(cptr);
				if(!linkp)
					break;
				clistp = linkp;
				num = -1;
				do{
					threadp = get_thread_by_seq_no(
						ctxp->thread_disp_list, linkp->n_data);
					if(!threadp)
						break;
						
					if(linkp->n_data != num){
						nt_usr_db_delete_thread_log(db_handle,
								board_name, 
								threadp->dat_name,
								&ctxp->thread_data_list);
						num = linkp->n_data;
					}
					linkp = linkp->next;
				}while(clistp != linkp);
				nt_all_link_free(linkp, NULL);
				result_state = DISP_STATE_REFRESH;
				goto ERROR_TRAP;
			case NT_CMD_SEARCH_THREAD:
				result_state = DISP_STATE_SEARCH_THREAD;
				goto ERROR_TRAP;
			case NT_CMD_FAVORITE:
				result_state = DISP_STATE_FAVORITE;
				goto ERROR_TRAP;
			default:
				break;
			}
			num = atoi(wp->cmd_param);
			if(0 == num)
				break;
			num = get_thread_index_by_seqno(
					ctxp->thread_disp_list, num);
			//num--;
		}
		wlines = ctxp->thread_num - num;
		if(wlines <= 0)
			break;

		wlines *= 2;
		ctxp->cursor_pos = num;
		if(wp->lines < wlines){
			ctxp->scroll_pos = num * 2;
		}else{
			ctxp->scroll_pos =
				ctxp->thread_num * 2 - wp->lines;
		}
		break;
	case NT_KEY_UP:
	case KEY_UP:
		ctxp->cursor_pos--;
		if(ctxp->cursor_pos < 0)
			ctxp->cursor_pos = 0;
		if(ctxp->scroll_pos > (ctxp->cursor_pos*2)){
			ctxp->scroll_pos = ctxp->cursor_pos*2;
		}
		break;
	case NT_KEY_DOWN:
	case KEY_DOWN:
		ctxp->cursor_pos++;
		if(ctxp->cursor_pos >= ctxp->thread_num)
			ctxp->cursor_pos = ctxp->thread_num - 1;
		btm_cur = ctxp->cursor_pos * 2 + 1;
		if(btm_cur >= (ctxp->scroll_pos + wp->lines)){
			ctxp->scroll_pos = btm_cur;
			ctxp->scroll_pos -= wp->lines;
			ctxp->scroll_pos++;
		}
		break;
	case NT_KEY_PAGEUP:
	case KEY_PPAGE:
		if(wp->lines >= ctxp->thread_num*2){
			ctxp->scroll_pos = 0;
			break;
		}
		ctxp->scroll_pos -= wp->lines;
		if(ctxp->scroll_pos < 0){
			ctxp->cursor_pos *= 2;
			ctxp->cursor_pos -= ctxp->scroll_pos + wp->lines;
			ctxp->scroll_pos =  0;
			//ctxp->cursor_pos += ctxp->scroll_pos;
			ctxp->cursor_pos /= 2;
		}else{
			ctxp->cursor_pos -= wp->lines / 2;
			if(ctxp->cursor_pos * 2 < ctxp->scroll_pos)
				ctxp->cursor_pos = ctxp->scroll_pos / 2;
			if(ctxp->cursor_pos * 2 >= ctxp->scroll_pos + wp->lines){
				ctxp->cursor_pos = (ctxp->scroll_pos + wp->lines) / 2;
				ctxp->cursor_pos -= (ctxp->scroll_pos + wp->lines) % 2;
			}
		}
		break;
	case NT_KEY_PAGEDOWN:
	case KEY_NPAGE:
		if(wp->lines >= ctxp->thread_num*2){
			ctxp->scroll_pos = 0;
			break;
		}
		ctxp->scroll_pos += wp->lines;
		if(ctxp->scroll_pos + wp->lines > ctxp->thread_num*2){
			ctxp->cursor_pos *= 2;
			ctxp->cursor_pos -= ctxp->scroll_pos - wp->lines;
			ctxp->scroll_pos =  (ctxp->thread_num * 2) - wp->lines;
			ctxp->cursor_pos += ctxp->scroll_pos;
			ctxp->cursor_pos /= 2;
		}else{
			ctxp->cursor_pos += wp->lines / 2;
			if(ctxp->cursor_pos * 2 < ctxp->scroll_pos)
				ctxp->cursor_pos = ctxp->scroll_pos / 2;
			if(ctxp->cursor_pos * 2 >= ctxp->scroll_pos + wp->lines){
				ctxp->cursor_pos = (ctxp->scroll_pos + wp->lines) / 2;
				ctxp->cursor_pos -= (ctxp->scroll_pos + wp->lines) % 2;
			}
		}
		break;
	case NT_KEY_SELECT:
	case KEY_RIGHT:
		threadp = nt_link_get_by_index(ctxp->thread_disp_list,
				ctxp->cursor_pos);
		if(!threadp)
			break;
		h_thread = nt_thread_dummy_alloc(
				threadp->title, threadp->dat_name, threadp->num_res);
		if(!h_thread)
			break;
		nt_set_selected_thread(h_select, h_thread);
		nt_thread_release_ref(h_thread);
		result_state = DISP_STATE_RESLIST;
		goto ERROR_TRAP;
	case NT_KEY_ADD:
		threadp = nt_link_get_by_index(ctxp->thread_disp_list,
				ctxp->cursor_pos);
		if(!threadp)
			break;
		h_thread = nt_thread_dummy_alloc(
				threadp->title, threadp->dat_name, threadp->num_res);
		if(!h_thread)
			break;
		nt_set_selected_thread(h_select, h_thread);
		nt_thread_release_ref(h_thread);
		result_state |= DISP_CMD_ADD_FAVORITE;
		break;
	}

	clistp = ctxp->thread_disp_list;
	rows = 0;
	for(i = 0; i < ctxp->thread_num; i++){
		if(rows >= (wp->lines+ctxp->scroll_pos))
			break;
		if(ctxp->cursor_pos == i)
			attr = WA_BOLD;
		else
			attr = 0;
		threadp = (thread_disp_data_tp)clistp->data;
		len = wcslen(threadp->title);
		if(rows >= ctxp->scroll_pos){
			wmove(wp->wp, rows - ctxp->scroll_pos, 0);
			if(-1 == swprintf(buf, sizeof(buf)/sizeof(wchar_t)-1, 
							L"%5d.", threadp->seq_no))
				continue;

			nt_add_wstr(wp->wp, buf, attr);
			nwrite = nt_add_wnstr(wp->wp, threadp->title, attr, 
						wp->cols - 5); 
		}else{
			nwrite = nt_get_wc_count_within_colmns(
					threadp->title, wp->cols - 5);
		}
		rows++;
		if(rows >= (wp->lines+ctxp->scroll_pos))
			break;
		if(rows >= ctxp->scroll_pos){
			wmove(wp->wp, rows - ctxp->scroll_pos, 0);
			nt_add_wnch(wp->wp, L' ', WA_UNDERLINE | attr, wp->cols);
			wmove(wp->wp, rows - ctxp->scroll_pos, 5);
			if(nwrite < len){
				nwrite = nt_add_wnstr(wp->wp, 
					threadp->title + nwrite, 
					WA_UNDERLINE | attr, wp->cols - 5); 
			}
			if(ctxp->thread_data_list){
				read_count = nt_usr_db_get_read_count_by_dat_name(
						ctxp->thread_data_list, threadp->dat_name);
				if(read_count < 0)
					read_count = 0;
			}else{
				read_count = 0;
			}
			buf[0] = L'\0';
			if(read_count == 0){
				if(-1 < swprintf(
					buf, sizeof(buf)/sizeof(wchar_t)-1, L"(%d)  ", threadp->num_res)){
				}
			}else{
				if(-1 < swprintf(
					buf, sizeof(buf)-1, L"(%d) 未読:%d ", 
						threadp->num_res, 
						threadp->num_res - read_count)){
				}
			}
			column = nt_get_column_length(buf);
			if(column > 0){
				if(read_count > 0 && read_count < threadp->num_res)
					attr |= WA_REVERSE;
				wmove(wp->wp, 
					rows - ctxp->scroll_pos, wp->cols - column);
				nwrite = nt_add_wstr(wp->wp, buf, WA_UNDERLINE | attr);
			}
		}
		rows++;
		clistp = clistp->next;
	}/* end for */

ERROR_TRAP:
	nt_board_release_ref(h_board);
	return result_state; 
}

static ctx_threadlist_tp init_context(nt_2ch_selected_item_handle h_select)
{
	ctx_threadlist_tp ctxp;
	nt_board_handle h_board;
	nt_thread_handle h_thread;
	nt_enum_handle h_enum_thread;
	nt_link_tp linkp;
	thread_disp_data_tp datap;
	int seq_no;
	int num_res;
	const wchar_t *title;
	const wchar_t *dat_name;
	
	
	ctxp = (ctx_threadlist_tp)calloc(1,sizeof(ctx_threadlist_t));

	if(!ctxp)
		return NULL;

	h_board = nt_get_selected_board(h_select);
	assert(h_board);
	ctxp->thread_num = nt_board_get_thread_count(h_board);
	ctxp->tracked_thread_num = ctxp->thread_num;

	ctxp->regex_init = FALSE;
	ctxp->sel_thread_no = -1;
	ctxp->prev_state = DISP_STATE_BOARDMENU;

	ctxp->thread_data_list = NULL;
	h_enum_thread = nt_board_get_thread_enum(h_board);
	nt_board_release_ref(h_board);
	if(!h_enum_thread){
		free(ctxp);
		return NULL;
	}
	while(NULL != (h_thread = (nt_thread_handle)nt_enum_fetch(h_enum_thread))){
		seq_no = nt_thread_get_seq_number(h_thread);
		title = nt_thread_get_title(h_thread);
		dat_name = nt_thread_get_dat_name(h_thread);
		num_res = nt_thread_get_res_count(h_thread);
		datap = disp_data_alloc(seq_no, num_res, title, dat_name);
		if(!datap){
			if(ctxp->thread_disp_list)
				nt_all_link_free(ctxp->thread_disp_list, disp_data_free);
			free(ctxp);
			nt_enum_unset(h_enum_thread);
			return NULL;
		}
		linkp = nt_link_add_data(ctxp->thread_disp_list, datap);
		if(!ctxp->thread_disp_list)
			ctxp->thread_disp_list = linkp;
	}
	ctxp->sort_type = NT_CMD_SORT_NUMBER;
	nt_enum_unset(h_enum_thread);
	return ctxp;
}


static int merge_untracked_thread_log(int init_seq_no, nt_board_handle h_board,
		nt_link_tp disp_thread_listp, nt_link_tp log_list)
{
	thread_disp_data_tp datap;
	wchar_t *title;
	const wchar_t *dat_name;//, *board_name;
	wchar_t buf[512];
	nt_usr_db_thread_data_tp log_datap;
	nt_link_tp log_linkp, linkp;
	int seq_no;
	
	assert(disp_thread_listp);
	
	//board_name = nt_board_get_name(h_board);
	seq_no = init_seq_no;
	log_linkp = log_list;
	do{
		log_datap =
			(nt_usr_db_thread_data_tp)log_linkp->data;
		dat_name = log_datap->dat_name;
		if(get_thread_data_by_dat_name(dat_name, disp_thread_listp)){
			;
		}else if(!(title = nt_read_thread_title(
				h_board, dat_name))){
			;
		}else{
			wcscpy(buf, NT_TEXT_UNTRACKED_LOG_NAME);
			wcscat(buf, title);
			free(title);
			datap = disp_data_alloc(
				seq_no, log_datap->read_count, buf, dat_name);
			if(datap){
				linkp = nt_link_add_data(disp_thread_listp, datap);
				assert(linkp);
				seq_no++;
			}
		}
		log_linkp = log_linkp->next;
	}while(log_linkp != log_list);
	return nt_link_num(disp_thread_listp);
}

static thread_disp_data_tp disp_data_alloc(
		int seq_no, int num_res, const wchar_t *title, const wchar_t *dat_name)
{
	thread_disp_data_tp datap;
	datap = malloc(sizeof(thread_disp_data_t));
	if(!datap)
		return NULL;
	datap->seq_no = seq_no;
	datap->num_read = 0;
	datap->num_res = num_res;
	datap->title = nt_w_str_clone(title);
	datap->dat_name = nt_w_str_clone(dat_name);
	return datap;
}

static void disp_data_free(void *ptr)
{
	thread_disp_data_tp datap;
	datap = (thread_disp_data_tp)ptr;
	free(datap->dat_name);
	free(datap->title);
	free(datap);
}

static BOOL get_thread_data_by_dat_name(const wchar_t *dat_name, nt_link_tp disp_list)
{
	thread_disp_data_tp datap;
	nt_link_tp linkp;
	
	linkp = disp_list;
	do{
		datap = (thread_disp_data_tp)linkp->data;
		if(0 == wcscmp(dat_name, datap->dat_name))
			return TRUE;
		linkp = linkp->next;
	}while(linkp != disp_list);
	return FALSE;
}


void init_threadlist_ctx(void *ptr)
{
	ctx_threadlist_tp ctxp;
	if(!ptr)
		return;
	ctxp = (ctx_threadlist_tp)ptr;
	if(ctxp->thread_data_list){
		nt_all_link_free(ctxp->thread_data_list,
			nt_usr_db_thread_data_free);
		ctxp->thread_data_list = NULL;
	}
}

void free_threadlist_ctx(void *ptr)
{
	//nt_link_tp linkp, nextp;
	//nt_thread_handle h_thread;
	//int num;
	//wchar_t *cptr;

	ctx_threadlist_tp ctxp;
	if(!ptr)
		return;
	ctxp = (ctx_threadlist_tp)ptr;
	if(ctxp->thread_data_list){
		nt_all_link_free(ctxp->thread_data_list,
			nt_usr_db_thread_data_free);
	}
	if(ctxp->thread_disp_list){
		nt_all_link_free(ctxp->thread_disp_list, disp_data_free);
		/*linkp = ctxp->thread_disp_list->next;
		num = 1;
		while(linkp != ctxp->thread_disp_list){
			nextp = linkp->next;
			if(ctxp->tracked_thread_num <= num){
				threadp = linkp->data;
				cptr = wcsstr(threadp->name, 
						NT_TEXT_UNTRACKED_LOG_NAME);
				if(cptr){
					nt_thread_free(threadp);
				}
			}
			free(linkp);
			num++;
			linkp = nextp;
		}
		free(linkp);*/
	}
	if(ctxp->regex_init){
		regfree(&(ctxp->regex));
	}
	free(ctxp);
}


static BOOL search_line_asc(regex_t *regexp, 
				nt_link_tp threadlistp, int *sel_thread_no)
{
	wchar_t *cptr;
	thread_disp_data_tp datap;
	nt_link_tp clistp;
	size_t nmatch = 5;
	regmatch_t pmatch[5];
	char buf[256];
	int cur;

	clistp = threadlistp;
	cur = 0;
	do{
		if(cur > *sel_thread_no){
			datap = (thread_disp_data_tp)clistp->data;
			cptr = datap->title;
			if(0 < wcstombs(buf, cptr, sizeof(buf))){
			    if(0 == regexec(regexp, buf,
					    nmatch, pmatch, 0)){
					*sel_thread_no = cur;
					return TRUE;
				}
			}
		}
		cur++;
		clistp = clistp->next;
	}while(clistp != threadlistp);
	clistp = threadlistp;
	cur = 0;
	do{
		if(cur > *sel_thread_no)
			break;

		datap = (thread_disp_data_tp)clistp->data;
		cptr = datap->title;
		if(0 < wcstombs(buf, cptr, sizeof(buf))){
		    if(0 == regexec(regexp, buf,
				    nmatch, pmatch, 0)){
				*sel_thread_no = cur;
				return TRUE;
			}
		}
		cur++;
		clistp = clistp->next;
	}while(clistp != threadlistp);
	return FALSE;
}
static BOOL search_line_desc(regex_t *regexp, 
				nt_link_tp threadlistp, int *sel_thread_no)
{
	wchar_t *cptr;
	thread_disp_data_tp datap;
	nt_link_tp clistp;
	size_t nmatch = 5;
	regmatch_t pmatch[5];
	char buf[256];
	int cur;
	int num;

	clistp = threadlistp->prev;
	num = nt_link_num(threadlistp);
	cur = num - 1;
	do{
		if(cur < *sel_thread_no){
			datap = (thread_disp_data_tp)clistp->data;
			cptr = datap->title;
			if(0 < wcstombs(buf, cptr, sizeof(buf))){
			    if(0 == regexec(regexp, buf,
					    nmatch, pmatch, 0)){
					*sel_thread_no = cur;
					return TRUE;
				}
			}
		}
		cur--;
		clistp = clistp->prev;
	}while(clistp != threadlistp->prev);
	clistp = threadlistp->prev;
	cur = num;
	do{
		if(cur < *sel_thread_no)
			break;

		datap = (thread_disp_data_tp)clistp->data;
		cptr = datap->title;
		if(0 < wcstombs(buf, cptr, sizeof(buf))){
		    if(0 == regexec(regexp, buf,
				    nmatch, pmatch, 0)){
				*sel_thread_no = cur;
				return TRUE;
			}
		}
		cur--;
		clistp = clistp->prev;
	}while(clistp != threadlistp->prev);
	return FALSE;
}

static thread_disp_data_tp get_thread_by_seq_no(nt_link_tp linkp, int seq_no)
{
	nt_link_tp workp;
	thread_disp_data_tp datap;
	int n;

	workp = linkp;
	do{
		datap = (thread_disp_data_tp)workp->data;
		n = datap->seq_no;
		if(n == seq_no)
			return datap;
		workp = workp->next;
	}while(workp != linkp);
	return NULL;
}

static int get_thread_index_by_seqno(nt_link_tp linkp, int seq_no)
{
	int idx, n;
	nt_link_tp workp;
	thread_disp_data_tp datap;

	workp = linkp;
	idx = 0;
	do{
		datap = (thread_disp_data_tp)workp->data;
		n = datap->seq_no;
		if(n == seq_no)
			return idx;
		workp = workp->next;
		idx++;
	}while(workp != linkp);
	return -1;
}


static int parse_cmd1(const char *param)
{
	int offset, len;
	const char *start, *end;

	assert(param);
	if(0 == strncmp(NT_COMMAND1_SEARCH_1,param,
			strlen(NT_COMMAND1_SEARCH_1)) ||
		0 == strncmp(NT_COMMAND1_SEARCH_2,param,
			strlen(NT_COMMAND1_SEARCH_2))){
		return NT_CMD_SEARCH_THREAD;
	}else if(0 == strncmp(NT_COMMAND1_FAVORITE_1,param,
			strlen(NT_COMMAND1_FAVORITE_1)) ||
		0 == strncmp(NT_COMMAND1_FAVORITE_2,param,
			strlen(NT_COMMAND1_FAVORITE_2))){
		return NT_CMD_FAVORITE;
	}else if(0 == strncmp(NT_COMMAND1_SORT_1, param,
			strlen(NT_COMMAND1_SORT_1))){
		offset = strlen(NT_COMMAND1_SORT_1);
	}else if( 0 == strncmp(NT_COMMAND1_SORT_2, param,
			strlen(NT_COMMAND1_SORT_2))){
		offset = strlen(NT_COMMAND1_SORT_2);
	}else if(0 == strncmp(NT_COMMAND1_DEL_THREAD_LOG_1,param,
			strlen(NT_COMMAND1_DEL_THREAD_LOG_1)) ||
		0 == strncmp(NT_COMMAND1_DEL_THREAD_LOG_2,param,
			strlen(NT_COMMAND1_DEL_THREAD_LOG_2))){
		return NT_CMD_DEL_THREAD_LOG;
	}else{
		return NT_CMD_NONE;
	}
	if(!nt_strtok(param+offset, ' ', &start, &end))
		return NT_CMD_ERR;
	
	len = end - start;
	if(len <= 0)
		return NT_CMD_ERR;

	if(0 == strncmp(start, NT_COMMAND1_SORT_NUMBER_1,len) ||
		0 == strncmp(start, NT_COMMAND1_SORT_NUMBER_2,len)){
		return NT_CMD_SORT_NUMBER;
	}else if(0 == strncmp(start, NT_COMMAND1_SORT_READ_1,len) ||
		0 == strncmp(start, NT_COMMAND1_SORT_READ_2,len)){
		return NT_CMD_SORT_READ;
	}else if(0 == strncmp(start, NT_COMMAND1_SORT_UNREAD_1,len) ||
		0 == strncmp(start, NT_COMMAND1_SORT_UNREAD_2,len)){
		return NT_CMD_SORT_UNREAD;
	}
	return NT_CMD_ERR;
}


static int sort_by_number(void *lhs, void *rhs)
{
	int num1, num2;
	thread_disp_data_tp lthread, rthread;
	lthread = (thread_disp_data_tp)lhs;
	rthread = (thread_disp_data_tp)rhs;
	num1 = lthread->seq_no;
	num2 = rthread->seq_no;

	if(num1 == num2)
		return 0;
	else if(num1 > num2)
		return -1;
	else
		return 1;
}

static int sort_by_unread(void *lhs, void *rhs)
{
	int num1, num2;
	int unread1, unread2;
	thread_disp_data_tp lthread, rthread;
	lthread = (thread_disp_data_tp)lhs;
	rthread = (thread_disp_data_tp)rhs;

	if(lthread->seq_no > g_ctxp->tracked_thread_num &&
		rthread->seq_no <= g_ctxp->tracked_thread_num){
		return -1;
	}else if(rthread->seq_no > g_ctxp->tracked_thread_num &&
		lthread->seq_no <= g_ctxp->tracked_thread_num){
		return 1;
	}
	num1 = nt_usr_db_get_read_count_by_dat_name(
		g_ctxp->thread_data_list, lthread->dat_name);
	num2 = nt_usr_db_get_read_count_by_dat_name(
		g_ctxp->thread_data_list, rthread->dat_name);
	unread1 = -1;
	unread2 = -1;
	if(num1 > 0)
		unread1 = lthread->num_res - num1;
	if(num2 > 0)
		unread2 = rthread->num_res - num2;

	if(unread1 == unread2)
		return 0;
	return (unread1 > unread2) ? 1 : -1;
}

static int sort_by_read(void *lhs, void *rhs)
{
	int num1, num2;
	thread_disp_data_tp lthread, rthread;
	lthread = (thread_disp_data_tp)lhs;
	rthread = (thread_disp_data_tp)rhs;
	if(lthread->seq_no > g_ctxp->tracked_thread_num &&
		rthread->seq_no <= g_ctxp->tracked_thread_num){
		return -1;
	}else if(rthread->seq_no > g_ctxp->tracked_thread_num &&
		lthread->seq_no <= g_ctxp->tracked_thread_num){
		return 1;
	}
	num1 = nt_usr_db_get_read_count_by_dat_name(
		g_ctxp->thread_data_list, lthread->dat_name);
	num2 = nt_usr_db_get_read_count_by_dat_name(
		g_ctxp->thread_data_list, rthread->dat_name);

	if(num1 == num2)
		return 0;
	if(num1 >= 0)
		return (num2 >= 0) ? 0 : 1;
	return (num2 >= 0) ? -1 : 0;
}
