#include <unistd.h>
#include "strerr.h"
#include "error.h"
#include "sgetopt.h"
#include "scan.h"
#include "stralloc.h"
#include "buffer.h"
#include "pathexec.h"
#include "fd.h"
#include "wait.h"
#include "taia.h"
#include "iopause.h"
#include "ndelay.h"
#include "sig.h"

/* defaults */
#define TIMEOUT 300
#define SIZEMAX 1024

#define USAGE " [-vo] [-t timeout] [-s size] prog"
#define WARNING "uncat: warning: "
#define FATAL "uncat: fatal: "

const char *progname;
int exitasap =0;

void exit_asap() {
  exitasap =1;
}

void usage () {
  strerr_die4x(1, "usage: ", progname, USAGE, "\n");
}

int main (int argc, char * const *argv, char * const *envp) {
  int opt;
  unsigned long timeout =TIMEOUT;
  unsigned long sizemax =SIZEMAX;
  int verbose =0;
  int once =0;
  static stralloc sa;
  int eof =0;

  progname =*argv;

  sig_block(sig_term);
  sig_catch(sig_term, exit_asap);

  while ((opt =getopt(argc, argv, "t:s:voV")) != opteof) {
    switch(opt) {
    case 'V':
      strerr_warn1("$Id$\n", 0);
    case '?':
      usage();
    case 't':
      scan_ulong(optarg, &timeout);
      if (timeout <= 0) timeout =TIMEOUT;
      break;
    case 's':
      scan_ulong(optarg, &sizemax);
      if (sizemax <= 0) sizemax =SIZEMAX;
      break;
    case 'v':
      verbose =1;
      break;
    case 'o':
      once =1;
      break;
    }
  }
  argv +=optind;
  if (!*argv) usage();
  
  if (verbose) strerr_warn2(WARNING, "starting.", 0);

  ndelay_on(0);

  for (;;) {
    struct taia now, deadline;
    int cpipe[2];
    int pid;
    int wstat;

    stralloc_copys(&sa, "");

    /* set timeout */
    taia_now(&now);
    taia_uint(&deadline, timeout);
    taia_add(&deadline, &now, &deadline);

    /* read fd 0, stop at maxsize bytes or timeout */
    for (;;) {
      int r;
      char *s;
      iopause_fd iofd;

      taia_now(&now);
      if (taia_less(&deadline, &now)) {
      	if (verbose && sa.len) strerr_warn2(WARNING, "timeout reached.", 0);
      	break;
      }
      iofd.fd =0;
      iofd.events =IOPAUSE_READ;

      sig_unblock(sig_term);
      iopause(&iofd, 1, &deadline, &now);
      sig_block(sig_term);
      
      if (exitasap) {
      	if (verbose) strerr_warn2(WARNING, "got sigterm.", 0);
      	break;
      }
      
      r =buffer_feed(buffer_0);
      if (r < 0) {
	if (errno == error_again) continue;
	strerr_die2sys(111, FATAL, "unable to read fd 0: ");
      }
      if (r == 0) {
	if (verbose) strerr_warn2(WARNING, "end of input.", 0);
	if (! once) continue;
	eof++;
  	break;
      }
      if (r >= sizemax) r =sizemax;
      if ((sa.len +r) > sizemax) {
      	if (verbose) strerr_warn2(WARNING, "max size reached.", 0);
      	break;
      }
      s =buffer_peek(buffer_0);
      if (! stralloc_catb(&sa, s, r)) {
	strerr_die2sys(111, FATAL, "out of memory: ");
      }
      buffer_seek(buffer_0, r);
    }
    if (sa.len) {
      /* run prog to process sa.s */
      if (pipe(cpipe) == -1) {
	strerr_die2sys(111, FATAL, "unable to create pipe for child: ");
      }
      while ((pid =fork()) == -1) {
	strerr_warn4(WARNING, "unable to fork for \"", *argv, "\" pausing: ",
		     &strerr_sys);
	sleep(5);
      }
      if (!pid) {
	/* child */
	
	sig_uncatch(sig_term);
	sig_unblock(sig_term);

	close(cpipe[1]);
	fd_move(0, cpipe[0]);
	fd_copy(1, 2);

	if (verbose) strerr_warn2(WARNING, "starting child.", 0);
	pathexec_run(*argv, argv, envp);
	strerr_die2sys(111, FATAL, "unable to start child: ");
      }
      
      close(cpipe[0]);
      if (write(cpipe[1], sa.s, sa.len) < sa.len) {
	strerr_warn2(WARNING, "unable to write to child: ", &strerr_sys);
      }
      close(cpipe[1]);
      
      if (wait_pid(&wstat, pid) != pid) {
	strerr_die2sys(111, FATAL, "wait_pid: ");
      }
      if (wait_crashed(wstat)) {
	strerr_warn2(WARNING, "child crashed.", 0);
      } else {
	if (verbose) strerr_warn2(WARNING, "child exited.", 0);
      }
    }

    if (exitasap || eof) break;
  }

  if (verbose) strerr_warn2(WARNING, "exit.", 0);
  _exit(0);
}
