#include <sys/param.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <netdb.h>
#include <fcntl.h>
#include <stdio.h>
#include <paths.h>
#include <signal.h>

int	fd;
char	*dev;
char    name[32];
char    devname[32];

#define DEFAULT_BAUD	38400

int hold_open = 6000;
int hold_digit = 600;
int interval = 50;		/* timer upcounter interval, mS */
int upcount = 0;		/* up counter */
int inhere = 0;			/* currently dialing */

int speed = DEFAULT_BAUD;
char digits[40];		/* aray of typed chars */
int ndigits = 0;		/* chars in array */

struct termios console_tp;	/* saved console TERMIOS settings */

struct itimerval it;		/* interval timer junk */

void *timeout();		/* our signal handler */

static char usage_str[] = "\
usage: %s [-s <speed> ] <device>\n\
	-o -- hold-open time\n\
	-k -- hold-digit time\n\
	-s -- baud rate (default %s)\n";

main(argc, argv)
	int argc;
	char *argv[];
{
	int option;
	int c;
	int f;

	extern char *optarg;
	extern int optind;

	while ((option = getopt(argc, argv, "o:k:s:")) != EOF) {
		switch (option) {
		case 'o':			/* speed */
			hold_open = atoi(optarg);
			break;
		case 'k':			/* speed */
			hold_digit = atoi(optarg);
			break;
		case 's':			/* speed */
			speed = atoi(optarg);
			break;
		case '?':
		default:
			fprintf(stderr, usage_str, argv[0], DEFAULT_BAUD);
			exit(1);
		}
	}
	if (optind == argc - 1)
		dev = argv[optind];
	if (dev == (char *)0) {
		fprintf(stderr, usage_str, argv[0], DEFAULT_BAUD);
		exit(2);
	}

	if (strncmp(_PATH_DEV, dev, sizeof(_PATH_DEV) - 1)) {
		strcpy(devname, _PATH_DEV);
		strcat(devname, "/");
		strncat(devname, dev, 10);
		dev = devname;
	}

	ndigits= 0;				/* no digits typed */
	fd= -1;					/* tty not open */
	f= open_console();			/* open the raw console */
	signal(SIGINT, SIG_IGN);		/* we handle SIGINT */
	if ((int) signal(SIGALRM, (void *) timeout) == -1) {
		perror("fone:signal");		/* point to event handler */
		exit(1);
	}
	settimer(interval);			/* timer tick every 100mS */

	for (;;) {
		read(f, &c, 1);
		switch (tolower(c)) {

		/* characters to digits */
			case 'a': case 'b': case 'c': c= '2'; goto digit;
			case 'd': case 'e': case 'f': c= '3'; goto digit; 
			case 'g': case 'h': case 'i': c= '4'; goto digit;
			case 'j': case 'k': case 'l': c= '5'; goto digit;
			case 'm': case 'n': case 'o': c= '6'; goto digit;
			case 'p': case 'r': case 's': c= '7'; goto digit;
			case 't': case 'u': case 'v': c= '8'; goto digit;
			case 'w': case 'x': case 'y': c= '9'; goto digit;

		/* on the telephone keypad */
			case '0': case '1': case '2':
			case '3': case '4': case '5':
			case '6': case '7': case '8':
			case '9': case '*': case '#':
digit:				digits[ndigits++]= c;
				if (! inhere) upcount= 0;/* reset the timer */
				putchar(c);
				break;

		/* commonly used */
			case '(': case ')': case '-': case ' ':
				putchar(c); break;
			case 27: case 3: 
				closetty();		/* close the tty port */
				close_console(f);	/* restore the console */
				exit(0); break;
		}
	}
}

/* Increment our local up counter. */

void *
timeout() {

	if (! inhere) {
		if (upcount >= hold_digit) dial();
		if (upcount > hold_open) closetty();
	}
	upcount += interval;
}

/* Delay N mS */

delay(n)
int n;
{
	sleep(1);
}

/* Set the real-time interval timer to go off in (n) mS. */

settimer(n)
	int n;
{
	struct itimerval foo;
#define ITIMER_REAL 0

	it.it_interval.tv_sec= it.it_value.tv_sec= 0;
	it.it_interval.tv_usec= it.it_value.tv_usec= n * 1000;
	if (setitimer(ITIMER_REAL, &it, &foo) < 0) {
		perror("fone:setitimer");
		fprintf(stderr,"it.it_value.tv_usec=%d\n", it.it_value.tv_usec);
		exit(1);
	}
}

/* Open the tty port, if necesary, and dial the digits. */

dial() {
int c;

	if (ndigits == 0) return;		/* nothing to do! */
	inhere= 1;				/* break fatal recursion */

	if (fd == -1) opentty();		/* open, if not already */
	if (write(fd, "ATS11=90S7=1S6=1E0DT", 20) == 0) {/* write command */
		perror("fone:dial:write");
		exit(1);
	}
	write(fd, digits, ndigits);		/* the digits */
	write(fd, ";\r", 2);			/* command terminator */
	putchar('\r'); putchar('\n');
	ndigits= 0;				/* digits used up */
	inhere= 0;
}

/* Close the tty port. */

closetty() {

	if (fd == -1) return;			/* not open */

	write(fd, "\r", 1);			/* abort any command */
	delay(200);
	write(fd, "ATH0\r", 5);			/* go onhook */
	delay(200);
	write(fd, "ATZ\r", 4);			/* reset the modem */
	delay(400);
	close(fd);
	fd= -1;
}

/* Open the raw console. There is no way to "un-raw" the console directly (see the
man page on crmakeraw for some excuses), we have to save the current termios
stuff, make our changes, and restore them upon exit. */

int
open_console() {
int f;
struct termios tp;

	f= open("/dev/stdin", O_RDWR);		/* open raw console */
	if (f < 0) {
		perror("fone:open");
		exit(1);
	}
	if (tcgetattr (f, &tp) < 0) {		/* get current termios junk */
		perror("fone:open_console1:tcgetattr");
		exit(1);
	}
	if (tcgetattr (f, &console_tp) < 0) {	/* another copy for later restore */
		perror("fone:open_console2:tcgetattr");
		exit(1);
	}
	cfmakeraw(&tp);
	if (tcsetattr (f, TCSAFLUSH, &tp) < 0) {
		perror("fone:open_console:tcsetattr");
		exit(1);
	}
	return(f);
}

/* Close the console, restoring the oroginal termios stuff. */

close_console(f)
int f;
{
	if (tcsetattr (f, TCSAFLUSH, &console_tp) < 0) {
		perror("fone:close_console:tcsetattr");
	}
}


/* Open the device on handle 'fd', exit if error (not very nice). */

opentty() {
	struct termios tp;

	if ((fd = open(dev, O_RDWR | O_NDELAY)) < 0) {
		perror(dev);
		exit(1);
	}

	if (tcgetattr (fd, &tp) < 0) {
		perror("fone:setup1:tcgetattr");
		exit(1);
	};
	cfmakeraw(&tp);
	tp.c_cflag |= CLOCAL | CREAD | HUPCL | CRTSCTS;
	cfsetspeed(&tp, speed);

	if (tcsetattr (fd,TCSADRAIN, &tp) < 0) {
		perror("fone:setup2:tcsetattr");
		exit(1);
	};
}
