/*
 * This is Ping-Pong convers/conversd derived from the wampes
 * convers package written by Dieter Deyke <deyke@mdddhd.fc.hp.com>
 *
 * Modifications by Fred Baumgarten <dc6iq@insu1.etec.uni-karlsruhe.de>
 * $Revision: 2.99 $$Date: 1994/10/02 10:33:12 $
 */

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#if defined(_AIX) && defined(_IBMR2)
#include <time.h>
#include <sys/select.h>
#endif
#include <termios.h>
#include <unistd.h>

#ifdef PCSP
#include <linux/autoconf.h>
#if CONFIG_PCSP
#include <linux/pcsp.h>
extern int speak_open(char *device, int now);
extern int speak_string(int fd, char *str);
extern int speak_close(int fd);
extern int speak_load_samples(char *dir);
#else
#undef PCSP
#endif
#endif

#ifdef linux
#include <sys/ioctl.h>
#endif

#ifdef READLINE
#include "readline/readline.h"
#endif

#if defined(__TURBOC__) || defined(__STDC__)
#define __ARGS(x)       x
#else
#define __ARGS(x)       ()
#define const
#endif

#if defined(mips)		/* no headerfiles for the whole stuff ? */
extern int select __ARGS((int nfsd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout));
#endif

#if defined(mips) || defined(_AIX)	
extern void bzero __ARGS((char *b1, int length));
extern int getopt __ARGS((int argc, char **argv, char *optstring));
extern int socket __ARGS((int af, int type, int protocol));
extern int connect __ARGS((int s, struct sockaddr *name, int namelen));
extern int ioctl __ARGS((int d, int request, void *argp));
#endif

#if defined(hpux)
#define fd_set fd_mask		/* not sure about this... comments ? */
#endif

extern struct sockaddr *build_sockaddr __ARGS((const char *name, int *addrlen));
extern char *optarg;
extern int optind;

static int issue_running = 1;	/* ignore these Issue messages */
static char convtype[512];
static int outcnt = 0;
static char outbuf[20480];
static struct timeval sel_timeout;
static struct termios prev_termios;
static struct termios curr_termios;
static int echo;
static long tt;

#ifdef READLINE
extern char *rl_display_prompt;
extern Function *rl_event_hook;
extern void rl_forced_update_display __ARGS((void));
extern void add_history  __ARGS((char *line));
#endif

#if defined(mips)	/* select(2) is not POSIX-compatible !!! */
#undef POSIX
#endif

#ifndef POSIX
static long tt2;
#endif

#ifdef READLINE
static char prompt[] = "x";
static int chars_avail = 0;
static output = 0;
static screenwidth;
static struct winsize window_size;
#else
static void stop __ARGS((char *arg));
#endif

#ifdef PCSP
static char *audiodir = "/usr/local/lib/phonemes";
static int speak_fd = 0;
static int do_talk = 0;
#endif

/*---------------------------------------------------------------------------*/

static void stop(arg)
char *arg;
{
  if (*arg) perror(arg);
  tcsetattr(0, TCSANOW, &prev_termios);
  exit(0);
}

/*---------------------------------------------------------------------------*/

#ifdef PCSP

void convers_speak_string(s, len)
char *s;
int len;
{
  char buffer[10*1024];
  int i,j;

  for (i = 0,j = 0; i < len; i++) {
    if (s[i] == ' ' || s[i] == '<' || s[i] == '>' ||
	(s[i] >= 'a' && s[i] <= 'z') ||
	(s[i] >= 'A' && s[i] <= 'Z') ||
	(s[i] >= '0' && s[i] <= '9')) {
      buffer[j++] = s[i];
    }
  }
  buffer[j] = '\0';
  
  /*
   * Now split the resulting string:
   *
   *   - if <something> look for callsign-sound
   *   - speak the rest
   */

  if (buffer[0] == '<') {
    char *rest,callsign[512],filename[512];
    rest = strchr(buffer,'>');
    if (rest && rest - buffer < 512) {
      bzero(callsign,20);
      strncpy(callsign,buffer+1,rest-buffer-1);
      rest++;
      sprintf(filename,"%s/%s.au",audiodir,callsign);

      /*
       * now look if callsign is sampled
       */

      if (!access(filename,4)) {
	sprintf(callsign,"cp %s /dev/audio",filename);
	speak_close(speak_fd);
	system(callsign);
	speak_fd = speak_open("/dev/audio",1);
	speak_string(speak_fd,rest);
	return;
      }
    }
  }
  speak_string(speak_fd,buffer);
}

#endif

/*---------------------------------------------------------------------------*/

void do_select_call()
{
  fd_set mask;
  int size;
  int i;
#ifndef READLINE
  int incnt = 0;
  char inbuf[20480];
#endif
  char buffer[20480];
  char c, *cp;

  FD_ZERO(&mask);
  FD_SET(0,&mask);
  FD_SET(3,&mask);
  sel_timeout.tv_sec = 0;
  sel_timeout.tv_usec = 200000;
  select(4, &mask, (fd_set *) 0, (fd_set *) 0, &sel_timeout);
  if (FD_ISSET(0,&mask)) {
#ifdef READLINE
    chars_avail = 1;
#else
    do {
      if ((size = read(0, buffer, sizeof(buffer))) <= 0) stop(convtype);
      for (i = 0; i < size; i++) {
	c = buffer[i];
	if (c == '\r') c = '\n';
	if (c == (char) prev_termios.c_cc[VERASE]) {
	  if (incnt) {
	    incnt--;
	    if (echo && write(1, "\b \b", 3) < 0) stop(convtype);
	  }
	} else if (c == (char) prev_termios.c_cc[VKILL]) {
	  for (; incnt; incnt--)
	    if (echo && write(1, "\b \b", 3) < 0) stop(convtype);
	} else if (echo && c == 18) {
	  if (write(1, "^R\n", 3) < 0) stop(convtype);
	  if (write(1, inbuf, incnt) < 0) stop(convtype);
	} else {
	  inbuf[incnt++] = c;
	  if (echo && write(1, &c, 1) < 0) stop(convtype);
	}
	if (c == '\n' || incnt == sizeof(inbuf) - 1) {
	  if (inbuf[0] == '!') {
	    if (inbuf[1] != '!') {
	      inbuf[incnt] = '\0';
	      if (tcsetattr(0, TCSANOW, &prev_termios)) stop(convtype);
	      system(inbuf + 1);
	      if (tcsetattr(0, TCSANOW, &curr_termios)) stop(convtype);
	      if (write(3, "\n", 1) < 0) stop(convtype);
	    } else {
	      if (write(3, inbuf+1, incnt-1) < 0) stop(convtype);
	    }
	  } else {
	    if (write(3, inbuf, incnt) < 0) stop(convtype);
	  }
	  incnt = 0;
#ifdef POSIX
	  fflush(NULL);
#endif
	}
      }
    } while (incnt);
#endif
  }
  if (FD_ISSET(3,&mask)) {
    size = read(3, buffer, sizeof(buffer));
    if (size <= 0) stop("");
#ifdef PCSP
    if (speak_fd)
      convers_speak_string(buffer,size);
#endif
    for (i = 0; i < size; i++) {
      c = buffer[i];
      if (c != '\r') outbuf[outcnt++] = c;
#ifdef READLINE
      if (c > ' ') prompt[0] = c;
#endif
    }
#ifndef POSIX
    time(&tt);
#else
    fflush(NULL);
#endif
  }
#ifndef POSIX
  time(&tt2);
  tt2--;
#endif
  if (outcnt) {
#ifdef POSIX
    if ((sel_timeout.tv_sec == 0) && (sel_timeout.tv_usec == 0)) {
#else
    if (tt < tt2) {
#endif
#ifdef READLINE
      if (echo) {
#ifndef AX25
        if (write(1, "\r", 1) < 0) stop("");
        for (i = 0; (i <= rl_end) && (i < (screenwidth-1)); i++) {
	  if (write(1, " ", 1) < 0) stop("");
        }
        if (write(1, "\r", 1) < 0) stop("");
#endif
        output = 1;
      }
#endif
      if (!issue_running) {
	if (write(1, outbuf, outcnt) < 0) stop("");
      } else {
        cp = outbuf;
	while (*cp) {
	  if (cp[0] != '*') {
	    issue_running = 0;
	    if (write(1, cp, outcnt) < 0) stop("");
	    break;
	  } else {
	    while (*cp != '\n') {
	      cp++;
	      outcnt--;
	    }
	    cp++;
	    outcnt--;
	  }
	}
      }
#ifdef READLINE
#ifdef AX25
      if (rl_end) {
	rl_display_prompt = "";
	rl_forced_update_display();
      }
#else
      if (rl_end) {
        if (write(1, "\r", 1) < 0) stop("");
	rl_display_prompt = prompt;
	rl_forced_update_display();
      }
#endif
#endif
      outcnt = 0;
#ifdef POSIX
      fflush(NULL);
#endif
    }
  }
}

/*---------------------------------------------------------------------------*/

int main(argc, argv)
int argc;
char **argv;
{
#ifdef AX25
  char *server = "unix:/tcp/.sockets/netcmd";
#else
  char *server = "localhost:3600";
#endif
  char buffer[512];
  char *sp;
#ifndef AX25
  FILE *f;
  int ch;
  int channel = 0;
  int errflag = 0;
  char fname[512];
#endif
  int addrlen;
  struct sockaddr *addr;
#ifdef READLINE
  char *line;
#endif

  time(&tt);
  sp = strrchr(argv[0],'/');
  if (sp) {
    sp++;
  } else {
    sp = argv[0];
  }
  strcpy(convtype, sp);

  if (strstr(convtype, "kaconvers")) server = "localhost:6809";
  if (strstr(convtype, "lconvers")) server = "localhost:6810";
  if (strstr(convtype, "suconvers")) server = "localhost:6811";
  if (strstr(convtype, "wconvers")) server = "localhost:3610";

  signal(SIGPIPE, SIG_IGN);

  if (tcgetattr(0, &prev_termios)) stop(convtype);
  curr_termios = prev_termios;
  echo = curr_termios.c_lflag & ECHO;
  curr_termios.c_lflag = 0;
#ifndef READLINE
  curr_termios.c_cc[VMIN] = 1;
  curr_termios.c_cc[VTIME] = 0;
#else
  screenwidth = 80;
#if defined (TIOCGWINSZ)
  if (ioctl (0, TIOCGWINSZ, &window_size) == 0)
    {
      screenwidth = (int) window_size.ws_col;
    }
#endif
  curr_termios.c_lflag = curr_termios.c_lflag & ~ECHO;
  rl_event_hook = (Function *) do_select_call;
#endif
  if (tcsetattr(0, TCSANOW, &curr_termios)) stop(convtype);

#ifdef AX25
  if (argc != 2) {
    fprintf(stderr, "usage: ax25 call\n");
    stop("");
  }
#else
#ifdef PCSP
  while ((ch = getopt(argc, argv, "c:s:a:t")) != EOF)
#else
  while ((ch = getopt(argc, argv, "c:s:")) != EOF)
#endif
    switch (ch) {
    case 'c':
      channel = atoi(optarg);
      break;
    case 's':
      server = optarg;
      break;
#ifdef PCSP
    case 't':
      do_talk = 1;
      break;
    case 'a':
      audiodir = optarg;
      speak_load_samples(audiodir);
      break;
#endif
    case '?':
      errflag = 1;
      break;
    }


  if (errflag || optind < argc) {
#ifdef PCSP
    fprintf(stderr, "usage: convers [-t] [-a audiodir] [-c channel] [-s host:service]\n");
#else
    fprintf(stderr, "usage: convers [-c channel] [-s host:service]\n");
#endif
    stop("");
  }
#endif
  if (!(addr = build_sockaddr(server, &addrlen))) {
    stop("build_sockaddr()");
  }

  close(3);
  if (socket(addr->sa_family, SOCK_STREAM, 0) != 3) stop("socket");
  if (connect(3, addr, addrlen)) stop("connect");

#ifdef PCSP
  if (do_talk) {
    speak_fd = speak_open("/dev/audio",1);
  }
#endif

#ifdef AX25
  sprintf(buffer, "connect ax25 %s\n", argv[1]);
  if (write(3, buffer, strlen(buffer)) < 0) stop("write(\"connect ax25\")");
#else
  sp = getlogin();
  if (!sp) sp = getpwuid(getuid())->pw_name;
  sprintf(buffer, "/NAME %s %d\n", sp, channel);
  if (write(3, buffer, strlen(buffer)) < 0) stop(convtype);

  /* read .conversrc */

  sprintf(fname,"%s/.%src",getenv("HOME"),convtype);
  if (!access(fname,R_OK)) {
    f = fopen(fname,"r");
    if (f != NULL) {
      for (fgets(buffer,2048,f); !feof(f); fgets(buffer,2048,f)) {
        if (write(3, buffer, strlen(buffer)) < 0) stop(convtype);
      }
      fclose(f);
    }
  }
#endif

  for (; ; ) {
#ifdef READLINE
    if (chars_avail) {
      prompt[0] = '\0';
      curr_termios.c_lflag = curr_termios.c_lflag | echo;
      if (tcsetattr(0, TCSANOW, &curr_termios)) stop(convtype);
      line = readline(prompt);
#if 0
      signal(SIGALRM, SIG_IGN);
#endif
      curr_termios.c_lflag = curr_termios.c_lflag & ~ECHO;
      if (tcsetattr(0, TCSANOW, &curr_termios)) stop(convtype);
      if (line) {
	if (*line) {
	  add_history (line);
	}
	if (line[0] == '!') {
	  if (line[1] != '!') {
	    if (tcsetattr(0, TCSANOW, &prev_termios)) stop(convtype);
	    system(line + 1);
	    if (tcsetattr(0, TCSANOW, &curr_termios)) stop(convtype);
	  } else {
	    if (write(3, line+1, strlen(line)-1) < 0) stop(convtype);
	  }
	} else {
	  if (write(3, line, strlen(line)) < 0) stop(convtype);
	}
	chars_avail = 0;
	if (write(3, "\n", 1) < 0) stop(convtype);
	free (line);
      }
      rl_end = 0;
    }
#endif
    do_select_call();
  }
}

