
/******************************************************************************
* MODULE     : pipes.gen.cc
* DESCRIPTION: TeXmacs pipes
* COPYRIGHT  : (C) 2000  Joris van der Hoeven
*******************************************************************************
* This software falls under the GNU general public license and comes WITHOUT
* ANY WARRANTY WHATSOEVER. See the file $TEXMACS_PATH/LICENSE for more details.
* If you don't have this file, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
******************************************************************************/

#include <file.gen.h>
#include <timer.gen.h>
#include <iterator.gen.h>

#module code_pipes
#import file
#import timer
#import hashmap (string, string)
#import hashmap_iterator (string, pointer)

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <malloc.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>

extern char **environ;
extern bool eval_scheme_func_string_string (
  string f, string s, string& r);
extern bool eval_scheme_func_string2_string (
  string f, string s1, string s2, string& r);
extern bool eval_scheme_func_tree_tree (
  string f, tree t, tree& r);

#define TRUE 1
#define FALSE 0
#define ERROR (-1)
#define STDIN 0
#define STDOUT 1
#define STDERR 2
#define IN 0
#define OUT 1
#define TERMCHAR '\1'

static hashmap<string,string> con_cmd ("");
static hashmap<string,string> con_in_prot ("verbatim");
static hashmap<string,string> con_out_prot ("verbatim");
static hashmap<string,string> con_in_tree ("(lambda (x) x)");
static hashmap<string,string> con_in_string ("(lambda (x y) y)");
static hashmap<string,string> con_out_string ("(lambda (x y) y)");
static hashmap<string,string> con_out_tree ("(lambda (x) x)");

/******************************************************************************
* The connection resource
******************************************************************************/

#import resource (connection_rep, connection)
struct connection_rep: rep<connection> {
  string name;          // name of the connection type
  string session;       // name of the session
  string in_prot;       // protocol for data going to the child;
  string out_prot;      // protocol for data coming from the child;

  int  pid;             // process identifier of the child
  int  tochild[2];      // for data going to the child
  int  fromchild[2];    // for data coming from the child
  int  in;              // file descriptor for data going to the child
  int  out;             // file descriptor for data coming from the child
  int  status;          // status of the connection
  int  delayed;         // delayed status of the connection
  bool silent;          // for silent reconnections

  texmacs_input tm_in;  // texmacs input handler for data from child

public:
  connection_rep (string name, string session);

  string start ();
  string silent_restart ();
  void   write (string s);
  void   read ();
  void   process ();
  void   transmit (string s);
  void   interrupt ();
  void   stop (bool emergency= FALSE);
};
#import code_resource (connection_rep, connection)

/******************************************************************************
* Routines for connections
******************************************************************************/

connection_rep::connection_rep (string name2, string session2):
  rep<connection> (name2 * "-" * session2),
  name (name2), session (session2),
  in_prot (con_in_prot [name]),
  out_prot (con_out_prot [name]),
  tm_in (out_prot)
{
  in     = tochild[0]= tochild[1]= -1;
  out    = fromchild[0]= fromchild[1]= -1;
  status = CONNECTION_DEAD;
  delayed= WAITING_FOR_OUTPUT;
  silent = FALSE;
}

extern char **environ;

void
execute_shell (string s) {
  char *argv[4];
  argv[0] = "sh";
  argv[1] = "-c";
  argv[2] = as_charp (s);
  argv[3] = 0;
  execve("/bin/sh", argv, environ);
}

string
connection_rep::start () {
  if (delayed == CONNECTION_DEAD) {
    status = CONNECTION_DEAD;
    delayed= WAITING_FOR_OUTPUT;
  }
  if ((status != CONNECTION_DEAD) || (delayed != WAITING_FOR_OUTPUT))
    return "continuation of#" * name * "#session#`" * session * "'";

  tm_in= texmacs_input (out_prot);
  pipe (tochild);
  pipe (fromchild);
  pid= fork ();
  if (pid==0) { // the child
    dup2 (tochild [IN], STDIN);
    close (tochild [IN]);
    close (fromchild [IN]);
    close (tochild [OUT]);
    dup2 (fromchild [OUT], STDOUT);
    dup2 (STDOUT, STDERR);
    close (fromchild [OUT]);
    execute_shell (con_cmd [name]);
    exit (127);
    // exit (system (con_cmd [name]) != 0);
  }
  else { // the main process
    out= fromchild [IN];
    close (fromchild [OUT]);
    in= tochild [OUT];
    close (tochild [IN]);

    status= WAITING_FOR_OUTPUT;
    if (/* !banner */ TRUE) return "ok";
    else {
      int r;
      char outbuf[1024];
      r = ::read (out, outbuf, 1024);
      if (r == 1 && outbuf[0] == TERMCHAR) return "ok";
      status= CONNECTION_DEAD;
      recursive_kill (pid);
      wait (NULL);
      if (r == ERROR) return "Error: the application does not reply";
      else
	return "Error: the application did not send its usual startup banner";
    }
  }
}

string
connection_rep::silent_restart () {
  string r= start ();
  if (r != "ok") return r;
  (void) connection_read (name, session, 10000);
  return r;
}

void
connection_rep::write (string s) {
  // cout << "---> [" << s << "]\n";
  if ((status != WAITING_FOR_INPUT) || (delayed != WAITING_FOR_OUTPUT)) return;
  char* _s= as_charp (s);
  ::write (in, _s, N(s));
  ::write (in, "\n", 1);
  delete[] _s;
  status= WAITING_FOR_OUTPUT;
}

void
connection_rep::read () {
  if (status != WAITING_FOR_OUTPUT) return;

  int i, r;
  char outbuf[1024];
  r = ::read (out, outbuf, 1024);
  if (r == ERROR) {
    cerr << "TeXmacs] read failed from " * res_name * "\n";
    wait (NULL);
    return;
  }
  if (r == 0) {
    recursive_kill (pid);
    tm_in->eof ();
    delayed= CONNECTION_DEAD;
  }
  else if (con_out_string->contains (name)) {
    string s1, s2;
    string fn= con_out_string [name];
    for (i=0; i<r; i++)
      if (outbuf[i]=='\\') s1 << "\\\\";
      else if (outbuf[i]=='\42') s1 << "\\\"";
      else s1 << outbuf[i];
    eval_scheme_func_string2_string (fn, session, s1, s2);
    for (i=0; i<N(s2); i++)
      if (tm_in->put (s2[i]))
	delayed= WAITING_FOR_INPUT;
  }
  else for (i=0; i<r; i++) {
    /*
    char c= outbuf[i];
    if (c == DATA_BEGIN) cout << "[BEGIN]";
    else if (c == DATA_END) cout << "[END]";
    else if (c == DATA_ESCAPE) cout << "[ESCAPE]";
    else cout << c;
    */
    if (tm_in->put (outbuf[i])) {
      delayed= WAITING_FOR_INPUT;
      // cout << "\n------------------------------------\n";
    }
  }
}

void
connection_rep::interrupt () {
  if ((status == CONNECTION_DEAD) || (delayed == CONNECTION_DEAD)) return;
  kill (pid, SIGINT);
  // system ("ps -l");
  // cout << "Interrupt " << pid << " (" << name << ", " << session << ")\n";
}

void
connection_rep::stop (bool emergency) {
  if ((status == CONNECTION_DEAD) || (delayed == CONNECTION_DEAD)) return;
  recursive_kill (pid);
  if (!emergency) {
    tm_in->eof ();
    if (status == WAITING_FOR_OUTPUT) delayed= CONNECTION_DEAD;
    else status= CONNECTION_DEAD;
    close (in);
    wait (NULL);
  }
}

/******************************************************************************
* Listen to all active pipes (may be optimized for speed)
******************************************************************************/

void
listen_to_pipes () {
  while (TRUE) {
    fd_set rfds;
    FD_ZERO (&rfds);
    int max_fd= 0;
    iterator<string> it= iterate (resource<connection>);
    while (it->busy()) {
      string name= it->next ();
      connection con (name);
      if ((con->status == WAITING_FOR_OUTPUT) &&
	  (con->delayed != CONNECTION_DEAD))
	{
	  FD_SET (con->out, &rfds);
	  if (con->out >= max_fd) max_fd= con->out+1;
	}
    }
    if (max_fd == 0) break;

    struct timeval tv;
    tv.tv_sec  = 0;
    tv.tv_usec = 0;
    int nr= select (max_fd, &rfds, NULL, NULL, &tv);
    if (nr==0) break;

    it= iterate (resource<connection>);
    while (it->busy()) {
      string name= it->next ();
      connection con (name);
      if ((con->status == WAITING_FOR_OUTPUT) &&
	  (con->delayed != CONNECTION_DEAD) &&
	  FD_ISSET (con->out, &rfds)) con->read ();
    }
  }
}

/******************************************************************************
* Interface (setting up a connection type)
******************************************************************************/

bool
connection_declared (string name) {
  return con_cmd->contains (name);
}

void
connection_declare (string name, string cmd, string con_info) {
  con_cmd (name)= cmd;
  (void) con_info; // will contain information about status pipes
}

void
connection_format (string name, string in_prot, string out_prot) {
  con_in_prot  (name)= in_prot;
  con_out_prot (name)= out_prot;
}

void
connection_filter_tree_in (string name, string filter) {
  con_in_tree (name)= filter;
}

void
connection_filter_string_in (string name, string filter) {
  con_in_string (name)= filter;
}

void
connection_filter_string_out (string name, string filter) {
  con_out_string (name)= filter;
}

void
connection_filter_tree_out (string name, string filter) {
  con_out_tree (name)= filter;
}

/******************************************************************************
* Interface (using a specific connection)
******************************************************************************/

string
connection_start (string name, string session, bool again=FALSE) {
  // cout << "Start " << name << ", " << session << "\n";
  if (!connection_declared (name))
    return "Error: connection " * name * " has not been declared";
  connection con= connection (name * "-" * session);
  if (nil (con)) {
    if (debug (0)) cout << "TeXmacs] Starting session '" << session << "'\n";
    con= new connection_rep (name, session);
  }
  if (debug (0)) {
    if (again) cout << "\a";
    cout << "TeXmacs] Launching '" << name
	 << "' via " << con_cmd [name] << "\n";
  }
  string message= again? con->silent_restart (): con->start ();
  con->tm_in->bof ();
  return message;
}

string
connection_shell_command (string session, string cmd) {
  con_cmd ("shell")= cmd;
  connection con= connection ("shell-" * session);
  if (nil (con)) con= new connection_rep ("shell", session);
  else con->stop ();
  con->in_prot = "verbatim";
  con->out_prot= "verbatim";
  return con->start ();
}

void
connection_write (string name, string session, tree t) {
  // cout << "Write " << name << ", " << session << ", " << t << "\n";
  connection con= connection (name * "-" * session);
  if (nil (con)) return;
  if (con_in_tree->contains (name)) {
    tree r;
    eval_scheme_func_tree_tree (con_in_tree [name], t, r);
    t= r;
  }
  string s= tree_to_verbatim (t);
  if (con_in_string->contains (name)) {
    string r;
    eval_scheme_func_string2_string (con_in_string [name], session, s, r);
    s= r;
  }
  else if (name == "ispell") {
    while ((N(s)>0) && (s[N(s)-1] == '\n')) s= s (0, N(s)-1);
    s= s * "\n";
  }
  con->write (s);
  con->tm_in->bof ();
}

tree
connection_read (string name, string session, int try_ms, string channel) {
  if (try_ms > 0) {
    tree t ("");
    int wait_until= texmacs_time () + try_ms;
    while (t == "") {
      listen_to_pipes ();
      t= connection_read (name, session);
      if (texmacs_time () > wait_until) break;
    }
    // cout << "Read " << t << "\n";
    return t;
  }

  // cout << "Read " << name << ", " << session << ", " << channel << " -> ";
  connection con= connection (name * "-" * session);
  if (nil (con)) return "";
  tree t= con->tm_in->get (channel);
  if (con_out_tree->contains (name)) {
    tree r;
    eval_scheme_func_tree_tree (con_out_tree [name], t, r);
    t= r;
  }
  if (channel == "output") {
    con->status = con->delayed;
    con->delayed= WAITING_FOR_OUTPUT;
  }
  // cout << t << "\n";
  return t;
}

void
connection_interrupt (string name, string session) {
  // cout << "Interrupt " << name << ", " << session << "\n";
  connection con= connection (name * "-" * session);
  if (nil (con)) return;
  con->interrupt ();
}

void
connection_stop (string name, string session) {
  // cout << "Stop " << name << ", " << session << "\n";
  connection con= connection (name * "-" * session);
  if (nil (con)) return;
  con->stop ();
}

void
connection_stop_all () {
  // cout << "Stop all connections\n";
  iterator<string> it= iterate (resource<connection>);
  while (it->busy()) {
    string name= it->next ();
    connection con (name);
    if (!nil (con))
      con->stop (TRUE);
  }
}

int
connection_status (string name, string session) {
  // cout << "Status " << name << ", " << session << " -> ";
  connection con= connection (name * "-" * session);
  if (nil (con)) return CONNECTION_DEAD;
  // cout << con->status << "\n";
  return con->status;
}

tree
connection_eval (string name, string session, tree t) {
  connection con= connection (name * "-" * session);
  if (nil (con)) return "";
  connection_write (name, session, t);
  tree doc (DOCUMENT);
  while (TRUE) {
    listen_to_pipes ();
    tree next= connection_read (name, session);
    if (next == "");
    else if (is_document (next)) doc << A (next);
    else doc << next;
    if (con->status == WAITING_FOR_INPUT) break;
  }
  if (N(doc) == 0) return "";
  return doc;
}

void
connection_exec (string name, string session, string s) {
  connection con= connection (name * "-" * session);
  if (nil (con)) return;
  if (con_in_string->contains (name)) {
    string r;
    eval_scheme_func_string2_string (con_in_string [name], session, s, r);
    s= r;
  }
  con->write (s);
  con->tm_in->bof ();
  while (TRUE) {
    listen_to_pipes ();
    (void) connection_read (name, session);
    if (con->status == WAITING_FOR_INPUT) break;
  }
}

#endmodule // code_pipes
