/* $Id$ */
#include "bbs.h"

#define MORE_MSG "���ť���i�C�X��h����"

void
ToggleVector(struct Vector *list, int *recipient, const char *listfile, const char *msg)
{
    FILE           *fp;
    char            genbuf[STRLEN];

    if ((fp = fopen(listfile, "r"))) {
	while (fgets(genbuf, sizeof(genbuf), fp)) {
	    char *space = strpbrk(genbuf, str_space);
	    if (space) *space = '\0';
	    if (!genbuf[0])
		continue;
	    if (Vector_search(list, genbuf) < 0) {
		Vector_add(list, genbuf);
		(*recipient)++;
	    } else {
		Vector_remove(list, genbuf);
		(*recipient)--;
	    }
	}
	fclose(fp);
	ShowVector(list, 3, 0, msg, 0);
    }
}

int
ShowVector(struct Vector *list, int row, int col, const char * msg, int idx)
{
    int i, len;

    move(row, col);
    clrtobot();

    if (msg) {
	outs(msg);
	row++;
    }
    col = 0;

    len = Vector_MaxLen(list, idx, b_lines - row);
    while (len + col < t_columns) {
	for (i = row; idx < Vector_length(list) && (i < b_lines); i++) {
	    move(i, col);
	    outs(Vector_get(list, idx));
	    idx++;
	}
	col += len + 2;
	if (idx == Vector_length(list)) {
	    idx = 0;
	    break;
	}
	len = Vector_MaxLen(list, idx, b_lines - row);
    }

    return idx;
}

struct namecomplete_int {
    const struct Vector * base;
    struct Vector sublist;
    int idx, dirty;
};

static int
nc_cb_peek(int key, VGET_RUNTIME *prt, void *instance)
{
    struct namecomplete_int * nc_int = (struct namecomplete_int *) instance;
    int tmp;

    prt->buf[prt->iend] = 0;

    if (nc_int->dirty < 0) {
	Vector_sublist(nc_int->base, &nc_int->sublist, prt->buf);
	nc_int->idx = 0;
	nc_int->dirty = 0;
    }

    switch (key) {
	case KEY_ENTER:
	    if (Vector_length(&nc_int->sublist) == 1)
		strlcpy(prt->buf, Vector_get(&nc_int->sublist, 0), prt->len);
	    else if ((tmp = Vector_search(&nc_int->sublist, prt->buf)) >= 0)
		strlcpy(prt->buf, Vector_get(&nc_int->sublist, tmp), prt->len);
	    else
		prt->buf[0] = '\0';
	    prt->icurr = prt->iend = strlen(prt->buf);
	    break;

	case ' ':
	    if (Vector_length(&nc_int->sublist) == 1) {
		strlcpy(prt->buf, Vector_get(&nc_int->sublist, 0), prt->len);
		prt->icurr = prt->iend = strlen(prt->buf);
		return VGETCB_NEXT;
	    }

	    move(2, 0);
	    clrtobot();
	    printdash("������T�@����", 0);

	    nc_int->idx = ShowVector(&nc_int->sublist, 3, 0, NULL, nc_int->idx);
	    if (nc_int->idx < Vector_length(&nc_int->sublist))
		vshowmsg(MORE_MSG);
	    return VGETCB_NEXT;
	    break;

	case KEY_BS2: case KEY_BS:  /* backspace */
	    nc_int->dirty = -1;
	    break;

	case KEY_HOME:  case Ctrl('A'):
	case KEY_END:   case Ctrl('E'):
	case KEY_LEFT:  case Ctrl('B'):
	case KEY_RIGHT: case Ctrl('F'):
	case KEY_DEL:   case Ctrl('D'):
	case Ctrl('Y'):
	case Ctrl('K'):
	    return VGETCB_NEXT;
	    break;

	default:
	    if (isprint(key)) {
		struct Vector tmplist;

		prt->buf[prt->iend] = key;
		prt->buf[prt->iend + 1] = 0;

		Vector_init(&tmplist, IDLEN + 1);
		Vector_sublist(&nc_int->sublist, &tmplist, prt->buf);

		if (Vector_length(&tmplist) == 0) {
		    Vector_delete(&tmplist);
		    prt->buf[prt->iend] = 0;
		    return VGETCB_NEXT;
		} else {
		    Vector_delete(&nc_int->sublist);
		    nc_int->sublist = tmplist;
		    nc_int->idx = 0;
		    prt->buf[prt->iend] = 0;
		}
	    }
    }
	    
    return VGETCB_NONE;
}

void
namecomplete2(const struct Vector *namelist, const char *prompt, char *data)
{
    struct namecomplete_int nc_int = {
	.base = namelist,
	.dirty = 0,
    };
    VGET_CALLBACKS vcb = {
	.peek = nc_cb_peek,
	.data = NULL,
	.post = NULL,
    };

    outs(prompt);
    clrtoeol();
    Vector_init(&nc_int.sublist, IDLEN+1);
    Vector_sublist(namelist, &nc_int.sublist, "");
    vgetstring(data, IDLEN + 1, VGET_ASCII_ONLY, NULL, &vcb, &nc_int);
    Vector_delete(&nc_int.sublist);
}

void
usercomplete(const char *prompt, char *data)
{
    struct Vector namelist;

    Vector_init_const(&namelist, SHM->userid[0], MAX_USERS, IDLEN+1);

    namecomplete2(&namelist, prompt, data);
}

static int
gnc_findbound(char *str, int *START, int *END,
	      size_t nmemb, gnc_comp_func compar)
{
    int             start, end, mid, cmp, strl;
    strl = strlen(str);

    start = -1, end = nmemb - 1;
    /* The first available element is always in the half-open interval
     * (start, end]. (or `end'-th it self if start == end) */
    while (end > start + 1) {
	mid = (start + end) / 2;
	cmp = (*compar)(mid, str, strl);
	if (cmp >= 0)
	    end = mid;
	else
	    start = mid;
    }
    if ((*compar)(end, str, strl) != 0) {
	*START = *END = -1;
	return -1;
    }
    *START = end;

    start = end;
    end = nmemb;
    /* The last available element is always in the half-open interval
     * [start, end). (or `start'-th it self if start == end) */
    while (end > start + 1) {
	mid = (start + end) / 2;
	cmp = (*compar)(mid, str, strl);
	if (cmp <= 0)
	    start = mid;
	else
	    end = mid;
    }
    *END = start;
    return 0;
}

static int
gnc_complete(char *data, int *start, int *end,
		gnc_perm_func permission, gnc_getname_func getname)
{
    int             i, count, first = -1, last = *end;
    if (*start < 0 || *end < 0)
	return 0;
    for (i = *start, count = 0; i <= *end; ++i)
	if ((*permission)(i)) {
	    if (first == -1)
		first = i;
	    last = i;
	    ++count;
	}
    if (count == 1)
	strcpy(data, (*getname)(first));

    *start = first;
    *end = last;
    return count;
}


int
generalnamecomplete(const char *prompt, char *data, int len, size_t nmemb,
		    gnc_comp_func compar, gnc_perm_func permission,
		    gnc_getname_func getname)
{
    int             x, y, origx, scrx, ch, i, morelist = -1, col, ret = -1;
    int             start, end, ptr;
    int             clearbot = NA;

    outs(prompt);
    clrtoeol();
    getyx(&y, &x);
    scrx = origx = x;

    ptr = 0;
    data[ptr] = 0;

    start = 0; end = nmemb - 1;
    while (1)
    {
	// print input field again
	move(y, scrx); outc(' '); clrtoeol(); move(y, scrx);
	outs(ANSI_REVERSE);
	// data[ptr] = 0;
	prints("%-*s", len, data);
	outs(ANSI_RESET);
	move(y, scrx + ptr);

	// get input
	if ((ch = igetch()) == EOF)
	    break;

	if (ch == KEY_ENTER) {
	    data[ptr] = 0;
	    outc('\n');
	    if (ptr != 0) {
		gnc_findbound(data, &start, &end, nmemb, compar);
		if (gnc_complete(data, &start, &end, permission, getname)
			== 1 || (*compar)(start, data, len) == 0) {
		    strcpy(data, (*getname)(start));
		    ret = start;
		} else {
		    data[0] = '\0';
		    ret = -1;
		}
	    } else
		ptr = -1;
	    break;
	} else if (ch == ' ') {
	    if (morelist == -1) {
		if (gnc_findbound(data, &start, &end, nmemb, compar) == -1)
		    continue;
		i = gnc_complete(data, &start, &end, permission, getname);
		if (i == 1) {
		    ptr = strlen(data);
		    continue;
		} else {
		    char* first = (*getname)(start);
		    i = ptr;
		    while (first[i] && (*compar)(end, first, i + 1) == 0) {
			data[i] = first[i];
			++i;
		    }
		    data[i] = '\0';

		    if (i != ptr) { /* did complete several words */
			ptr = i;
		    }
		}
		morelist = start;
	    } else if (morelist > end)
		continue;
	    clearbot = YEA;
	    move(2, 0);
	    clrtobot();
	    printdash("������T�@����", 0);

	    col = 0;
	    while (len + col < t_columns-1) {
		for (i = 0; morelist <= end && i < p_lines; ++morelist) {
		    if ((*permission)(morelist)) {
			move(3 + i, col);
			prints("%s ", (*getname)(morelist));
			++i;
		    }
		}

		col += len + 2;
	    }
	    if (morelist != end + 1) {
		vshowmsg(MORE_MSG);
	    }
	    continue;

	} else if (ch == KEY_BS2 || ch == KEY_BS) {	/* backspace */
	    if (ptr == 0)
		continue;
	    morelist = -1;
	    --ptr;
	    data[ptr] = 0;
	    continue;
	} else if (isprint(ch) && ptr <= (len - 2)) {
	    morelist = -1;
	    data[ptr] = ch;
	    ++ptr;
	    data[ptr] = 0;
	    if (gnc_findbound(data, &start, &end, nmemb, compar) < 0)
		data[--ptr] = 0;
	    else {
		for (i = start; i <= end; ++i)
		    if ((*permission)(i))
			break;
		if (i == end + 1)
		    data[--ptr] = 0;
	    }
	} else if (ch == KEY_UP || ch == KEY_DOWN) {
	    if (!InputHistoryExists(data))
		InputHistoryAdd(data);

	    if (ch == KEY_DOWN)
		InputHistoryNext(data, len);
	    else
		InputHistoryPrev(data, len);

	    ptr = strlen(data);
	}
    }

    outc('\n');
    if (clearbot) {
	move(2, 0);
	clrtobot();
    }
    if (*data) {
	move(y, origx);
	outs(data);
	outc('\n');

	// save the history
	InputHistoryAdd(data);
    }
    return ret;
}

/* general complete functions (brdshm) */
int
completeboard_compar(int where, const char *str, int len)
{
    boardheader_t *bh = &bcache[SHM->bsorted[0][where]];
    return strncasecmp(bh->brdname, str, len);
}

int
completeboard_permission(int where)
{
    boardheader_t *bptr = &bcache[SHM->bsorted[0][where]];
    return (!(bptr->brdattr & BRD_SYMBOLIC) &&
	    (GROUPOP() || HasBoardPerm(bptr)) &&
	    !(bptr->brdattr & BRD_GROUPBOARD));
}

int 
complete_board_and_group_permission(int where)
{
    boardheader_t *bptr = &bcache[SHM->bsorted[0][where]];
    return (!(bptr->brdattr & BRD_SYMBOLIC) &&
	    (GROUPOP() || HasBoardPerm(bptr)));

}

char           *
completeboard_getname(int where)
{
    return bcache[SHM->bsorted[0][where]].brdname;
}

/* general complete functions (utmpshm) */
int
completeutmp_compar(int where, const char *str, int len)
{
    userinfo_t *u = &SHM->uinfo[SHM->sorted[SHM->currsorted][0][where]];
    return strncasecmp(u->userid, str, len);
}

int
completeutmp_permission(int where)
{
   userinfo_t *u = &SHM->uinfo[SHM->sorted[SHM->currsorted][0][where]];
    return (unlikely(HasUserPerm(PERM_SYSOP)) ||
	    unlikely(HasUserPerm(PERM_SEECLOAK)) ||
//	    !SHM->sorted[SHM->currsorted][0][where]->invisible);
	    isvisible(currutmp, u));
}

char           *
completeutmp_getname(int where)
{
    return SHM->uinfo[SHM->sorted[SHM->currsorted][0][where]].userid;
}