/*
** Copyright (C) 1991, 1997 Free Software Foundation, Inc.
** 
** This file is part of TACK.
** 
** TACK 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, or (at your option)
** any later version.
** 
** TACK 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 TACK; see the file COPYING.  If not, write to
** the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
*/

#include <tack.h>
#include <time.h>

MODULE_ID("$Id: sync.c,v 1.3 2001/06/16 17:55:48 tom Exp $")

/* terminal-synchronization and performance tests */

static void sync_home(struct test_list *, int *, int *);
static void sync_lines(struct test_list *, int *, int *);
static void sync_clear(struct test_list *, int *, int *);
static void sync_summary(struct test_list *, int *, int *);

struct test_list sync_test_list[] = {
	{MENU_NEXT, 0, 0, 0, "b) baud rate test", sync_home, 0},
	{MENU_NEXT, 0, 0, 0, "l) scroll performance", sync_lines, 0},
	{MENU_NEXT, 0, 0, 0, "c) clear screen performance", sync_clear, 0},
	{MENU_NEXT, 0, 0, 0, "p) summary of results", sync_summary, 0},
	{0, 0, 0, 0, txt_longer_test_time, longer_test_time, 0},
	{0, 0, 0, 0, txt_shorter_test_time, shorter_test_time, 0},
	{MENU_LAST, 0, 0, 0, 0, 0, 0}
};

struct test_menu sync_menu = {
	0, 'n', 0,
	"Performance tests", "perf", "n) run standard tests",
	sync_test, sync_test_list, 0, 0, 0
};

int tty_can_sync;		/* TRUE if tty_sync_error() returned FALSE */
int tty_newline_rate;		/* The number of newlines per second */
int tty_clear_rate;		/* The number of clear-screens per second */
int tty_cps;			/* The number of characters per second */

#define TTY_ACK_SIZE 64

int ACK_terminator;		/* terminating ACK character */
int ACK_length;			/* length of ACK string */
const char *tty_ENQ;		/* enquire string */
char tty_ACK[TTY_ACK_SIZE];	/* ACK response, set by tty_sync_error() */

/*****************************************************************************
 *
 * Terminal synchronization.
 *
 *	These functions handle the messy business of enq-ack handshaking
 *	for timing purposes.
 *
 *****************************************************************************/

int
tty_sync_error(void)
{
	int ch, trouble, ack;

	trouble = FALSE;
	for (;;) {
		tt_putp(tty_ENQ);	/* send ENQ */
		ch = getnext(STRIP_PARITY);
		event_start(TIME_SYNC);	/* start the timer */

		/*
		   The timer doesn't start until we get the first character.
		   After that I expect to get the remaining characters of
		   the acknowledge string in a short period of time.  If
		   that is not true then these characters are coming from
		   the user and we need to send the ENQ sequence out again.
		*/
		for (ack = 0; ; ) {
			if (ack < TTY_ACK_SIZE - 2) {
				tty_ACK[ack] = ch;
				tty_ACK[ack + 1] = '\0';
			}
			if (ch == ACK_terminator) {
				return trouble;
			}
			if (++ack >= ACK_length) {
				return trouble;
			}
			ch = getnext(STRIP_PARITY);
			if (event_time(TIME_SYNC) > 400000) {
				break;
			}
		}

		set_attr(0);	/* just in case */
		put_crlf();
		if (trouble) {
			/* The terminal won't sync.  Life is not good. */
			return TRUE;
		}
		put_str(" -- sync -- ");
		trouble = TRUE;
	}
}

/*
**	flush_input()
**
**	Throw away any output.
*/
void 
flush_input(void)
{
	if (tty_can_sync == SYNC_TESTED && ACK_terminator >= 0) {
		(void) tty_sync_error();
	} else {
		spin_flush();
	}
}

/*
**	probe_enq_ok()
**
**	does the terminal do enq/ack handshaking?
*/
static void 
probe_enq_ok(void)
{
	int tc, len, ulen;

	put_str("Testing ENQ/ACK, standby...");
	fflush(stdout);
	can_test("u8 u9", FLAG_TESTED);

#ifdef user9
	tty_ENQ = user9 ? user9 : "\005";
#else
	tty_ENQ = "\005";
#endif
	tc_putp(tty_ENQ);
	event_start(TIME_SYNC);	/* start the timer */
	read_key(tty_ACK, TTY_ACK_SIZE - 1);

	if (event_time(TIME_SYNC) > 400000 || tty_ACK[0] == '\0') {
		/* These characters came from the user.  Sigh. */
		tty_can_sync = SYNC_FAILED;
		ptext("\nThis program expects the ENQ sequence to be");
		ptext(" answered with the ACK character.  This will help");
		ptext(" the program reestablish synchronization when");
		ptextln(" the terminal is overrun with data.");
		ptext("\nENQ sequence from (u9): ");
		putln(expand(tty_ENQ));
		ptext("ACK received: ");
		putln(expand(tty_ACK));
#ifdef user8
		len = user8 ? strlen(user8) : 0;
#else
		len = 0;
#endif
		sprintf(temp, "Length of ACK %d.  Expected length of ACK %d.",
			(int) strlen(tty_ACK), len);
		ptextln(temp);
#ifdef user8
		if (len) {
			temp[0] = user8[len - 1];
			temp[1] = '\0';
			ptext("Terminating character found in (u8): ");
			putln(expand(temp));
		}
#endif
		return;
	}

	tty_can_sync = SYNC_TESTED;
	if ((len = strlen(tty_ACK)) == 1) {
		/* single character acknowledge string */
		ACK_terminator = tty_ACK[0];
		ACK_length = 4096;
		return;
	}
	tc = tty_ACK[len - 1];
#ifdef user8
	if (user8) {
		ulen = strlen(user8);
		if (tc == user8[ulen - 1]) {
			/* ANSI style acknowledge string */
			ACK_terminator = tc;
			ACK_length = 4096;
			return;
		}
	}
#endif
	/* fixed length acknowledge string */
	ACK_length = len;
	ACK_terminator = -2;
}

/*
**	verify_time()
**
**	verify that the time tests are ready to run.
**	If the baud rate is not set then compute it.
*/
void
verify_time(void)
{
	int status, ch;

	if (tty_can_sync == SYNC_FAILED) {
		return;
	}
	probe_enq_ok();
	put_crlf();
	if (tty_can_sync == SYNC_TESTED) {
		put_crlf();
		if (ACK_terminator >= 0) {
			ptext("ACK terminating character: ");
			temp[0] = ACK_terminator;
			temp[1] = '\0';
			ptextln(expand(temp));
		} else {
			sprintf(temp, "Fixed length ACK, %d characters",
				ACK_length);
			ptextln(temp);
		}
	}
	if (tty_baud_rate == 0) {
		sync_home(&sync_test_list[0], &status, &ch);
	}
}

/*****************************************************************************
 *
 * Terminal performance tests
 *
 *	Find out how fast the terminal can:
 *	  1) accept characters
 *	  2) scroll the screen
 *	  3) clear the screen
 *
 *****************************************************************************/

/*
**	sync_home(test_list, status, ch)
**
**	Baudrate test
*/
void
sync_home(
	struct test_list *t,
	int *state,
	int *ch)
{
	int j, k;
	unsigned long rate;

	if (!cursor_home && !cursor_address && !row_address) {
		ptext("Terminal can not home cursor.  ");
		generic_done_message(t, state, ch);
		return;
	}
	if (skip_pad_test(t, state, ch,
		"(home) Start baudrate search")) {
		return;
	}
	pad_test_startup(1);
	do {
		go_home();
		for (j = 1; j < lines; j++) {
			for (k = 0; k < columns; k++) {
				if (k & 0xF) {
					put_this(letter);
				} else {
					put_this('.');
				}
			}
			SLOW_TERMINAL_EXIT;
		}
		NEXT_LETTER;
	} while(still_testing());
	pad_test_shutdown(t, auto_right_margin == 0);
	/* note:  tty_frame_size is the real framesize times two.
	   This takes care of half bits. */
	rate = (tx_cps * tty_frame_size) >> 1;
	if (rate > tty_baud_rate) {
		tty_baud_rate = rate;
	}
	if (tx_cps > tty_cps) {
		tty_cps = tx_cps;
	}
	sprintf(temp, "%d characters per second.  Baudrate %d  ", tx_cps, j);
	ptext(temp);
	generic_done_message(t, state, ch);
}

/*
**	sync_lines(test_list, status, ch)
**
**	How many newlines/second?
*/
static void
sync_lines(
	struct test_list *t,
	int *state,
	int *ch)
{
	int j;

	if (skip_pad_test(t, state, ch,
		"(nel) Start scroll performance test")) {
		return;
	}
	pad_test_startup(0);
	repeats = 100;
	do {
		sprintf(temp, "%d", test_complete);
		put_str(temp);
		put_newlines(repeats);
	} while(still_testing());
	pad_test_shutdown(t, 0);
	j = sliding_scale(tx_count[0], 1000000, usec_run_time);
	if (j > tty_newline_rate) {
		tty_newline_rate = j;
	}
	sprintf(temp, "%d linefeeds per second.  ", j);
	ptext(temp);
	generic_done_message(t, state, ch);
}

/*
**	sync_clear(test_list, status, ch)
**
**	How many clear-screens/second?
*/
static void
sync_clear(
	struct test_list *t,
	int *state,
	int *ch)
{
	int j;

	if (!clear_screen) {
		ptext("Terminal can not clear-screen.  ");
		generic_done_message(t, state, ch);
		return;
	}
	if (skip_pad_test(t, state, ch,
		"(clear) Start clear-screen performance test")) {
		return;
	}
	pad_test_startup(0);
	repeats = 20;
	do {
		sprintf(temp, "%d", test_complete);
		put_str(temp);
		for (j = 0; j < repeats; j++) {
			put_clear();
		}
	} while(still_testing());
	pad_test_shutdown(t, 0);
	j = sliding_scale(tx_count[0], 1000000, usec_run_time);
	if (j > tty_clear_rate) {
		tty_clear_rate = j;
	}
	sprintf(temp, "%d clear-screens per second.  ", j);
	ptext(temp);
	generic_done_message(t, state, ch);
}

/*
**	sync_summary(test_list, status, ch)
**
**	Print out the test results.
*/
static void
sync_summary(
	struct test_list *t,
	int *state,
	int *ch)
{
	char size[32];

	put_crlf();
	ptextln("Terminal  size    characters/sec linefeeds/sec  clears/sec");
	sprintf(size, "%dx%d", columns, lines);
	sprintf(temp, "%-10s%-11s%11d   %11d %11d", tty_basename, size,
		tty_cps, tty_newline_rate, tty_clear_rate);
	ptextln(temp);
	generic_done_message(t, state, ch);
}

/*
**	sync_test(menu)
**
**	Run at the beginning of the pad tests and function key tests
*/
void
sync_test(
	struct test_menu *menu)
{
	control_init();
	if (tty_can_sync == SYNC_NOT_TESTED) {
		verify_time();
	}
	if (menu->menu_title) {
		put_crlf();
		ptextln(menu->menu_title);
	}
}

/*
**	sync_handshake(test_list, status, ch)
**
**	Test or retest the ENQ/ACK handshake
*/
void
sync_handshake(
	struct test_list *t GCC_UNUSED,
	int *state GCC_UNUSED,
	int *ch GCC_UNUSED)
{
	tty_can_sync = SYNC_NOT_TESTED;
	verify_time();
}
