/*
 * *************************************************************************
 *
 * Trimble Navigation, Ltd.
 * OEM Products Development Group
 * P.O. Box 3642
 * 645 North Mary Avenue
 * Sunnyvale, California 94088-3642
 *
 * Corporate Headquarter:
 *    Telephone:  (408) 481-8000
 *    Fax:        (408) 481-6005
 *
 * Technical Support Center:
 *    Telephone:  (800) 767-4822	(U.S. and Canada)
 *                (408) 481-6940    (outside U.S. and Canada)
 *    Fax:        (408) 481-6020
 *    BBS:        (408) 481-7800
 *    e-mail:     trimble_support@trimble.com
 *
 * *************************************************************************
 *
 * Vers		Date		   Changes									 Author
 * ----		---------   ----------------------------------------	----------
 * 1.1		16 Jun 93	Initial version								 pvwl
 * 1.12		14 Feb 94	Revisions      								 pvwl
 * 1.12a	   31 Mar 94	Added cts display window				pvwl
 * 1.20      5 Jun 95   Revisions: timezone                         ahl
 *          26 Jul 95   Communications above 9600 bps
 *          28 Aug 95   Revision: ask_byte() for maxage
 *
 * *************************************************************************
 *
 * TSIP_UTL.C contains five types of functions:
 * 
 * 1) Borland-compatible window control functions:  The Borland version of
 * TSIPCHAT supports two windows: a command and report window and an auto
 * window.  The command and report window displays the command list, command
 * entries and parameters, and display of requested packets.  The auto window
 * scrolls through unrequested reports, which are generated according to the
 * I/O options specified.
 *
 * The following are Borland C++ functions used to create text windows.  If
 * suitable functions can be found in the Microsoft Visual C++ Compiler, the
 * windowing feaures may be recreated. (Creating TSIPCHAT without BORLAND
 * defined results in all commands, both auto and requested, scrolling up the
 * screen.)
 *
 *    ;; create a window
 *    void window (short left, short top, short right, short bottom) 
 *
 *    ;; set backgrounf and foreground colors
 *    void textattr (char attr)
 *
 *    ;; go to row x, colw y w.r.t window origin
 *    void gotoxy (short x, short y)
 *
 *    ;; report row relative to window origin
 *    short wherex (void)
 *
 *    ;; report column relative to window origin
 *    short wherey (void)
 *
 *    ;; clear window to the end of the line
 *    void clrscr (void), clreol (void)
 *
 *    ;; get text attributes
 *    void gettextinfo (struct *textinfo)
 *
 * 2) User interface functions:  Specifically, these are functions that query
 * integer, double, or YES/NO inputs.  There is also a function for toggling
 * file storage on and off.
 *
 * 3) Storage file functions:  These functions open and close binary
 * storage files.
 *
 * 4) DOS-specific time functions:  These functions synchronize the receiver
 * and PC clocks.
 *
 * 5) Serial port functions:  These functions send, receive, and set
 * parameters.  They are DOS-specific.
 *
 * *************************************************************************
 *
 */

#define END_STRING '\0'
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#include <dos.h>
#include <time.h>
#include "tsip_ifc.h"
#include "tsipincl.h"

const short days_in_year[32] =
{
	361, 365, 365, 365, 366, 365, 365, 365,			/* 1980-1987 */
	366, 365, 365, 365, 366, 365, 365, 365,			/* 1988-1995 */
	366, 365, 365, 365, 366, 365, 365, 365,			/* 1996-2003 */
	366, 365, 365, 365, 366, 365, 365, 365			/* 2004-2011 */
};
	/*
		Note:	1980 is short because GPS time starts midnight 1/6/1980.
				2000 IS a leap year.
	*/

const unsigned char leap_year[32] =
{
	1, 0, 0, 0, 1, 0, 0, 0,  
	1, 0, 0, 0, 1, 0, 0, 0,  
	1, 0, 0, 0, 1, 0, 0, 0,
	1, 0, 0, 0, 1, 0, 0, 0
};

const char days_in_month[2][12] =
{
	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
	{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

/**/
/*  *******************  1. CONSOLE WINDOW CONTROL ****************** */
/*
 * The following routines set up two windows: one which prints only
 * commands and requested reports, the other only unrequested reports.
 * The routines are set up for Borland compilers.  Similar functions can
 * be found with Microsoft compilers.  Generic versions are included
 * for non-Borland compilers.
 */

/* backspace, enter, printable characters except control characters */
#include <conio.h>
#define MENUWIN 2

/* text attributes for different screen functions */
static short
	auto_y,
	text_color[3];
static struct text_info
	t_info,
	w_info[2];

/* current window = command or requested reply, or auto report */
/* TRUE if current window is command window */
static short
	curr_win = AUTOWIN;

void show_crlf (void)
{
	clreol ();
	xprintf ("\r\n");
	clreol ();
}

short which_window (void)
{
	return (curr_win);
}

void switch_window (short new_win)
{
	struct text_info *old_info, *new_info;

	old_info = &w_info[curr_win];
	gettextinfo (old_info);
	if (new_win == curr_win) {
		return;
	}
	curr_win = new_win;
	new_info = &w_info[curr_win];

	switch (new_win) {
	case AUTOWIN:
		/* auto window is the bottom of the screen; unrequested reports */
		/* are printed here in scrolled mode */
		window (1, old_info->cury + 1, 80, 24);

		textattr (text_color[new_win]);
		/* start at bottom of auto window and scroll */
		gotoxy (80, auto_y - (old_info->cury + 1));
		break;

	case CMDWIN:
		/* cmd window is the top of the screen; commands and requested  */
		/* reports are printed in scrolled mode in blue or reverse text */

		/* autowin absolute line coordinate */
		auto_y = old_info->cury + old_info->wintop;

		window (1, 1, 80, 24);
		textattr (text_color[new_win]);
		gotoxy (new_info->curx, new_info->cury);
		clreol ();
	  break;
	}
}

void new_cmd_window (void)
{
	/* blank out old command window */
	if (curr_win == AUTOWIN) {
		gettextinfo (&w_info[curr_win]);
		auto_y = w_info[curr_win].cury + w_info[curr_win].wintop;
	}
	textattr (text_color[AUTOWIN]);
	if (w_info[CMDWIN].cury > 1) {
		window (1, 2, 80, w_info[CMDWIN].cury);
		clrscr ();
	}
	w_info[CMDWIN].cury = 1;
	w_info[CMDWIN].curx = 1;

	/* perch in the auto window at the last location */
	curr_win = AUTOWIN;
	window (1, 2, 80, 24);
	gotoxy (80, auto_y - 2);

	switch_window (CMDWIN);
	clreol ();
	highvideo ();
	textbackground (BANNER_BACKGROUND);
	textcolor (BANNER_TEXT);
	xprintf (
		"       TSIPCHAT %s(%s)    Use <Esc> to Exit; Use '?' for Help Screen",
		TSIPCHAT_VERNUM, TSIP_VERNUM);
	clreol ();
	normvideo ();
	textcolor (LIGHTGRAY);
	textbackground (BLUE);
	gotoxy (1, 1);
}

void clr_cmd_window (short mode)
{
	short
		i;
	static short
		endline;
	switch_window (CMDWIN);
	if (mode) {
		for (i = w_info[CMDWIN].cury + 1; i <= endline; i++) {
			gotoxy (1, i);
			clreol ();
		}
	} else {
		endline = w_info[CMDWIN].cury;
		for (i = 1; i <= endline; i++) {
			gotoxy (1,i);
			clreol ();
		}
		gotoxy (1, 1);
		highvideo ();
		textbackground (BANNER_BACKGROUND);
		textcolor (BANNER_TEXT);
		xprintf (
			"            TSIPCHAT %s:%s     Use <CR> to Return to Normal Mode",
			TSIPCHAT_VERNUM, TSIP_VERNUM);
		clreol (); show_crlf ();
		normvideo ();
		textcolor (LIGHTGRAY);
		textbackground (BLUE);
	}
	switch_window (AUTOWIN);
}

void start_menu_window (void)
{
	textattr (text_color[MENUWIN]);
}

void end_menu_window (void)
{
	textattr (text_color[CMDWIN]);
}

void initialize_screen (void)
{
	/* get initial text attributes and save for restore before exit. */
	gettextinfo (&t_info);
	clrscr();
	text_color[AUTOWIN] = LIGHTGRAY + (BLACK << 4);
	text_color[CMDWIN]  = LIGHTGRAY + (BLUE  << 4);
	text_color[MENUWIN] = LIGHTGRAY + (BLUE  << 4);
	w_info[CMDWIN].cury = 1;
	w_info[CMDWIN].curx = 1;

	/* start in auto window */
	textattr (text_color[AUTOWIN]);
	clrscr ();
	gotoxy (1, 25);
}

void reset_screen (void)
{
	window (1,1,80,25);
	/* return text to original */
	textattr (t_info.normattr);
	gotoxy (80, 25);
	show_crlf ();
   clrscr();
}

/**/
/* ****************** 2. USER INPUT PROMPTS ********************* */
/* 
 * Prompt functions: allows user to supply a character string in response 
 * to a prompt.
 */

/* These routines are to set cmdesc (TRUE = abort command).
 * Used in TSIP_CMD.C.
 */
static short cmd_abort = FALSE;

short cmd_esc (void)
{
   return cmd_abort;
}

void set_cmd_esc (short x)
{
   cmd_abort = x;
}

/* Common prompt escape sequence. */
static short esc_out (char c) {
	short cmdesc;
	/* backspace, <CR>, printable characters except control characters */
	cmdesc = (c != 0x08) && (c != 0x0D) && (!isprint (c) || iscntrl (c));
	set_cmd_esc (cmdesc);
	return cmdesc;
}

static short get_prompt_string (char kbbuf[], short maxi)
{
	char kbch;
	short i;
	
	i = 0;
	for (i = 0; i < maxi; ) {
		kbch = read_rpts_wait_for_kbhit ();
		if (kbch == 0x08) {		      /* backspace */
			xprintf ("%c", kbch);
			if (i) i--;
		} else if (kbch == 0x0D) {    /* <CR> */
			break;
		} else if (esc_out(kbch)) {
			i = 0;
			break;
		} else {
			xprintf ("%c", kbch);
			kbbuf[i] = kbch;
			i++;
		}
	}
	/* terminate string */
	kbbuf[i] = END_STRING;
	show_crlf();
	set_cmd_esc (!i);
	return i;
}

/* prompt for an integer from user */
short ask_byte (char *prompt)
{
	short	n;
	char kbbuf[30];

   n = 0;
	clreol ();
	xprintf (prompt);
	if (get_prompt_string (kbbuf, 30)) {
		set_cmd_esc (sscanf (kbbuf, "%d", &n) < 1);
	}
	return n;
}

/* prompt for a choice from among nine or fewer */
short pick_one (char *prompt, char *code_txt[], short nopts)
{
	short i, j, kbch;

	set_cmd_esc (FALSE);
	/* cycle through a series of choices & allow user to choose one. */
	/* Routine returns numerical value corresponding to choice. */
	xprintf ("%s(<Space> cycle, <CR> select, ^Z abort)", prompt);
	kbch = read_rpts_wait_for_kbhit ();
	clreol ();
	if (esc_out(kbch)) {
		return 0;
	}
	/* direct choice by digit (0..9) */
	j = (kbch - '0');
	i = 0;
	if ((j < nopts) && (j >= 0)) {
      if (code_txt[j][0] != END_STRING) {
         i = j;
      }
   }
   xprintf ("\r%s                                      ", prompt);
	for ( ; ; i++) {

		xprintf ("\r%s", prompt);
		if (i >= nopts) i -= nopts;
		/* skip choices with null character string */
		if (code_txt[i][0] == END_STRING) continue;

      highvideo ();
		xprintf ("%s (%d)", code_txt[i], i);
		lowvideo ();
		clreol ();

		for ( ; ; ) {  /* wait for <CR> or <space> or digit selection */
			kbch = read_rpts_wait_for_kbhit ();
			if (kbch == 0x0D) {  /* <CR>: choice is made */
				show_crlf ();
				return i;
			} else if (kbch == 0x20) { /* <Space>: drops out to new choice */
				break;
			} else if (esc_out(kbch)) {
				show_crlf();
				return 0;
			} else {
				j = (kbch - '0');
				if ((j < nopts) && (j >= 0)) {
					if (code_txt[j][0] != END_STRING) {
						/* direct choice by digit (0..9); offset at top of loop */
						i = j - 1;
						break;
					}
				}
			}
		}
	}
}

/* prompt user to enter an double */
double ask_dbl (char *prompt)
{
	double d = 0.0;
	char kbbuf[30];

	clreol ();
	xprintf (prompt);
	if (get_prompt_string (kbbuf, 30)) {
		set_cmd_esc (sscanf (kbbuf, "%lf", &d) < 1);
	}
	return d;
}

/* ask user for up to 4 hex codes */
unsigned char ask_hex_string (char *prompt, unsigned char *h)
{
	char kbbuf [30];

	clreol ();
	xprintf (prompt);
	if (get_prompt_string (kbbuf, 30)) {
		if (6==sscanf(kbbuf,"%2x %2x %2x %2x %2x %2x",
			&h[0],&h[1],&h[2],&h[3],&h[4],&h[5])) return 6;
		if (5==sscanf(kbbuf,"%2x %2x %2x %2x %2x",
			&h[0],&h[1],&h[2],&h[3],&h[4])) return 5;
		if (4==sscanf(kbbuf,"%2x %2x %2x %2x",
			&h[0],&h[1],&h[2],&h[3])) return 4;
		if (3==sscanf(kbbuf,"%2x %2x %2x",
			&h[0],&h[1],&h[2])) return 3;
		if (2==sscanf(kbbuf,"%2x %2x",
			&h[0],&h[1])) return 2;
		if (1==sscanf(kbbuf,"%2x",&h[0])) return 1;
		set_cmd_esc (TRUE);
	}
	return 0;
}


/* prompt user to verify choice with a 'Y' or 'y' */
short ask_verify (void)
{
	unsigned char kbch;
	short affirm;

	xprintf (" (Y/N) ");
	kbch = read_rpts_wait_for_kbhit ();
	if (esc_out(kbch)) return FALSE;
	affirm = ((kbch == 'Y') || (kbch == 'y'));
	xprintf (affirm ? "YES" : "NO");
	show_crlf ();
	return affirm;
}

short ask_verify_nocmdcrlf (void)
{
	unsigned char kbch;
	short affirm;

	xprintf (" (Y/N) ");
	kbch = read_rpts_wait_for_kbhit ();
	if (esc_out(kbch)) return FALSE;
	affirm = ((kbch == 'Y') || (kbch == 'y'));
	xprintf (affirm ? "YES" : "NO");
	return affirm;
}

/**/
/* ********************  3. TIMING ROUTINES *********************** */

/* logical indicating desire to set time */
static double
	timezone_hrs = -24.0;		/* timezone offset, hours; nonsense value to start */
static short
	timezone_set = FALSE;
const short days_of_month [12] = {
		  0,  31,  59,  90, 120, 151,
		181, 212, 243, 273, 304, 334};

double raw_PC_time (void)
{
	/*
	** Note: Borland routines have a timezone implicit in them, most often
	** -5 hours.  We use DOS access directly so this does not screw us up.
	*/
	union REGS
		regs;
	double
		day, year,
		gpsday,
		hour, minute, second;
	short
		month;

	regs.h.ah = 0x2A;	/*  get date from DOS  */
	intdos (&regs, &regs);
	year = regs.x.cx;			/*  year  */
	month = regs.h.dh;		/*  month  */
	day = regs.h.dl;			/*  day  */
	gpsday = day + days_of_month[month - 1] + (year - 1980) * 365 - 6;
	/* leap years - add one for every fourth previous February */
	gpsday += (short)(12*(year-1976)+month-3)/48;

	regs.h.ah = 0x2C;			/*  get time from DOS  */
	intdos (&regs, &regs);
	hour = regs.h.ch;			/*  hours  */
	minute = regs.h.cl;		/*  minutes  */
	second = regs.h.dh;		/*  seconds  */
	return (second + 60. * (minute + 60. * (hour + 24. * gpsday)));
}

short set_raw_PC_time (double gpstime)
{
	long	days;
	short		days_this_month, years, months;
	union REGS
		regs;
	short
		hour, minute, second,
		year, month, day;

//=========================
	days = (long)(gpstime / 86400.);

	/* determine elapsed years since start of GPS */
	years = 0;
	while(TRUE)
	{
		if(days < days_in_year[years])
			break;
		days -= days_in_year[years];
		years++;
	}
	year = 1980 + years;

	/* determine elapsed months since start of year */
	months = 0;
	while (TRUE)
	{
		if (leap_year[years])
			days_this_month = days_in_month[1][months];
		else
			days_this_month = days_in_month[0][months];

		if (days < days_this_month)
			break;

		days -= days_this_month;
		months++;
	}
	month = months + 1;
	day = (short)days + 1;

//========================

	regs.h.ah = 0x2B;			/* set date in DOS */
	regs.x.cx = year;			/* year */
	regs.h.dh = month;		/* month */
	regs.h.dl = day;			/* day */

	intdos (&regs, &regs);
	if (regs.h.al != 0x00) {
		return FALSE;
	}

	regs.h.ah = 0x2D;		   /* set time in DOS */
	hour = (short) (fmod (gpstime, 86400.) / 3600.);	/* hours */
	minute = (short) (fmod (gpstime, 3600.) / 60.);		/* minutes */
	second = (short) (fmod (gpstime, 60.));				/* seconds */
	regs.h.ch = hour;
	regs.h.cl = minute;
	regs.h.dh = second;
	regs.h.dl = 0;														/* csecs */

	intdos (&regs, &regs);
	if (regs.h.al != 0x00) {
		return TRUE;
	}
	else {
		intdos (&regs, &regs);
		return FALSE;
	}
}


void display_current_PC_time (void)
{
	time_t
		pctime;

	pctime = time(NULL);
	clreol (); show_crlf ();
	/* asctime() inserts a \n and \0 after the first 24 characters */
	xprintf ("Current PC time is %s\r", asctime (localtime(&pctime)));
	clreol ();
}

void display_timezone (void)
{
		clreol (); show_crlf ();
		xprintf ("Local offset relative to GMT is %.1f hours", timezone_hrs);
		show_crlf ();
}

void ask_for_timezone (void)
{
	show_crlf (); show_crlf ();
	xprintf (
"Before setting the clock, the time zone offset must be entered.  If the PC");
	show_crlf ();
	xprintf (
"is configured for local time, it must be adjusted to GPS (GMT) time with the");
	show_crlf ();
	xprintf (
"proper time zone offset equal to the number of time zones east of Greenwich.");
	show_crlf ();
	xprintf (
"Time zones west of Greenwich are negative.  Add an additional '1' for Daylight");
	show_crlf ();
	xprintf (
"time (value must be between -14 and 14).");
	show_crlf ();
	show_crlf ();
	timezone_set = TRUE;
	do {
		/*
		** Some time zones are not integer hour offsets: Newfoundland,
		** Saudi Arabia, India, Singapore, parts of Australia, etc.
		*/
		timezone_hrs = ask_dbl (
			"Enter timezone (PST=-8, PDT=-7, EST=-5, GMT=0): ");
		if (cmd_esc ()) {
			timezone_set = FALSE;
			timezone_hrs = -24.;
			break;
		}
	} while ((timezone_hrs < -14.) || (timezone_hrs > 14.));
	show_crlf ();
}

void GPS_time_from_PC (double *gpstime)
{

	switch_window (CMDWIN);
	display_current_PC_time();

	if (timezone_set) {
		display_timezone ();
	}
	else
	{
		ask_for_timezone();
	}

	if (cmd_esc ()) {
		xprintf ("Timezone set failure");
		return;
	}

	*gpstime = (double)raw_PC_time() - (timezone_hrs * 3600.);
}

/* setting receiver time from PC */
#define WAITING_FOR_TIMEZONE	2
#define TIME_REQUEST_ACTIVE	1
#define NO_TIME_REQUEST			0
static short
	status_of_PC_time_set = NO_TIME_REQUEST;
	/*
	** 2 = requested, timezone unknown;
	** 1 = timezone known, & re-requested;
	** 0 = no pending request.
	*/

void request_PC_time_set (void)
{
	status_of_PC_time_set = timezone_set?
		TIME_REQUEST_ACTIVE : WAITING_FOR_TIMEZONE;
}

void GPS_time_to_PC (double gpstime)
{
	static short
		under_way = FALSE;

	if (status_of_PC_time_set == NO_TIME_REQUEST || under_way) return;

	/*
	**	under_way prevents the routine from being invoked again by a 0x41
	** while it is waiting for inputs in compute_timezone_secs().
	*/

	under_way = TRUE;
	if (gpstime == 0.0)
	{
		clreol (); show_crlf ();
		xprintf ("GPS time is not available yet. Try again later.");
		set_cmd_esc (TRUE);
		timezone_set = FALSE;
		status_of_PC_time_set = NO_TIME_REQUEST;
	}
	else
	{

		switch_window (CMDWIN);

		display_current_PC_time();

		if (timezone_set)
		{
			display_timezone ();
		}
		else
		{
			ask_for_timezone();
		}

		if (cmd_esc ()) {
			xprintf ("Timezone set aborted");
			status_of_PC_time_set = NO_TIME_REQUEST;
		}
		else
		{
			if (status_of_PC_time_set == WAITING_FOR_TIMEZONE) {
				/* timezone set; re-get gpstime to get updated time */
				cmd_0x21 ();
				status_of_PC_time_set = TIME_REQUEST_ACTIVE;
			}
			else
			{
				if (set_raw_PC_time(gpstime + timezone_hrs*3600.))
				{
					xprintf ("Time set failure");
				}
				status_of_PC_time_set = NO_TIME_REQUEST;
				display_current_PC_time();
			}
		}
	}
	show_crlf ();
	under_way = FALSE;
}
/**/
/* *******************  4. STORAGE FILE CONTROL ****************** */
/* Following two routines called by open_, close_file_storage () below.
 * Return TRUE or FALSE.
 */
static short append_or_overwrite (char *newname, short *append_mode)
/* returns need_filename */
{
	FILE *stream_ptr;

	if (newname[0] == END_STRING) return TRUE;

	stream_ptr = fopen (newname, "r");
	if (stream_ptr != NULL) {
		fclose (stream_ptr);
		xprintf ("Append to existing file '%s'?", newname);
		if (ask_verify ()) {
			*append_mode = TRUE;
			return FALSE;
		} else {
			*append_mode = FALSE;
			xprintf ("Overwrite existing file '%s'?", newname);
			return (!ask_verify ());
		}
	}
	return FALSE;
}

static void autoname (char *filename)
{
	short
		week_num,
		hour_of_week;
	double
		current_time;

	current_time = get_tsip_time ();
	if (current_time == 0.0) {
		xprintf ("No current time - aborted - please retry.");
		show_crlf ();
		cmd_0x21 ();
		set_cmd_esc (TRUE);
		return;
	}
	week_num = (short) (current_time / WEEK);
	hour_of_week = (short)((current_time - week_num * WEEK) / 3600.);
	sprintf (filename, "&%03d-%03d.tsp", week_num%1000, hour_of_week);
}

void open_file_storage (FILE **stream_ptr, char *filename, short filetype)
{
	short
		append_mode,
		need_filename;
	char
		newname[80];
	char
		open_mode[3] = {END_STRING, END_STRING, END_STRING};

	set_cmd_esc (FALSE);
	if (*stream_ptr != NULL) return;
	
	append_mode = FALSE;
	if (filetype == AUTOFILE) {
		autoname (newname);
		need_filename = FALSE;
	} else {
		strcpy (newname, filename);
		if (newname[0] == END_STRING) {
			need_filename = TRUE;
			xprintf ("No previous storage file.");
         clreol ();
			show_crlf ();
		} else {
			need_filename = append_or_overwrite (newname, &append_mode);
		}
	}
	if (cmd_esc ()) return;

	do {  /* open file */
		while (need_filename) {
			xprintf ("Enter new output file name ('^Z' to abort): ");
			clreol ();
			if (!get_prompt_string (newname, 70)) return;
			/* open to read; check to see if file exists already */
			need_filename = append_or_overwrite (newname, &append_mode);
			if (cmd_esc ()) return;
		}
		open_mode[0] = (append_mode) ? 'a' : 'w';
		open_mode[1] = 'b';
		*stream_ptr = fopen (newname, open_mode);
		/* open file for binary write or append */
		if (*stream_ptr != NULL)
		{
			break;
		}
		/* file open failed; loop and ask for new file name. */
		xprintf ("Could not open '%s'.", newname);
		show_crlf ();
		if (filetype == AUTOFILE) return;
		need_filename = TRUE;
	} while (*stream_ptr == NULL);

	strcpy (filename, newname);
	xprintf ("Saving to '%s'.", filename);
	show_crlf ();
}

void close_file_storage (FILE **stream_ptr, char *filename, short filetype)
{
	set_cmd_esc (FALSE);
	if (*stream_ptr == NULL) return;

	if (filetype != AUTOFILE) {
		xprintf ("Currently storing data to '%s'. Close file?", filename);
		if (!ask_verify ()) {
			show_crlf ();
			set_cmd_esc (TRUE);
			return ;
		}
	}
	if (fclose (*stream_ptr) == EOF) {
	   xprintf ("File close error, '%s'", filename);
	} else {
		*stream_ptr = NULL;
		xprintf ("Closing '%s'.", filename);
	}
	show_crlf ();
}

/**/
/* *****************  5. SERIAL PORT CONTROL  ****************** */

short serial_port_select (short *io_code)
{
	static char
		*databit_text[4]  =  {"", "", "7", "8"},
		*parity_text[4]   =  {"no", "odd", "", "even"},
		*baud_text[10]    =  {"50 bps", "110 bps", "300 bps", "600 bps",
						   "1200 bps",	"2400 bps", "4800 bps",
						   "9600 bps", "38.4k bps", "19.2k bps"},
		*stopbit_text[2]  =  {"1", "2"};
	unsigned char
		baud, databits, stopbits, parity;
	short
		iocode;

	set_cmd_esc (FALSE);
	show_crlf ();
	xprintf ("Current serial I\\O setting: %s, %s-%s-%s",
		baud_text[(*io_code >> 5) & 0x0F], databit_text[*io_code & 3],
		parity_text[(*io_code >> 3) & 3], stopbit_text[(*io_code >> 2) & 1]);
	show_crlf ();

	baud = pick_one ("Baud rate: ", baud_text, 10);
	if (cmd_esc ()) return FALSE;

	databits = pick_one ("Data bits: ", databit_text, 4);
	if (cmd_esc ()) return FALSE;

	parity = pick_one ("Parity: ", parity_text, 4);
	if (cmd_esc ()) return FALSE;

	stopbits = pick_one ("Stop bits: ", stopbit_text, 2);
	if (cmd_esc ()) return FALSE;

	iocode = databits + (stopbits << 2) + (parity << 3) + (baud << 5);
	xprintf ("Set port to %s, %s-%s-%s?",
		baud_text[(iocode >> 5) & 0x0F], databit_text[iocode & 3],
		parity_text[(iocode >> 3) & 3], stopbit_text[(iocode >> 2) & 1]);

	if (!ask_verify_nocmdcrlf ()) return FALSE;
	*io_code = iocode;
	return TRUE;
}

