static char rcsid[] = "$Id: wizcom.c,v 1.21 1993/10/05 19:23:28 putz Exp $";
/**********************************************************************
 * wizcom - Unix to Sharp OZ-7000/8000 Wizard Organizer interface
 **********************************************************************
 * wizcom.c created 09/91 by Steve Putz (putz@parc.xerox.com)
 * derived from:
 *  oz.c rewritten 12/91 by Steve Putz (putz@parc.xerox.com)
 *  wizard.c (12/91) by Rod Whitby (rwhitby@research.canon.oz.au)
 *  original oz.c by J. Bruce Dawson, Nashua, NH (uunet!virgin!jbd)
 **********************************************************************/
static char options[] = "\
wizcom options:\n\
 -tel {1,2,3}          Telephone data (TEL 1, TEL 2, TEL 3)\n\
 -telname {1,2,3}      Telephone file name\n\
 -telfree {1,2,3}      Telephone free field names\n\
 -business             Business card data\n\
 -busfree              Business card free field names\n\
 -memo                 Memo data\n\
 -outline              Outline data\n\
 -sched                Schedule data\n\
 -ann {1,2}            Anniversary data (ANN 1, ANN 2)\n\
 -period               Periodic event data\n\
 -alarm                Alarm data\n\
 -todo                 To Do List data\n\
 -dict                 User dictionary data\n\
 -username             Get username from Wizard\n\
 -status               Get system condition from Wizard\n\
 -all                  Retrieve data from all built-in applications (above)\n\
 -userfile             User File data\n\
 -dolist               Do List data (expansion card)\n\
 -timeacct             Time Accounting data (expansion card)\n\
 -expense              Expense data (expansion card)\n\
 -spread name          Spreadsheet data (expansion card)\n\
 -macros	       Spreadsheet macros (expansion card)\n\
 -custom num name flds Specify application number, name, number of fields\n\
 -dir                  List application directory\n\
 -read                 Retrieve data from Wizard (default)\n\
 -recnum N             Specify which record to retrieve (default all)\n\
 -write                Append data to Wizard without replacing existing data\n\
 -overwrite            Store data to Wizard replacing existing data\n\
 -card  	       Use expansion memory card (same as -expansion)\n\
 -level {1,2}          Use level 1 or 2 protocol (default 2 or $WIZARDLEVEL)\n\
 -xp		       Use expanded protocol (default if $WIZARDXP is 1)\n\
 -model number         Specify model number (7000, 7600, 8000, 8600)\n\
 -raw                  Do not convert data to/from Unix format\n\
 -label                Put a [label] around each application file (default)\n\
 -nolabel              Do not put a [label] around each application file\n\
 -index count	       Request index entries rather than full records\n\
 -delete checksum      Delete the specified record from the organizer\n\
 -settime citynum date Set the Wizard's city and clock (YYYYMMDDHHMM)\n\
 -exit                 Cause Wizard to exit PC-Link\n\
 -off                  Cause Wizard to turn off\n\
 -device dev           Serial port device, e.g. /dev/ttya (default $WIZARD)\n\
 -infile file          Read from file instead of stdin\n\
 -outfile file         Write to file instead of stdout\n\
 -v                    Set verbose level 1\n\
 -V {0,1,2,3,4,5}      Set verbose level (default 0)\n\
";
/**********************************************************************
 * This program can probably handle the following Sharp Organizer models:
 *  OZ-7000/7200/ZQ5XXX		(Level I)
 *  OZ-7600/7620		(Expanded level I) (untested)
 *  OZ-8000/8200		(Level II)
 *  OZ-8600/YO-600/610		(Expanded level II)
 * Application cards should work using the -custom option or by adding
 * entries to WizApps[] like -dolist, -timeacct, -expense.
 *
 * Retrieving -dict data seems to drop data on some models (i.e. my OZ-8200).
 * The -index and -delete protocols are not supported by the organizers.
 * Use "IMPORT OTHER FORMAT" mode on 9600 models.
 **********************************************************************/

#include <stdio.h>
#include <termio.h>
#include <string.h>

extern char *getenv();		/* OS */

/* #define DATABUFLEN	(2048+3) */
#define DATABUFLEN	65536	/* plenty for spreadsheet files */
#define APPNAMELEN	11	/* application name length */
#define IB1LEN		21	/* information block 1 length */
#define IB2LEN		21	/* information block 1 length */
#define IBMAX		51	/* max level II directory response length */

/* For the Level I protocol (e.g. Wizard 7000 series) the commands
   are prefixed by 0x04.  For the Level II (e.g. the 8000 series)
   the prefix is 0x03. */

#define CMDLEV1		0x04
#define CMDLEV2		0x03

/* The following commands are supported by level I and II */

#define DIRCMD		"D"
#define DIRCMD_XP	"d"
#define SNDCMD		"S"
#define RCVCMD		"R"
#define EXITCMD		"E"	/* Exit PC-Link */
#define OFFCMD		"O"	/* Power off */

/* The following commands are supported only by level II */

#define USERCMD		"U"	/* Get username from Wizard */
#define STATCMD		"Z"	/* Get system condition from Wizard */

#define TIMECMD		"T"	/* Time Set */

/* The following commands are not supported by the 7000 or 8000 */

#define DELCMD		"C"	/* Delete data */
#define INDEXCMD	"I"	/* Get index data */

/* The following are additional special character codes */

#define WIZ_EOF		0x1A	/* control-Z indicates EOF */
#define WIZ_ERR		0x0A	/* indicates error */
#define WIZ_DATAOK	0x06	/* indicates no error on data */
#define WIZ_COMOK	0x02	/* indicates no error on command */

/* also 0x09 (\t), 0x0D (\r), 0x0A (\n) are used as separators */

/* The following are the numbers for the known Built-in applications */

#define ANUM_SCH	"0110"
#define ANUM_TEL	"0200"
#define ANUM_MEM	"0300"
#define ANUM_OUT	"1D00"
#define ANUM_BUS	"1E00"
#define ANUM_DIC	"1F00"
#define ANUM_TDL	"3110"	/* to do list (models 8600/YO-600/610) */
#define ANUM_UFL	"2F00"	/* user file (models 7600/7620) */
#define ANUM_TEM	"0b10"	/* time expense manager (untested) */
#define ANUM_SPR	"2800"	/* spreadsheet */

/* Information Block I send modes */
#define MODE_SEND	0x00
#define MODE_RECEIVE	0x10
#define MODE_OVERWRITE	0x01
#define MODE_EXPANSION	0x08

/* Information Block II fields */
#define REC_ALL		0	/* flag for retrieval all */
#define NO_CKSUM	1	/* do not check checksum */

/* modelmask bit masks */
#define MM_LEV1		0x01	/* on all level 1 models */
#define MM_LEV2		0x02	/* on all level 2 models */
#define MM_XP		0x04	/* only on expanded protocol models */
#define MM_CARD		0x08	/* only on expansion card */

#define M2		MM_LEV2
#define M12		(MM_LEV1|MM_LEV2)
#define M1X		(MM_LEV1|MM_XP)
#define M2X		(MM_LEV2|MM_XP)
#define M12C		(MM_LEV1|MM_LEV2|MM_CARD)

typedef struct _WizardApp {	/* describes an application file type */
  char *optflag;
  int modelmask;	/* bits indicate applicable models */
  int nfiles;		/* number of numbered application files */
  int nfields[2];	/* number of level I and level II fields */
  char *appnum;		/* four digit hex application number */
  char *appname;	/* initial "-" indicates appnum is special command */
} WizardApp;

/* nfiles value of 0 indicates application file is not numbered */
/* nfields value of 0 indicates command has no associated data */
/* nfields value of -1 indicates command not available at that level */

WizardApp WizApps[] = {		/* known application file types */
/* optflag,	   modelmask,
     		   |  nfiles,
     		   |  |  nfields, appnum, appname */
 { "-username",	  M2, 0, {-1,  3}, USERCMD,  "-username" },
 { "-status",	  M2, 0, {-1,  8}, STATCMD,  "-status" },
 { "-dir",	 M12, 0, { 2,  2}, DIRCMD,   "-dir" },	/* "-dir" special */
 { "-exit",	 M12, 0, { 0,  0}, EXITCMD,  "-exit" },
 { "-off",	 M12, 0, { 0,  0}, OFFCMD,   "-off" },
 { "-ann",	 M12, 2, { 2,  2}, ANUM_SCH, "ANN     %d  " },
 { "-period",	  M2, 1, { 2,  2}, ANUM_SCH, "PERIOD  %d  " },
 { "-alarm",	  M2, 1, { 1,  1}, ANUM_SCH, "D ALARM %d  " },
 { "-sched",	 M12, 1, { 2,  2}, ANUM_SCH, "SCHEDULE%d  " },
 { "-telname",	  M2, 3, { 1,  1}, ANUM_TEL, "TEL FILE%d  " },
 { "-telfree",	  M2, 3, { 5,  5}, ANUM_TEL, "TEL FREE%d  " },
 { "-tel",	 M12, 3, { 3,  8}, ANUM_TEL, "TEL     %d  " },
 { "-busfree",	  M2, 1, { 5,  5}, ANUM_BUS, "BUS FREE%d  " },
 { "-business",	  M2, 1, {13, 13}, ANUM_BUS, "BUSINESS%d  " },
 { "-memo",	 M12, 1, { 1,  1}, ANUM_MEM, "MEMO    %d  " },
 { "-outline",    M2, 1, { 1,  1}, ANUM_OUT, "OUTLINE %d  " },
 { "-dict",	  M2, 0, { 1,  1}, ANUM_DIC, "USER'S  DIC" },
 { "-todo",	 M2X, 1, { 6,  6}, ANUM_TDL, "TODO    %d  " },
 { "-userfile",	 M1X, 3, { 1,  1}, ANUM_UFL, "USER    %d  " }, /* untested */
 { "-dolist",	M12C, 0, { 1,  1}, ANUM_TEM, "DO LIST    " },
 { "-expense",	M12C, 0, { 1,  1}, ANUM_TEM, "EXPENSE    " },
 { "-timeacct", M12C, 0, { 1,  1}, ANUM_TEM, "TIME       " },
 { "-macros",	M12C, 0, { 1,  1}, ANUM_SPR, "{MACROS}   " },
 { "-spread",	M12C, 0, { 1,  1}, ANUM_SPR, NULL },
 { NULL,	   0, 0, { 0,  0}, NULL,     NULL }
};

typedef struct _DoOption {	/* describes a send or retrieve request */
  WizardApp *app;
  int level;		/* protocol level 1 or 2 */
  int xp;		/* expanded protocol flag */
  int putdata;
  int overwrite;
  int card;
  int recnum;
  int fnum;
  int indexcount;
  char *delcksum;
} DoOption;

				/* I could not verify these error codes */
#define WIZERR_IOERR	0x41	/* I/O Device Error */
#define WIZERR_MEMFULL	0x42	/* Memory overflow */
#define WIZERR_BUFFULL	0x43	/* Buffer overflow */
#define WIZERR_DATAERR	0x44	/* Data Error */
#define WIZERR_SECRET	0x46	/* Secret mode on */
#define WIZERR_NOAPP	0x48	/* Application not found */
#define WIZERR_NODATA	0x49	/* Data not found */
#define WIZERR_DATAMOD	0x4A	/* Data has been modified */
#define WIZERR_NOTSUPT	0x4B	/* command not support */
#define WIZERR_LOWBATT	0xFE	/* Low battery */
#define WIZERR_ONBREAK	0xFF	/* On/break key */

#define TRUE		1
#define FALSE		0
#define MAXOPTS		50

static int verbose = 0;		/* level for message output */

/****************************************************************************/
static int
mywrite(fd, buf, nbytes)
    int fd;
    char *buf;
    int nbytes;
{
    int count = write(fd, buf, nbytes);
    if (count == nbytes) return(0);	/* success */
    if (count == -1) perror("write failed");
    else fprintf(stderr, "write failed\n");
    return(-1);	/* failure */
} /* mywrite */

/****************************************************************************/
static int
myread(fd, buf, nbytes)
    int fd;
    char *buf;
    int nbytes;
{
    int count = read(fd, buf, nbytes);
    if (count == nbytes) return(0);	/* success */
    if (count == -1) perror("read failed");
    else if (verbose) fprintf(stderr, "end of file\n");
    return(-1);	/* failure */
} /* myread */

/****************************************************************************/
static int
wizard_open(devName)
    char	*devName;
{
    int device;
    struct termio   t;

    /* Open the line with the PC Link on it */
    if (devName == NULL) {
	devName = getenv("WIZARD");
	if (devName == NULL) {
	    fprintf(stderr, "must set WIZARD or use -device option\n");
	    exit(1);
	}
    }
    if (verbose >= 2)
	fprintf(stderr, "Opening %s\n", devName);
    device = open(devName, 2);
    if (device < 0) {
	fprintf(stderr, "Can't open %s for update\n", devName);
	exit(1);
    }
    /* Set wizard's line to noecho B9600, raw */
    if (ioctl(device, TCGETA, &t) < 0) {
	perror("ioctl TCGETA failed");
	exit(1);
    }
    t.c_iflag = IGNBRK | IXON | IXOFF;
    t.c_oflag = 0;
    t.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    t.c_lflag = 0;
    t.c_cc[VEOF] = 100;
    t.c_cc[VEOL] = 10;
    if (ioctl(device, TCSETA, &t) < 0) {
	perror("ioctl TCSETA failed");
	exit(1);
    }
    return(device);
} /* wizard_open */

/****************************************************************************/
static void
wizard_close(fd)
    int	fd;
{
    if (ioctl(fd, TCSBRK, 1) < 0) perror("TCSBRK");
    if (close(fd) < 0) perror("close");
} /* wizard_close */

/****************************************************************************/
static void
print_data(outfile, conv, buf)
    FILE *outfile;
    int conv;
    char *buf;
{
    char ch;

    if (!conv) {
	fprintf(outfile, "%s", buf);
	return;
    }
    /* convert mode: print CR-LF as "\", TAB as "`" */

    while ((ch = *buf) != '\0') {
	switch (ch) {
	    case '\t':
		putc('`', outfile);
		break;
	    case '\r':
		putc('\\', outfile);
		break;
	    case '`':
	    case '\\':
		putc('\t', outfile);	/* quote actual ` or \ with a TAB */
		putc(ch, outfile);
		break;
	    case '\n':		/* ignore LF */
		break;
	    default:
		putc(ch, outfile);
		break;
	}
	buf++;
    }
} /* print_data */

/****************************************************************************/
static int s_fields[7];
static char s_model[41];

/****************************************************************************/
static void
collect_status(field, buf)
    int field;
    char *buf;
{
    if (field < 7) {
	s_fields[field] = strtol(buf,NULL,16);
    } else if (field == 7) {
	int len = strlen(buf) - 2;
	if (len < 0) len = 0;
	else if (len > 40) len = 40;
	strncpy(s_model, buf, len);
        s_model[len] = '\0';
    }
} /* collect_status */

/****************************************************************************/
static void
print_status(outfile)
    FILE *outfile;
{
    static char *langs[] = { "English", "?", "German", "French",
	"Italian", "Spanish", "Swedish", "Finnish"};

    if (s_fields[4] < 0 || s_fields[4] > 7) s_fields[4] = 1;

    fprintf(outfile, "\
	secret mode:	 %s\n\
	calendar mode:	 %s\n\
	time mode:	 %s\n\
	date mode:	 %s\n\
	multi-language:	 %s\n\
	link mode:	 %d\n\
	used memory:	 %d (%.0f%%)\n\
	memory capacity: %d\n\
	language:	 %s\n\
	display size:	 %d x %d\n\
	model name:	 %s\n",
	s_fields[0] & 0x01 ? "on" : "off",
	s_fields[0] & 0x04 ? "Mon->Sun" : "Sun->Sat",
	s_fields[0] & 0x08 ? "12hr" : "24hr",
	s_fields[0] & 0x10 ? "DD/MM/YYYY" : "MM/DD/YYYY",
	s_fields[0] & 0x20 ? "yes" : "no",
	s_fields[1],
	s_fields[2], s_fields[2]*100.0/s_fields[3],
	s_fields[3],
	langs[s_fields[4]],
	s_fields[5],
	s_fields[6],
	s_model);
} /* print_status */

/****************************************************************************/
static int
get_raw_record(fd, buf, buflen)
    int 	fd; 
    char	*buf;
    int 	buflen; 
{
    char    *obuf = buf;
    char    c;

    if (myread(fd, &c, 1)) return(-1);
    if (c == WIZ_EOF) return(-1);
    *buf++ = c;
    while (!myread(fd, &c, 1)) {
	if (--buflen <= 0) {
	    fprintf(stderr, "buffer overflow\n");
	    break;
	}
	if (*(buf - 1) == '\r' && c == '\n') {
	    *buf++ = c;
	    *buf++ = '\0';
	    return(0);		/* success */
	}
	*buf++ = c;
    }
    *buf++ = '\0';
    fprintf(stderr, "Truncated buffer:\n%s\n", obuf);
    return(-1);	/* failed */
} /* get_raw_record */

/****************************************************************************/
static int
skipsection(fd)
    int		fd; 
{
    char c;
    int foundtag = FALSE;

    /* look for a line containing [^] */
    while (!myread(fd, &c, 1)) {
	if (c != '\n') continue;
	if (foundtag) break;		/* reached end of tag line */
	if (myread(fd, &c, 1)) break;
	if (c != '[') continue;
	if (myread(fd, &c, 1)) break;
	if (c == '^') foundtag = TRUE;
	else fprintf(stderr, "warning: missing [^]\n");
    }
    return(0);
} /* skipsection */

/****************************************************************************/
static int
get_unix_record(fd, label, buf)
    int		fd; 
    char	*label; 
    char	*buf;
{
    char    *tail = buf;
    int	    err;
    char    c;

    /* convert "\" to CR-LF, convert "`" to TAB, ignore LF */
    /* TAB is used to quote the following char (e.g. \ or `) */
    /* if labelcheck, process lines between [label] and [^] */

    while (!myread(fd, &c, 1)) {
	if (c == '\n') {
	    continue;
	} else if (label && tail == buf && c == '[') { /* verify label */
	    if (myread(fd, tail, 1)) break;
	    tail++; 
	    for (;;) {
		err = myread(fd, tail, 1);
		if (err || *tail == '\n') break;
		tail++;
	    }
	    *tail = '\0';
	    if (err) break;
	    if (verbose >= 3) fprintf(stderr, "label: [%s\n", buf);
	    if (buf[0] == '^') {		/* end of application */
		return(-1);
	    }
	    if (strncmp(buf, label, strlen(label))) {
		fprintf(stderr, "skipping section: [%s\n", buf);
		skipsection(fd);
	    }
	    tail = buf;		/* label ok or section skipped, start over */
	} else if (c == '\t') {		/* TAB quotes next character */
	    if (myread(fd, &c, 1)) break;
	    *tail++ = c;
	} else if (c == '`') {
	    *tail++ = '\t';
	} else if (c == '\\') {
	    *tail++ = '\r';
	    *tail++ = '\n';
	    *tail++ = '\0';
	    return(0);		/* success */
	} else {
	    *tail++ = c;
	}
    }
    *tail = '\0';
    if (tail > buf) fprintf(stderr, "Truncated input:\n%s\n", buf);
    return(-1);	/* error or end of file */
} /* get_unix_record */

/****************************************************************************/
static int
get_record(infd, conv, label, buf, buflen)
    int		infd; 
    int		conv;
    char	*label; 
    char	*buf;
    int		buflen;
{
    if (conv) return(get_unix_record(infd, label, buf));
    else return(get_raw_record(infd, buf, buflen));
} /* get_record */

/****************************************************************************/
static int
put_string(wizfd, comstr, comment)
    int wizfd;
    char *comstr, *comment;
{
    if (verbose >= 3 && comment != NULL) {
	fprintf(stderr, "Sending %s\n", comment);
    }
    if (verbose >= 6) {
	fprintf(stderr, "Sending: '%s'\n", comstr);
    }
    while (*comstr) {
	if (mywrite(wizfd, comstr++, 1)) return(-1);	/* failed */
    }	
    return(0);	/* success */
} /* put_string */

/****************************************************************************/
static int
put_command(wizfd, level, command)
    int wizfd, level;
    char *command;
{
    char combuf[3];

    if (verbose >= 3)
	fprintf(stderr, "sending level %d command: %s\n", level, command);

    sprintf(combuf, "%c%1.1s", (level == 1) ? CMDLEV1 : CMDLEV2, command);
    return(put_string(wizfd, combuf, NULL));
} /* put_command */

/****************************************************************************/
static int
get_errorcode(wizfd, level, okcode)
    int wizfd, level, okcode;
{
    char errorcode[3], *msg;
    int errorval;

    if (verbose >= 2) fprintf(stderr, "Checking return code\n");

    if (myread(wizfd, errorcode, 1)) return(-1);	/* failed */
    if (errorcode[0] == WIZ_ERR) {
	if (level == 1) {
	    fprintf(stderr, "Received error code\n");
	    return(-1);
	}
	/* get level II error code */
	if (myread(wizfd, errorcode, 2)) return(-1);	/* failed */
	errorcode[2] = '\0';
	errorval = strtol(errorcode, NULL, 16);
	switch (errorval) {
	case WIZERR_IOERR:	msg = "I/O Device Error"; 	break;
	case WIZERR_MEMFULL:	msg = "Memory overflow"; 	break;
	case WIZERR_BUFFULL:	msg = "Buffer overflow";	break;
	case WIZERR_DATAERR:	msg = "Data Error"; 		break;
	case WIZERR_SECRET:	msg = "Secret mode on"; 	break;
	case WIZERR_NOAPP:	msg = "Application not found"; 	break;
	case WIZERR_NODATA:	msg = "Data not found"; 	break;
	case WIZERR_DATAMOD:	msg = "Data has been modified"; break;
	case WIZERR_NOTSUPT:	msg = "Command not supported"; 	break;
	case WIZERR_LOWBATT:	msg = "Low battery"; 		break;
	case WIZERR_ONBREAK:	msg = "On/break key"; 		break;
	default:		msg = "unexpected error code";	break;
	}
	fprintf(stderr, "Received error code %s: %s\n", errorcode, msg);
	if (errorval == 0) errorval = -1;	/* return 0 only if no error */
    } else if (errorcode[0] == okcode) {
	if (verbose) fprintf(stderr, "ok\n");
	errorval = 0;
    } else {
	fprintf(stderr, "unexpected return code: %02X, expected %02X\n",
		errorcode[0], okcode);
	errorval = 0;	/* forgive */
    }
    return(errorval);
} /* get_errorcode */

/****************************************************************************/
static int
get_checksum(cksum, wizfd)
    unsigned short	cksum;
    int		wizfd;
{
    char    myhexsum[8];
    char    wizhexsum[8];

    if (verbose >= 2) fprintf(stderr, "Comparing checksums\n");

    sprintf(myhexsum, "%02X%02X\r\n%c",
	cksum & 0xFF, (cksum >> 8) & 0xFF, WIZ_EOF);
    if (myread(wizfd, wizhexsum, 7)) return(-1);	/* failed */
    wizhexsum[7] = '\0';

    /* Compare checksums */
    if (strcmp(myhexsum, wizhexsum) != 0) {
	fprintf(stderr, "Checksums different: expected: %s received: %s\n",
		myhexsum, wizhexsum);
	return(-1);
    } else {
	if (verbose >= 4) fprintf(stderr, "Checksum: %s\n", wizhexsum);
	if (verbose) fprintf(stderr, "Checksum ok\n");
    }
    return(0);		/* success */
} /* get_checksum */

/****************************************************************************/
static int
put_checksum(cksum, wizfd, level)
    unsigned short cksum;
    int wizfd, level;
{
    char    myhexsum[8];

    sprintf(myhexsum, "%02X%02X\r\n%c",
	cksum & 0xFF, (cksum >> 8) & 0xFF, WIZ_EOF);

    if (put_string(wizfd, myhexsum, "checksum")) return(-1);

    if (verbose >= 2) fprintf(stderr, "getting checksum response\n");
    /* Read error if any */
    if (get_errorcode(wizfd, level, WIZ_DATAOK)) {
	return(-1);
    } else {
	if (verbose >= 4) fprintf(stderr, "Checksum: %s\n", myhexsum);
	if (verbose) fprintf(stderr, "Checksum ok\n");
	return(0);
    }
} /* put_checksum */

/****************************************************************************/
unsigned short
compute_checksum(str)
    unsigned char *str;
{
    unsigned short cs = 0;

    while (*str) {
	cs += *str;
	str++;
    }
    return(cs);
} /* compute_checksum */

/****************************************************************************/
int
receive_data(wizfd, outfile, doit, convert, labels)
    int wizfd;
    FILE *outfile;
    DoOption *doit;
    int convert, labels;
{
    unsigned short computed_checksum = 0;
    char *com = RCVCMD;
    int useapp = TRUE;
    char appname[APPNAMELEN+1];
    unsigned char infdata[IB1LEN+IB2LEN+1], *head1;
    unsigned char wizinfdata[IB1LEN+IB2LEN+1];
    unsigned char modebyte = MODE_RECEIVE;
    char buf[DATABUFLEN];
    int count;

    if (doit->indexcount >= 0) com = INDEXCMD;	/* try index request */
    else if (doit->delcksum) com = DELCMD;	/* try delete request */

    if (doit->app->appname[0] == '-' || doit->app->appname[0] == '\0') {
	/* special command, e.g. "D" for -dir */
	com = doit->app->appnum;
	if (!strcmp(com, DIRCMD) &&  doit->xp) com = DIRCMD_XP;
	sprintf(appname, "%s", doit->app->appname);
	useapp = FALSE;
    } else if (doit->fnum > 0) {		/* insert file number */
	sprintf(appname, doit->app->appname, doit->fnum);
    } else {
	sprintf(appname, "%-11.11s", doit->app->appname);
    }
    if (verbose) fprintf(stderr, "Sending request for '%s'\n", appname);
    if (put_command(wizfd, doit->level, com)) return(-1);
    if (doit->app->nfields[doit->level-1] == 0) return(0); /* no fields */

    if (useapp) {
	if (!strcmp(com, INDEXCMD)) {		/* send index request */
	    sprintf(infdata, "0000\t%02X%02X\t%02X%02X\t\r\n",
		    doit->recnum & 0xFF, (doit->recnum >> 8) & 0xFF,
		    doit->indexcount & 0xFF, (doit->indexcount >> 8) & 0xFF);
	} else if (!strcmp(com, DELCMD)) {	/* send delete request */
	    sprintf(infdata, "0000\t%02X%02X\t%06s\t\r\n",
		    doit->recnum & 0xFF, (doit->recnum >> 8) & 0xFF,
		    doit->delcksum);
	} else if (doit->level >= 2) {		/* Insert level II block */
	    int csum = 0;
	    sprintf(infdata, "0000\t%02X%02X\t%02X%02X%02X%02X\t\r\n",
		    doit->recnum & 0xFF, (doit->recnum >> 8) & 0xFF,
		    NO_CKSUM, csum & 0xFF, (csum >> 8) & 0xFF,
		    (csum >> 16) & 0xFF);
	} else {
	    infdata[0] = '\0';
	}
	head1 = infdata+strlen(infdata);
	if (doit->card) {
	    modebyte |= MODE_EXPANSION;
	    if (verbose) fprintf(stderr, "Reading from expansion memory\n");
	}
	sprintf(head1, "%4.4s%02X\r\n%-11.11s\r\n",
	    doit->app->appnum, modebyte, appname);
	if (put_string(wizfd, infdata, "data request")) return(-1);
    
	if (!strcmp(doit->app->appnum, ANUM_TEM)) {
	    /* Special case. Apparently with the Time Expense Manager IC
	     * card, the returned mode byte is always 00 regardless, so
	     * pretend that's what we sent (including the checksum). Also,
	     * only level 1 appears to work correctly for reading.
	     */
	    if (verbose >= 2) fprintf(stderr,
		"Expecting mode 00 (vs. %02X) from Time Expense card\n",
		modebyte);
	    sprintf(head1, "%4.4s%02x\r\n%-11.11s\r\n",
		doit->app->appnum, 0, appname);
	}
	/* The checksum is only calculated over the Level I header */
	computed_checksum = compute_checksum(head1);
	if (get_errorcode(wizfd, doit->level, WIZ_COMOK)) return(-1);
    
	count = (doit->level == 1) ? IB1LEN : IB1LEN+IB2LEN;
	if (myread(wizfd, wizinfdata, count)) return(-1);
	wizinfdata[count] = '\0';
    
	if (strcmp(infdata, wizinfdata) != 0) {
	    fprintf(stderr,
		"Info blocks mismatch, expected: '%s'\nbut received: '%s'\n",
		infdata , wizinfdata);
	    /*return(-1);*/
	}
    }
    if (labels) fprintf(outfile, "[%s]\n", appname);

    /* note special case: level II directory listing starts with model name
	which has only one field and is not included in the checksum */

    if (doit->level >= 2 && !strcmp(appname, "-dir")) count = 0;
    else count = doit->app->nfields[doit->level-1];

    while (!get_raw_record(wizfd, buf, DATABUFLEN)) {
	unsigned short cs;
	if (verbose >= 4) {
	    fprintf(stderr, ".");
	    fflush(stderr);
	}
	cs = compute_checksum(buf);
	if (count > 0) computed_checksum += cs;		/* see note above */
	if (!strcmp(appname, "-status")) {
	    collect_status(doit->app->nfields[doit->level-1]-count, buf);
	}
	print_data(outfile, convert, buf);
	if (--count <= 0) {
	    putc('\n', outfile);
	    count = doit->app->nfields[doit->level-1];
	}
    }
    if (count != doit->app->nfields[doit->level-1]) {
	putc('\n', outfile);
	fprintf(stderr, "Warning: expected %d more fields\n", count);
    }
    if (labels) fprintf(outfile, "[^]\n");
    fflush(outfile);
    if (verbose && !strcmp(appname, "-status")) {
	print_status(stderr);
    }
    computed_checksum += WIZ_EOF;
    return(get_checksum(computed_checksum, wizfd));
} /* receive_data */

/****************************************************************************/
int
send_data(infile, wizfd, doit, convert, labels)
    FILE *infile;
    int wizfd;
    DoOption *doit;
    int convert, labels;
{
    unsigned short computed_checksum = 0;
    char *com = SNDCMD;
    char appname[APPNAMELEN+1], *labelcheck = NULL;
    unsigned char infdata[IB1LEN+1];
    unsigned char modebyte = MODE_SEND;
    char buf[DATABUFLEN];
    int infd, count;

    infd = fileno(infile);
    if (doit->app->appname[0] == '-' || doit->app->appname[0] == '\0') {
	/* special command */
	com = doit->app->appnum;
	sprintf(appname, "%s", doit->app->appname);
    } else if (doit->app->appnum > 0) {		/* insert file number */
	sprintf(appname, doit->app->appname, doit->fnum);
    } else {
	sprintf(appname, "%-11.11s", doit->app->appname);
    }
    if (strcmp(com, SNDCMD) && doit->app->nfields[doit->level-1] > 0) {
	fprintf(stderr, "error: writing %s data is not supported\n", appname);
	return(-1);
    }
    if (verbose) fprintf(stderr, "Telling Wizard to receive '%s'\n", appname);
    if (put_command(wizfd, doit->level, com)) return(-1);
    if (doit->app->nfields[doit->level-1] == 0) return(0); /* no fields */

    if (doit->overwrite) {
	modebyte |= MODE_OVERWRITE;
	if (verbose) fprintf(stderr, "Setting overwrite mode\n");
    }
    if (doit->card) {
	modebyte |= MODE_EXPANSION;
	if (verbose) fprintf(stderr, "Sending to expansion memory\n");
    }
    sprintf(infdata, "%4.4s%02X\r\n%-11.11s\r\n",
	doit->app->appnum, modebyte, appname);
    if (put_string(wizfd, infdata, "information block")) return(-1);
    computed_checksum = compute_checksum(infdata);

    /* Read records from stdin */
    count = doit->app->nfields[doit->level-1];
    if (labels) labelcheck = appname;
    while (!get_record(infd, convert, labelcheck, buf, DATABUFLEN)) {
	if (verbose >= 5) fprintf(stderr, buf);
	else if (verbose >= 4) {
	    fprintf(stderr, ".");
	    fflush(stderr);
	}
	if (put_string(wizfd, buf, NULL)) break;
	computed_checksum += compute_checksum(buf);
	if (--count == 0) {
	    count = doit->app->nfields[doit->level-1];
	    if (labels) labelcheck = appname;
	} else {
	    labelcheck = NULL;
	}
    }
    if (count != doit->app->nfields[doit->level-1]) {
	fprintf(stderr, "Warning: expected %d more fields\n", count);
    }
    buf[0] = WIZ_EOF;
    buf[1] = '\0';
    put_string(wizfd, buf, "EOF");		/* Output EOF */
    computed_checksum += compute_checksum(buf);
    return(put_checksum(computed_checksum, wizfd, doit->level));
} /* send_data */

/****************************************************************************/
static int
set_citytime(fd, citynum, timestr)
    int fd, citynum;
    char *timestr;
{
    char buf[19];
    unsigned short computed_checksum;

    put_command(fd, 2, TIMECMD);
    sprintf(buf, "%02X\r\n%12.12s\r\n%c", citynum, timestr, WIZ_EOF);
    put_string(fd, buf, "city and time");
    computed_checksum = compute_checksum(buf);
    return(put_checksum(computed_checksum, fd, 2));
} /* set_citytime */

/****************************************************************************/
main(argc, argv)
    int 	argc;
    char	**argv;
{
    DoOption	doList[MAXOPTS], *doit;
    int 	doCount = 0;		/* number of apps specified */
    char	*devName = NULL;	/* serial port device name */
    int 	device;			/* serial port file descriptor */
    int 	convert = TRUE;		/* for Unix editable format */
    int 	labels = TRUE;		/* for section labels */
    int 	appcount = 0;
    int 	citynum = -1;
    char 	*str, *timestr;

    int 	level = 2;		/* protocol level 1 or 2 */
    int 	xp = FALSE;		/* expanded protocol */
    int 	putdata = FALSE;	/* write data */
    int 	overwrite = FALSE;	/* overwrite existing data */
    int 	recnum = REC_ALL;	/* retrieve record number */
    int 	card = FALSE;		/* expansion memory card */
    int 	indexcount = -1;	/* retrieve index entries */
    char	*delcksum = NULL;	/* delete flag/checksum */

    bzero(doList, sizeof(doList));
    str = getenv("WIZARDLEVEL");
    if (str != NULL) level = atoi(str);
    str = getenv("WIZARDXP");
    if (str != NULL) xp = atoi(str);

    if (argc <= 1) {
	fprintf(stderr, "%s", options);
	exit(0);
    }

    /* first increment will skip program name in argv[0] */
    while (*++argv != NULL) {
	int foundopt = 0;
	WizardApp *opt;
	for (opt = WizApps; opt->optflag != NULL; opt++) {
	    if (!strcmp(*argv, opt->optflag) && doCount < MAXOPTS) {
		doList[doCount].app = opt;
		if (opt->nfields[level-1] == -1)
		    doList[doCount].level = (level == 1 ? 2 : 1); /* force */
		else if (!strcmp(opt->appnum, ANUM_TEM) &&
			level == 2 && !putdata)
		    doList[doCount].level = 1;		/* special case */
		else
		    doList[doCount].level = level;
		doList[doCount].xp = xp;
		doList[doCount].putdata = putdata;
		doList[doCount].overwrite = overwrite;
		doList[doCount].recnum = recnum;
		doList[doCount].indexcount = indexcount;
		doList[doCount].delcksum = delcksum;
		if (opt->modelmask & MM_CARD) doList[doCount].card = TRUE;
		else doList[doCount].card = card;
		if (opt->appname == NULL) opt->appname = *++argv;
		if (opt->nfiles > 1) doList[doCount].fnum = atoi(*++argv);
		else doList[doCount].fnum = opt->nfiles;
		doCount++;
		if (opt->appname[0] != '-') appcount++;
		card = FALSE;		/* reset these defaults */
		recnum = REC_ALL;
		indexcount = -1;
		delcksum = NULL;
		foundopt++;
		break;
	    }
	}
	if (foundopt) continue;
	if (!strcmp(*argv, "-all")) {
	    WizardApp *opt;
	    labels = convert = TRUE;
	    for (opt = WizApps; opt->optflag != NULL; opt++) {
		int fn = 1;
		if (opt->nfiles == 0) fn = 0;
		for (; fn <= opt->nfiles; fn++) {
		    if (doCount >= MAXOPTS) break;
		    if (opt->nfields[level-1] == 0) continue;
		    if (putdata && opt->appname[0] == '-') continue;
				/* check if application not available */
		    if ((opt->modelmask & level) == 0) continue;
		    if ((opt->modelmask & MM_XP) && !xp) continue;
		    if (opt->modelmask & MM_CARD) continue;
		    doList[doCount].app = opt;
		    doList[doCount].level = level;
		    doList[doCount].xp = xp;
		    doList[doCount].putdata = putdata;
		    doList[doCount].overwrite = overwrite;
		    doList[doCount].recnum = recnum;
		    doList[doCount].indexcount = indexcount;
		    doList[doCount].delcksum = delcksum;
		    doList[doCount].card = FALSE;
		    doList[doCount++].fnum = fn;
		    appcount++;
		    if (verbose >= 4) fprintf(stderr, "%s %s %d %d\n",
			opt->appnum, opt->appname, fn, opt->modelmask);
		};
	    }
	} else if (!strcmp(*argv, "-custom")) {	/* custom app */
	    opt = (WizardApp *)malloc(sizeof(WizardApp));
	    /* optflag, modelmask, nfiles don't matter */
	    opt->appnum = *++argv;
	    opt->appname = *++argv;
	    opt->nfields[level-1] = atoi(*++argv);
	    doList[doCount].app = opt;
	    doList[doCount].level = level;
	    doList[doCount].xp = xp;
	    doList[doCount].putdata = putdata;
	    doList[doCount].recnum = recnum;
	    doList[doCount].indexcount = indexcount;
	    doList[doCount].delcksum = delcksum;
	    doList[doCount].card = card;
	    doList[doCount++].fnum = 0;
	} else if (!strcmp(*argv, "-settime")) {
	    citynum = atoi(*++argv);
	    timestr = *++argv;
	} else if (!strcmp(*argv, "-level")) {
	    level = atoi(*++argv);
	} else if (!strcmp(*argv, "-xp")) {
	    xp = TRUE;
	} else if (!strcmp(*argv, "-model")) {
	    int model = atoi(*++argv);
	    if (model == 7000 || model == 7200) {
		level = 1; xp = FALSE;
	    } else if (model == 7600 || model == 7620) {
		level = 1; xp = TRUE;
	    } else if (model == 8000 || model == 8200) {
		level = 2; xp = FALSE;
	    } else if (model == 8600 || model == 600 || model == 610) {
		level = 2; xp = TRUE;
	    } else {
		fprintf(stderr, "unknown model: %s\n", *argv);
		exit(1);
	    }
	} else if (!strcmp(*argv, "-read")) {
	    putdata = FALSE;
	} else if (!strcmp(*argv, "-recnum")) {
	    recnum = atoi(*++argv);
	    putdata = FALSE;
	} else if (!strcmp(*argv, "-index")) {
	    indexcount = atoi(*++argv);
	    putdata = FALSE;
	} else if (!strcmp(*argv, "-delete")) {
	    delcksum = *++argv;
	    putdata = FALSE;
	} else if (!strcmp(*argv, "-overwrite")) {
	    putdata = TRUE;
	    overwrite = TRUE;
	} else if (!strcmp(*argv, "-write")) {
	    putdata = TRUE;
	    overwrite = FALSE;
	} else if (!strcmp(*argv, "-raw")) {
	    convert = FALSE;
	    labels = FALSE;
	} else if (!strcmp(*argv, "-label")) {
	    labels = TRUE;
	} else if (!strcmp(*argv, "-nolabel")) {
	    labels = FALSE;
	} else if (!strcmp(*argv, "-expansion") || !strcmp(*argv, "-card")) {
	    card = TRUE;
	} else if (!strcmp(*argv, "-device")) {
	    devName = *++argv;
	} else if (!strcmp(*argv, "-v")) {
	    verbose = 1;
	} else if (!strcmp(*argv, "-V")) {
	    verbose = atoi(*++argv);
	} else if (!strcmp(*argv, "-infile")) {
	    if (freopen(*++argv, "r", stdin) == NULL) {
		perror(*argv);
		exit(1);
	    }
	} else if (!strcmp(*argv, "-outfile")) {
	    if (freopen(*++argv, "w", stdout) == NULL) {
		perror(*argv);
		exit(1);
	    }
	} else {
	    fprintf(stderr, "invalid option: %s\n%s", *argv, options);
	    exit(1);
	}
    }

    if (!convert && labels) {
	fprintf(stderr,
	"warning: data produced with -raw -label will not read in correctly\n");
    }
    if (!labels && appcount > 1) {
	fprintf(stderr,
	"warning: multiple applications will not read in correctly with\
-raw or -nolabel\n");
    }

    device = wizard_open(devName);
    if (device < 0) exit(1);

    if (timestr) {
	set_citytime(device, citynum, timestr);
    }

    for (doit = doList; doit->app != NULL; doit++) {
	if (doit->putdata) {
	    if (send_data(stdin, device, doit, convert, labels)) break;
	} else {
	    receive_data(device, stdout, doit, convert, labels);
	    /* keep going if error */
	}
    }

    wizard_close(device);
    exit(0);
} /* main */

/****************************************************************************/
