/*
 * btscanner - Displays the output of Bluetooth scans
 * Copyright (C) 2003 Pentest Limited
 * 
 * Written 2003 by Tim Hurman <timh at pentest.co.uk>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY
 * RIGHTS.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE
 * FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY
 * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 * ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
 * COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE
 * IS DISCLAIMED.
 */

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

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>

#include <ncurses.h>
#include <form.h>
#include <menu.h>
#include <errno.h>
#include <regex.h>
#include <pthread.h>

#include <main.h>
#include <screen.h>
#include <threader.h>
#include <log.h>
#include <scan.h>
#include <cfg.h>
#include <misc.h>
#include <ll.h>
#include <oui.h>

#define C_BLK_TMOUT 5
#ifndef CTRL
#define CTRL(x) ((x) & 0x1f)
#endif

#ifndef BD_RE
#define BD_RE "^[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2} *$"
#endif

#ifndef FN_RE
#define FN_RE "^[a-zA-Z0-9/.]* *$"
#endif

static const char *screen_mm = "";
static int screen_sort = LL_SORT_NONE;
static pthread_mutex_t screen_log_mux = PTHREAD_MUTEX_INITIALIZER;
cbuf_t screen_log_lines[4];

extern char bts_run;
extern char bts_run_scan;
extern hcicfg_t *hcihead;

/* prototypes */
void screen_destroy(void);

/* curses startup */
int screen_on(void)
{
	initscr(); /* initialise */
	keypad(stdscr, TRUE); /* enable keys */
	nonl(); /* \n not translated */
	cbreak(); /* flush keys immediately */
	halfdelay(C_BLK_TMOUT);
	noecho(); /* dont echo char */
	idlok(stdscr, TRUE);
	leaveok(stdscr, TRUE);
	intrflush(stdscr, FALSE); /* flush on interrupt */

	curs_set(0); /* invisible cursor */
	assume_default_colors(COLOR_BLACK, COLOR_WHITE);

	if (has_colors()) {
		start_color();
		init_pair(1, COLOR_BLACK, COLOR_WHITE);
		bkgd(COLOR_PAIR(1));
	}

	set_menu_mark(NULL, screen_mm);
	touchwin(stdscr);
	refresh();

	return 0;
}

/* screen off */
int screen_off(void)
{
	curs_set(1);
	delwin(stdscr);
	endwin();
	return 0;
}


/*
 * screen structures
 */
enum screen_types { LINE=0, BLOCK };
enum screens { NONE=-1, MAIN=0, INFO };
static int screens_max = 2; /* the max no or screens */
static int screens_curr = 0; /* the currently displayed screen */

/*
 * to hold the sizes of the main windows
 * 0 = content window, 1 = log window, 2 = title window
 */
static struct {
	size_t x; /* position on screen of window/pad */
	size_t y;
	size_t rows; /* pyhsical width/height of window/pad */
	size_t cols;
	WINDOW *w; /* the sctual window */
} screen_sizes[3];

/* block info holder */
static struct {
	size_t sx; /* pad start x,y inside viewable window */
	size_t sy;
	size_t mcols; /* max pad area (bigger than phys area) */
	size_t mrows;
	cbuf_t text;
	time_t updated;
} screen_block_info;

/* line info holder */
static struct {
	int num_items;
	uint64_t *ids;
	uint64_t selected; /* 0 means whatever is first */
	char **labels;
	ITEM **items;
	MENU *m;
	WINDOW *sw;
} screen_line_info;

/* key transforms */
struct screen_keys {
	int key;
	int (*func)(int);
};


/*
 * logging
 */
int screen_log(char *fmt, ...)
{
    va_list ap;
	int y,x;
	int ret = 0;
	cbuf_t new;
	if (NULL == screen_sizes[1].w) return 1;
	memset (&new, 0, sizeof(cbuf_t));

	pthread_mutex_lock(&screen_log_mux);
	/* first print the line into a new string */
	do {
		if (new.len > 0) {
			new.sz = new.len+1;
			new.buf = (char*)realloc(new.buf, sizeof(char)*new.sz);
			if (NULL == new.buf) {
				ret = 1;
				goto screen_log_leave;
			}
		}
		va_start(ap, fmt);
		new.len = vsnprintf(new.buf, new.sz, fmt, ap);
		va_end(ap);
	} while (new.len >= new.sz);

	/* shift all the lines up one */
	if (NULL != screen_log_lines[0].buf)
		free(screen_log_lines[0].buf);

	for (y=1; y<4; y++) {
		memcpy(&screen_log_lines[y-1], &screen_log_lines[y], sizeof(cbuf_t));
	}
	memcpy(&screen_log_lines[3], &new, sizeof(cbuf_t));


	/* get the max size, scroll up and print on the bottom */
	if (NULL != screen_sizes[1].w) {
		getmaxyx(screen_sizes[1].w, y, x);
		wscrl(screen_sizes[1].w, 1);
		mvwaddstr(screen_sizes[1].w, y-1, 0, screen_log_lines[3].buf);
		wrefresh(screen_sizes[1].w);
	}

	screen_log_leave:
	pthread_mutex_unlock(&screen_log_mux);

    return ret;
}

/*
 * redisplay the entire log buffer
 */
int screen_log_redisplay(void)
{
	int i;
	if (NULL == screen_sizes[1].w)
		return 1;

	pthread_mutex_lock(&screen_log_mux);
	werase(screen_sizes[1].w);
	for (i=0; i<4; i++) {
		if(NULL != screen_log_lines[i].buf)
			mvwaddstr(screen_sizes[1].w, i, 0, screen_log_lines[i].buf);
	}
	wnoutrefresh(screen_sizes[1].w);
	pthread_mutex_unlock(&screen_log_mux);

	return 0;
}


/*
 * screen driver funtions
 */
int screen_line_move(int ch)
{
	int cmd = 0;

	switch (ch) {
	case KEY_UP:
	case 'k':
		cmd = REQ_PREV_ITEM;
		break;
	case KEY_DOWN:
	case 'j':
		cmd = REQ_NEXT_ITEM;
		break;
	case KEY_NPAGE:
		cmd = REQ_SCR_DPAGE;
		break;
	case KEY_PPAGE:
		cmd = REQ_SCR_UPAGE;
		break;
	default:
		return E_UNKNOWN_COMMAND;
	}

	return menu_driver(screen_line_info.m, cmd);
}
int screen_block_move(int ch)
{
	switch (ch) {
	case KEY_UP:
	case 'k': /* move the view port up one */
		if (screen_block_info.sy > 0) screen_block_info.sy--;
		break;
	case KEY_DOWN:
	case 'j': /* move the view port down one */
		if ((screen_block_info.sy+screen_sizes[0].rows)
		  < screen_block_info.mrows)
			screen_block_info.sy++;
		break;
	case KEY_NPAGE: /* move down a page */
		if (screen_block_info.mrows > screen_sizes[0].rows) {
			if (screen_block_info.sy + (screen_sizes[0].rows*2) <
			  screen_block_info.mrows)
				screen_block_info.sy += screen_sizes[0].rows;
			else
				screen_block_info.sy =
				  screen_block_info.mrows - screen_sizes[0].rows;
		}
		break;
	case KEY_PPAGE: /* move up a page */
		if (screen_block_info.mrows > screen_sizes[0].rows) {
			if (((int)screen_block_info.sy - (int)screen_sizes[0].rows) < 0)
				screen_block_info.sy = 0;
			else
				screen_block_info.sy -= screen_sizes[0].rows;
		}
		break;
	default:
		return E_UNKNOWN_COMMAND;
	}

	pnoutrefresh(screen_sizes[0].w,
	  screen_block_info.sy, screen_block_info.sx,
	  screen_sizes[0].y, screen_sizes[0].x,
	  (screen_sizes[0].rows+screen_sizes[0].y)-1,
	  (screen_sizes[0].cols+screen_sizes[0].x)-1);
	doupdate();
	return 0;
}
int screen_main_key_enter(int ch)
{
	int i;
	ch=ch;

	/* save the value */
	i = item_index(current_item(screen_line_info.m));
	if (-1 == i) return 0;
	screen_line_info.selected = screen_line_info.ids[i];
	/* destroy the current screen, and show the info screen */
	screen_destroy();
	/* we are going to make the new screen next time */
	screens_curr = INFO;
	return 0;
}
int screen_info_quit(int ch)
{
	ch=ch;
	screen_destroy();
	screens_curr = MAIN;
	return 0;
}

/* the the menu box with list items */
int screen_main_get_content(int width)
{
	int i, ret;
	device_t *d = NULL;

	ret = 0;
	/* lock the entire list */
	ll_lock_list();

	/* get the number of items */
	screen_line_info.num_items = ll_dev_count(); /* + NULL */
	if (0 == screen_line_info.num_items)
		goto screen_main_get_content_leave;

	/* apply the sort function to the list */
	ll_sortlist(screen_sort);

	/* lines */
	screen_line_info.labels = (char**)malloc(screen_line_info.num_items
	  * sizeof(char*));
	if (NULL == screen_line_info.labels) {
		applog(LOG_ERR, "%s::malloc(): %s", __FUNCTION__, strerror(errno));
		ret = 1;
		goto screen_main_get_content_leave;
	}
	/* ids */
	screen_line_info.ids = (uint64_t*)malloc(screen_line_info.num_items
	  * sizeof(uint64_t));
	if (NULL == screen_line_info.ids) {
		applog(LOG_ERR, "%s::malloc(): %s", __FUNCTION__, strerror(errno));
		ret = 1;
		goto screen_main_get_content_leave;
	}

	/* get each item and id */
	for(i=0; i<screen_line_info.num_items; i++) {
		if (0 == i)
			d = ll_first();
		else
			d = ll_next(d);

		screen_line_info.labels[i] = (char*)malloc((width+1)*sizeof(char));
		if (NULL == screen_line_info.labels[i]) {
			applog(LOG_ERR, "%s::malloc(): %s",
			  __FUNCTION__, strerror(errno));
			ret = 1;
			break;
		}

		ll_print_dev_line(d, &(screen_line_info.ids[i]),
		  screen_line_info.labels[i], width);
	}

	screen_main_get_content_leave:
	/* unlock the list */
	ll_unlock_list();

	return ret;
}

int screen_info_get_content(int width)
{
	/* init */
	width=width;

	/* grab the content */
	if (ll_print_dev_info(screen_line_info.selected,
	  &(screen_block_info.text)))
		return 1;

	/* finally what time was it updated */
	ll_get_last_update_time(screen_line_info.selected,
	  &(screen_block_info.updated));
	return 0;
}
int screen_info_is_changed(void)
{
	time_t t;
	ll_get_last_update_time(screen_line_info.selected, &t);
	if (t == screen_block_info.updated)
		return 0;
	screen_block_info.updated = t;
	return 1;
}

void screen_main_print_title(void)
{
	mvwaddstr(screen_sizes[2].w, 0, 0, "Time                 Address            Clk off  Class     Name");
}
void screen_info_print_title(void)
{
	device_t *dev;
	ll_lock_list();
	dev = ll_find_device(screen_line_info.selected);
	ll_unlock_list();

	if (NULL == dev) {
		mvwaddstr(screen_sizes[2].w, 0, 0,
		  "RSSI:          LQ:          TXPWR:");
		return;
	}

	wmove(screen_sizes[2].w, 0, 0);
	if (dev->rssi_status)
		wprintw(screen_sizes[2].w, "RSSI:  E0x%2.2x", dev->rssi_status);
	else
		wprintw(screen_sizes[2].w, "RSSI:  %+4d ", dev->rssi);

	if (dev->lq_status)
		wprintw(screen_sizes[2].w, "  LQ:  E0x%2.2x", dev->lq_status);
	else
		wprintw(screen_sizes[2].w, "  LQ:  %03d  ", dev->lq);

	if (dev->txpwr_status)
		wprintw(screen_sizes[2].w, "  TXPWR:  E0x%2.2x", dev->txpwr_status);
	else
		wprintw(screen_sizes[2].w, "  TXPWR:  %s %+4d  ",
		  (dev->txpwr_type == 0) ? "Cur":"Max", dev->txpwr_level);
}


/*
 * key handler structs
 */
static struct screen_keys screen_main_keys[] = {
	{KEY_UP, screen_line_move},
	{'k', screen_line_move},
	{KEY_DOWN, screen_line_move},
	{'j', screen_line_move},
	{KEY_NPAGE, screen_line_move},
	{KEY_PPAGE, screen_line_move},
	{KEY_ENTER, screen_main_key_enter},
	{0x0d, screen_main_key_enter},
	{-1, NULL}
};
static struct screen_keys screen_info_keys[] = {
	{KEY_UP, screen_block_move},
	{'k', screen_block_move},
	{KEY_DOWN, screen_block_move},
	{'j', screen_block_move},
	{KEY_NPAGE, screen_block_move},
	{KEY_PPAGE, screen_block_move},
	{'q', screen_info_quit},
	{-1, NULL}
};

/* register them */
static struct {
	enum screen_types type;
	void (*title)(void); /* print the title on the window */
	int (*changed)(void); /* did the content change */
	int (*get_content)(int); /* fill the screen buffer content */
	struct screen_keys *keymap;
} screens_cfg[2] = {
	{LINE, screen_main_print_title, ll_ischanged,
	  screen_main_get_content, screen_main_keys},
	{BLOCK, screen_info_print_title, screen_info_is_changed,
	  screen_info_get_content, screen_info_keys},
};



/*
 * screen manipulation
 */

/* check the size and maybe resize the windows */
int screen_chksize(void)
{
	int y,x;
	getmaxyx(stdscr, y, x);
	if(y < 11 || x < 80) {
		applog(LOG_ERR, "Screen size too small");
		return 1;
	}

	/* the main window */
	screen_sizes[0].x = 1;
	screen_sizes[0].y = 2;
	screen_sizes[0].rows = y-8;
	screen_sizes[0].cols = x-2;

	/* the log window */
	screen_sizes[1].x = 1;
	screen_sizes[1].y = y-5;
	screen_sizes[1].rows = 4;
	screen_sizes[1].cols = x-2;

	/* the title window */
	screen_sizes[2].x = 1;
	screen_sizes[2].y = 1;
	screen_sizes[2].rows = 1;
	screen_sizes[2].cols = x-2;

	return 0;
}


/* create */
int screen_create(void)
{
	/* create the new pad and initialise the all the vars */
	if (LINE == screens_cfg[screens_curr].type) {
		screen_line_info.selected = 0;
		screen_sizes[0].w = derwin(stdscr,
		  screen_sizes[0].rows, screen_sizes[0].cols,
	      screen_sizes[0].y, screen_sizes[0].x);
	} else {
		/* initially, the pad is the same size as the screen */
		
		screen_sizes[0].w = newpad(screen_block_info.mrows,
		  screen_block_info.mcols);
		memset(&(screen_block_info.text), 0, sizeof(cbuf_t));
	}

	if(NULL == screen_sizes[0].w) {
		applog(LOG_ERR, "%s::newpad(): unable to create pad",
		  __FUNCTION__);
		return 1;
	}
	return 0;
}


/* destroy */
void screen_destroy(void)
{
	int i;

	if (LINE == screens_cfg[screens_curr].type) {
		/* if we have a menu present, destroy it */
		if (NULL != screen_line_info.m) {
			unpost_menu(screen_line_info.m);

			delwin(screen_line_info.sw);
			screen_line_info.sw = NULL;

			free_menu(screen_line_info.m);
			screen_line_info.m = NULL;

			free(screen_line_info.ids);
			screen_line_info.ids = NULL;

			for (i=0; i<screen_line_info.num_items; i++) {
				if (screen_line_info.items[i])
				  free_item(screen_line_info.items[i]);
				if (screen_line_info.labels[i])
				  free(screen_line_info.labels[i]);
			}
			free(screen_line_info.items);
			screen_line_info.items = NULL;
			free(screen_line_info.labels);
			screen_line_info.labels = NULL;
			screen_line_info.num_items = 0;
		}
	} else {
		screen_block_info.mrows = screen_sizes[0].rows;
		screen_block_info.mcols = screen_sizes[0].cols;
		screen_block_info.sx = 0;
		screen_block_info.sy = 0;
		if (screen_block_info.text.buf) free(screen_block_info.text.buf);
		memset(&(screen_block_info.text), 0, sizeof(cbuf_t));
	}

	/* erase then destroy, this means we dont erase later */
	if (screen_sizes[0].w) {
		delwin(screen_sizes[0].w);
		screen_sizes[0].w = NULL;
	}
}


/* redraw the text content */
int screen_redraw(void)
{
	int ret;
	int mrows, mcols;
	ITEM *cur;

	werase(screen_sizes[0].w);
	ret = 0;

	/* the title */
	werase(screen_sizes[2].w);
	if (screens_cfg[screens_curr].title)
		screens_cfg[screens_curr].title();
	wnoutrefresh(screen_sizes[2].w);

	/* the main content */
	if (LINE == screens_cfg[screens_curr].type) {
		/* 
		 * we are going to put this in a new menu
		 */
		int i;

		/* first up, if we had an old menu in the box, destroy it */
		if (NULL != screen_line_info.m) {
			/* save the old index */
			i = item_index(current_item(screen_line_info.m));
			if (i < 0) i=0;
			screen_line_info.selected = screen_line_info.ids[i];

			unpost_menu(screen_line_info.m);

			delwin(screen_line_info.sw);
			screen_line_info.sw = NULL;

			free_menu(screen_line_info.m);
			screen_line_info.m = NULL;

			free(screen_line_info.ids);
			screen_line_info.ids = NULL;

			for (i=0; i<screen_line_info.num_items; i++) {
				if (screen_line_info.items[i])
				  free_item(screen_line_info.items[i]);
				if (screen_line_info.labels[i])
				  free(screen_line_info.labels[i]);
			}
			free(screen_line_info.items);
			screen_line_info.items = NULL;
			free(screen_line_info.labels);
			screen_line_info.labels = NULL;
			screen_line_info.num_items = 0;
		}

		/* fill the content */
		if(0 != screens_cfg[screens_curr].get_content(screen_sizes[0].cols)) {
			ret = 1;
			goto screen_redraw_leave;
		}
		if (0 == screen_line_info.num_items)
			goto screen_redraw_leave;

		screen_line_info.items = (ITEM**)
		  malloc(sizeof(ITEM*)*(screen_line_info.num_items+1));
		if (NULL == screen_line_info.items) {
			applog(LOG_ERR, "%s::malloc(): %s",
			  __FUNCTION__, strerror(errno));
			ret = 1;
			goto screen_redraw_leave;
		}

		/* create the new menu */
		for(cur=NULL, i=0; i<screen_line_info.num_items; i++) {
			screen_line_info.items[i] =
			  new_item(screen_line_info.labels[i], "");
			if (screen_line_info.selected == screen_line_info.ids[i])
			  cur = screen_line_info.items[i];
		}
		screen_line_info.items[i] = NULL;
		screen_line_info.m = new_menu(screen_line_info.items);

		set_menu_format(screen_line_info.m, screen_sizes[0].rows, 1);
		scale_menu(screen_line_info.m, &mrows, &mcols);

		set_menu_win(screen_line_info.m, screen_sizes[0].w);
		screen_line_info.sw = derwin(screen_sizes[0].w,
		  screen_sizes[0].rows, screen_sizes[0].cols, 0, 0);
		set_menu_sub(screen_line_info.m, screen_line_info.sw);

		post_menu(screen_line_info.m);

		/* now set the selected item */
		if (NULL != cur) set_current_item(screen_line_info.m, cur);

	} else {
		/*
		 * basically a scrolling block screen
		 */
		size_t i, j, trows, tcols, len;
		cbuf_t cb;
		memset(&(screen_block_info.text), 0, sizeof(cbuf_t));

		/* clean up */
		if (screen_block_info.text.buf) free(screen_block_info.text.buf);
		memset(&(screen_block_info.text), 0, sizeof(cbuf_t));

		/* get the text content */
		if(0 != screens_cfg[screens_curr].get_content(screen_sizes[0].cols)) {
			ret = 1;
			goto screen_redraw_leave;
		}

		/* allocate a new charbuf, to sanitise the string */
		cb.sz = screen_block_info.text.sz;
		cb.buf = (char*)malloc(sizeof(char)*cb.sz);
		if (NULL == cb.buf) {
			applog(LOG_ERR, "%s::malloc(): %s",
			  __FUNCTION__, strerror(errno));
			ret = 1;
			goto screen_redraw_leave;
		}

		/* trim any excess newlines from the string content */
		if (screen_block_info.text.len > 0) {
			for (i=screen_block_info.text.len-1;
			  screen_block_info.text.buf[i] == '\n' ||
			  screen_block_info.text.buf[i] == '\r' ||
			  screen_block_info.text.buf[i] == 0; i--);
			screen_block_info.text.len=i+1;
			screen_block_info.text.buf[screen_block_info.text.len] = 0;
		}

		trows = tcols = 1;
		/* count the lines and cols */
		for (cb.len=j=i=0; i < screen_block_info.text.len; cb.len++, i++) {
			cb.buf[cb.len] = screen_block_info.text.buf[i];
			if ('\n' == screen_block_info.text.buf[i]) {
				trows++;
				len = i - j;
				if (len > tcols) tcols = len;
				j=i+1;
			}
			if ('\r' == screen_block_info.text.buf[i])
				cb.len--;
		}
		cb.buf[cb.len] = 0;

		/* copy across */
		free(screen_block_info.text.buf);
		screen_block_info.text.buf = cb.buf;
		screen_block_info.text.len = cb.len;

		/* resize the pad accordingly */
		screen_block_info.mrows =
		  (trows > screen_sizes[0].rows)?trows:screen_sizes[0].rows;
		screen_block_info.mcols =
		  (tcols > screen_sizes[0].cols)?tcols:screen_sizes[0].cols;
		wresize(screen_sizes[0].w, screen_block_info.mrows,
		  screen_block_info.mcols);

		/* put the string in the box */
		mvwaddnstr(screen_sizes[0].w, 0, 0,
		  screen_block_info.text.buf, screen_block_info.text.len);
	}

	screen_redraw_leave:
	/* refresh this */
	if (LINE == screens_cfg[screens_curr].type) {
		wnoutrefresh(screen_sizes[0].w);
	} else {
		pnoutrefresh(screen_sizes[0].w,
		  screen_block_info.sy, screen_block_info.sx,
		  screen_sizes[0].y, screen_sizes[0].x,
		  (screen_sizes[0].rows+screen_sizes[0].y)-1,
		  (screen_sizes[0].cols+screen_sizes[0].x)-1);
	}

	return ret;
}


/* get the length of a field, minus the spaces. */
size_t strlen_minus_spaces(const char *s)
{
	size_t i;
	for(i=strlen(s)-1; (int)i > -1 && (s[i] == ' ' || s[i] == 0); i--);
	return (i+1);
}


/*
 * make a popup box appear and ask for data 
 */
int screen_textbox(const char *label, const char *re, cbuf_t *cb)
{
	int i, j, ret, ch;
	int width, height, x, y; /* of the inner window */
	FIELD *fields[2];
	FORM *f = NULL;
	WINDOW *mw, *sw, *tw;

	/* this will take control of the screen for a moment */
	if (NULL == label)
		return 1;

	/* initial width, height */
	width = screen_sizes[0].cols/3;
	height = 2;
	mw = sw = tw = NULL;
	fields[0] = NULL;
	ret = 0;

	/* count the lines needed by the label */
	for(j=i=0; 0 != label[i]; j++, i++) {
		if ('\n' == label[i] || j == (width-1)) {
			j = 0;
			height ++;
		}
	}

	/* work out the initial x and y */
	y = ((screen_sizes[0].rows - height)/2)-1;
	x = width;
	mw = sw = NULL;

	/* create the fields */
	fields[0] = new_field(1, width, 0, 0, 0, 0);
	fields[1] = NULL;

	set_field_back(fields[0], A_UNDERLINE);
	field_opts_off(fields[0], O_AUTOSKIP);
	field_opts_off(fields[0], O_STATIC);
	if(E_OK != set_field_type(fields[0], TYPE_REGEXP, re)) {
		summary("Failed to apply regexp to field");
		ret = -1;
		goto screen_textbox_leave;
	}

	f = new_form(fields);
	if (NULL == f) {
		applog(LOG_ERR, "%s:new_form(): unable to create form");
		ret = -1;
		goto screen_textbox_leave;
	}

	/* make the windows */
	mw = derwin(screen_sizes[0].w, height+2, width+2, y-1, x-1);
	if (NULL == mw) {
		applog(LOG_ERR, "%s:derwin(): unable to create subwindow");
		ret = -1;
		goto screen_textbox_leave;
	}
	tw = derwin(mw, height, width, 1, 1);
	if (NULL == mw) {
		applog(LOG_ERR, "%s:derwin(): unable to create subwindow");
		ret = -1;
		goto screen_textbox_leave;
	}
	sw = derwin(mw, 1, width, height, 1);
	if (NULL == mw) {
		applog(LOG_ERR, "%s:derwin(): unable to create subwindow");
		ret = -1;
		goto screen_textbox_leave;
	}

	set_form_win(f, mw);
	set_form_sub(f, sw);
	wborder(mw, ACS_VLINE, ACS_VLINE, ACS_HLINE, ACS_HLINE,
	  ACS_ULCORNER, ACS_URCORNER, ACS_LLCORNER, ACS_LRCORNER);
	mvwaddstr(tw, 0, 0, label);
	post_form(f);
	curs_set(1);
	wrefresh(mw);

	/* input */
	for(i=1; i && bts_run && (ch = getch());) {
		switch(ch) {
			case KEY_RESIZE:
				ret = 1;
			case KEY_ENTER:
			case 0x0d:
				i=0;
				break;
			case KEY_LEFT:
				ch = REQ_LEFT_CHAR;
				break;
			case KEY_RIGHT:
				ch = REQ_RIGHT_CHAR;
				break;
			case KEY_BACKSPACE:
				ch = REQ_DEL_PREV;
				break;
			case CTRL('A'):
				ch = REQ_BEG_FIELD;
				break;
			case CTRL('E'):
				ch = REQ_END_FIELD;
				break;
			case CTRL('U'):
				ch = REQ_DEL_LINE;
				break;
			case CTRL('W'):
				ch = REQ_DEL_WORD;
				break;
			case CTRL('V'):
				ch = REQ_DEL_CHAR;
				break;
		}

		if (1 == i)
			form_driver(f, ch);
	}
	curs_set(0);

	/* was a signal sent? */
	if (0 == bts_run) {
		ret = -1;
		goto screen_textbox_leave;
	}

	/* validate */
	if(0 != form_driver(f, REQ_VALIDATION)) {
		screen_log("validation failed, input needs to be: %s", re);
		ret = 1;
	} else {
		cb->len = strlen_minus_spaces(field_buffer(fields[0], 0));
		cb->sz = cb->len+1;
		cb->buf = (char*)malloc(sizeof(char)*cb->sz);
		if (NULL == cb->buf) {
			applog(LOG_ERR, "%s::malloc(): %s",
			  __FUNCTION__, strerror(errno));
			ret = -1;
			goto screen_textbox_leave;
		}
		strncpy(cb->buf, field_buffer(fields[0], 0), cb->len);
		cb->buf[cb->len] = 0;
	}
	
	screen_textbox_leave:
	if (NULL != f) {
		unpost_form(f);
		free_form(f);
	}
	if(NULL != fields[0]) free_field(fields[0]);
	if(NULL != sw) delwin(sw);
	if(NULL != tw) delwin(tw);
	if(NULL != mw) delwin(mw);

	return ret;
}


/* get the brute force values */
int screen_init_bf(void)
{
	cbuf_t cb;
	int dl, ret;
	bdaddr_t start, end;

	screen_init_bf_retry:
	memset(&cb, 0, sizeof(cbuf_t));
	memset(&start, 0, sizeof(cbuf_t));
	memset(&end, 0, sizeof(cbuf_t));
	ret = 0;
	for (dl = 1; dl && bts_run; ) {
		switch(screen_textbox("Start address", BD_RE, &cb)) {
		case 0:
			if (cb.len > 0)
				str2ba(cb.buf, &start);
			else
				ret = 1;
			free(cb.buf);
			dl = 0;
			break;
		case -1:
			dl = 0;
			ret = 1;
			break;
		}
	}
	if (1 == ret) return ret;


	memset(&cb, 0, sizeof(cbuf_t));
	for (dl = 1; dl && bts_run; ) {
		switch(screen_textbox("End address", BD_RE, &cb)) {
		case 0:
			if (cb.len > 0)
				str2ba(cb.buf, &end);
			else
				ret = 1;
			free(cb.buf);
			dl = 0;
			break;
		case -1:
			dl = 0;
			ret = 1;
			break;
		}
	}
	if (1 == ret) return ret;

	/* checks */
	if (bd2int(&end) < bd2int(&start)) {
		screen_log("End address is smaller than start address");
		goto screen_init_bf_retry;
	} else {
		char s[32], e[32];
		scan_bf_init(&start, &end);
		ba2str(&start, s);
		ba2str(&end, e);
		screen_log("Starting bruteforce scan from %s to %s", s, e);
	}

	return ret;
}


/* get the sort method */
int screen_get_sort(void)
{
	int i, rev;
	cbuf_t cb;
	memset(&cb, 0, sizeof(cbuf_t));

	if(0 != screen_textbox( "Enter a sort method\n"
" b = Bluetooth address\n"
" f = First seen\n"
" l = Last seen\n"
" r = Reverse sort\n"
" E.G. br", "^[bflr]* *$", &cb))
		return 1;

	if (0 == cb.len) {
		free(cb.buf);
		return 0;
	}

	for(i=rev=0; 0 != cb.buf[i]; i++) {
		switch (cb.buf[i]) {
		case 'b':
			screen_sort = LL_SORT_BD;
			break;
		case 'l':
			screen_sort = LL_SORT_LS;
			break;
		case 'f':
			screen_sort = LL_SORT_FS;
			break;
		case 'r':
			rev = LL_SORT_REV;
			break;
		}
	}
	screen_sort |= rev;

	return 0;
}


/* create the windows */
int screen_make_windows(void)
{
	int i;
	uint64_t sel;

	/* clear down */
	screen_destroy();
	for(i=1; i<3; i++)
		if (NULL != screen_sizes[i].w) {
			delwin(screen_sizes[i].w);
			screen_sizes[i].w = NULL;
		}


	/* start from scratch */
	werase(stdscr);
	touchwin(stdscr);

	/* borders if you please */
	wborder(stdscr, ACS_VLINE, ACS_VLINE, ACS_HLINE, ACS_HLINE,
	  ACS_ULCORNER, ACS_URCORNER, ACS_LLCORNER, ACS_LRCORNER);
	mvwhline(stdscr, screen_sizes[0].rows+screen_sizes[0].y,
	  screen_sizes[0].x, ACS_HLINE, screen_sizes[0].cols);
	mvwaddch(stdscr, screen_sizes[0].rows+screen_sizes[0].y,
	  0, ACS_LTEE);
	mvwaddch(stdscr, screen_sizes[0].rows+screen_sizes[0].y,
	  screen_sizes[0].cols+1, ACS_RTEE);

	/* log window */
	screen_sizes[1].w  =
	  derwin(stdscr, screen_sizes[1].rows, screen_sizes[1].cols,
	  screen_sizes[1].y, screen_sizes[1].x);
	if (!screen_sizes[1].w) {
		applog(LOG_ERR, "%s::derwin(): unable to create window",
		  __FUNCTION__);
		return 1;
	}
	/* the title window */
	screen_sizes[2].w  =
	  derwin(stdscr, screen_sizes[2].rows, screen_sizes[2].cols,
	  screen_sizes[2].y, screen_sizes[2].x);
	if (!screen_sizes[2].w) {
		applog(LOG_ERR, "%s::derwin(): unable to create window",
		  __FUNCTION__);
		return 1;
	}
	scrollok(screen_sizes[1].w, TRUE);

	/* initial pad size */
	screen_sizes[0].w = NULL;
	screen_block_info.sx = screen_block_info.sy = 0;
	screen_block_info.mrows = screen_sizes[0].rows;
	screen_block_info.mcols = screen_sizes[0].cols;
	sel = screen_line_info.selected;
	memset(&screen_line_info, 0, sizeof(screen_line_info));
	screen_line_info.selected = sel;

	/* now the main content pad */
	if(screen_create())
		return 1;

	/* setup the args on the pad */
	for (i=0; i< 3; i++) {
		keypad(screen_sizes[i].w, TRUE); /* enable keys */
		idlok(screen_sizes[i].w, TRUE);
		leaveok(screen_sizes[i].w, TRUE);
		intrflush(screen_sizes[i].w, FALSE);
	}

	/* redisplay the log content */
	screen_log_redisplay();

	return 0;
}



/*
 * main screen loop
 */
int screen_run(void)
{
	int ch, ret, i, found;
	int do_resize, do_update;

	/* setup */
	memset(screen_log_lines, 0, 4 * sizeof(cbuf_t));
	memset(&screen_line_info, 0, sizeof(screen_line_info));

	/* check the screen size */
	if (screen_chksize())
		return 1;

	if(screen_make_windows())
		return 1;

	screen_log("%s %s", PACKAGE, VERSION);
	screen_log("keys: h=help, i=inquiry scan, b=brute force scan,"
	  " a=abort scan, s=save summary, o=select sort, enter=select, Q=quit");

	/* main screen loop */
	do_resize = do_update = ret = 0;
	while(bts_run) {
		/* detect the exit condition */
		if (NONE == screens_curr) {
			bts_run = 0;
			break;
		}

		/* resize event */
		if(do_resize) {
			if(screen_chksize()) {
				bts_run = 0;
				break;
			}

			if (screens_curr >= screens_max)
				screens_curr = MAIN;

			if(screen_make_windows()) {
				bts_run = 0;
				break;
			}

			do_update = 1; /* just in case */
			do_resize = 0;
		}

		/* changed windows */
		if (NULL == screen_sizes[0].w) {
			if (screens_curr >= screens_max)
				screens_curr = MAIN;

			if(screen_create()) {
				bts_run = 0;
				break;
			}
			do_update = 1;
		}

		/* does the window content need updating? */
		if (do_update) {
			wnoutrefresh(stdscr);
			screen_redraw();
			do_update = 0;
		}
		doupdate();


		/* key entry */
		ch = getch();
		/* handled by a screen specific? */
		for (found=i=0; -1 != screens_cfg[screens_curr].keymap[i].key; i++) {
			if (ch == screens_cfg[screens_curr].keymap[i].key) {
				screens_cfg[screens_curr].keymap[i].func(ch);
				found=1;
				break;
			}
		}
		/* general handlers */
		if (!found) {
			switch(ch) {
			case KEY_EXIT:
			case 'Q':
				bts_run = 0;
				break;
			case KEY_RESIZE:
				wresize(stdscr, LINES, COLS);
				do_resize = 1;
				break;
			case 'i':
				if (SCAN_NONE == threader_running()) {
					if (0 == threader_start(SCAN_INQ)) {
						screen_log("error: no threads started, check the log");
					} else {
						screen_log("starting inquiry scan");
					}
				} else {
					screen_log("a scan is already running");
				}
				break;
			case 'b':
				if(SCAN_NONE == threader_running() &&
				  0 == screen_init_bf()) {
					if (0 == threader_start(SCAN_BF)) {
						screen_log("error: no threads started, check the log");
					}
				}
				do_update = 1;
				break;
			case 'a':
				if (SCAN_NONE != threader_running()) {
					screen_log("aborting scan");
					threader_stop();
					screen_log("aborted");
				}
				break;
			case 'h':
				screen_log("keys: h=help, i=inquiry scan, b=brute force scan,"
				  " a=abort scan, s=save summary, o=select sort, enter=select,"
				  " Q=quit");
				break;
			case 's':
				do_update = 1;
				{
				cbuf_t cb;
				memset(&cb, 0, sizeof(cbuf_t));
				if (screen_textbox("Enter filename", FN_RE, &cb))
					break;
				if (0 == cb.len) {
					free(cb.buf);
					break;
				}
				ll_save_summary(cb.buf);
				free(cb.buf);
				}
				break;
			case 'o':
				do_update = 1;
				screen_get_sort();
			}
		}
		/* done key setup */

		/* do we need to redraw the widow next time? */
		if (NULL != screens_cfg[screens_curr].changed)
			do_update |= screens_cfg[screens_curr].changed();
	}


	/*
	 * leave and tidy up
	 */

	/* retire any working threads */
	if (SCAN_NONE != threader_running()) {
		screen_log("stopping scan");
		threader_stop();
	}
		
	
	/* cleanup */
	if(screen_sizes[1].w) delwin(screen_sizes[1].w);
	if(screen_sizes[2].w) delwin(screen_sizes[2].w);
	screen_destroy();

	/* wait for any threads to exit */
	return ret;
}
