#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <wchar.h>
#include <ncursesw/ncurses.h>
#include <regex.h>

#include	"env.h"
#include	"error.h"
#include	"utils/nt_std_t.h"
#include	"_2ch/_2ch.h"
#include	"_2ch/model_2ch.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 thread_num;
	int cursor_pos;
	int scroll_pos;

	regex_t regex;
	BOOL regex_init;
	int sel_thread_no;

} ctx_threadlist_t;


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_model_tp modelp);

int disp_threadlist(nt_window_tp wp, nt_2ch_model_tp modelp)
{
	ctx_threadlist_tp ctxp;
	nt_board_tp boardp;
	nt_thread_tp threadp;
	nt_link_tp clistp;
	int i, rows, nwrite, len, ch;
	int btm_cur, num, wlines;
	wchar_t buf[16];
	attr_t attr;
	BOOL search_asc;

	ctxp = (ctx_threadlist_tp)wp->data;
	if(!ctxp){
		ctxp = init_context(modelp);
		if(!ctxp)
			return DISP_STATE_ERROR;
		wp->data = ctxp;
	}
	ch = wp->key;

	boardp = modelp->selected_boardp;

	switch(ch){
	case NT_KEY_REFRESH: 
		return DISP_STATE_REFRESH;
	case NT_KEY_CLOSE:
		return DISP_STATE_BOARDMENU;
	case NT_KEY_BOTTOM:
		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), boardp->threadlistp,
			        &ctxp->sel_thread_no)){
				wp->status_msg = NT_ERR_MSG_SEARCH_NOT_FOUND;
				break;
			}
		}else{
		    if(!search_line_desc(&(ctxp->regex), boardp->threadlistp,
			        &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);
			num = atoi(wp->cmd_param);
			if(0 == num)
				break;
			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:
		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:
		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:
		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:
		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:
		threadp = nt_link_get_by_index(boardp->threadlistp,
				ctxp->cursor_pos);
		if(!threadp)
			break;
		nt_set_selected_thread(modelp, threadp);
		return DISP_STATE_RESLIST;
	}

	clistp = boardp->threadlistp;
	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 = (nt_thread_tp)clistp->data;
		len = wcslen(threadp->name);
		if(rows >= ctxp->scroll_pos){
			wmove(wp->wp, rows - ctxp->scroll_pos, 0);
			if(-1 == swprintf(buf, sizeof(buf)-1, L"%5d.", i+1))
				continue;

			nt_add_wstr(wp->wp, buf, attr);
			nwrite = nt_add_wnstr(wp->wp, threadp->name, attr, 
						wp->cols - 5); 
		}else{
			nwrite = nt_get_wc_count_within_colmns(
					threadp->name, 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->name + nwrite, 
					WA_UNDERLINE | attr, wp->cols - 5); 
			}
		}
		rows++;
		clistp = clistp->next;
	}/* end for */
	
	return DISP_STATE_THREADTITLE; 
}

static ctx_threadlist_tp init_context(nt_2ch_model_tp modelp)
{
	ctx_threadlist_tp ctxp;
	nt_board_tp boardp;
	ctxp = (ctx_threadlist_tp)calloc(1,sizeof(ctx_threadlist_t));

	if(!ctxp)
		return NULL;

	boardp = modelp->selected_boardp;
	ctxp->thread_num = nt_link_num(boardp->threadlistp);

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

	return ctxp;
}

void free_threadlist_ctx(void *ptr)
{
	ctx_threadlist_tp ctxp;
	if(!ptr)
		return;
	 ctxp = (ctx_threadlist_tp)ptr;
	 free(ctxp);
}


static BOOL search_line_asc(regex_t *regexp, 
				nt_link_tp threadlistp, int *sel_thread_no)
{
	wchar_t *cptr;
	nt_thread_tp threadp;
	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){
			threadp = (nt_thread_tp)clistp->data;
			cptr = threadp->name;
			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;

		threadp = (nt_thread_tp)clistp->data;
		cptr = threadp->name;
		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;
	nt_thread_tp threadp;
	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){
			threadp = (nt_thread_tp)clistp->data;
			cptr = threadp->name;
			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;

		threadp = (nt_thread_tp)clistp->data;
		cptr = threadp->name;
		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;
}


