/*
** $Id: vbox.c,v 1.9 1998/12/31 09:57:27 paul Exp $
**
** Copyright (C) 1996, 1997 Michael 'Ghandi' Herold
*/

#include "config.h"

#if TIME_WITH_SYS_TIME
#   include <sys/time.h>
#   include <time.h>
#else
#   if HAVE_SYS_TIME_H
#      include <sys/time.h>
#   else
#      include <time.h>
#   endif
#endif

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <ncurses.h>
#include <panel.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <signal.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <pwd.h>
#include <errno.h>
#include <getopt.h>

#include "libvbox.h"
#include "vbox.h"
#include "streamio.h"

/** Variables *************************************************************/

static char loginname[VBOX_MAX_USERNAME + 1];
static char loginpass[VBOX_MAX_PASSWORD + 1];

static char *messagesmp = NULL;
static int   messagesnr = 0;
static char *vbasename  = NULL;
static char *vboxdname  = NULL;
static char *playerbin  = NULL;
static int   vboxdport  = -1;
static int   usecolors  = FALSE;
static int   leaveloop  = FALSE;
static int   leavevbox  = FALSE;
static int   monocolor  = FALSE;
static int   ledstatus  = TRUE;
static int   rloadtime  = 0;
static int   messagenr  = 0;
static int   messageyp  = 0;
static int   sndvolume  = 10;
static int   newmessys  = FALSE;
static int   forcepass  = FALSE;

static struct statusled statusleds[] =
{
	{  1, CTRL_NAME_STOP     , 0, "" },
	{  5, CTRL_NAME_REJECT   , 0, "" },
	{  7, CTRL_NAME_ANSWERNOW, 0, "" },
	{  9, CTRL_NAME_ANSWERALL, 0, "" },
	{ 11, CTRL_NAME_AUDIO    , 0, "" },
	{ 13, CTRL_NAME_SUSPEND  , 0, "" },
	{ -1, NULL               , 0, "" }
};

static struct colortable colortable[] =
{
	{ NULL             ,  0, 0           , 0          , A_NORMAL, A_REVERSE        },
	{ "C_BACKGROUND"   ,  1, COLOR_WHITE , COLOR_BLACK, A_NORMAL, A_NORMAL         },
	{ "C_STATUSBAR"    ,  2, COLOR_WHITE , COLOR_BLUE , A_NORMAL, A_REVERSE        },
	{ "C_STATUSBAR_HL" ,  3, COLOR_YELLOW, COLOR_BLUE , A_BOLD  , A_REVERSE|A_BOLD },
   { "C_POWERLED_ON"  ,  4, COLOR_GREEN , COLOR_BLUE , A_NORMAL, A_REVERSE|A_BOLD },
	{ "C_POWERLED_OFF" ,  5, COLOR_RED   , COLOR_BLUE , A_NORMAL, A_REVERSE        },
	{ "C_STATUSLED_ON" ,  6, COLOR_YELLOW, COLOR_BLUE , A_BOLD  , A_REVERSE|A_BOLD },
	{ "C_STATUSLED_OFF",  7, COLOR_BLACK , COLOR_BLUE , A_NORMAL, A_REVERSE        },
   { "C_LIST"         ,  8, COLOR_WHITE , COLOR_BLACK, A_NORMAL, A_NORMAL         },
	{ "C_LIST_SELECTED",  9, COLOR_WHITE , COLOR_RED  , A_NORMAL, A_REVERSE        },
	{ "C_INFOTEXT"     , 10, COLOR_GREEN , COLOR_BLACK, A_NORMAL, A_NORMAL         },
	{ "C_HELP"         , 11, COLOR_WHITE , COLOR_BLUE , A_NORMAL, A_REVERSE        },
   { "C_HELP_BORDER"  , 12, COLOR_YELLOW, COLOR_BLUE , A_BOLD  , A_REVERSE|A_BOLD },
	{ "C_STATUS"       , 13, COLOR_WHITE , COLOR_RED  , A_NORMAL, A_REVERSE        },
	{ "C_STATUS_BORDER", 14, COLOR_YELLOW, COLOR_RED  , A_BOLD  , A_REVERSE|A_BOLD },
	{ "C_INFO"         , 15, COLOR_WHITE , COLOR_YELLOW, A_NORMAL, A_REVERSE        },
   { "C_INFO_BORDER"  , 16, COLOR_YELLOW, COLOR_YELLOW, A_BOLD  , A_REVERSE|A_BOLD },
   { NULL             , -1, 0           , 0          , 0       , 0                }
};

static char *colornames[] =
{
	"BLACK", "RED", "GREEN", "BROWN", "BLUE", "MAGENTA", "CYAN", "GRAY",
	"DARKGRAY", "LIGHTRED", "LIGHTGREEN", "YELLOW", "LIGHTBLUE", "LIGHTMAGENTA",
	"LIGHTCYAN", "WHITE",
	NULL
};

static struct option arguments[] =
{
	{ "version"	   , no_argument		  , NULL, 'v' },
	{ "help"		   , no_argument		  , NULL, 'h' },
	{ "mono"		   , no_argument		  , NULL, 'o' },
	{ "force"	   , no_argument		  , NULL, 'f' },
	{ "noledstatus", no_argument		  , NULL, 's' },
	{ "hostname"   , required_argument , NULL, 'm' },
	{ "reload"     , required_argument , NULL, 'r' },
	{ "port"       , required_argument , NULL, 'p' },
	{ "playcmd"    , required_argument , NULL, 'c' },
	{ NULL		   , 0					  , NULL,  0  }
};

/** Prototypes ************************************************************/

static int    message(char *, char *, ...);
static int    sort_message_list(const void *, const void *);
static int    count_new_messages(void);
static int    delete_message(char *);
static int    get_color_nr(char *);
static int    load_message(int, int);
static int    init_screen(void);
static void   exit_screen(void);
static void   draw_main_screen(void);
static void   draw_status_bar(void);
static void   draw_bottom_bar(void);
static void   draw_ctrl_status(void);
static void   draw_message_list(void);
static void   draw_message_line(int, int, int);
static void   clear_screen(void);
static void   process_input(void);
static void   get_message_list(void);
static void   sig_handling_resize(int);
static void   sig_handling_interrupt(int);
static void   one_line_down(void);
static void   one_line_up(void);
static void   init_locale(void);
static void   play_message(int);
static void   toggle_new_flag(int);
static void   toggle_delete_flag(int);
static void   transfer_message_list(void);
static void   delete_selected_messages(void);
static void   parse_vboxrc(void);
static void   parse_colors(char *, char *);
static void   usage(void);
static void   version(void);
static void   help(void);
static void   status(void);
static void   statuscontrol(int);
static void   set_window_background(WINDOW *, chtype);
static void   messageinfo(int);
static chtype color(int);

/**************************************************************************/
/** The magic main...                                                    **/
/**************************************************************************/

int main(int argc, char **argv)
{
	struct servent *vboxdserv;
	int             dimension;
	char           *pass;
	int             opts;

	if (!(vbasename = rindex(argv[0], '/')))
	{
		vbasename = argv[0];
	}
	else vbasename++;

	init_locale();

	signal(SIGPIPE, SIG_IGN);
	signal(SIGINT , SIG_IGN);

	vboxdname = "localhost";
	vboxdport = -1;
	rloadtime = 60;
	monocolor = FALSE;
	ledstatus = TRUE;
	playerbin = PLAY;
	forcepass = FALSE;

	*loginname = '\0';
	*loginpass = '\0';

	parse_vboxrc();

	while ((opts = getopt_long(argc, argv, "vhofsm:r:p:c:", arguments, (int *)0)) != EOF)
	{
		switch (opts)
		{
			case 'o':
				monocolor = TRUE;
				break;

			case 'f':
				forcepass = TRUE;
				break;

			case 's':
				ledstatus = FALSE;
				break;

			case 'm':
				vboxdname = optarg;
				break;

			case 'p':
				vboxdport = xstrtol(optarg, -1);
				break;

			case 'r':
				rloadtime = xstrtol(optarg, 60);
				break;

			case 'c':
				playerbin = optarg;
				break;

			case 'h':
				usage();
				break;

			case 'v':
				version();
				break;
		}
	}

	/*
	 * Check if login and password are given. If not, we prompt for user
	 * input.
	 */

	if ((!*loginname) || (!*loginpass) || (forcepass))
	{
		printf("\n");
		printf("Username: ");
		fflush(stdout);

		fgets(loginname, VBOX_MAX_USERNAME, stdin);

		loginname[strlen(loginname) - 1] = '\0';

		if ((pass = getpass("Password: ")))
		{
			xstrncpy(loginpass, pass, VBOX_MAX_PASSWORD);

			memset(pass, '\0', strlen(pass));
		}
		else *loginpass = '\0';

		printf("\n");
		fflush(stdout);
	}

	if ((!loginname) || (!*loginname) || (!loginpass) || (!*loginpass))
	{
		fprintf(stderr, "%s: you must enter a login name and a password.\n", vbasename);

		exit(5);
	}

	/*
	 * Connect to the server, login and get the list with all messages
	 * for the user.
	 */

	if (vboxdport == -1)
	{
		if (!(vboxdserv = getservbyname("vboxd", "tcp")))
		{
			fprintf(stderr, "%s: can't get service 'vboxd/tcp' - please read the manual.\n", vbasename);

			exit(5);
		}

		vboxdport = ntohs(vboxdserv->s_port);
	}

	fprintf(stderr, "Connecting to '%s:%d'...\n", vboxdname, vboxdport);

	if (vboxd_connect(vboxdname, vboxdport) != VBOXC_ERR_OK)
	{
		fprintf(stderr, "%s: can't connect to '%s:%d'.\n", vbasename, vboxdname, vboxdport);

		exit(5);
	}

	if (vboxd_login(loginname, loginpass) != VBOXC_ERR_OK)
	{
		memset(loginpass, '\0', VBOX_MAX_PASSWORD);

		fprintf(stderr, "%s: can't login as '%s'.\n", vbasename, loginname);

		vboxd_disconnect();
		exit(5);
	}

	memset(loginpass, '\0', VBOX_MAX_PASSWORD);

	fprintf(stderr, "Transfering message list...\n");

	get_message_list();

	/*
	 * Do a endless loop: initialize ncurses, draw the screen, process user
	 * action & exit ncurses. This loop will only end if the variable leave-
	 * vbox is true.
	 */

	leavevbox = FALSE;
	dimension = TRUE;

	if (init_screen())
	{
		while (!leavevbox)
		{
			if ((LINES >= 24) && (COLS >= 80))
			{
				draw_main_screen();
				process_input();
			}
			else
			{
				dimension = FALSE;
				leavevbox = TRUE;
			}
		}

		exit_screen();
	}
	else fprintf(stderr, "%s: can't initialize screen.\n", vbasename);

	if (!dimension)
	{
		fprintf(stderr, "%s: screen dimensions too small - need 80x24 or greater.\n", vbasename);
	}

	vboxd_disconnect();

	if (messagesmp) free(messagesmp);
	return 0;
}

/**************************************************************************/
/** usage(): Displays usage message.                                     **/
/**************************************************************************/

static void usage(void)
{
	fprintf(stderr, "\n");
	fprintf(stderr, "Usage: %s OPTION [ OPTION ] [ ... ]\n", vbasename);
	fprintf(stderr, "\n");
	fprintf(stderr, "-m, --hostname NAME   Connect to host NAME (localhost).\n");
	fprintf(stderr, "-p, --port PORT       Connect to port PORT (vboxd/tcp).\n");
	fprintf(stderr, "-c, --playcmd PROG    Use PROG to play messages (%s).\n", playerbin);
	fprintf(stderr, "-r, --reload SECS     Reload message list all SECS seconds (%d).\n", rloadtime);
	fprintf(stderr, "-o, --mono            Force mono color (color if terminal have).\n");
	fprintf(stderr, "-f, --force           Force login prompt.\n");
	fprintf(stderr, "-s, --noledstatus     Don't get led status from server (get status).\n");
	fprintf(stderr, "-v, --version         Display program version.\n");
	fprintf(stderr, "-h, --help            Display usage message.\n");
	fprintf(stderr, "\n");

	exit(1);
}

/**************************************************************************/
/** version(): Displays program version.                                 **/
/**************************************************************************/

static void version(void)
{
	fprintf(stderr, "\n");
	fprintf(stderr, "%s version %s (%s)\n", vbasename, VERSION, VERDATE);
	fprintf(stderr, "\n");

	exit(1);
}

/**************************************************************************/
/** process_input(): Main input loop.                                    **/
/**************************************************************************/

static void process_input(void)
{
	int timerreload;
	int timerstatus;
	int c;

	signal(SIGWINCH, sig_handling_resize);
	signal(SIGINT  , sig_handling_interrupt);
	signal(SIGTERM , sig_handling_interrupt);
	signal(SIGHUP  , sig_handling_interrupt);
	signal(SIGQUIT , SIG_IGN);

	timerstatus = 0;
	timerreload = 0;
	messageyp   = 4;
   messagenr   = 0;
	leaveloop   = FALSE;

	noecho();
	cbreak();
	setscrreg(4, LINES - 3);
	scrollok(stdscr, FALSE);
	keypad(stdscr, TRUE);

	draw_message_line(messageyp, messagenr, TRUE);

	while (!leaveloop)
	{
		timeout(1000);
		curs_set(0);
		move((LINES - 2), 0);

		c = wgetch(stdscr);

		if (c != ERR) timerreload = 0;

		switch (c)
		{
			case 27:
				c = wgetch(stdscr);
				break;

			case ERR:
				if (++timerstatus > 30)
				{
					timerstatus = 0;

					draw_ctrl_status();
				}
				if (++timerreload > rloadtime)
				{
					timerreload = 0;

					transfer_message_list();
				}
				break;

			case 'Q':
			case 'q':
				delete_selected_messages();
				leavevbox = TRUE;
				leaveloop = TRUE;
				break;

			case 'R':
			case 'r':
				transfer_message_list();
				break;

			case KEY_DOWN:
				one_line_down();
				break;

			case KEY_UP:
				one_line_up();
				break;

			case '+':
				if (sndvolume < 1000)
				{
					sndvolume++;

					draw_bottom_bar();
				}
				break;

			case '-':
				if (sndvolume > 0)
				{
					sndvolume--;

					draw_bottom_bar();
				}
				break;

			case 'D':
			case 'd':
				toggle_delete_flag(messagenr);
				one_line_down();
				break;

			case 'T':
			case 't':
			case 'N':
			case 'n':
				toggle_new_flag(messagenr);
				draw_bottom_bar();
				one_line_down();
				break;

			case '\r':
			case '\n':
				play_message(messagenr);
				draw_message_line(messageyp, messagenr, TRUE);
				draw_bottom_bar();
				break;

			case 'H':
			case 'h':
				help();
				break;

			case 'S':
			case 's':
				status();
				break;

			case 'I':
			case 'i':
				messageinfo(messagenr);
				break;

			default:
				beep();
				break;
		}
	}
}

/**************************************************************************/
/** status(): Displays status window.                                    **/
/**************************************************************************/

static void status(void)
{
	WINDOW *win;
	PANEL  *pan;
	int     c;
	int     noop;
	int     done;
	int     max;
   int     led;
	int     w;
	int     h;
	char   *b;

	if (ledstatus)
	{
		led = 0;

		while (statusleds[led].name) led++;

		h = (4 + led);
		w = 48;
		b = " Press 'Q' to quit ";

		if ((win = newwin(h, w, 1, 0)))
		{
			if ((pan = new_panel(win)))
			{
				set_window_background(win, color(13));
				wattrset(win, color(14));
				box(win, ACS_VLINE, ACS_HLINE);

				mvwprintw(win, (h - 1), ((w - strlen(b)) / 2), "%s", b);

				wattrset(win, color(13));

				max = 0;
				led = 0;

				while (statusleds[led].name)
				{
					max = led;

					mvwprintw(win, 0, statusleds[led].x, "%d", led);
					mvwprintw(win, (2 + led), 3, "%d) %s '%s' control...", led, (statusleds[led].status ? "Remove" : "Create"), statusleds[led].name);

					led++;
				}

				wmove(win, (h - 2), (w - 2));
				update_panels();
				wrefresh(win);

				done = FALSE;
				noop = 0;

				cbreak();
				noecho();
				wtimeout(win, 1000);

				while (!done)
				{
					switch ((c = wgetch(win)))
					{
						case ERR:
							if (++noop > 30)
							{
								vboxd_put_message("NOOP");

								noop = 0;
							}
							break;

						case '0':
						case '1':
						case '2':
						case '3':
						case '4':
						case '5':
						case '6':
						case '7':
						case '8':
						case '9':
							c -= 48;

							if ((c >= 0) && (c <= max))
							{
								statuscontrol(c);

								done = TRUE;
							}

							break;

						case 'Q':
						case 'q':
							done = TRUE;
							break;
					}
				}

				del_panel(pan);
			}

			delwin(win);
		}
	}
	else message("\r\n", "You must enable 'ledstatus' first!%s", " [RETURN]");
}

/**************************************************************************/
/** statuscontrol(): Removes or creates a control file.                  **/
/**************************************************************************/

static void statuscontrol(int nr)
{
	char *resp;
	char *todo;
	char *answer;
	int   status;

	status = statusleds[nr].status;

	message("", "%s '%s'...", (status ? "Removing" : "Creating"), statusleds[nr].name);

	if (status)
	{
		todo = "removectrl";
		resp = VBOXD_VAL_REMOVECTRLOK;
	}
	else
	{
		todo = "createctrl";
		resp = VBOXD_VAL_CREATECTRLOK;
	}

	vboxd_put_message("%s %s", todo, statusleds[nr].name);

	if ((answer = vboxd_get_message()))
	{
		if (vboxd_test_response(resp))
		{
			if (answer[4] != '1') message("\r\n", "Can't %s '%s'! [RETURN]", (status ? "remove" : "create"), statusleds[nr].name);

			draw_ctrl_status();
		}
	}

	draw_bottom_bar();
}

/**************************************************************************/
/** help(): Displays the help window.                                    **/
/**************************************************************************/

static void help(void)
{
	WINDOW *win;
	PANEL  *pan;
	int     h;
   int     w;
	char   *t;
	char   *b;
	int     n;
	int     done;

   w = 37;
   h = 12;
	n = 0;
	t = " HELP ";
   b = " Press 'Q' to quit ";

	if ((win = newwin(h, w, ((LINES - h) / 2), ((COLS - w) / 2))))
	{
		if ((pan = new_panel(win)))
		{
			set_window_background(win, color(11));
			wattrset(win, color(12));
			box(win, ACS_VLINE, ACS_HLINE);

			mvwprintw(win,       0, ((w - strlen(t)) / 2), "%s", t);
			mvwprintw(win, (h - 1), ((w - strlen(b)) / 2), "%s", b);

			wattrset(win, color(11));

			mvwprintw(win,  2, 3, "RETURN             Play message");
			mvwprintw(win,  3, 3, "R           Reload message list");
			mvwprintw(win,  4, 3, "N            Toggle read/unread");
			mvwprintw(win,  5, 3, "D        Toggle delete/undelete");
			mvwprintw(win,  6, 3, "S                Status control");
			mvwprintw(win,  7, 3, "I           Message information");
			mvwprintw(win,  8, 3, "+/-                  Volume +/-");
			mvwprintw(win,  9, 3, "Q                     Quit vbox");

			update_panels();
			wrefresh(win);
			move((LINES - 2), 0);

			cbreak();
			noecho();
			wtimeout(win, 1000);

			done = FALSE;

			while (!done)
			{
				switch (wgetch(win))
				{
					case ERR:
						if (++n > 30)
						{
							vboxd_put_message("NOOP");

							n = 0;
						}
						break;

					case 'Q':
					case 'q':
						done = TRUE;
						break;
				}
			}

			del_panel(pan);
		}

		delwin(win);
	}
}

/**************************************************************************/
/** parse_vboxrc(): Reads and parse ~/.vboxrc.                           **/
/**************************************************************************/

void parse_vboxrc(void)
{
	struct passwd *userpw;
	streamio_t    *vboxrc;
	char           rcname[PATH_MAX + 1];
	char           rcline[128 + 1];
	char          *cmd;
	char          *arg;

	if ((userpw = getpwuid(getuid())))
	{
		xstrncpy(rcname, userpw->pw_dir, PATH_MAX);
		xstrncat(rcname, "/.vboxrc"    , PATH_MAX);

		if ((vboxrc = streamio_open(rcname)))
		{
			while (streamio_gets(rcline, 128, vboxrc))
			{
				cmd = strtok(rcline, "\t ");
				arg = strtok(NULL  , "\t ");

				if ((cmd) && (arg))
				{
					if (strncasecmp(cmd, "C_", 2) == 0)
					{
						parse_colors(cmd, arg);
					}
					else
					{
						if (strcasecmp(cmd, "USERNAME") == 0)
						{
							xstrncpy(loginname, arg, VBOX_MAX_USERNAME);

							continue;
						}

						if (strcasecmp(cmd, "PASSWORD") == 0)
						{
							xstrncpy(loginpass, arg, VBOX_MAX_PASSWORD);

							continue;
						}

						if (strcasecmp(cmd, "VOLUME") == 0)
						{
							sndvolume = atoi(arg);

							if (sndvolume < 0  ) sndvolume = 0;
							if (sndvolume > 999) sndvolume = 999;
						}
					}
				}
			}

			streamio_close(vboxrc);
		}
	}
}

/**************************************************************************/
/** parse_colors(): Parse a colorname.                                   **/
/**************************************************************************/

static void parse_colors(char *cmd, char *arg)
{
	char *ctext;
	char *cback;
	int   i;

	ctext = arg;

	if ((cback = index(ctext, ':'))) *cback++ = '\0';

	i = 1;

	while (colortable[i].pair > 0)
	{
		if (strcasecmp(colortable[i].name, cmd) == 0)
		{
			colortable[i].fg = get_color_nr(ctext);
			colortable[i].bg = get_color_nr(cback);

			colortable[i].cattr = (colortable[i].fg > 7 ? A_BOLD : A_NORMAL);

			while (colortable[i].bg > 7) colortable[i].bg -= 8;
			while (colortable[i].fg > 7) colortable[i].fg -= 8;

			break;
		}

		i++;
	}
}

/**************************************************************************/
/** get_color_nr(): Returns the color number of a color in colortable.   **/
/**************************************************************************/

static int get_color_nr(char *cname)
{
	int i;

	if ((cname) && (*cname))
	{
		for (i = 0; i < 16; i++)
		{
			if (strcasecmp(colornames[i], cname) == 0) return(i);
		}
	}

	return(0);
}

/**************************************************************************/
/** delete_selected_messages(): Deletes all selected messages.           **/
/**************************************************************************/

static void delete_selected_messages(void)
{
	struct messageline *msgline;
	int                 i;
	int                 s;

	if ((messagesmp) && (messagesnr > 0))
	{
		for (s = 0, i = 0; i < messagesnr; i++)
		{
			msgline = (struct messageline *)(messagesmp + (sizeof(struct messageline) * i));

			if (msgline->delete) s++;
		}

		if (s > 0)
		{
			i = message("yn", "Really delete all marked messages (y/n)? ");

			if (i == 'y')
			{
				for (i = 0; i < messagesnr; i++)
				{
					msgline = (struct messageline *)(messagesmp + (sizeof(struct messageline) * i));

					if (msgline->delete)
					{
						message("", "Removing message #%d...", i);

						if (!delete_message(msgline->messagename)) break;
					}
				}
			}
		}
	}
}

/**************************************************************************/
/** delete_message(): Deletes one message.                               **/
/**************************************************************************/

static int delete_message(char *name)
{
	vboxd_put_message("delete %s", name);

	if (vboxd_get_message())
	{
		if (vboxd_test_response(VBOXD_VAL_DELETEOK)) returnok();

		message("\r\n", "Can't delete; invalid server response! %s", "[RETURN]");
	}
	else message("\r\n", "Can't delete; no server response! %s", "[RETURN]");

	returnerror();
}

/**************************************************************************/
/** toggle_new_flag(): Toggles the message new flag.                     **/
/**************************************************************************/

static void toggle_new_flag(int nr)
{
	struct messageline *msgline;
	char               *answer;

	if ((messagesmp) && (messagesnr > 0))
	{
		message("", "Toggle message new flag...");

		msgline = (struct messageline *)(messagesmp + (sizeof(struct messageline) * nr));

		vboxd_put_message("toggle %s", msgline->messagename);

		if ((answer = vboxd_get_message()))
		{
			if (vboxd_test_response(VBOXD_VAL_TOGGLE))
			{
				msgline->mtime = xstrtoul(&answer[4], 0);
				msgline->new   = msgline->mtime > 0 ? TRUE : FALSE;

				draw_message_line(messageyp, messagenr, TRUE);
			}
			else message("\r\n", "Can't toggle; invalid server response! %s", "[RETURN]");
		}
		else message("\r\n", "Can't toggle; no answer from server! %s", "[RETURN]");
	}
}

/**************************************************************************/
/** toggle_delete_flag(): Toggles the message delete flag.               **/
/**************************************************************************/

static void toggle_delete_flag(int nr)
{
	struct messageline *msgline;

	if ((messagesmp) && (messagesnr > 0))
	{
		msgline = (struct messageline *)(messagesmp + (sizeof(struct messageline) * nr));

		msgline->delete = msgline->delete ? FALSE : TRUE;

		draw_message_line(messageyp, messagenr, TRUE);
	}
}

/**************************************************************************/
/** transfer_message_list(): Transfers the message list.                 **/
/**************************************************************************/

static void transfer_message_list(void)
{
	message("", "Transfering message list...");

	get_message_list();

	if (newmessys)
	{
		draw_message_list();
		messagenr = 0;
		messageyp = 4;
		draw_message_line(messageyp, messagenr, TRUE);
	}

	draw_bottom_bar();
}

/**************************************************************************/
/** color(): Returns the attributes for a given color.                   **/
/**************************************************************************/

static chtype color(int c)
{
	if ((usecolors) && (!monocolor))
	{
		return(COLOR_PAIR(colortable[c].pair) | colortable[c].cattr);
	}
	else return(COLOR_PAIR(0) | colortable[c].mattr);
}

/**************************************************************************/
/** draw_main_screen(): Draw the hole main screen.                       **/
/**************************************************************************/

static void draw_main_screen(void)
{
	clear_screen();
	draw_bottom_bar();
	draw_status_bar();
	draw_message_list();
}

/**************************************************************************/
/** clear_screen(): Clears screen and set background.                    **/
/**************************************************************************/

static void clear_screen(void)
{
	set_window_background(stdscr, COLTAB(1));
}

/**************************************************************************/
/** init_screen(): Initialize the ncurses screen and the colors.         **/
/**************************************************************************/

static int init_screen(void)
{
	int i;

	if (initscr())
	{
		if (start_color() != ERR)
		{
			if ((has_colors()) && (!monocolor))
			{
				if ((COLORS >= 8) && (COLOR_PAIRS >= 64))
				{
					i = 1;

					while (colortable[i].pair > 0)
					{
						init_pair(colortable[i].pair, colortable[i].fg, colortable[i].bg);

						i++;
					}

					usecolors = TRUE;
				}
			}
		}

		returnok();
	}

	returnerror();
}

/**************************************************************************/
/** exit_screen(): Exit ncurses screen management.                       **/
/**************************************************************************/

static void exit_screen(void)
{
	endwin();
	printf("%c%c%c\n", 27, 'c', 12);
	fflush(stdout);
}

/**************************************************************************/
/** sig_handling_resize(): Signal handler for screen resizing.           **/
/**************************************************************************/

static void sig_handling_resize(int s)
{
#ifdef HAVE_RESIZETERM

	struct winsize win;
	int            newsizec;
	int            newsizel;

	newsizel = LINES;
	newsizec = COLS;

   if (ioctl(0, TIOCGWINSZ, &win) == 0)
	{
      if (win.ws_row != 0) newsizel = win.ws_row;
      if (win.ws_col != 0) newsizec = win.ws_col;
   }

	if (resizeterm(newsizel, newsizec) == ERR) leavevbox = TRUE;

#endif

	leaveloop = TRUE;
}

/**************************************************************************/
/** sig_handling_interrupt(): Signal handler for ctrl-c or other breaks. **/
/**************************************************************************/

static void sig_handling_interrupt(int s)
{
	leaveloop = TRUE;
	leavevbox = TRUE;

	/*
	 * We *not* re-set the interrupt handler, so a 2nd control-c will
	 * break the program!
	 */
}

/**************************************************************************/
/** get_message_list(): Get the list with all messages. The list replace **/
/**                     any old messages. If the function can't get the  **/
/**                     new list, the old one is not touched.            **/
/**************************************************************************/

static void get_message_list(void)
{
	struct messageline *mrgline;
	struct messageline *msgline;
	char               *newline;
	char               *tmpmessagesmp;
	char               *newmessagesmp;
	int                 newmessagesnr;
	int                 o;
	int                 n;

	newmessagesmp = NULL;
	newmessagesnr = 0;
	newmessys     = FALSE;

	vboxd_put_message("list");

	while ((newline = vboxd_get_message()))
	{
		if ((!vboxd_test_response(VBOXD_VAL_LIST)) || (strlen(newline) < 5))
		{
			if (newmessagesmp) free(newmessagesmp);

			return;
		}

		if (newline[4] == '.') break;

		if (newline[4] == '+')
		{
			newmessagesnr++;

			if ((tmpmessagesmp = realloc(newmessagesmp, (sizeof(struct messageline) * newmessagesnr))))
			{
				newmessagesmp = tmpmessagesmp;

				msgline = (struct messageline *)(newmessagesmp + (sizeof(struct messageline) * (newmessagesnr - 1)));

				msgline->ctime        = 0;
				msgline->mtime        = 0;
				msgline->compression  = 0;
				msgline->size         = 0;
				msgline->new          = FALSE;
				msgline->delete       = FALSE;

				strcpy(msgline->messagename, "");
				strcpy(msgline->name       , "");
				strcpy(msgline->callerid   , "");
				strcpy(msgline->phone      , "");
				strcpy(msgline->location   , "");
			}
			else newmessagesnr--;

			continue;
		}

		if (newmessagesnr > 0)
		{
			msgline = (struct messageline *)(newmessagesmp + (sizeof(struct messageline) * (newmessagesnr - 1)));

			switch (newline[4])
			{
				case 'F':
					xstrncpy(msgline->messagename, &newline[6], NAME_MAX);
					break;

				case 'N':
					xstrncpy(msgline->name, &newline[6], VAH_MAX_NAME);
					break;

				case 'I':
					xstrncpy(msgline->callerid, &newline[6], VAH_MAX_CALLERID);
					break;

				case 'P':
					xstrncpy(msgline->phone, &newline[6], VAH_MAX_PHONE);
					break;

				case 'L':
					xstrncpy(msgline->location, &newline[6], VAH_MAX_LOCATION);
					break;

				case 'T':
					msgline->ctime = (time_t)xstrtoul(&newline[6], 0);
					break;

				case 'M':
					msgline->mtime = (time_t)xstrtoul(&newline[6], 0);
					msgline->new = msgline->mtime > 0 ? TRUE : FALSE;
					break;

				case 'C':
					msgline->compression = (int)xstrtol(&newline[6], 6);
					break;

				case 'S':
					msgline->size = (int)xstrtol(&newline[6], 0);
					break;
			}
		}
	}

	if (newmessagesnr > 0)
	{
		if (messagesmp)
		{
			if (messagesnr > 0)
			{
				/*
				 * Try to merge the old and the new message list, so no status
				 * flags are lost.
				 */

				for (n = 0; n < newmessagesnr; n++)
				{
					msgline = (struct messageline *)(newmessagesmp + (sizeof(struct messageline) * n));

					for (o = 0; o < messagesnr; o++)
					{
						mrgline = (struct messageline *)(messagesmp + (sizeof(struct messageline) * o));

						if (strcmp(mrgline->messagename, msgline->messagename) == 0)
						{
							msgline->delete = mrgline->delete;
						}
					}
				}
			}

			free(messagesmp);
		}

		if (newmessagesnr != messagesnr) newmessys = TRUE;

		messagesmp = newmessagesmp;
		messagesnr = newmessagesnr;

		qsort(messagesmp, messagesnr, sizeof(struct messageline), sort_message_list);
	}
}

/**************************************************************************/
/** sort_message_list(): Sorts the message list.                         **/
/**************************************************************************/

static int sort_message_list(const void *a, const void *b)
{
	struct messageline *linea = (struct messageline *)a;
	struct messageline *lineb = (struct messageline *)b;

	if (lineb->ctime == linea->ctime) return( 0);
	if (lineb->ctime  < linea->ctime) return(-1);

	return(1);
}

/**************************************************************************/
/** init_locale(): Starts and initialize the locale functions.           **/
/**************************************************************************/

static void init_locale(void)
{
}

/**************************************************************************/
/** one_line_down(): Moves cursor in message list one line down.         **/
/**************************************************************************/

static void one_line_down(void)
{
	if ((messagesnr > 0) && (messagesmp) && (messagenr < (messagesnr - 1)))
	{
		draw_message_line(messageyp, messagenr, FALSE);

		messagenr++;

		if (messageyp >= (LINES - 3))
		{
			wrefresh(stdscr);
			scrollok(stdscr, TRUE);
			wscrl(stdscr, 1);
			scrollok(stdscr, FALSE);
		}
		else messageyp++;

		draw_message_line(messageyp, messagenr, TRUE);
	}
	else beep();
}

/**************************************************************************/
/** one_line_up(): Moves cursor in message list one line up.             **/
/**************************************************************************/

static void one_line_up(void)
{
	if ((messagesnr > 0) && (messagesmp) && (messagenr > 0))
	{
		draw_message_line(messageyp, messagenr, FALSE);

		messagenr--;

		if (messageyp <= 4)
		{
			wrefresh(stdscr);
			scrollok(stdscr, TRUE);
			wscrl(stdscr, -1);
			scrollok(stdscr, FALSE);
		}
		else messageyp--;

		draw_message_line(messageyp, messagenr, TRUE);
	}
	else beep();
}

/**************************************************************************/
/** draw_message_line(): Draws one message line.                         **/
/**************************************************************************/

static void draw_message_line(int y, int nr, int selected)
{
	struct tm          *msgtime;
	char                strtime[32];
	struct messageline *msgline;
	chtype              msgattr;
	int                 secs;
	int                 mins;

	if ((messagesmp) && (messagesnr >= nr))
	{
		msgline = (struct messageline *)(messagesmp + (sizeof(struct messageline) * nr));
		msgattr = (selected ? COLTAB(9) : COLTAB(8));

		attrset(msgattr);
		mvhline(y, 0, ' ', COLS);

		mvprintw(y, 1, "%s", msgline->mtime > 0 ? "+" : "");
		mvprintw(y, 1, "%s", msgline->delete ? "-" : "");
		
		strcpy(strtime, "??-???-???? ??:??:??");

		if ((msgtime = localtime(&msgline->ctime)))
		{
			strftime(strtime, 30, "%d-%b-%Y %H:%M:%S", msgtime);
		}

		secs = get_message_ptime(msgline->compression, msgline->size);
		mins = (secs / 60);
		secs = (secs - (mins * 60));

		mvprintw(y, 3, "%s", strtime);
		mvprintw(y, 25, "%02d:%02d", mins, secs), 
		mvprintw(y, 32, "%s", msgline->name);

		if ((strcmp(msgline->phone, "*** Unknown ***")) != 0 && (strcmp(msgline->phone, "<not supported>") != 0) && (strcmp(msgline->phone, "") != 0))
		{
			mvprintw(y, COLS - 3 - strlen(msgline->phone), "%s P", msgline->phone);
		}
		else mvprintw(y, COLS - 3 - strlen(msgline->callerid), "%s I", msgline->callerid);

		move(LINES - 2, 0);

		if (selected) wrefresh(stdscr);
	}
}

/**************************************************************************/
/** draw_ctrl_status(): Gets and draws the control status leds.          **/
/**************************************************************************/

static void draw_ctrl_status(void)
{
	char   *answer;
	int     i;
	int     x;
	chtype  statuschar;
	chtype  statusattr;

	i = 0;
	x = 0;

	while (statusleds[i].name)
	{
		statuschar = '-';
		statusattr = COLTAB(7);

		if (ledstatus)
		{
			if (i == 0) message("", "Getting control status...");

			vboxd_put_message("statusctrl %s", statusleds[i].name);

			statusleds[i].status = TRUE;

			if ((answer = vboxd_get_message()))
			{
				if (vboxd_test_response(VBOXD_VAL_STATUSCTRLOK))
				{
					statuschar = ACS_DIAMOND;

					if (answer[4] == '1')
					{
						statusleds[i].status = TRUE;
						statusattr = i ? COLTAB(6) : COLTAB(5);
					}
					else
					{
						statusleds[i].status = FALSE;
						statusattr = i ? COLTAB(7) : COLTAB(4);
					}
				}
			}
		}

		attrset(statusattr);
		mvaddch(0, statusleds[i].x, statuschar);

		x = statusleds[i].x;

		i++;
	}

	draw_bottom_bar();
}

/**************************************************************************/
/** draw_status_bar(): Draws the top status bar. This function also get  **/
/**                    the control status information.                   **/
/**************************************************************************/

static void draw_status_bar(void)
{
	char *helpmsg = "HELP";
	int   i;
   int   x;

	attrset(COLTAB(2));

	mvhline(0, 0, ' ', COLS);
	mvvline(0, 3, ACS_VLINE, 1);
	mvvline(0, (COLS - strlen(helpmsg) - 7), ACS_VLINE, 1);

	wattrset(stdscr, COLTAB(2));
	mvprintw(0, COLS - strlen(helpmsg) - 5, "( ) %s", helpmsg);
	wattrset(stdscr, COLTAB(3));
	mvprintw(0, COLS - strlen(helpmsg) - 4, "H");

	i = 0;
	x = 0;

	while (statusleds[i].name)
	{
		x = statusleds[i].x;

		i++;
	}

	attrset(COLTAB(2));
	mvaddch(0, (x + 2), ACS_VLINE);
	printw(" %s %s", PACKAGE, VERSION);

	draw_ctrl_status();
}

/**************************************************************************/
/** draw_message_list(): Draws the message list.                         **/
/**************************************************************************/

static void draw_message_list(void)
{
	char phone[30];
	int m;
	int y;

	printstring(phone, "%28.28s", "Number");

	wattrset(stdscr, COLTAB(10));
	mvprintw(2, 1, "* %-20.20s", "Incoming date");
	mvprintw(2, 25, "%5.5s", "Len");
	mvprintw(2, 32, "%-28.28s", "Name");
	mvprintw(2, COLS - 1 - strlen(phone), "%s", phone);

	mvhline(3, 1, ACS_HLINE, (COLS - 2));

	y = 4;
	m = 0;

	while (m < messagesnr)
	{
		draw_message_line(y, m, FALSE);

		y++;
		m++;

		if (y >= (LINES - 2)) break;
	}
}

/**************************************************************************/
/** play_message(): Play the selected message.                           **/
/**************************************************************************/

static void play_message(int msg)
{
	struct messageline *msgline;
	char               *msgname;
	char               *command;
	char               *answer;
	int                 size;
	int                 have;
	int                 fd;

	if ((!messagesmp) || (messagesnr < 1)) return;

	msgline = (struct messageline *)(messagesmp + (sizeof(struct messageline) * msg));
	msgname = tmpnam(NULL);

	if ((!msgline) || (!msgname))
	{
		message("\r\n", "Can't create temporary file! %s", "[RETURN]");

		return;
	}

	if ((fd = open(msgname, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR)) == -1)
	{
		message("\r\n", "Can't open temporary file! %s", "[RETURN]");

		return;
	}

	message("", "Searching message...");

	vboxd_put_message("message %s", msgline->messagename);

	if ((answer = vboxd_get_message()))
	{
		if (vboxd_test_response(VBOXD_VAL_MESSAGE))
		{
			if ((size = (int)xstrtol(&answer[4], 0)) > 0)
			{
				message("", "Transfering message (%d bytes)...", size);

				if ((have = load_message(fd, size)) == size)
				{
					message("", "Searching end sequence...");

					if ((answer = vboxd_get_message()))
					{
						if (!vboxd_test_response(VBOXD_VAL_MESSAGE))
						{
							message("\r\n", "Can't get end sequence (bad response)! %s", "[RETURN]");
						}
					}
					else message("\r\n", "Can't get end sequence! %s", "[RETURN]");

					close_and_mone(fd);

					size = 100 + strlen(msgname) + strlen(playerbin);

					if ((command = malloc(size)))
					{
						message("", "Playing message...");

						printstring(command, "%s %s %d 1>/dev/null 2>/dev/null", playerbin, msgname, sndvolume);
						system(command);
						free(command);

						if (msgline->new) toggle_new_flag(msg);
					}
				}
				else message("\n\r", "Can only get %d of %d bytes! %s", have, size, "[RETURN]");
			}
			else message("\r\n", "Message size is zero! %s", "[RETURN]");
		}
		else message("\r\n", "Can't get init sequence (bad response)! %s", "[RETURN]");
	}
	else message("\r\n", "Can't get init sequence! %s", "[RETURN]");

	if (fd != -1) close(fd);

	unlink(msgname);
}

/**************************************************************************/
/** load_message(): Loads the message from server.                       **/
/**************************************************************************/

static int load_message(int fd, int size)
{
   struct timeval timeval;
   fd_set         rmask;

	unsigned char temp[256];
	int  have;
	int  take;
   int  rc;

	have = 0;

	while (have < size)
	{
      VBOX_ONE_FD_MASK(&rmask, vboxd_r_fd);

      timeval.tv_sec  = VBOXD_GET_MSG_TIMEOUT;
      timeval.tv_usec = 0;

      rc = select((vboxd_r_fd + 1), &rmask, NULL, NULL, &timeval);

		if (rc <= 0)
		{
			if ((rc < 0) && (errno == EINTR)) continue;

			break;
		}

		if (!FD_ISSET(vboxd_r_fd, &rmask)) break;

		if ((take = (size - have)) > 255) take = 255;

		rc = read(vboxd_r_fd, temp, take);

		if (rc <= 0)
		{
			if ((rc < 0) && (errno == EINTR)) continue;

			break;
		}

		have += rc;

		write(fd, temp, rc);
	}

	return(have);
}

/**************************************************************************/
/** message(): Prints a message into the bottom bar. The function can    **/
/**            also wait for a special keypress. While waiting, a NOOP   **/
/**            message is send all 10 seconds.                           **/
/**************************************************************************/

static int message(char *keys, char *fmt, ...)
{
	char    message[256];
	va_list arg;
	int     k;
	int     timernoop;
	int     returnkey;

	va_start(arg, fmt);
	vnprintstring(message, 255, fmt, arg);
	va_end(arg);

	attrset(COLTAB(2));
	mvhline(LINES - 1, 0, ' ', COLS);
	mvprintw(LINES - 1, 1, "%s", message);
	wrefresh(stdscr);

	returnkey = -1;

	if ((keys) && (*keys))
	{
		timeout(1000);
		noecho();
		cbreak();
		beep();
		refresh();

		timernoop = 0;

		while (returnkey == -1)
		{
			k = wgetch(stdscr);

			if (k == ERR)
			{
				if (timernoop++ > 10)
				{
					vboxd_put_message("noop");

					timernoop = 0;
				}

				continue;
			}

			if (index(keys, k)) returnkey = k;
		}

		draw_bottom_bar();
	}

	return(returnkey);
}

/**************************************************************************/
/** draw_bottom_bar(): Draws the bottom bar.                             **/
/**************************************************************************/

static void draw_bottom_bar(void)
{
	struct passwd *pass;
	char           temp[32];
	char           svol[16];

   attrset(COLTAB(2));
   mvhline(LINES - 1, 0, ' ', COLS);

	printstring(temp, "%d/%d", messagesnr, count_new_messages());
	printstring(svol, "%d", sndvolume);

	mvhline(LINES - 1, COLS - strlen(temp) - 3, ACS_VLINE, 1);
	mvhline(LINES - 1, COLS - strlen(temp) - strlen(svol) - 6, ACS_VLINE, 1);

	mvprintw(LINES - 1, COLS - strlen(temp) - 1, "%s", temp);
	mvprintw(LINES - 1, COLS - strlen(temp) - strlen(svol) - 4, "%s", svol);

	mvprintw(LINES - 1, 1, "%s", loginname);

	if ((pass = getpwuid(getuid())))
	{
		mvprintw(LINES - 1, 2 + strlen(loginname), "[%s]", pass->pw_name);
	}
}

/**************************************************************************/
/** count_new_messages(): Counts new messages in the message list.       **/
/**************************************************************************/

static int count_new_messages(void)
{
	struct messageline *msgline;
	int                 newmsgs;
	int                 i;

	newmsgs = 0;

	if ((messagesmp) && (messagesnr > 0))
	{
		for (i = 0; i < messagesnr; i++)
		{
			msgline = (struct messageline *)(messagesmp + (sizeof(struct messageline) * i));

			if (msgline->new) newmsgs++;
		}
	}

	return(newmsgs);
}

/**************************************************************************/
/** set_window_background(): Clears a windows.                           **/
/**************************************************************************/

static void set_window_background(WINDOW *win, chtype col)
{
	int y;

	wattrset(win, col);

	for (y = 0; y < (win->_maxy + 1); y++)
	{
		mvwhline(win, y, 0, ' ', (win->_maxx + 1));
	}
}

static void messageinfo(int nr)
{
	struct messageline *msgline;
	WINDOW             *win;
	PANEL              *pan;
	int                 h;
   int                 w;
	char               *t;
	char               *b;
	int                 n;
	int                 done;

	if ((!messagesmp) || (messagesnr < nr)) return;

	msgline = (struct messageline *)(messagesmp + (sizeof(struct messageline) * nr));

   w = 51;
   h = 15;
	n = 0;
	t = " INFO ";
   b = " Press 'Q' to quit ";

	if ((win = newwin(h, w, ((LINES - h) / 2), ((COLS - w) / 2))))
	{
		if ((pan = new_panel(win)))
		{
			set_window_background(win, color(15));
			wattrset(win, color(16));
			box(win, ACS_VLINE, ACS_HLINE);

			mvwprintw(win,       0, ((w - strlen(t)) / 2), "%s", t);
			mvwprintw(win, (h - 1), ((w - strlen(b)) / 2), "%s", b);

			wattrset(win, color(15));

			mvwprintw(win,  2, 3, "Filename   : %-32.32s", msgline->messagename);
			mvwprintw(win,  3, 3, "CTime      : %ld", msgline->ctime);
			mvwprintw(win,  4, 3, "MTime      : %ld", msgline->mtime);
			mvwprintw(win,  5, 3, "Compression: %d", msgline->compression);
			mvwprintw(win,  6, 3, "Size       : %d", msgline->size);
			mvwprintw(win,  7, 3, "Name       : %-32.32s", msgline->name);
			mvwprintw(win,  8, 3, "CallerID   : %-32.32s", msgline->callerid);
			mvwprintw(win,  9, 3, "Phone      : %-32.32s", msgline->phone);
			mvwprintw(win, 10, 3, "Location   : %-32.32s", msgline->location);
			mvwprintw(win, 11, 3, "Flag new   : %s", (msgline->new ? "Yes" : "No"));
			mvwprintw(win, 12, 3, "Flag delete: %s", (msgline->delete ? "Yes" : "No"));

			update_panels();
			wrefresh(win);
			wmove(win, (h - 2), (w - 2));

			cbreak();
			noecho();
			wtimeout(win, 1000);

			done = FALSE;

			while (!done)
			{
				switch (wgetch(win))
				{
					case ERR:
						if (++n > 30)
						{
							vboxd_put_message("NOOP");

							n = 0;
						}
						break;

					case 'Q':
					case 'q':
						done = TRUE;
						break;
				}
			}

			del_panel(pan);
		}

		delwin(win);
	}
}
