#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <time.h>

extern int errno;

#ifndef VERSION
#define VERSION ("test-version")
#endif


ssize_t atomic_write(int fd, const void *buf, size_t count)
{
  char *b = (char *) buf;
  ssize_t bytes_written = 0;
  int ret;
  while (bytes_written < count) {
    ret = write(fd, &b[bytes_written], count - bytes_written);
    if (ret < 0) {
      if (errno == EINTR)
        continue;
      if (errno == EAGAIN) {
        fd_set s;
        FD_ZERO(&s);
        FD_SET(fd, &s);
        if (select(fd + 1, NULL, &s, NULL, NULL) == 0)
          fprintf(stderr, "atomic_write: very strange. infinite select() returned 0. report this!\n");
        continue;
      }
      return -1;
    }
    bytes_written += ret;
  }

  return bytes_written;
}

int main(int argc,char** argv)
{
  int fdin;
  int i;
  int timeout = -1; /* indefinite timeout */
  const size_t buflen = 10240;
  ssize_t size;
  char buf[buflen];
  int didtimeout;
  //  const int defaulttimeout = 1; /* microseconds */
  int switchtimeout = 10;
  int debug = 0;
  fd_set rfds;
  struct timeval tv;
  int retval;
  time_t timeoutset;

  i = 1;
  while (i < argc) {
    if(strcmp(argv[i],"--") == 0) {
      i++;
      break;
    }
    if (strcasecmp(argv[i], "-t") == 0) {
      if((i + 1) == argc) {
	fprintf(stderr,"timeout parameter out of array!\n");
	exit(-1);
      }
      timeout = atoi(argv[i + 1]);
      i+=2;
      continue;
    }
    if(strcasecmp(argv[i],"-s")==0) {
      if((i+1)==argc) {
	fprintf(stderr,"switch timeout parameter out of array!\n");
	exit(-1);
      }
      switchtimeout = atoi(argv[i+1]);
      i+=2;
      continue;
    }
    if(strcasecmp(argv[i],"-d")==0) {
      debug = 1;
      i++;
      continue;
    }
    if(strcasecmp(argv[i],"--help")==0 || strcasecmp(argv[i],"-h")==0) {
      fprintf(stdout,"pcat (persistent cat) %s by Heikki Orsila <heikki.orsila@tut.fi>\n",VERSION);
      fprintf(stdout,"This program is Public Domain (use the source code & program as you please)\n\n");
      fprintf(stdout,"Usage:\n");
      fprintf(stdout,"\tpcat [options] source(s)\n\n");
      fprintf(stdout,"\t-t timeout\tset tailing timeout (default = infinity)\n");
      fprintf(stdout,"\t-s timeout\tset source switch timeout (default = %d)\n",switchtimeout);
      fprintf(stdout,"\t-h or --help\tshow help\n");
      fprintf(stdout,"\t-d\t\tshow debug messages\n");
      fprintf(stdout,"\n - means stdin in sources\n");
      fprintf(stdout,"\nusage hint:\n Assume you're downloading 650MB file (say, test.mpg) to a remote machine,\n");
      fprintf(stdout," and you want to have that file copied simultaneously to your machine. Do:\n");
      fprintf(stdout,"\tssh user@remotemachine \"pcat /path/test.mpg\" >test.mpg\n");
      return 0;
    }
    break;
  }

  if(i == argc) {
    fprintf(stderr,"no input file, assuming stdin!\n");
    fdin = 0;
    fcntl(fdin, F_SETFL, O_NONBLOCK);
  } else {
    if(strcmp(argv[i], "-")==0) {
      fdin = 0;
      fcntl(fdin, F_SETFL, O_NONBLOCK);
    } else {
      fdin = open(argv[i], O_RDONLY | O_NONBLOCK);
    }
    i++;
  }

  if(fdin<0) {
    fprintf(stderr,"couldnt open input file '%s'!\n",argv[i]);
    exit(-1);
  }

  if(i<argc && timeout<0) {
    fprintf(stderr,"warning: multiple sources with infinite timeout (only first source is used)\n");
  }  

  didtimeout = 0;
  timeoutset = time(0);

  while (1) {

    size = read(fdin, buf, buflen);

    if (size < 0) {
      if (errno == EAGAIN) {
	size = 0;
      } else if (errno == EINTR) {
	continue;
      } else {
	perror("Read error on input");
	break;
      }
    }

    if (debug)
      fprintf(stderr, "debug: read %zd\n", size);

    if (size == 0) {
      if (timeout < 0) {
	usleep(10000);
	continue;
      }

      if (didtimeout == 0) {
	FD_ZERO(&rfds);
	FD_SET(fdin, &rfds);
	tv.tv_sec = timeout;
	tv.tv_usec = 0;
	retval = select(fdin + 1, &rfds, NULL, NULL, &tv);
	didtimeout = 1;
	timeoutset = time(NULL);
	continue;
      }

      if ((time(NULL) - timeoutset) <= timeout) {
	usleep(10000);
	continue;
      }
      didtimeout = 0;
      
      /* file end => read new file if exists, otherwise end */
      
      if (i == argc) {
	break;
      } else {
	
	/* close current fd (if not zero) */
	if (fdin)
	  close(fdin);

	/* try to take next fd */
	if (strcmp(argv[i], "-") == 0) {
	  fdin = 0;
	  fcntl(fdin,F_SETFL,O_NONBLOCK);
	} else
	  fdin = open(argv[i],O_RDONLY | O_NONBLOCK);
	
	if (fdin < 0) {
	  sleep(switchtimeout);
	  if(strcmp(argv[i],"-")==0) {
	    fdin = 0;
	    fcntl(fdin, F_SETFL, O_NONBLOCK);
	  } else
	    fdin = open(argv[i], O_RDONLY | O_NONBLOCK);
	  
	  if (fdin < 0) {
	    fprintf(stderr,"couldnt open input file '%s'!\n",argv[i]);
	    exit(-1);
	  }
	}
	
	i++;
	continue;
      }
      
    }
    
    didtimeout = 0;
    
    if (atomic_write(1, buf, size) < 0) {
      fprintf(stderr, "Sorry, can't write everything: %s\n", strerror(errno));
      break;
    }
  }

  close(fdin);
  return 0;
}

