/* 
 * vmodem.c
 *
 * Voice modem specific functions.
 *
 */

char vmodem_c[] = "$Id: vmodem.c,v 1.35 1995/04/11 19:18:33 marc Exp $";


#include "voice.h"
#include <fcntl.h>
#include "../tio.h"
#include "../version.h"
#include "../fax_lib.h"

#define FRACT(x,m) ((int)((x)*(m)+0.5))

int messages_waiting_ack;
int voice_modem;

static int vio_tab[][3]={{2,8,16},{2,8,16},{0,1,2}};
static char* vm_header[] = {"ZyXEL", "Dolph", "RockJ"};

int mg_init_voice _P1( (fd), int fd )
{
    lprintf( L_MESG, "vgetty: %s", vgetty_version);
    lprintf( L_MESG, "mgetty: %s", mgetty_version);

    /* We need a flag, if we already decremented rings_wanted,
     * because voice_rings is called more then one time. This is
     * only a quick fix for the moment.
     */
     
    messages_waiting_ack = 0;

    /* find out what kind of voice modem this is */
    voice_modem = VM_ZYXEL;
    
    if ( voice_command( "AT+FCLASS=8", "VCON|OK", fd ) != ERROR ) {
	/* ZyXEL or Dolphin */
	if( voice_command( "ATI", "1496|2864|OK", fd ) < 2 ) {
	    voice_command( "", "OK", fd );
	    voice_modem = VM_ZYXEL;
	} else {
	    voice_modem = VM_DOLPHIN;
	}
    } else if ( voice_command( "AT#CLS=8", "VCON|OK", fd) != ERROR ) {
	/* ROCKWELL */
	voice_modem = VM_ROCKWELL;
    } else {
	lprintf( L_MESG, "voice init failed, using data/fax mode");
	return FAIL;
    }

    switch(voice_modem) {
      case VM_ZYXEL:
	lprintf(L_NOISE, "modem type ZyXEL");

	if (dist_ring && (zyxel_rom >= 611)) {
	    if ( voice_command( dist_ring_init,
			       "VCON|OK", fd ) == ERROR ) {
		lprintf( L_WARN,
			"coudn't initialize distinctive RING" );
	    }
	}

	break;
      case VM_DOLPHIN:
	lprintf(L_NOISE, "modem type DOLPHIN");
	break;
      case VM_ROCKWELL:
	lprintf(L_NOISE, "modem type ROCKWELL");
	break;
    }
    return SUCCESS;
}

void
voice_mode_init _P1((fd), int fd)
{
    char cmd[256];
    
    switch(voice_modem) {
      case VM_ZYXEL:
	if (zyxel_rom >= 611) {
	    if ( voice_command( "ATS39.7=0 S39.6=1 +VIT=60",
			       "VCON|OK", fd ) == ERROR ) {
		lprintf( L_WARN,
			"voice init failed, continuing" );
	    }
	}
	if (zyxel_rom >= 612) {
	    sprintf( cmd, "AT+VDH=%d +VDD=%d",
		     FRACT(dtmf_threshold, 31),
		     (int)((dtmf_len + 3)/6));
	    if ( voice_command( cmd, "VCON|OK", fd )==ERROR ) {
		lprintf( L_WARN, "setting DTMF threshold didn't work" );
	    }
	}
	if (zyxel_rom >= 613) {
	    if ( voice_command( "AT+FLO=2", "VCON|OK", fd ) == ERROR ) {
		lprintf( L_WARN, "can't turn on hardware flow control" );
	    }
	}	    
	break;
      case VM_DOLPHIN:
	/* should anything be done here? */
	break;
      case VM_ROCKWELL:
	/* turn off autobauding, this also turns on the
	 * inactivity timer.
	 * Use #BDR=x to set the bps rate to x * 2400 bps.
	 * #BDR=16 means use 38400 bps, as far as I know other
	 * bit rates don't work anyway...
	 * Also set deadman timer to 60 seconds.
	 */

	if (voice_command( "ATS30=60 #BDR=16", "VCON|OK", fd ) == ERROR ) {
	    lprintf( L_WARN, "can't set baud rate");
	}
	break;
    }
}

void
voice_mode_off _P1((fd), int fd)
{
    switch(voice_modem) {
      case VM_ZYXEL:
      case VM_DOLPHIN:
	voice_command( "AT+VLS=0 +FCLASS=0", "VCON|OK", fd );
	break;
      case VM_ROCKWELL:
	voice_command( "AT#CLS=0", "OK", fd );
	break;
    }
}

boolean
voice_format_supported _P1((compr), int compr)
{
    boolean known=FALSE;
    
    switch(voice_modem) {
      case VM_ZYXEL:
	if(compr!=1) known=TRUE;
	break;
      case VM_DOLPHIN:
      case VM_ROCKWELL:
	break;
    }
    return known;
}

int
voice_data_rate _P1((compr), int compr)
{
    int rate=0;
    
    switch(voice_modem) {
      case VM_ZYXEL:
	rate = 1200 * compr;
	break;
      case VM_DOLPHIN:
	rate = 1200 * compr; /* FIXME: is this right? */
	break;
      case VM_ROCKWELL:
	rate = 900 * compr;
	break;
    }

    return rate;
}

static void
voice_set_volume _P2((fd, speaker), int fd, boolean speaker)
{
    double vol;
    char cmd[256];
    
    vol = speaker ? zplay_speaker_volume : speaker_answer_volume;
    
    switch(voice_modem) {
      case VM_ZYXEL:
	sprintf( cmd, "ATL%d", FRACT(vol, 7) );
	break;
      case VM_DOLPHIN:
      case VM_ROCKWELL:
	sprintf( cmd, "ATL%d", FRACT(vol, 3) );
	break;
    }

    if ( voice_command( cmd, "VCON|OK", fd ) == ERROR )
    {
	lprintf( L_WARN, "ATL -> some error, continuing");
    }
}

int
voice_beep _P3((fd, voice_io, beep), int fd, int voice_io, char *beep )
{
    char cmd[256];

    voice_set_volume(fd, voice_io==VIO_SPEAKER);

    switch(voice_modem) {
      case VM_ZYXEL:
      case VM_DOLPHIN:
	sprintf( cmd, "AT+VLS=%d", vio_tab[voice_modem][voice_io] );
	break;
      case VM_ROCKWELL:
	sprintf( cmd, "AT#VLS=%d #VBT=12", vio_tab[voice_modem][voice_io] );
	break;
    }
	
    if ( voice_command( cmd, "VCON|OK", fd ) == ERROR )
    {
	lprintf( L_WARN, "init error, abort beep!" );
	return ERROR;
    }
    
    switch(voice_modem) {
      case VM_ZYXEL:
      case VM_DOLPHIN:
	sprintf( cmd, "AT+VTS=%s", beep );
	break;
      case VM_ROCKWELL:
	sprintf( cmd, "AT#VTS=%s", beep );
	break;
    }

    if ( voice_command( cmd , "VCON|OK", fd ) == ERROR )
    {
	lprintf( L_WARN, "beep failed, aborting" );
	return ERROR;
    }
    return 0;
}

int /* dialout */
voice_dialout _P3((fd, voice_io, dialout), int fd, int voice_io, char *dialout )
	{
	char cmd[256];
	int ret;
	int rings;
	
	delay(FAX_COMMAND_DELAY);

	sprintf(cmd, "ATD%s", dialout);
	
	if ((write(fd, cmd, strlen(cmd)) != strlen(cmd)) ||
	  (write(fd, "\r\n", 2) != 2))
		{
		lprintf(L_ERROR, "voice_dialout: cannot write");
		return(1);
		};
	
	ret = 2;

	for (rings = 0; (rings < zplay_max_rings) && ((ret == 2) || (ret == 3));
	  rings++)
		{
	  
		if ((ret = voice_wait_for(
		  "VCON|OK|RINGING|RING|BUSY|NO DIALTONE|NO CARRIER", fd)) == ERROR)
			{
			lprintf(L_WARN, "voice_dialout: error while waiting");
			return(1);
			};

		};
		
	if ((rings == zplay_max_rings) && ((ret == 2) || (ret == 3)))
		{
		cmd[0] = 0;

		if (voice_command(cmd, "NO CARRIER", fd) == ERROR)
			{
			lprintf(L_WARN, "voice_dialout: error while ending call");
			return(1);
			};

		return(2);
		}

	if ((ret == 0) || (ret == 1))
		return(0);
		
	return(1);
	};

int /* compression */
voice_send_init _P3((fd, vocfd, voice_io), int fd, int vocfd, int voice_io)
{
    char cmd[128], buf[16];
    TIO vtio;
    int flow;
    int r;
    int compr=ERROR;

    if (voice_modem == VM_ZYXEL && zyxel_rom >= 613) {
	flow = FLOW_HARD;
    } else {
	flow = FLOW_XON_OUT;
    }

    /* read zfax header */
    r = read(vocfd, buf, 16);
    
    if (r >= 16 && strncmp(buf, vm_header[voice_modem], 5) == 0) {
	compr=buf[10] + 1;
	lprintf(L_MESG,	"reading zfax header, compression type %d", compr);
    } else {
	lprintf(L_WARN, "No ZFAX header found, aborting!");
	return ERROR;
    }
        
    tio_get(fd, &vtio);
    fcntl(fd, F_SETFL, O_RDWR);
    tio_mode_sane(&vtio, TRUE);
    tio_set_flow_control(fd, &vtio, flow);
    tio_mode_raw(&vtio);
    tio_set(fd, &vtio);

    voice_mode_init(fd);
    voice_set_volume(fd, voice_io==VIO_SPEAKER);

    switch(voice_modem) {
      case VM_ZYXEL:
      case VM_DOLPHIN:
	sprintf( cmd, "AT+VSM=%d +VLS=%d",
		compr, vio_tab[voice_modem][voice_io] );
	break;
      case VM_ROCKWELL:
	sprintf( cmd, "AT#VBS=%d #VLS=%d",
		compr, vio_tab[voice_modem][voice_io] );
	break;
    }
	
    if ( voice_command( cmd, "VCON|OK", fd ) == ERROR )
    {
	lprintf( L_WARN, "init failed, abort voice send!");
	return ERROR;
    }

    switch(voice_modem) {
      case VM_ZYXEL:
      case VM_DOLPHIN:
	sprintf( cmd, "AT+VTX" );
	break;
      case VM_ROCKWELL:
	sprintf( cmd, "AT#VTX" );
	break;
    }
	
    if ( voice_command( cmd, "CONNECT", fd ) == ERROR )
    {
	lprintf( L_WARN, "voice send failed, aborting" );
	return ERROR;
    }
    return compr;
}
    
int
voice_record_init _P6((fd, compr, voice_io, silence, threshold, header),
		      int fd, int compr, int voice_io,
		      int silence, double threshold, char *header)
{
    char cmd[128];
    TIO vtio;
    int flow;
    
    /* Setup tty interface
     * Do not set c_cflag, assume that caller has set bit rate,
     * hardware handshake, ... properly
     */

    if (voice_modem == VM_ZYXEL && zyxel_rom >= 613) {
	flow = FLOW_HARD;
    } else {
	flow = FLOW_XON_IN;
    }
    
    tio_get(fd, &vtio);
    fcntl(fd, F_SETFL, O_RDWR);
    tio_mode_sane(&vtio, TRUE);
    tio_set_flow_control(fd, &vtio, flow);
    tio_mode_raw(&vtio);
    tio_map_cr(&vtio, FALSE);
    tio_set(fd, &vtio);
    
    voice_mode_init(fd);
    voice_set_volume(fd, voice_io==VIO_SPEAKER);

    strcpy(header, vm_header[voice_modem]);
    header[5]='\002';
    switch(voice_modem) {
      case VM_ZYXEL:
	sprintf( cmd, "AT+VSM=%d +VLS=%d +VSD=%d,%d",
		 compr, vio_tab[voice_modem][voice_io],
		 FRACT(threshold, 31), silence );
	break;
      case VM_DOLPHIN:
	sprintf( cmd, "AT+VSM=%d +VLS=%d +VSD=%d,%d",
		 compr, vio_tab[voice_modem][voice_io],
		 FRACT(threshold, 31), silence );
	break;
      case VM_ROCKWELL:
	sprintf( cmd, "AT#VBS=%d #VSS=%d #VSD=1 #VSP=%d #VLS=%d",
		 compr, FRACT(threshold, 3), silence,
		 vio_tab[voice_modem][voice_io]);
	break;
    }

    if ( voice_command( cmd, "VCON|OK", fd ) == ERROR )
    {
	lprintf( L_WARN, "init failed, abort voice recording!" );
	return ERROR;
    }

    switch(voice_modem) {
      case VM_ZYXEL:
      case VM_DOLPHIN:
	sprintf( cmd, "AT+VRX" );
	break;
      case VM_ROCKWELL:
	sprintf( cmd, "AT#VRX" );
	break;
    }

    if ( voice_command( cmd, "CONNECT", fd ) == ERROR )
    {
	lprintf( L_WARN, "voice recording failed, aborting" );
	return ERROR;
    }
    return SUCCESS;
}
