/*
    yamm, Yet Another Micro Monitor
    curses.c
    Copyright (C) 1992  Andrea Marangoni
    Copyright (C) 1994, 1995  Riccardo Facchetti

    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 2 of the License, 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 "kversion.h"

#include "yamm.h"

#if !defined(NO_CURSES)
#if !defined(NCURSES)
#include <curses.h>
#else
#include <termios.h>
#include "ncurses.h"
#if defined(NCURSES_186)
#include "ncurses-1.8.6/curses.priv.h"
#endif /* NCURSES_186 */
#endif /* !NCURSES */
#include <signal.h>
#include <errno.h>
#if !defined(linux) || (KERNEL_VERSION < 1001076)
#include <string.h>
#else
#include <linux/string.h>
#endif /* !linux || KERNEL_VERSION < 1001076 */
#include <time.h>
#include <termios.h>
#include <sys/ioctl.h>
#if defined(linux)
#include <linux/time.h>
#if !(KERNEL_VERSION < 1001076)
#include <asm/bitops.h>
#endif /* !KERNEL_VERSION < 1001076 */
#include <sys/time.h>
#include <sys/types.h>
#include <sys/param.h>
#include <unistd.h>
#endif /* linux */

#if defined(NCURSES)
# if !defined(FGC)
# define FGC COLOR_WHITE
# endif /* !FGC */
# if !defined(BGC)
# define BGC COLOR_BLACK
# endif /* !BGC */

/*
 * fore/back ground colors
 */
int fgc, bgc;

#endif /* NCURSES */

/*
 * For Linux use the gets and not getstr, because ncurses package seems
 * to behave not very well.
 */
#if defined(linux)
#define GETSTR(x) \
	nocbreak(); \
	echo(); \
	gets ( x ); \
	noecho(); \
	cbreak(); \
	refresh_all()
#endif /* linux */

#if defined(__hpux)
#define GETSTR(x) \
	nocbreak(); \
	echo(); \
	getstr ( x ); \
	noecho(); \
	cbreak(); \
	refresh_all()
#endif /* __hpux */

#if !defined(GETSTR)
Error! You must define one macro called GETSTR.
#endif /* !GETSTR */

#define CTRL(x) ((x) & 037)
#define PF (void)printw

#define BUF 128

typedef struct termios MYSGTTY;
static MYSGTTY _mytty;

#if defined(SIGWINCH) && defined(OWN_WINCH)

/*
 * this is defined into ifdef because of the system-specific behaviour
 * of the signal management
 */
void winch_func( int sig )
{
# if defined(linux) || defined(__hpux)
	signal(SIGWINCH, winch_func);
#endif /* linux || __hpux */

	do_chsize();
}

void do_chsize(void)
{
#if defined(NCURSES_186)
	extern void *cur_term;
	extern WINDOW *newscr, *curscr, *stdscr;
	extern SCREEN *SP;
#endif /* NCURSES_186 */

/*
 * End curses package
 */
	endwin();

#if defined(NCURSES_186)
	ttytype [0] = '\0';
	memset(newscr, 0, sizeof(WINDOW));
	memset(curscr, 0, sizeof(WINDOW));
	memset(stdscr, 0, sizeof(WINDOW));
	memset(SP, 0, sizeof(SCREEN));
	free(cur_term);
	free(newscr);
	free(curscr);
	free(stdscr);
	free(SP);
	cur_term = NULL;
	newscr = NULL;
	curscr = NULL;
	stdscr = NULL;
	SP = NULL;
#endif /* NCURSES_186 */

/*
 * Tell the parent task (the shell)  that the window is changed.
 */
	kill(getppid(), SIGWINCH);

#if defined(NCURSES_186)
/*
 * rewritten the do_chsize ... now works in a smoother way, but i use some
 * ncurses internals. I hope they will not change everything from time to time
 */
	init_curses();
#else
/*
 * This thing works well:
 * restart yamm from scratch because restart only (n)curses
 * seems not work very well ... (coredumps and the like).
 * Anyway if you want the SIGWINCH trapped and not ignored,
 * this is the only solution
 * for now: i have not time nor the will to do the things better.
 * If anyone of you would like to write down the code that handle the do_chsize
 * function in a better way, (s)he's welcome!! :)
 */

	execv(yammav[0], yammav);
#endif /* NCURSES_186 */
}
#endif /* SIGWINCH && OWN_WINCH */

void quit ( int sig )
{
	move ( LINES-1 , 0 );
	clrtoeol();
	curses_refresh();
	endwin();

#if defined(MODULE)

/*
 * close the yammfd before exit
 */
	close(yammfd);

#endif /* MODULE */

/*
 * reset the screen attribs
 */
	tcsetattr(fileno(stdin), TCSADRAIN, &_mytty);

	if ( sig > 0 ) {
		fprintf ( stderr, "Trapped signal %d\n", sig );
		exit ( sig );
	}
	exit ( sig );
}

void init_curses(void)
{
	register int count;
#if defined(NCURSES)
	extern void use_env(bool);
#endif /* NCURSES */

/*
 * save screen attribs
 */
	tcgetattr(fileno(stdin), &_mytty);

#if defined(NCURSES)
/*	trace(TRACE_MAXIMUM);*/
/*
 * Force the ncurses package to get the screen size
 * using TIOCGWINSZ, setting env variables to 0
 */
 	setenv("LINES", "0", 1);
 	setenv("COLUMNS", "0", 1);

/*
 * See ncurses sources
 */
	use_env(TRUE);

#endif /* NCURSES */
	initscr();
#if defined(NCURSES_186)
/*
 * I need to set SP->_coloron due to a little bug in ncurses.
 * All the malloc'ed memory is not memset to 0, but ncurses assume that
 * the memory returned by malloc is set to 0. Reinitializing (do_chsize)
 * ncurses, mallocs dirty memory, so i need to initialize all the things
 * that ncurses have not yet initialized.
 */
 	SP->_coloron = 0;
#endif /* NCURSES_186 */
	erase ();
	clear();
	curses_refresh();
	cbreak();
	noecho();

	if ( COLS < 80 ) {
		echo();
		fprintf ( stderr, "You must have at least 80 cols.\n" );
		quit ( 0 );
	}

	if ( LINES < 8 ) {
		echo();
		fprintf ( stderr, "You must have at least 8 lines.\n" );
		quit ( 0 );
	}

#if defined(NCURSES) || !defined(linux)
	keypad(stdscr,TRUE);
#endif /* NCURSES || !linux */

	for ( count = 1; count < NSIG; ++count ) {

#if defined(SIGWINCH)
/*
 * trap SIGWINCH for use in X11 windows when resizing a window in which yamm run
 */
		if ( count == SIGWINCH ) {
#if defined(OWN_WINCH)
			signal(SIGWINCH, winch_func);
#endif /* OWN_WINCH */
			continue;
		}
#endif /* SIGWINCH */

#if defined(PROFILING)
/*
 * If profiling the code, do not trap the SIGPROF
 */
		if ( count == SIGPROF )
			continue;
#endif /* PROFILING */

		if ( count == SIGCLD )
			continue;
		if ( count == SIGSEGV )
			continue;

		( void ) signal ( count , quit );
	}

	if ( Num_display_proc > LINES - 6 || Num_display_proc < 0 )
		Num_display_proc = LINES - 6 ;

#if defined(NCURSES)
	if (has_colors()) {
		start_color();
		fgc = FGC;
		bgc = BGC;
		init_pair(1, fgc, bgc);
		attron(COLOR_PAIR(MAIN_PAIR));
	}
#endif /* NCURSES */

	curses_refresh();
}

void clear_screen(void)
{
	erase();
	move ( 0, 0 );
}

int look_lines(void)
{
	return ( LINES );
}

int look_cols (void)
{
	return ( COLS );
}

void update_first_line( int nproc, struct pst_dynamic *dinfo )
{
#if !defined(linux)
	extern struct tm *localtime();
#endif /* !linux */
	struct tm actual;
	time_t mclock = time ( ( time_t *)0 );

	long user_processes = 0L, t_size = 0L, t_rss = 0L;

	clear_screen();

	actual = *localtime ( &mclock );
	count_status ( nproc );

	PF ( "[%2d:%.2d:%.2d] Load averages: %.2f  %.2f  %.2f.  Pages: %d - %s Idle\n" ,
		actual.tm_hour, actual.tm_min, actual.tm_sec,
		dinfo->psd_avg_1_min, dinfo->psd_avg_5_min,
		dinfo->psd_avg_15_min, pages,
		info_cpu ( dinfo->psd_cpu_time ));

	PF ( "%d Processes: %d Sleeping, %d Running, %d Stopped, %d Zombie, %d Others.\n",
		nproc, status_sleep(), status_run(), status_stop(),
		status_zombie(), status_other() );

	PF ( "Memory: %s  ", get_dimension ( dinfo->psd_rm) );
	PF ( "( %s ) real,  ", get_dimension ( dinfo->psd_arm ) );
	PF ( "%s  ", get_dimension ( dinfo->psd_vm ) );
	PF ( "( %s ) virtual, ", get_dimension ( dinfo->psd_avm ) );
	PF ( "%s free\n", get_dimension ( dinfo->psd_free ) );

	if ( look_uid >= 0 ) {
		get_user_sum( nproc, &user_processes, &t_size, &t_rss);
		curses_in_reverse();
		PF ( "UID %d: %ld processes,", look_uid, user_processes );
		PF ( "  %s  TOTAL SIZE,", get_dimension ( t_size ) );
		PF ( "  %s  TOTAL RSS.", get_dimension ( t_rss ) );
		curses_out_reverse();
	}
		
}

void curses_help(void)
{
	static int first = 1;
	int page = 0, car = 0;

	if ( LINES < 24 ) {
		curses_error ( "Sorry, you must have at least 24 lines." );
		return;
	}

/*
 * The first time start page 0 else 1
 */
	if ( !first )
		car = ( int )'f';
	else
		car = ( int )'B';
	
	first = 0;

	do {
		switch ( car ) {

		case 'f':
		case ' ':
#if defined(KEY_NPAGE)
		case KEY_NPAGE:
#endif /* KEY_NPAGE */

#if !defined(NCURSES)
			if ( page < 2 )
#else
			if ( page < 3 )
#endif /* !NCURSES */
				++page;
		break;


		case 'b':
#if defined(KEY_PPAGE)
		case KEY_PPAGE:
#endif /* KEY_PPAGE */
			if ( page > 0 )
				--page;
		break;


		case 'B':
#if defined(KEY_HOME)
		case KEY_HOME:
#endif /* KEY_HOME */
			page = 0;
		break;	


		default:
		break;

		}

		switch ( page ) {
		case 0:
		clear_screen();
		PF ( "YAMM ( Yet Another Micro Monitor ) " YAMM_VERSION 
#if defined(linux)
# if defined(MODULE)
			" ( Modular )"
# else
			" ( Procfs )"
# endif /* MODULE */
#endif /* linux */
			" "
			YAMM_DATE
			"\n" );
		PF ( "(C) Riccardo Facchetti (key \"$\" for GPL notice).\n" );
		PF ( "E-mail: riccardo@cdc8g5.cdc.polimi.it.\n");
		PF ( "Derived from V2.4 Jun 1992 by Marangoni Andrea.\n\n" );
		PF ( "Explanation of fields:\n\n" );
#if !defined(linux)
		PF ( "S           - Status of process.\n");
		PF ( "    'S' Sleep, 'R' Running or waiting for Cpu, 'T' Stopped,\n");
		PF ( "    'Z' Zombie, 'I' Being created, 'O' Other ( forking, exiting, etc. ).\n");
		PF ( "\nUSER        - Name of user.\n" );
		PF ( "SUID        - Effective user id ( an '*' means that euid != uid ).\n");
#else
		PF ( "S           - Status of process.\n");
		PF ( "    'S' Sleep, 'R' Running or waiting for Cpu, 'T' Stopped,\n");
		PF ( "    'Z' Zombie, 'D' Swapping or uninterruptible.\n");
		PF ( "\nUSER        - Name of user.\n" );
		PF ( "UID         - User id.\n");
#endif /* linux */
		PF ( "PID         - Process id.\n" );
		PF ( "NI          - Nice.\n" );
		PF ( "PRI         - Priority.\n" );
		PF ( "SIZE        - Size of process ( data + text + stack ).\n");
		PF ( "RSS         - Resident set size ( private pages only ).\n");
		PF ( "STIME       - Start time.\n");
		PF ( "\"u|s| \"TIME - ( 'TIME' Total time, 'uTIME' User time, 'sTIME' System time ).\n" );
		PF ( "CMD         - Command.\n" );
		move ( LINES - 1 , 0 );
		PF ( "Type 'f' to next page or <RETURN> to exit." );
		break;

		case 1:
		clear_screen();
		PF ( "YAMM ( Yet Another Micro Monitor ) "
		YAMM_VERSION
		" "
		YAMM_DATE
		"\n\n" );
		PF ( "\"f\" or PAGEDOWN Forward  1 page.\n" );
		PF ( "\"b\" or PAGEUP Backward 1 page.\n" );
		PF ( "\"B\" or HOME Go to page 0.\n\n" );
		PF ( "\"s\" Sort by 's'tart time.\n" );
		PF ( "\"t\" Sort by process 't'ime.\n" );
		PF ( "\"c\" Sort by process %%cpu time, default.\n" ); /* HV */
		PF ( "\"d\" Sort by process size ( 'd'ata + text + stack ).\n" );
		PF ( "\"D\" Sort by process resident set size ( private pages ).\n" );
		PF ( "\"i\" Invert sorting order.\n");
		PF ( "\"<SPACE>\" Disable sorting.\n\n" );
		PF ( "\"m\" Display only 'm'y processes. Press \"m\" again to return to previous state.\n");
		PF ( "\"r\" Allow display of 'r'oot processes ( default = off ).\n" );
		PF ( "\"E\" Show processes with UID != EUID. ( \"E\" again to return to previous state ).\n" );
		PF ( "\"w\" Like \"who\". ( \"w\" again or <RETURN> to return to previous state ).\n" );
		PF ( "\"e\" Display the entries in utmp.\n\n" );
		PF ( "\"P\" or DELETE Kill process ( After choose it with slide bar ).\n" );
		PF ( "\"K\" Kill user ( After choose it with slide bar ).\n" );
		PF ( "\"I\" User Info ( After choose it with slide bar ).\n" );
		move ( LINES - 1 , 0 );
		PF ( "Type 'f' to next page, 'b' to previous page or <RETURN> to exit." );
		break;

		case 2:
		clear_screen();
		PF ( "YAMM ( Yet Another Micro Monitor ) "
		YAMM_VERSION
		" "
		YAMM_DATE
		"\n\n" );
		PF ( "\"u\" Restrict to a specific 'u'id ( You may choose it with slidebar ).\n" );
		PF ( "    Type \"m\"  to return to previous state.\n" );
		PF ( "    Type \"I\" for full user info.\n\n" );
		PF ( "\"S\" Display only System time in field \"TIME\".\n");
		PF ( "\"U\" Display only User time in field \"TIME\".\n");
		PF ( "\"%%\" Switch between  STIME-TIME and WCPU-WCHAN fields.\n" );
		PF ( "    If you have >= 90 cols both fields are displayed.\n" );
		PF ( "\"L\" Display Load Average 1 min bar chart.\n" );
		PF ( "\"N\" Display only \"number\" processes.\n" );
		PF ( "\"p\" Analyze a single process ( Type <SPACE> to return to previous state ).\n" );
		PF ( "\"C\" Display configuration of machine.\n" );
		PF ( "\"V\" Virtual memory info.\n" );
		PF ( "\"W\" Loop forever waiting \"second\" for each loop.\n" );
		PF ( "\"/\" Search \"string\" in command line. NULL string or <SPACE> to annull.\n" );
		PF ( "\"R\" Renice process ( You must be root :-) ).\n" );
		PF ( "\"G\" Renice process group ( You must be root :-) ).\n" );
		PF ( "\"A\" Renice user ( You must be root :-) ).\n" );
		PF ( "\"j\" or DOWN ARROW Move slide bar down.\n" );
		PF ( "\"k\" or UP ARROW Move slide bar up.\n\n" );
		PF ( "\"Q\" or \"CTRL-D\" or ESCAPE to exit.\n" );
		move ( LINES - 1 , 0 );
#if !defined(NCURSES)
		PF ( "Type 'b' to previous page or <RETURN> to exit." );
#else
		PF ( "Type 'f' to next page, 'b' to previous page or <RETURN> to exit." );
#endif /* !NCURSES */
		break;

#if defined(NCURSES)
		case 3:
		clear_screen();
		PF ( "YAMM ( Yet Another Micro Monitor ) "
		YAMM_VERSION
		" "
		YAMM_DATE
		"\n\n" );
		PF ( "\"-\" or LEFT ARROW to backward cycle the foreground color.\n" );
		PF ( "\"=\" or RIGHT ARROW to forward cycle the foreground color.\n" );
		PF ( "\"_\" to backward cycle the background color.\n" );
		PF ( "\"+\" to forward cycle the foreground color.\n" );
		move ( LINES - 1 , 0 );
		PF ( "Type 'b' to previous page or <RETURN> to exit." );
		break;
#endif /* NCURSES */

		default:
		break;
	}
    curses_refresh();
	} while ( ( car = getch() ) != 'q' && car != '\n' && car != 27); 

#if defined(linux)
	if (car == '\n')
		refresh_all();
#endif /* linux */
}

void curses_refresh(void)
{
	refresh();
}

void refresh_all (void)
{
	clear();
	touchwin( stdscr );
	curses_refresh();
}

int curses_wait( int second )
{
	struct timeval timeout;
	fd_set input_set;

	timeout.tv_sec = ( second <= 0 ) ?  1L : ( long )second;
	timeout.tv_usec = 0L;

	FD_ZERO ( &input_set );
	FD_SET ( fileno ( stdin ), &input_set );

#if !defined(linux)
	if ( select ( ( size_t )(fileno ( stdin ) + 1), ( int *)&input_set,
		( int *)0, ( int *)0, &timeout ) == -1 ) {
#else
	if ( select ( ( size_t )(fileno ( stdin ) + 1), ( fd_set *)&input_set,
		( fd_set *)0, ( fd_set *)0, &timeout ) == -1 ) {
#endif /* !linux */
	if (errno) curses_error( "Select" );
		return ( 0 );
	}


	if ( FD_ISSET ( 0, &input_set ) ) {
		int ch = getch();

		if ( ch == 'q' || ch == 'Q' || ch == CTRL('D') || ch == 27 )
			quit ( 0 );

		if ( ch == CTRL('L') || ch == CTRL('R') ) {
			refresh_all();
			return ( 0 );
		}

#if defined(KEY_F)
		if ( ch == '?' || ch == 'h' || ch == KEY_F(1))
#else
		if ( ch == '?' || ch == 'h' )
#endif /* KEY_F */
			curses_help();

		mpreprocess ( ch, NULL, NULL );
		return ( ch );
	}
	return ( 0 );
}

long ask_curses ( char *ask )
{
static char buffer [ BUF ];
#if !defined(linux)
	extern long atol();
#endif /* !linux */
	register int count;

	move ( LINES - 1 , 0 );
	clrtoeol();
	PF ( ask );
	curses_refresh();
	GETSTR ( buffer );

/*
 * Skip space and tab
 */
	for ( count = 0; buffer [ count ] == ' ' || buffer [ count ] == '\t';
				++count );

	if ( buffer[ count ] == '\n' || buffer[ count ] == '\0' )
		return ( -1L );

	return ( atol ( buffer + count ) );
}

char *curses_get_string ( char *ask )
{
	static char buf [ BUF ];
	register int count;

	move ( LINES - 1 , 0 );
	clrtoeol();
	PF ( ask );
	curses_refresh();
	GETSTR ( buf );

/*
 * Skip space and tab
 */
	for ( count = 0; buf [ count ] == ' ' || buf [ count ] == '\t'; ++count );

	if ( buf [ count ] == '\n' || buf [ count ] == '\0' )
		return ( NULL );

	return ( buf + count  );
}


void curses_error ( char *message )
{
	move ( 3, 0 );
	clrtoeol();
	curses_in_reverse();
	PF ( message );
	curses_out_reverse();
	curses_refresh();
/*
 * Refresh is too fast
 */
	( void )sleep ( 1 );
}

/*
 * If reverse don't work comment "standout()" and "standend()"
 */
void curses_in_reverse(void)
{
#if !defined(NCURSES)
	standout();
#else
#if 0
	attron(A_STANDOUT);
#else
	attron(A_REVERSE);
	if (has_colors())
		attron(COLOR_PAIR(MAIN_PAIR));
#endif /* 0 */
#endif /* !NCURSES */
}

void curses_out_reverse(void)
{
#if !defined(NCURSES)
	standend();
#else
#if 0
	attroff(A_STANDOUT);
#else
	attroff(A_REVERSE);
#endif /* 0 */
	if (has_colors())
		attron(COLOR_PAIR(MAIN_PAIR));
#endif /* !NCURSES */
}

#if defined(__hpux)
/*
 * 64 + 1
 */
#define HOSTNAMELEN 65
#endif /* __hpux */

#if defined(linux)
#define HOSTNAMELEN MAXHOSTNAMELEN
#endif /* linux */

void curses_print_help(void)
{
	static int yet = 0;
	static char host_name [ HOSTNAMELEN ];

	if ( !yet ) {
		host_name [ 0 ] = '\0';
		if ( gethostname ( host_name, ( size_t ) HOSTNAMELEN ) == -1 )
			host_name [ 0 ] = '\0';
		yet = 1;
		host_name [ HOSTNAMELEN - 1 ] = '\0';
	}

	move ( LINES - 1 , 0 );
	clrtoeol();

	if ( host_name [ 0 ] )
		PF ( "[ %s ] Type <?> for help." , host_name );
	else
		PF ( "Type < ? > for help." );
}

#else /* !NO_CURSES */

long ask_curses( char *msg )
{
	return ( -1L ); 
}

char *curses_get_string( char *msg )
{
	return ( NULL );
}

int look_lines(void)
{
/*
 * Not necessary only for lint
 */
	return ( 0 );
}

int look_cols(void)
{
	if ( long_format )
		return ( 90 ); 
	else
		return ( 80 );
}

#endif /* !NO_CURSES */
