/*
 *	Copyright (c) 1998,2004 Rinet Corp., Novosibirsk, Russia
 *
 * Redistribution and use in source forms, with and without modification,
 * are permitted provided that this entire comment appears intact.
 *
 * THIS SOURCE CODE IS PROVIDED ``AS IS'' WITHOUT ANY WARRANTIES OF ANY KIND.
 */

#ifdef	HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef	HAVE_SLCURSES
#include <slcurses.h>
#elif	HAVE_NCURSES
#include <ncurses.h>
#else
#include <curses.h>
#endif
#include <stdlib.h>
#include <string.h>

#include "selector.h"
#include "screen.h"
#include "getkey.h"

#define	BLANK	' '

#ifdef	ACS_HLINE
#define	HLINE	ACS_HLINE
#else
#define	HLINE	'-'
#endif

static void get_size(const SELECTOR *sp, int *lines, int *cols);
static void get_colors(const SELECTOR *sp, int *foreground, int *cursor);

static void
get_size(sp, lines, cols)
	const SELECTOR *sp;
	int *lines, *cols;
{
	int ln = LINES, cl = COLS;
	if (sp) {
		if (sp->LINES > 0 && sp->LINES < LINES)
			ln = sp->LINES;
		if (sp->get_header)
			ln -= 2;
		if (sp->get_footer)
			ln -= 2;
		if (ln < 1)
			ln = 1;

		if (sp->COLS > 0 && sp->COLS < COLS)
			cl = sp->COLS;
		if (cl < 1)
			cl = 1;
	}
	if (lines) *lines = ln;
	if (cols) *cols = cl;
}

static void
get_colors(sp, fore, curs)
	const SELECTOR *sp;
	int *fore, *curs;
{
	int fg = A_NORMAL, cr = A_REVERSE;
	if (sp && use_colors) {
		if (sp->window_color != -1)
			fg = sp->window_color;
		if (sp->cursor_color != -1)
			cr = sp->cursor_color;
	}
	if (prompt_mode) cr = fg;
	if (fore) *fore = fg;
	if (curs) *curs = cr;
}

/*
 * Allocate & initialize selector handler.
 */
SELECTOR *
selector_init()
{
	SELECTOR *sp = (SELECTOR *)malloc(sizeof(SELECTOR));
	if (sp) {
		memset(sp, 0, sizeof(SELECTOR));
		sp->window_color = -1;
		sp->cursor_color = -1;
	}
	return sp;
}

/*
 * Redraw selector region and refresh screen.
 */
void
selector_redraw(sp)
	SELECTOR *sp;
{
	int i, l, r, lines, cols, first_line, first_col;
	int fg_color, cr_color, attr;
	char *cp, buf[1024];

	/* sanity check */
	if (!sp) return;

	if (sp->index >= sp->items)
		selector_set(sp->items - 1, sp);

	get_size(sp, &lines, &cols);
	get_colors(sp, &fg_color, &cr_color);

	first_line = sp->LINE;
	first_col = sp->COL;

	attrset(A_NORMAL);

#ifdef	HAVE_WREDRAWLN
	wredrawln(stdscr, first_line, lines + 2);
#endif

	/* draw header */
	if (sp->get_header) {
		attr = fg_color;
		attr |= (*sp->get_header)(buf, sizeof(buf), sp->header);

		move(first_line, first_col);
		r = cols;
		for (cp = buf; *cp && r-- > 0; cp++) {
			if (*cp > ' ')
				addch(*cp | attr);
			else	addch(BLANK | attr);
		}
		while (r-- > 0) addch(BLANK | attr);

		move(first_line + 1, first_col);
		r = cols;
		while (r-- > 0) addch(HLINE | attr);

		first_line += 2;
	}

	/* draw main area */
	for (i = sp->fline, l = 0; l < lines; i++, l++) {
		attr = fg_color;
		move(first_line + l, first_col);
		r = cols;
		if (i < sp->items && sp->get_line) {
			attr |= (*sp->get_line)(buf, sizeof(buf), sp->list, i);
			if (i == sp->index)
				attr = cr_color;
			for (cp = buf; *cp && r-- > 0; cp++) {
				if (*cp > ' ')
					addch(*cp | attr);
				else	addch(BLANK | attr);

				/* workaround -- last attr mustbe foreground */
				if (r == 1 && i == sp->items - 1)
					attr = fg_color;
			}
		}
		while (r-- > 0) addch(BLANK | attr);
	}

	/* draw footer */
	if (sp->get_footer) {
		attr = fg_color;
		attr |= (*sp->get_footer)(buf, sizeof(buf), sp->footer);

		first_line += l;
		move(first_line++, first_col);
		r = cols;
		while (r-- > 0) addch(HLINE | attr);

		if (!prompt_mode || first_line != LINES-1) {
			move(first_line, first_col);
			r = cols;
			for (cp = buf; *cp && r-- > 0; cp++) {
				if (*cp > ' ')
					addch(*cp | attr);
				else	addch(BLANK | attr);
			}
			while (r-- > 0) addch(BLANK | attr);
		}
	}

	/* refresh screen */
	screen_update();
}

/*
 * Withdraw selector from the screen.
 */
void
selector_withdraw(sp)
	SELECTOR *sp;
{
	int i, l, r, lines, cols, first_line, first_col;
	int fg_color, attr;
	char *cp, buf[1024];

	/* sanity check */
	if (!sp || !sp->get_line) return;

	get_size(sp, &lines, &cols);
	get_colors(sp, &fg_color, 0);

	first_line = sp->LINE;
	first_col = sp->COL;

	attrset(A_NORMAL);

	if (sp->get_header)
		first_line += 2;

#ifdef	HAVE_WREDRAWLN
	wredrawln(stdscr, first_line, lines);
#endif
	for (i = sp->fline, l = 0; l < lines; i++, l++) {
		if (i != sp->index)
			continue;
		attr = fg_color;
		move(first_line + l, first_col);
		r = cols;
		if (i < sp->items && sp->get_line) {
			attr |= (*sp->get_line)(buf, sizeof(buf), sp->list, i);
			for (cp = buf; *cp && r-- > 0; cp++) {
				if (*cp > ' ')
					addch(*cp | attr);
				else	addch(BLANK | attr);

				/* workaround -- last attr mustbe foreground */
				if (r == 1 && i == sp->items - 1)
					attr = fg_color;
			}
			while (r-- > 0) addch(BLANK | attr);
		}
		break;
	}
}

/*
 * Return current selector position or -1 if no selector present.
 */
int
selector_get(sp)
	SELECTOR *sp;
{
	/* sanity check */
	if (!sp || !sp->list || sp->items < 1 ||
	    sp->index < 0 || sp->index >= sp->items)
		return -1;
	return sp->index;
}

/*
 * Set selector to requested position.
 */
void
selector_set(new_index, sp)
	int new_index;
	SELECTOR *sp;
{
	int lines;

	/* sanity check */
	if (!sp) return;

	if (sp->items < 1) {
		sp->index = 0;
		sp->fline = 0;
		sp->cline = 0;
		return;
	}
	get_size(sp, &lines, 0);

	if (new_index >= sp->items)
		new_index = sp->items - 1;

	sp->fline = (new_index / lines) * lines;
	sp->cline = new_index % lines;
	if (sp->fline + lines >= sp->items) {
		sp->fline = sp->items - lines;
		if (sp->fline < 0) sp->fline = 0;
		sp->cline = new_index - sp->fline;
	}
	sp->index = new_index;
}

/*
 * Move selector on screen.
 * Return index selected or -1.
 */
int
selector_move(ch, sp)
	int ch;
	SELECTOR *sp;
{
	int lines;

	/* sanity check */
	if (!sp) return -1;

	get_size(sp, &lines, 0);

	switch (ch) {
		case 'k':
		case K_CTRL('P'):
		case K_UP:		/* line up */
			if (sp->index - 1 < 0) break;
			sp->index--;
			if (--sp->cline < 0 && sp->fline) {
				sp->cline = 0;
				if (--sp->fline < 0)
					sp->fline = 0;
			}
			break;

		case 'j':
		case K_CTRL('N'):
		case K_DOWN:		/* line down */
			if (sp->index + 1 >= sp->items) break;
			sp->index++;
			if (++sp->cline >= lines) {
				sp->cline--;
				sp->fline = sp->index - sp->cline;
			}
			break;

		case K_BS:
		case K_CTRL('U'):
		case K_CTRL('B'):
		case K_PAGEUP:		/* page up */
			if (sp->cline > 0) {
				sp->index -= sp->cline;
			} else {
				sp->index -= lines;
				sp->fline -= lines;
				if (sp->index < 0 || sp->fline < 0)
					sp->index = sp->fline = 0;
			}
			sp->cline = 0;
			break;

		case ' ':
		case K_CTRL('D'):
		case K_CTRL('F'):
		case K_PAGEDOWN:	/* page down */
			if (sp->items <= lines) {
				sp->index = sp->cline = sp->items - 1;
			} else {
				if (sp->cline < lines - 1) {
					sp->index += lines - sp->cline - 1;
				} else {
					sp->index += lines;
					sp->fline = sp->index - lines + 1;
				}
				if (sp->index >= sp->items) {
					sp->index = sp->items - 1;
					sp->fline = sp->items - lines;
				}
				sp->cline = lines - 1;
			}
			break;

		case K_CTRL('A'):
		case K_HOME:		/* home */
			sp->index = 0;
			sp->fline = 0;
			sp->cline = 0;
			break;

		case K_CTRL('E'):
		case K_END:		/* end */
			if (sp->items <= lines) {
				sp->index = sp->cline = sp->items - 1;
			} else {
				sp->index = sp->items - 1;
				sp->fline = sp->items - lines;
				sp->cline = lines - 1;
			}
			break;

		case K_CR:	/* select index */
		case K_NL:
			return sp->index;

		case K_CTRL('G'):	/* trace; for debug purpose only */
			mvprintw(LINES-1, 0, "items=%d index=%d fline=%d cline=%d LINE=%d COL=%d LINES=%d func=%p",
				 sp->items, sp->index, sp->fline,
				 sp->cline, sp->LINE, sp->COL,
				 lines, sp->get_line);
			clrtoeol();
			break;
	}
	return -1;
}

