/* 
 * voclib.c
 *
 * (mostly stolen from Gert Doering's fax routines)
 *
 * Various utility functions for using the ZyXEL voice commands.
 * ALPHA software, beware...
 *
 */

char voclib_c[] = "$Id: voclib.c,v 1.56 1995/04/11 19:18:34 marc Exp $";

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

#ifndef _NOSTDLIB_H
#include <stdlib.h>
#endif

#include <signal.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "voice.h"
#include "../tio.h"

#define DTMF_BUFSIZE 512

static char dtmf_digits[DTMF_BUFSIZE];
static int dtmf_ndigits = 0;
static boolean use_dtmf_digits = FALSE;
static char* dtmf_ignore = "";

static char dtmf_chars[DTMF_BUFSIZE];
static int dtmf_nchars = 0;

boolean verbose;

#define VREAD_ERROR -1
#define VREAD_NULL  0
#define VREAD_CHAR  1
#define VREAD_DTMF  2
#define VREAD_DIGIT 3

static int /* type of response */
handle_dtmf _P1((ch), int ch)
{
    int ret = VREAD_NULL;
    
    lprintf(L_NOISE, "input: got <DLE>");
    if (ch>='0' && ch<='9') {
	lputs(L_NOISE, "<digit>");
    } else {
	lputc(L_NOISE, ch);
    }

    if ( ch == 'u' || ch == 'U' )
    {
	lputs(L_NOISE, ", ignoring it"); return ret;
    }

    if (strchr(dtmf_ignore, ch)) {
	lputs(L_NOISE, ", ignoring it");
    } else if (use_dtmf_digits && ch>='0' && ch<='9') {
	if(dtmf_ndigits<DTMF_BUFSIZE-1) {
	    dtmf_digits[dtmf_ndigits++] = ch;
	    dtmf_digits[dtmf_ndigits] = 0;
	}
	ret = VREAD_DIGIT;
    } else if (use_dtmf_digits && ch=='*') {
	dtmf_ndigits = 0;
	dtmf_digits[0] = 0;
    } else {
	ret = VREAD_DTMF;
    }
    return ret;
}

static boolean fwf_timeout = FALSE;

static void
fwf_sig_alarm _P1((sig), int sig)	/* SIGALRM handler */
{
    signal(SIGALRM, fwf_sig_alarm);
    lprintf(L_WARN, "Warning: got alarm signal!");
    fwf_timeout = TRUE;
}

/* voice_read_byte
 * read one byte from "fd", with buffering
 * caveat: only one fd allowed (only one buffer), no way to flush buffers
 * All data is assumed to be DLE shielded
 */

#define FRB_BUFSIZE 512
static int
voice_read_byte _P3((fd, c, wait), int fd, char * c, boolean wait)
{
    static char frb_buf[FRB_BUFSIZE];
    static char frb_DLE[FRB_BUFSIZE];
    static int  frb_rp = 0;
    static int  frb_len = 0;
    static boolean was_dle = FALSE;

    if (frb_rp >= frb_len)
    {
	int i;
	
	if (!wait) {
	    /* return immediately if no input is available */
	    if (!check_for_input(fd)) return VREAD_NULL;
	}
	frb_len = read(fd, frb_buf, FRB_BUFSIZE);
	if (frb_len <= 0) {
	    lprintf(L_ERROR, "voice_read_byte: read returned %d", frb_len);
	    return VREAD_ERROR;
	}
	frb_rp = 0;

	/* handle <DLE> shielded codes */
	
	for (i=0; i<frb_len; i++) {
	    frb_DLE[i] = 0;
	    if (was_dle) {
		if(frb_buf[i] == DLE) {
		    /* don't do anything, keep the DLE in the buffer */
		} else {
		    /* mark the character */
		    frb_DLE[i] = handle_dtmf(frb_buf[i]);
		    if (frb_DLE[i] != VREAD_DTMF) {
			/* delete it */
			if (frb_len-i-1 > 0)
			    memmove(frb_buf+i, frb_buf+i+1, frb_len-i-1);
			frb_len --;
			i--;
		    }
		}
		was_dle = FALSE;
	    } else {
		if (frb_buf[i] == DLE) {
		    was_dle = TRUE;

		    /* delete the DLE */
		    if (frb_len-i-1 > 0)
			memmove(frb_buf+i, frb_buf+i+1, frb_len-i-1);
		    frb_len --;
		    i--;
		}
	    }
	}
    }

    *c = frb_buf[frb_rp];

    if (frb_DLE[frb_rp] > VREAD_NULL) {
	if (dtmf_nchars < DTMF_BUFSIZE-1)
	    dtmf_chars[dtmf_nchars++] = frb_buf[frb_rp];
	return frb_DLE[frb_rp++];
    } else {
	frb_rp++;
	return VREAD_CHAR;
    }
}

/*** DTMF handling functions ***/

char * /* dtmf chars */
voice_flush _P1((fd), int fd)
/* warning- returns a pointer to a static string, the string
 * will be invalid as soon as any voice functions are called.
 */
{
    char c;

    lprintf(L_MESG, "flushing modem output");
    while (voice_read_byte(fd, &c, FALSE) > VREAD_NULL)
	;

    dtmf_chars[dtmf_nchars] = 0;
    dtmf_nchars = 0;
    return dtmf_chars;
} 

void
voice_activate_digits _P0(void)
{
    use_dtmf_digits = TRUE;
    dtmf_digits[0] = 0;
    dtmf_ndigits = 0;
}

char *
voice_get_digits _P0(void)
{
    return dtmf_digits;
}

void
voice_set_ignore _P1((ignore), char *ignore)
{
    dtmf_ignore = ignore;
}

/*** *** ***/

#define NALT 20

int /* -1=ERROR or string number 0..n-1 */
voice_wait_for _P2((s_in, fd),  char * s_in, int fd)
{
    char buffer[1000];
    char str[1000];
    char c;
    int bufferp;
    char *st[NALT], *si;
    int nalt;
    int found=-1;

    lprintf(L_MESG, "voice_wait_for(%s)", s_in);

    strcpy(str, s_in);
    st[0]=str;
    nalt=1;
    for(si=str; *si; si++) {
	if(*si=='|') {
	    *si=0;
	    st[nalt++]=si+1;
	}
    }

    fwf_timeout = FALSE;
    signal(SIGALRM, fwf_sig_alarm);
    alarm(FAX_RESPONSE_TIMEOUT);

    while (found<0) 
    {
	int i;
	
	bufferp = 0;
	lprintf(L_JUNK, "got:");
	
	/* get one string, not empty, printable chars only, ended by '\n' */
	do
	{
	    int vrb;
	    
	    vrb = voice_read_byte(fd, &c, TRUE);
	    if(vrb == VREAD_ERROR) {
		lprintf(L_ERROR, "voice_wait_for: cannot read byte, return");
		alarm(0);
		signal(SIGALRM, SIG_DFL);
		return ERROR;
	    }
	    if (vrb == VREAD_CHAR) {
		lputc(L_JUNK, c);
		if (isprint(c)) buffer[ bufferp++ ] = c;
	    }
	}
	while (bufferp == 0 || c != 0x0a);
	buffer[bufferp] = 0;
	
	lprintf(L_NOISE, "voice_wait_for: string '%s'", buffer);
	
	for(i=0; i<nalt; i++) {
	    if (strncmp(st[i], buffer, strlen(st[i])) == 0)
		found=i;
	}
	if (strncmp(buffer, "ERROR", 5) == 0) {
	    alarm(0); 
	    signal(SIGALRM, SIG_DFL);
	    return ERROR;
	}
    }
    lputs(L_MESG, "** found **");

    alarm(0);
    signal(SIGALRM, SIG_DFL);
    return found;
}

int voice_send _P2((s, fd),  char * s, int fd)
{
    lprintf(L_NOISE, "voice_send: '%s'", s);
    return write(fd, s, strlen(s));
}

int voice_command _P3((send, expect, fd), char * send, char * expect, int fd)
{
    lprintf(L_MESG, "voice_command: send '%s'", send);

     /* The ZyXEL seems to have problems keeping up with fast
      * computers. This ought to help.
      */
    delay(FAX_COMMAND_DELAY);	/* wait a few milliseconds */

    if (write(fd, send, strlen(send)) != strlen(send) ||
	write(fd, "\r\n", 2) != 2)
    {
	lprintf(L_ERROR, "voice_command: cannot write");
	return ERROR;
    }
    return voice_wait_for(expect, fd);
}

int voice_open_device _P1((voice_tty), char * voice_tty)
{
    char device[MAXPATH];
    int	fd;
    TIO vtio;
    
    /* ignore leading "/dev/" prefix */
    if (strncmp( voice_tty, "/dev/", 5 ) == 0 ) voice_tty += 5;

    if (verbose) printf("Trying voice device '/dev/%s'... ", voice_tty);

    if (makelock(voice_tty) != SUCCESS)
    {
	if (verbose) printf("locked!\n");
	lprintf(L_MESG, "cannot lock %s", voice_tty);
	return -1;
    }
    
    sprintf(device, "/dev/%s", voice_tty);

    if ((fd = open(device, O_RDWR | O_NDELAY)) == -1)
    {
	lprintf(L_ERROR, "error opening %s", device);
	if (verbose) printf("cannot open!\n");
	rmlocks();
	return fd;
    }

    /* unset O_NDELAY (otherwise waiting for characters */
    /* would be "busy waiting", eating up all cpu) */

    if (fcntl(fd, F_SETFL, O_RDWR) == -1)
    {
	lprintf(L_ERROR, "error in fcntl");
	close(fd);
	if (verbose) printf("cannot fcntl!\n");
	rmlocks();
	return -1;
    }

    /* initialize baud rate, hardware handshake, ... */
    tio_get(fd, &vtio);
    
    if ( tio_check_speed( zplay_send_baud ) >= 0 ) {	/* valid */
	tio_set_speed(&vtio, zplay_send_baud);
    } else {
	lprintf( L_FATAL, "invalid port speed: %d", zplay_send_baud);
	return -1;
    }
    
    tio_mode_sane(&vtio, TRUE);
    tio_set_flow_control(fd, &vtio, FLOW_SOFT);
    tio_mode_raw(&vtio);

    if (tio_set(fd, &vtio) == ERROR)
    {
	lprintf(L_ERROR, "error in tio_set");
	if (verbose) printf("cannot set termio values!\n");
	rmlocks();
	return -1;
	close(fd);
    }        
    
    lprintf(L_NOISE, "voice_open_device succeeded, %s -> %d", voice_tty, fd);
    if (verbose) printf("OK.\n");
    return fd;
}

/* voice_open: loop through all devices in voice_ttys until voice_open_device()
 * succeeds on one of them; then return file descriptor
 * return "-1" of no open succeeded (all locked, permission denied, ...)
 */

int voice_open _P1((voice_ttys), char * voice_ttys)
{
    char * p, * voice_tty;
    int fd;

    p = voice_tty = voice_ttys;
    do
    {
	p = strchr(voice_tty, ':');
	if (p != NULL) *p = 0;
	fd = voice_open_device(voice_tty);
	if (p != NULL) *p = ':';
	voice_tty = p+1;
    }
    while (p != NULL && fd == -1);
    return fd;
}

/* finish off - close modem device, rm lockfile */

void
voice_close _P1((fd), int fd)
{
    close(fd);
    rmlocks();
}

static volatile int voice_quit = FALSE;

#ifndef RETSIGTYPE
# define RETSIGTYPE void
#endif

typedef RETSIGTYPE (*sighandler_t) _PROTO((int));

static RETSIGTYPE
voice_sig_hangup _P1((sig_num), int sig_num)
{
    signal(SIGHUP, voice_sig_hangup);
    lprintf(L_WARN, "got hangup! what's this? exiting...");
    voice_quit = 'q';
}

static RETSIGTYPE
voice_sig_alarm _P1((sig_num), int sig_num)
{
    signal(SIGALRM, voice_sig_alarm);
    lprintf(L_MESG, "timeout...");
    voice_quit = 'T';
}

static FILE *fft_fp;
static int fft_pid;

static RETSIGTYPE
voice_sig_usr2 _P1((sig_num), int sig_num)
{
    lprintf(L_MESG, "FFT test: sounds like a data call...");
    voice_quit = 's'; /* silence --> data mode */
}

static RETSIGTYPE
voice_sig_pipe _P1((sig_num), int sig_num)
{
  signal(SIGPIPE, voice_sig_pipe);
  fft_fp = 0;
  lprintf(L_NOISE, "FFT test done reading...");
}

static FILE* /* file pointer to the writing end of the pipe */
open_fft_pipe _P1((compr), int compr)
{
    FILE *fp = 0;

    if (voice_format_supported(compr)) {
	int p[2];
	char pid[10];

	sprintf(pid, "%d", getpid());
	if(pipe(p)) lprintf(L_WARN, "pipe failed, skipping fft");
    
	switch((fft_pid=fork())) {
	  case -1:
	    lprintf(L_WARN, "can't fork fft program");
	    break;
	  case 0:
	    /* child */
	    close(STDIN);
	    dup(p[0]);
	    close(p[0]);
	    close(p[1]);
		  
	    lprintf(L_NOISE, "executing %s %s", fft_program, pid);
	    (void) execl(make_path(voice_dir, fft_program),
	      make_path(voice_dir, fft_program), pid, (char *) NULL);
	    (void) execl("/bin/sh", "sh", make_path(voice_dir,
	      fft_program), pid, (char *) NULL);
	    lprintf(L_ERROR, "cannot execute %s", fft_program);
	    exit(1);
	  default:
	    /* parent */
	    close(p[0]);
	    fp = fdopen(p[1], "w");
	}
    } else {
	lprintf(L_MESG, "data format not supported, skipping fft");
    }
    return fp;
}

/* voice_record_file
 *
 * record a file and listen for DTMF tones and other
 * <DLE>-shielded messages from the modem
 */
int /* result */
voice_record_file _P8((voc_file, fd, voc_io, compr, silence, threshold,
			timeout, digits, nmax, do_fft),
		       char * voc_file, int fd, int voc_io,
		       int compr, int silence, double threshold,
		       int timeout, boolean do_fft)
{
    TIO vtio, save_tio;
    FILE * voc_fp;
    char c;
    int ErrorCount = 0;
    int ByteCount = 0;
    int MaxBytes;
    int ret = ERROR;
    static char header[16];
    static int data_rate;
    sighandler_t Sigalrm, Sighup, Sigquit, Sigterm, Sigint;
    sighandler_t Sigpipe, Sigusr2;
    char *cur_blk, *blk_base;
    int blksize, blk_read, blk_wrt, nblk, bi;
    
    /* install signal handlers */
    Sigalrm = signal(SIGALRM, voice_sig_alarm);
    Sighup  = signal(SIGHUP,  voice_sig_hangup);
    Sigquit = signal(SIGQUIT, voice_sig_hangup);
    Sigterm = signal(SIGTERM, voice_sig_hangup);
    Sigint  = signal(SIGINT , voice_sig_hangup);

	if (strcmp(fft_program, ""))
		{
		Sigpipe = signal(SIGPIPE, voice_sig_pipe);
		Sigusr2 = signal(SIGUSR2, voice_sig_usr2);
		}
	else
		{
		Sigpipe = NULL;
		Sigusr2 = NULL;
		};

    voice_quit = FALSE;

    data_rate = voice_data_rate(compr);
    MaxBytes = data_rate * timeout;

    tio_get(fd, &vtio);
    save_tio = vtio;

    voc_fp = fopen(voc_file, "w");
    if (! voc_fp)
    {
	lprintf(L_ERROR, "opening %s failed!", voc_file);
	ret = ERROR;
	goto exit;
    }

    if (voice_record_init(fd, compr, voc_io, silence, threshold, header)
	== ERROR) {
	ret = ERROR;
	goto exit;
    }

    header[10] = compr - 1;
    fwrite(header, 16, 1, voc_fp);
    
    if (strcmp(fft_program, ""))
    		{
    		if(do_fft) fft_fp = open_fft_pipe(compr);
    		if(fft_fp) fwrite(header, 16, 1, fft_fp);
    		};

    lprintf(L_MESG, "voice_record: receiving %s...", voc_file);

    /* Remove the silence at the end of recordings, if appropiate.
     * To do this, a ring buffer is kept, it contains n+1 blocks
     * that each contain 1/10 second of data.
     */

    nblk = silence + 1;
    blksize = voice_data_rate(compr)/10;

    cur_blk = blk_base = (char *)malloc(nblk * blksize);

    if (rec_remove_silence && (!blk_base)) {
	lprintf(L_WARN, "not enough memory for ring buffer, continuing");
	/* DON'T exit, just deactivate the ring buffer */
    }
    else
	    blk_base = 0;
    
    blk_read = blk_wrt = 0;
    bi = 0;
    
    do
    {
	int vrb;

	/* refresh alarm timer every 1024 bytes
	 * (to refresh it for every byte is considered too expensive)
	 */
	if ((ByteCount & 0x3ff) == 0)
	{
	    alarm(FAX_PAGE_TIMEOUT);
	}

	if ((ByteCount & 0xfff) == 0)
	{
	    if (!checkspace(voc_file))
	    {
		lprintf(L_ERROR, "Out of space - exiting");
		ret = ERROR;
		goto exit;
	    }
	}

	vrb=voice_read_byte(fd, &c, TRUE);
	if (vrb == VREAD_ERROR) {
	    ErrorCount++;
	    lprintf(L_ERROR, "voice record: cannot read from port (%d)!",
	                      ErrorCount);
	    if (ErrorCount > 10) {
		ret = ERROR;
		goto exit;
	    }
	    continue;
	} else if (vrb == VREAD_DTMF) {
	    if (c==ETX) {
		/* we're done */
		dtmf_nchars--;    /* don't print the ETX */
		lprintf(L_NOISE, "got DLE ETX");
		break;
	    } else {
		/* stop recording */
		ret = c;
		voice_send("Q", fd);
	    }
	}

	if (++ByteCount >= MaxBytes) {
	    lprintf(L_MESG, "voice record: max speaking time exceeded.");
    	    ret = 'T';
	    voice_send("Q", fd);
	}

     if (strcmp(fft_program, "") && (fft_fp))
     	putc(c, fft_fp);

	if(!blk_base) {
	    putc(c, voc_fp);
	} else {	
	    /* put one byte in the ring buffer */
	    cur_blk[bi++] = c;
	    if(bi==blksize) {
		/* current block is finished, get a new one */
		bi = 0;
		blk_read ++;
		cur_blk = blk_base + (blk_read % nblk) * blksize;
		if(blk_read >= nblk) {
		    /* new block was filled, write old contents */
		    fwrite(cur_blk, 1, blksize, voc_fp);
		    blk_wrt ++;
		}
	    }
	}
    } while (!voice_quit);

    if(blk_base) {
	/* write out any stuff left in the ring buffer */
	if(ret != 'q') {
	    /* not `quiet', write out all the old blocks */
	    int i;

	    for(i=blk_wrt; i<blk_read; i++) {
		fwrite(blk_base + (i % nblk) * blksize,
		       1, blksize, voc_fp);
	    }
	    if (bi) {
		/* write the partly filled last block */
		fwrite(cur_blk, 1, bi, voc_fp);
	    }
	} else {
	    /* `quiet' - throw away silence_len seconds from the
	     * end of the recording */
	    if(bi) {
		/* write the start of the oldest saved block */
		fwrite(blk_base + (blk_wrt % nblk) * blksize,
		       1, bi, voc_fp);
	    }
	}
    }
	
    fclose(voc_fp);
   
    if (strcmp(fft_program, ""))
    {
    /* Call complete or found out on our own; don't want to be disturbed
     * by the result of fft_test any more.
     */
    if (fft_fp) fclose(fft_fp);
    if (fft_pid) {
	int status;
	
	kill(fft_pid, SIGKILL);
 	lprintf(L_NOISE, "waiting for fft program to exit...");
	while ((wait(&status)) != fft_pid)
	    ;
	lprintf(L_NOISE, "... done");
    }
    }
    
    lprintf(L_MESG, "voice record: end, bytes received: %d", ByteCount);

    /* wait for the modem */
    voice_command("", "VCON|OK", fd);

    if ( ret == 's' && ByteCount > (nblk * blksize) ) {
	/* Don't throw away the recorded data, it's voice */
	/* The modem must have reported 's' erroneously. */
	ret = 'q';
    }

    if (voice_quit) {
	lprintf(L_MESG, "voice record: recording aborted");
	ret = voice_quit;
	goto exit;
    }

  exit:
    tio_set(fd, &save_tio);
    alarm(0);
    signal(SIGALRM, Sigalrm);
    signal(SIGHUP,  Sighup);
    signal(SIGINT,  Sigint);
    signal(SIGQUIT, Sigquit);
    signal(SIGTERM, Sigterm);

    if (strcmp(fft_program, ""))
    		{
    		signal(SIGPIPE, Sigpipe);
    		signal(SIGUSR2, Sigusr2);
    		};

    return (ret == ERROR) ? 'E' : ret;
}

/* voice_send_file - send one complete voice file to the modem
 *
 */
int /* result */
voice_send_file _P4((voc_file, fd, voc_io, silence),
		    char * voc_file, int fd, int voc_io,
		    int silence)
{
    int vocfd;
    char ch;
    char buf[1024];
    char wbuf[ sizeof(buf) * 2 ];
    int compr;
    static char voice_end_of_page[] = { DLE, ETX };
    int ret = 's';
    int r, i, w;
    TIO vtio, save_tio;
    sighandler_t Sighup, Sigquit, Sigterm, Sigint;
    int nblk=0, cblk=0;
    int data_rate;

    Sighup  = signal(SIGHUP,  voice_sig_hangup);
    Sigquit = signal(SIGQUIT, voice_sig_hangup);
    Sigterm = signal(SIGTERM, voice_sig_hangup);
    Sigint  = signal(SIGINT , voice_sig_hangup);
    voice_quit = FALSE;

    lprintf(L_NOISE, "voice_send_file(\"%s\") started...", voc_file);

    tio_get(fd, &vtio);
    save_tio = vtio;

    lprintf(L_MESG, "sending %s...", voc_file);

    vocfd = open(voc_file, O_RDONLY);
    if (vocfd == -1) {
	lprintf(L_ERROR, "cannot open %s", voc_file);
	goto exit;
    }
    if ((compr = voice_send_init(fd, vocfd, voc_io)) == ERROR) {
	ret = ERROR;
	goto exit;
    }
    data_rate = voice_data_rate(compr);
    nblk = silence * data_rate / (10 * 1024);
    cblk = 0;
    
    while (!voice_quit) {
	int vrb;
	
	if((r = read(vocfd, buf, 1024)) <= 0) {
	    if(cblk < nblk) {
		/* invent some data to allow entering digits */
		memset(buf, 0, 1024);
		r=1024;
		cblk++;
	    } else {
		/* the file is finished, quit */
		break;
	    }
	}
	
	i = 0;
	for (w = 0; i < r; i++) {
	    wbuf[ w ] = buf[ i ];
	    if (wbuf[ w++ ] == DLE) wbuf[ w++ ] = DLE;
	}

	lprintf(L_JUNK, "read %d, write %d", r, w);
	
	if (write(fd, wbuf, w) != w) {
	    lprintf(L_ERROR, "could not write all %d bytes", w);
	}

	/* look if there's something to read */
	vrb=voice_read_byte(fd, &ch, FALSE);
	if (vrb == VREAD_DIGIT) {
	    cblk = 0; /* reset timeout */
	} else if (vrb == VREAD_ERROR) {
	    lprintf(L_ERROR, "read failed");
	    ret = ERROR;
	    break;
	} else if (vrb == VREAD_DTMF) {
	    ret = ch;
	    voice_quit = TRUE;
	}
    } /* end while (more voice data to read) */

    if (ret != 'V') {
	/* transmit end of page */
	lprintf(L_NOISE, "sending DLE ETX...");
	write(fd, voice_end_of_page, sizeof(voice_end_of_page));
	
	if (voice_wait_for("VCON|OK", fd) == ERROR) {
	    ret=ERROR;
	    goto exit;
	}
    }

  exit:
    tio_set(fd, &save_tio);
    signal(SIGHUP,  Sighup);
    signal(SIGINT,  Sigint);
    signal(SIGQUIT, Sigquit);
    signal(SIGTERM, Sigterm);

    return (ret == ERROR) ? 'E' : ret;
}

