#define RCS_ID $Id: pbctrl.c,v 1.5 2001/03/04 17:10:32 oz6bl Exp $
/*
 *   Copyright 2000 Bent Bagger (OZ6BL)
 *              All Rights Reserved
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *   
 */

#include <stdio.h>
#include <curses.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>

#include "fifo.h"

#define LINELENGTH 1024 
#define TAGSIZE 1024 

/* Screen control pointers */

WINDOW *scr_download, *scr_files, *scr_log, *scr_status;

/* Prototypes */

void FromServer (PIPE *) ;
void InitializeDisplay (void) ;
int  InitValues (PIPE *, int) ;
int  TerminateDisplay (PIPE *, int) ;
int  FilesConsumer (PIPE *, int) ;
int  LogConsumer (PIPE *, int) ;
int  DownloadConsumer (PIPE *, int) ;
int  StatusConsumer (PIPE *, int) ;
char SatName1 [100] ;
char MyCall [100] ;
int  MyCallLength ;

/* FIFO variables and prototypes */

int monitorfd, controlfd, stopFlag ; 
int count ;
FILE *in;
FILE *controlfifo;
PIPE *monitor_pipe;
PIPE *monitor_list = 0;
char control[] = "/etc/pbpg/pb-fifo";
char *monitor;

void init_fifo( void ) ;
int read_fifo(PIPE *) ;
void close_fifo( void ) ;
void handlesig(int) ;
void parse_keyboard( void ) ;
void update_screen( void ) ;

/* Debug variables */
int debugmask ;
#define DEBUG_FROM_KEYB 0x0001
#define DEBUG_TO_FIFO   0x0010
#define DEBUG_FROM_FIFO 0x0100

char logFileName [] = "pbctrl.log" ; /* Name of log file for PBctrl */

void InitializeDisplay (void) {
	int n;
	/* Initialize the curses screen management */
	initscr ();
	cbreak ();		/* Set the keyboard in raw mode */
	noecho ();
	/* set colors */
	start_color();
	init_pair(1, COLOR_WHITE, COLOR_BLUE);
	init_pair(2, COLOR_YELLOW, COLOR_BLUE);

	/* define windows:
	   --> x        (y,x)
		-------------------------------------------------------
	|	| 0,0                || 0,25                          |
	|	|                    ||                               |
	Y	|     download       ||             files             |
	y	|                    ||            (scroll)           |
		|                    ||                               |
		|             (10,23)||                        (10,53)|
		|-----------------------------------------------------|
		|-----------------------------------------------------|
		| 12,0                                                |
		|                                                     |
		|                                                     |
		|                       log                           |
		|                     (scroll)                        |
		|                                                     |
		|                                              (11,79)|
		|-----------------------------------------------------|
		| 24,0               status                     (0,79)|
		-------------------------------------------------------  */

 
	if (COLS < 80) {
		fprintf(stderr, "Window must have at least 80 columns.\n");
		endwin ();
		exit(1);
	}  
	if (LINES < 25) {
		fprintf(stderr, "Window must have at least 25 lines.\n");
		endwin ();
		exit(1);
	}
	scr_download = newwin (11, 24,  0,  0);
	scr_files    = newwin (11, 54,  0, 25);
	scr_log      = newwin (12, COLS,        12,  0);
	scr_status   = newwin ( 1, COLS, LINES - 1,  0);                      

	scrollok (scr_files, TRUE);
	scrollok (scr_log,   TRUE);
	
	/* define background colors and attributes */

	wbkgdset(scr_download, COLOR_PAIR(1));
	wbkgdset(scr_files, COLOR_PAIR(1));
	wbkgdset(scr_log, COLOR_PAIR(1));
	wbkgdset(scr_status, COLOR_PAIR(1));
	wbkgdset(stdscr, COLOR_PAIR(1));

	wattrset(scr_download,A_BOLD);
	wattrset(scr_files,A_BOLD);
	wattrset(scr_log,A_BOLD);
	wattrset(scr_status,A_BOLD);
	wattrset(stdscr,A_BOLD);

	wclear(scr_download);
	wclear(scr_files);
	wclear(scr_log);
	wclear(scr_status);
	wclear(stdscr);

	curs_set(0);
	refresh();

	/* Initialise the screen */
	/* Header in the download window */
	mvwprintw (scr_download, 0, 0, "File Holes  Offset  Head");
	wrefresh (scr_download);

	/* Create frames in the background */
	wattron   (stdscr, A_REVERSE);
	mvwprintw (stdscr, 11, 0, "%80s", SatName1);
	for (n = 0; n < 11; n++) mvwprintw (stdscr, n, 24, " ");
	wattroff  (stdscr, A_REVERSE);
	wrefresh  (stdscr);

	/* Invert the status line */
	wattron   (scr_status, A_REVERSE);
	mvwprintw (scr_status, 0, 0, "%80s", "");
	wrefresh  (scr_status);
}

/*
 * Handle monitor input
 */
void FromServer (PIPE *pipe) {
	char tag [TAGSIZE+1];
	int i, count = pipe->count ;

	i = pipe_read (pipe) ;
	if ((debugmask & DEBUG_FROM_FIFO) == DEBUG_FROM_FIFO) {
		if (i > 0) {
			strncpy (tag, pipe->buf, i) ;
			tag[i] = '\0' ; }
		else tag[0] = '\0' ;	
		sprintf (debugline, "From FIFO:%s (%d from pos %d)", tag, i, count) ;
		WriteToLog (debugline);}
	if (i <= 0) {	
		close (pipe->fd) ;
		stopFlag = 1 ; 
		return ;
	}
	while ((count = getAnyTag (pipe, tag)) != 0) {
		if ((debugmask & DEBUG_FROM_FIFO) == DEBUG_FROM_FIFO) {
			sprintf (debugline, "FromServer: Got tag %s", tag) ;
			WriteToLog (debugline) ;
		}
		if (strcmp (tag, "DOWNLOAD") == 0)    count = DownloadConsumer (pipe, count) ;
		else if (strcmp (tag, "FILES") == 0)  count = FilesConsumer (pipe, count) ;
		else if (strcmp (tag, "LOG") == 0)    count = LogConsumer (pipe, count) ;
		else if (strcmp (tag, "STATUS") == 0) count = StatusConsumer (pipe, count) ;
		else if (strcmp (tag, "INIT") == 0)   count = InitValues (pipe, count) ;
		else if (strcmp (tag, "FINAL/") == 0) count = TerminateDisplay (pipe, count) ;
		else {
			fprintf (stderr, "unknown tag: %s \n", tag) ;
			/*exit (1) ;*/ }
		if (pipe->count < 5 || count == 0) break ; /* the smallest tag length is 5 */
		else pipe_flush (pipe, count) ; /* discard the used characters */
	}
}
			;
/*
 * Close the windows
 */
int TerminateDisplay (PIPE *pipe, int count) {
	endwin() ;
	return count ;
}
/*
 *	Set 'global' variables and initialize screens 
 */
int InitValues (PIPE *pipe, int count) {
	static int initialized = 0 ;
	char tag [100] ;
	
	count = getTagWithValue(pipe, "SATNAME", SatName1, count) ;
	count = getTagWithValue(pipe, "MYCALL", MyCall, count) ;
	count = getTag(pipe, tag, count) ; /* get the closing </INIT> */
	MyCallLength = strlen (MyCall);
	if (initialized == 0) {
		InitializeDisplay () ;
		initialized = 1 ; }
	return count ;
}
	
/*
 *	Write a text line on the log screen
 */
int LogConsumer (PIPE *pipe, int count) {
	char *ptr, text[1024] ;

	if ((count = getValue (pipe, "/LOG", text, count)) == 0) return 0 ;
	if ((debugmask & DEBUG_FROM_FIFO) == DEBUG_FROM_FIFO) {
		sprintf (debugline, "LOG       : Text:%s", text) ;
		WriteToLog (debugline) ;
	}
	/* See if MYCALL is contained in the text. If so, highlight it. */
	if ((ptr = strstr (text, MyCall))) {
		ptr[0] = '\0' ; /* terminate first part */
		wprintw (scr_log, "%s", text);
		wattron (scr_log, COLOR_PAIR(2));
		wprintw (scr_log, "%s", MyCall);
		wattroff (scr_log, COLOR_PAIR(2));
		wprintw (scr_log, "%s\n", &ptr[MyCallLength]); }
	else {
			wprintw (scr_log, "%s\n", text); }
	wrefresh (scr_log); 
	refresh ();
	return count ;
}
/*
 *	Write a string on the files screen
 */
int FilesConsumer (PIPE *pipe, int count)
{
	char text[1024] ;
	if ((count = getValue (pipe, "/FILES", text, count)) == 0) return 0 ;
	if ((debugmask & DEBUG_FROM_FIFO) == DEBUG_FROM_FIFO) {
		sprintf (debugline, "FILES     : Text:%s", text) ;
		WriteToLog (debugline) ;
	}
	wprintw (scr_files, "%s\n", text);
	wrefresh (scr_files);
	return count ;
}


/*
 *	Write a text line on the download screen
 */
int DownloadConsumer (PIPE *pipe, int count)
{
	int lin ; char text[TAGSIZE], temp [TAGSIZE] ;
	
	if ((count = getTagWithValue(pipe, "LNO", temp, count)) == 0) return 0 ;
	sscanf (temp, "%d", &lin);
	if ((count = getTagWithValue(pipe, "LTXT", text, count)) == 0) return 0 ;
	count = getTag(pipe, temp, count) ; /* get the closing </DOWNLOAD> */
	if (count == 0) return 0 ;
	mvwprintw (scr_download, lin, 0, "%s", text);
	wclrtoeol (scr_download);
	wrefresh (scr_download);
	return count ;
}

/*
 *	Write a string on the status line
 */
void writestatus (int col, char *text) {
	mvwprintw (scr_status, 0, col, "%s", text);
	wrefresh (scr_status);
}

int StatusConsumer (PIPE *pipe, int count)
{
	int dir_stat, bytes, fileBytes, dirBytes, tlmBytes, crcErrors ;
	long int req_stat ;
	char label[30], temp [100] ;
	
	if ((count = getTagWithValue(pipe, "DSTAT", temp, count)) == 0) return 0 ;
	sscanf (temp, "%d", &dir_stat);
	if ((count = getTagWithValue(pipe, "ID", temp, count)) == 0) return 0 ;
	sscanf (temp, "%ld", &req_stat);
	if ((count = getTagWithValue(pipe, "BYTES", temp, count)) == 0) return 0 ;
	sscanf (temp, "%d", &bytes);
	if ((count = getTagWithValue(pipe, "FBYTES", temp, count)) == 0) return 0 ;
	sscanf (temp, "%d", &fileBytes);
	if ((count = getTagWithValue(pipe, "DBYTES", temp, count)) == 0) return 0 ;
	sscanf (temp, "%d", &dirBytes);
	if ((count = getTagWithValue(pipe, "TBYTES", temp, count)) == 0) return 0 ;
	sscanf (temp, "%d", &tlmBytes);
	if ((count = getTagWithValue(pipe, "CRC", temp, count)) == 0) return 0 ;
	sscanf (temp, "%d", &crcErrors);
	count = getTag(pipe, temp, count) ; /* get the closing </STATUS> */
	if (count == 0) return 0 ;
	
	switch (dir_stat) {
		case -1: sprintf (label, "DIR: Old        "); break ;
		case -2: sprintf (label, "DIR: Up-to-date "); break ;
		default: sprintf (label, "DIR: %3d Parts ", dir_stat);
	}
	writestatus (0, label) ;
	switch (req_stat) {
		case -1: writestatus (17, "req:------   "); break;
		case -2: writestatus (17, "req:DIR      "); break;
		default:
			sprintf (label, "req:%-8lx ", req_stat);
			writestatus (17, label);
	}
	sprintf(label, "T:%06d", bytes);     writestatus (40, label);
	sprintf(label, "F:%06d", fileBytes); writestatus (49, label); 
	sprintf(label, "D:%06d", dirBytes);  writestatus (58, label);
	sprintf(label, "T:%05d", tlmBytes);  writestatus (67, label);
	sprintf(label, "E:%03d", crcErrors); writestatus (75, label);
	return count ;
}

void handlesig( int signum )
{
	fprintf( stderr, "Received signal %d - aborting\n\n", signum );
	exit( 0 );
}



void init_fifo( void ) {
	char	*line;
	fd_set	rdfd;
	struct	timeval tv;
	int 	res;

	line = (char *)alloca(LINELENGTH);

	count = 0;
	/*
	 * Open the control FIFO for writing
	 */
	if ( (controlfd = open(control, O_WRONLY | O_APPEND | O_NONBLOCK) ) < 0 ) {
		perror("Could not open PB control pipe");
		exit(1);
	}
	/* Associate a stream with the control FIFO */
	controlfifo = fdopen (controlfd, "a") ;
	/*
	 * Create a monitor FIFO
	 */
	monitor = strdup( tmpnam(NULL) );
	umask( 0002 );
	mknod( monitor, S_IFIFO | 0660, 0 );
	/*
	 * Open the monitor pipe non-blocking mode initially
 	*/
	if ( (monitorfd = open(monitor, O_RDONLY | O_NONBLOCK) ) < 0 ) {
		perror("Could not open pb monitor FIFO");
		exit(1);
	}
	/*
	 * Initialize the pipe structure for the monitor pipe
	 */
	if ((monitor_pipe = (PIPE *)malloc(sizeof(PIPE))) == 0) {
		fprintf(stderr, "Could not open monitor pipe\n") ;
		exit (1) ;
	}
	pipe_init (monitor_list, "MONITOR", monitorfd, monitor_pipe, 1);

	/*
	 * Send the 'monitor' command on the control pipe
	 */
	putTag (controlfifo, "MONITOR") ;
	putTagWithValue (controlfifo, "FIFO", monitor) ;
	putTag (controlfifo, "/MONITOR") ;
	fflush (controlfifo) ;
	/*
 	 * Wait for activity on the monitor FIFO to ensure PB is
	 * running.   If no activity within 5 seconds, abort.
	 * PB is likely screwed.  This timeout may be lengthened.
 	 *
 	 * Idea originally from:
 	 * 	Ian T Zimmerman <itz@rahul.net>
 	 */
	FD_ZERO( &rdfd );
	FD_SET( monitorfd, &rdfd );
	tv.tv_sec = 5;
	tv.tv_usec = 0;

	res = select( monitorfd+1, &rdfd, NULL, NULL, &tv );
	if( res <= 0 ) {
		fprintf( stderr, "No activity on monitor FIFO for 5 "
				 "sec.  Aborting!\n");
		exit(1);
	}
	/*
	 * Now that we know there is a writer for the FIFO, we make it
	 * non-blocking and 
	 * Associate stream 'in' with the monitor FIFO
	 */
	{
		long int res ;
		fcntl (monitorfd, F_GETFL, res) ;
		res &= !O_NONBLOCK ;
		fcntl (monitorfd, F_SETFL, res) ;
	}
	if( (in = fdopen(monitorfd,"r") ) == NULL ) {
		fprintf( stderr, "Could not fdopen PB monitor FIFO\n" );
		exit( 1 );
	}
}


void close_fifo( void ) {
	fclose( controlfifo );
	fclose( in );
	unlink( monitor );
}

void parse_keyboard( void ) {
	char key;

	key = getc(stdin);
	if ((debugmask & DEBUG_FROM_KEYB) == DEBUG_FROM_KEYB) {
		sprintf (debugline, "From keyb: %c(0x%x)", key, key) ;
		WriteToLog (debugline) ; }
	switch( key ) {
		case 'Q':	/* Q: Leave the program */
		case 'q':
			stopFlag = 1; 
			break;
		case 'R':   /* R: Re-initialize download control */
		case 'r':
			putTag (controlfifo, "INITDL/") ;
			fflush (controlfifo);
			break;
		case 'd':	/* D: Directory Fill */
		case 'D':
			putTag (controlfifo, "DFILL/") ;
			fflush (controlfifo);
			break;
		case 'x':	/* X: terminate PB */
		case 'X':
			putTag (controlfifo, "QUIT/") ;
			fflush (controlfifo);
			break;
		}
	}
void update_screen( void )
{
	/* presently empty */
}
							
int main (int argc, char **argv) {
	fd_set fd_ctl ;
	int fd_in ;
	//debugmask = DEBUG_FROM_FIFO ;
	//debugmask = DEBUG_FROM_KEYB | DEBUG_FROM_FIFO | DEBUG_TO_FIFO ;
	debugmask = 0 ;

	init_fifo();
	atexit( close_fifo );
	stopFlag = 0;
	signal (SIGTERM, handlesig);
	signal (SIGINT,  handlesig);
	signal (SIGHUP,  handlesig);
	signal (SIGPIPE, SIG_IGN);
	fd_in = fileno (in) ;
	while (!stopFlag)
	{	
		fd_set in, err;

		FD_ZERO (&fd_ctl );
		FD_SET (fileno (stdin), &fd_ctl );
		FD_SET (fd_in , &fd_ctl );
		in = err = fd_ctl;
		if( select( fd_in+1, &in, NULL, &err, NULL ) == -1 ) {
			continue;
		}
		if( FD_ISSET( fd_in, &err ) || FD_ISSET( fileno (stdin), &err ) ) {
			stopFlag = 1;
			continue;
		}
		if( FD_ISSET(fileno (stdin) , &in ) ) {
			parse_keyboard();
		}
		if( FD_ISSET( fd_in, &in ) ) {
			FromServer(monitor_pipe) ;
		}
	}
	TerminateDisplay (monitor_pipe, -1) ;
	return 0 ;
}
