#include "bbs.h"

int talkscreen(const int sockd, confrecordtyp *confrecord)
{
  int k, n, zeilen;
  char c, buf[BUFSIZE], *promptptr;
  WINDOW *w_win, *r_win;
  FD_SET_T readset;

  clear();
  zeilen = (LINES -1) / 2;
  if ((w_win=newwin(zeilen,0,0,0)) == NULL ||
      (r_win=newwin(0,0,zeilen+1,0)) == NULL) {
    errormsg(E_SYSLOG|E_USER,confrecord,"bbs","talkscreen","cannot create windows");
    return(-1);
  }
  scrollok(w_win,TRUE);
  scrollok(r_win,TRUE);
  move(zeilen,0);
  promptptr = msg("talkscreen",0,confrecord->userrecord.lang);
  n = strlen(promptptr);
  for (k=0; k<(COLS-n)/2; k++)  addch('-');
  addstr(promptptr);
  for (k=(COLS-n)/2+n; k<COLS; k++)  addch('-');
  refresh();
  FD_ZERO(&readset);
  do {
    FD_SET(sockd, &readset);
    FD_SET(STDIN_FILENO, &readset);
    if (select(sockd+1,FD_SETPTR_CAST &readset,NULL,NULL,NULL) > 0) {
      if (FD_ISSET(STDIN_FILENO, &readset)) {
        n = read(STDIN_FILENO,(void *)buf,(SIZE_T)BUFSIZE);
	if ((c=inserttalktext(buf,n,w_win)) != ESCAPE_KEY) {
  	  sendn(sockd,(void *)buf,(SIZE_T)n,0);
	}
      }
      if (FD_ISSET(sockd, &readset)) {
	n = recv(sockd,(void *)buf,(SIZE_T)BUFSIZE,0);
	inserttalktext(buf,n,r_win);
      }
    }
  } while (c != ESCAPE_KEY);
  delwin(w_win);
  delwin(r_win);
  clear();
  refresh();
  return(0);
}


char inserttalktext(char *buf, const int n, WINDOW *win)
{
  int k, x_p, y_p;
  char c;
  
  for (k=0; k<n; k++) {
    c = buf[k];
    if (buf[k] == ESCAPE_KEY)  buf[k] = ' ';
    if (c==BACKSPACE_KEY || c==DELETE_KEY) {
      getyx(win,y_p,x_p);
      if (x_p > 0) {
	x_p--;
	mvwdelch(win,y_p,x_p);
      }
      else if (y_p > 0) {
	y_p--;
	x_p = COLS - 1;
	wmove(win,y_p,x_p);
	while(x_p>0 && isspace(winch(win))) {
	  x_p--;
	  wmove(win,y_p,x_p);
	}
	if (x_p == COLS-1) {
	  mvwdelch(win,y_p,x_p);
	}
	else if (x_p > 0) {
	  wmove(win,y_p,x_p+1);
	}
      }
    }
    else if (c != ESCAPE_KEY) {
      waddch(win,c);
    }
  }
  wrefresh(win);
  return(c);
}


int iniconnecttouser(const PID_T remotepid, confrecordtyp *confrecord)
{
  PID_T mypid;
  int n, sockd, nsockd, len, lang;
  struct sockaddr_un saun;
  char sockname[TALKSOCKNAMELEN+1], sockpath[PATH_MAX+1];
  char cset[] = {'Q','\0'};
  SIGSET_T sigset;

  lang = confrecord->userrecord.lang;

  /* Talk beim Daemon anmelden */
  mypid = getpid();
  n = iniconnecttouserd(mypid, remotepid, sockname, confrecord);
  if (n == 1) {
    writetouser(confrecord,msg("iniconnecttouser",4,lang));
  }
  if (n != 0) {
    praeleaveiniconnecttouser();
    return(n);
  }
  
  /* Talksocket aufbauen */
  nsockd = 0;
  if ((sockd=socket(AF_UNIX,SOCK_STREAM,0))<0) {
    errormsg(E_SYSLOG|E_USER,confrecord,"bbs","iniconnecttouser","socket: %m");
    praeleaveiniconnecttouser();
    return(-2);
  }
  BZERO(&saun, sizeof(saun));
  saun.sun_family = AF_UNIX;
  sprintf(sockpath,"%s/%s",confrecord->scratchdir,sockname);
  strcpy(saun.sun_path,sockpath);
  len = SUN_LEN(&saun);
#ifdef SCM_RIGHTS
  saun.sun_len = len;
#endif
  unlink(saun.sun_path);
  if (bind(sockd,(struct sockaddr *)&saun,len)<0) {
  errormsg(E_SYSLOG|E_USER,confrecord,"bbs","iniconnecttouser","bind %s: %m",
	    sockpath);
  praeleaveiniconnecttouser();
  return(-2);
  }
  if (listen(sockd,5)<0) {
    errormsg(E_SYSLOG|E_USER,confrecord,"bbs","iniconnecttouser",
	      "listen %s: %m", sockpath);
    praeleaveiniconnecttouser();
    return(-2);
  }
  
  /* Ansprungstellen fuer Signale einrichten */
  /* zunaechst eventuell veraltetes sigusr2 loeschen */
  sigpending(&sigset);
  if (sigismember(&sigset, SIGUSR2)) {
    setsighandler(SIGUSR2, SIG_IGN);
    sigprocmask(SIG_UNBLOCK, &sigset, NULL);
  }
  if (sigsetjmp(sigusr1env,(int)FALSE)==0) {
    canjump_sigusr1 = TRUE;
    setsighandler(SIGUSR1,sighandler);
    if (sigsetjmp(sigusr2env,(int)FALSE)==0) {
      canjump_sigusr2 = TRUE;
      setsighandler(SIGUSR2,sighandler);
    }
    else {
      /* Ablehnung oder Beendigung vom Annehmer */
      abortconnecttouser(mypid,remotepid,sockd,sockpath,confrecord);
      if (nsockd) {
        close(nsockd);
        writetouser(confrecord,msg("iniconnecttouser",0,lang));
      }
      else {
        writetouser(confrecord,msg("iniconnecttouser",1,lang));
      }
      return(0);
    }
    
    /* Annehmer um Annahme benachrichtigen */
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGUSR1);
    sigaddset(&sigset, SIGUSR2);
    sigprocmask(SIG_BLOCK,&sigset,NULL);
    if (kill(remotepid,SIGUSR1) < 0) {
      errormsg(E_SYSLOG|E_USER,confrecord,"bbs","iniconnecttouser",
		"kill SIGUSR1 %d: %m",remotepid);
      praeleaveiniconnecttouser();
      return(-2);
    }
    sigprocmask(SIG_UNBLOCK,&sigset,NULL);
    /* Auf Antwortsignal warten */
    getkeyonprompt(msg("iniconnecttouser",2,lang),cset,TRUE,FALSE,confrecord);
    /* 'Q' (oder Escape) wurde gedrueckt */
    abortconnecttouser(mypid,remotepid,sockd,sockpath,confrecord);
    writetouser(confrecord,msg("iniconnecttouser",3,lang));
    return(0); 
  }
  else {
    /* Annehmer hat angenommen */
    if ((nsockd=accept(sockd,(struct sockaddr *)&saun,&len))<0) {
      errormsg(E_SYSLOG|E_USER,confrecord,"bbs","iniconnecttouser","accept: %m");
      praeleaveiniconnecttouser();
      return(-2);
    }
    /* Dialog durchfuehren */
    talkscreen(nsockd, confrecord);
    /* hierher gelangt man dann, wenn man selber beendet */
    close(nsockd);
    abortconnecttouser(mypid,remotepid,sockd,sockpath,confrecord);
    writetouser(confrecord,msg("iniconnecttouser",0,lang));
  }

  praeleaveiniconnecttouser();
  return(0);
}


void praeleaveiniconnecttouser(void)
{
  SIGSET_T sigset;

  sigemptyset(&sigset);
  sigaddset(&sigset, SIGUSR1);
  sigaddset(&sigset, SIGUSR2);
  sigprocmask(SIG_BLOCK,&sigset,NULL);
  setsighandler(SIGUSR1,sigtalkaccepthandler);
  canjump_sigusr2 = FALSE;
}


int abortconnecttouser(const PID_T inipid, const PID_T rempid, const int tsockd,
                       const char *talksockpath, confrecordtyp *confrecord)
{
  int sockd, n;
  char bstr[BEFSTRLEN+1];
  
  /* Talksocket schliessen und entfernen */
  close(tsockd);
  if (unlink(talksockpath) < 0) {
    errormsg(E_SYSLOG|E_USER,confrecord,"bbs","abortconnecttouser",
             "cannot remove talk-socket %s: %m",talksockpath);
  }
  /* Signalbehandlung zuruecksetzen */
  praeleaveiniconnecttouser();
  /* Talk beim Daemon abmelden */
  if ((sockd=connect2bbsd(confrecord))<0) {
    errormsg(E_SYSLOG|E_USER,confrecord,"bbs","abortconnecttouser",
	      "connection to daemon failed");
    exit(1);
  }
  sprintf(bstr,"#%2d\n",DO_TALKABORT);
  sendn(sockd,(void *)bstr,(SIZE_T)BEFSTRLEN,0);
  sendn(sockd,(void *)&inipid,(SIZE_T)sizeof(PID_T),0);
  sendn(sockd,(void *)&rempid,(SIZE_T)sizeof(PID_T),0);
  recvn(sockd,(void *)&n,(SIZE_T)sizeof(int),0);
  unconnect2bbsd(sockd,confrecord);
  /* Beendigung an Annehmer melden */
  if (kill(rempid,SIGUSR2) < 0) {
    errormsg(E_SYSLOG|E_USER,confrecord,"bbs","abortconnecttouser",
             "kill SIGUSR2 %d: %m",rempid);
    return(-2);
  }
  return(0);
}


int remconnecttouser(confrecordtyp *confrecord)
{
  int n, sockd, len, lang;
  struct sockaddr_un saun;
  char sockname[TALKSOCKNAMELEN+1], prompt[2*S_STRLEN+1], key;
  char cset[] = {'A','Q','\0'};
  sessionrecordtyp sr;
  SIGSET_T sigset;

  lang = confrecord->userrecord.lang;
  /* falls Initiator bereits abgebrochen hat sofort zurueckkehren */
  sigpending(&sigset);
  if (sigismember(&sigset,SIGUSR2)) {
    setsighandler(SIGUSR2, SIG_IGN);
    sigemptyset(&sigset);
    sigaddset(&sigset,SIGUSR2);
    sigprocmask(SIG_UNBLOCK,&sigset,NULL);
    return(0);
  }
  /* Initiator beim Daemon erfragen */
  sendstatustodaemon(BBS_BUSY,confrecord);
  if ((n=remconnecttouserd(&sr,sockname,getpid(),confrecord)) < 0)  return(n);
  
  /* Ansprungstelle fuer Beendigung der Verbindung */
  sockd = 0;
  if (sigsetjmp(sigusr2env,(int)FALSE)==0) {
    canjump_sigusr2 = TRUE;
    sigemptyset(&sigset);
    sigaddset(&sigset,SIGALRM);
    setsighandler2(SIGUSR2,sigset,sighandler);
    sigemptyset(&sigset);
    sigaddset(&sigset,SIGUSR2);
    sigprocmask(SIG_UNBLOCK,&sigset,NULL);
  }
  else {
    alarm(0);
    setsighandler(SIGALRM, SIG_IGN);
    sigemptyset(&sigset);
    sigaddset(&sigset,SIGALRM);
    sigprocmask(SIG_UNBLOCK,&sigset,NULL);    
    if (sockd) {
      close(sockd);
      writetouser(confrecord,msg("remconnecttouser",0,lang));
    }
    else {
      writetouser(confrecord,msg("remconnecttouser",1,lang));
    }
    praeleaveremconnecttouser(0);
    return(0);
  }
  
  /* User per Prompt abfragen */
  move(LINES-1,0);
  clrtoeol();
  addstr("TALK:");
  refresh();
  putchar(BELL_KEY);
#ifdef NO_USLEEP
  sleep(1);
#else
  usleep(200000);
#endif
  sprintf(prompt,msg("remconnecttouser",2,lang),sr.user);
  setsighandler(SIGALRM,sighandler);
  sigsetjmp(sigalrmenv,(int)TRUE);
  canjump_sigalrm = TRUE;
  alarm(5);
  putchar(BELL_KEY);
  
  key = getkeyonprompt(prompt,cset,TRUE,FALSE,confrecord);
  alarm(0);
  setsighandler(SIGALRM,SIG_IGN);
  canjump_sigalrm = FALSE;
  
  if (key == 'A') {
    /* Talk angenommen */
    /* Initiator Annahmebestaetigung senden */
    if (kill(sr.pid,SIGUSR1) < 0) {
      errormsg(E_SYSLOG|E_USER,confrecord,"bbs","remconnecttouser",
               "kill SIGUSR1 %d: %m",sr.pid);
      praeleaveremconnecttouser(sockd);
      return(-2);
    }
    /* Verbindung aufnehmen */
    if ((sockd=socket(AF_UNIX,SOCK_STREAM,0)) < 0) {
      errormsg(E_SYSLOG|E_USER,confrecord,"bbs","remconnecttouser","socket: %m");
      praeleaveremconnecttouser(sockd);
      return(-2);
    }
    BZERO(&saun, sizeof(saun));
    saun.sun_family = AF_UNIX;
    sprintf(saun.sun_path,"%s/%s",confrecord->scratchdir,sockname);
    len = SUN_LEN(&saun);
#ifdef SCM_RIGHTS
    saun.sun_len = len;
#endif
    if (connect(sockd,(struct sockaddr *)&saun,len) < 0) {
      errormsg(E_SYSLOG|E_USER,confrecord,"bbs","remconnecttouser",
		"connect %s: %m", saun.sun_path);
      praeleaveremconnecttouser(sockd);
      return(-2);
    }
    /* Dialog durchfuehren */
    talkscreen(sockd, confrecord);
    
    /* hierhin gelangt man nur, wenn der Annehmer (diese Seite) beendet */
    /* Talkende an Initiator senden */
    if (kill(sr.pid,SIGUSR2) < 0) {
      errormsg(E_SYSLOG|E_USER,confrecord,"bbs","remconnecttouser",
               "kill SIGUSR2 %d: %m",sr.pid);
      praeleaveremconnecttouser(sockd);
      return(-2);
    }    
  }
  else {
    /* Talk ablehnen */
    /* Initiator Ablehnung senden */
    if (kill(sr.pid,SIGUSR2) < 0) {
      errormsg(E_SYSLOG|E_USER,confrecord,"bbs","remconnecttouser",
               "kill SIGUSR2 %d: %m",sr.pid);
      praeleaveremconnecttouser(sockd);
      return(-2);
    }    
  }
  
  /* der Initiator sendet immer SIGUSR2 zum beenden, auch wenn der Annehmer
     zuerst beendete -> gegebenenfalls darauf warten (mit Timeout)          */
  sleep(TALKQUITTIMEOUT);
  praeleaveremconnecttouser(sockd);
  
  return(0);
}


void praeleaveremconnecttouser(const int sockd)
{
  SIGSET_T sigset;
  
  if (sockd)  close(sockd);
  canjump_sigusr2 = FALSE;
  sigemptyset(&sigset);
  sigaddset(&sigset,SIGUSR2);
  sigprocmask(SIG_BLOCK,&sigset,NULL);
}
