/*
 This is bmv.c, part of the source code for

 BMV version 1.1
 copyright by Jan Kybic, 26th July 1994

 Jan Kybic, Prosecka 681, Praha 9, Czech Republic, <kybic@earn.cvut.cz>

 BMV is a very simple viewer of images in the pbm(5)-raw format
     and a front end for GhostScript		
     based on Svgalib library for Linux

 BMV is distributed under GNU GPL (General Public License),
   you can obtain a copy from many FTP sites if you are interested for details
*/

/* This file contains main() and functions for handling PBM files */
/* see also bmv.h for description of some globals */

#include "bmv.h"
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <time.h>

int vgaon=0,vidmode=-1 ; /* we are in text mode originally */

FILE *file=NULL ;       /* we have not opened PBM file yet */

long offset ;
int screenw,screenh,imagew,imageh ;
char image_magic ;
int image_num_components, image_max_component ;
int image_pixels_per_byte ;

int mag = INIMAG ;             /* initial magnification */

int pixthresh = PIXTRESH ;	/* initial pixel threshold */

int psview=1,testpbm=0 ; /* default is Postscript, no testing */

int leaving = 0 ;	/* true if we are cleaning up before leaving */

void sigtermhandler(int sig) ; /* prototypes of signal handlers */
void sigchldhandler(int sig) ;

/* initial message */
static const char *initmsg=
"\nThis is BMV version " VER ", writen by Jan Kybic <kybic@earn.cvut.cz>." ;

/* and some brief help */
static const char *helpmsg=
"\nBMV is a front end for GhostScript intended for viewing PS files.\n"
"    You can also use it for viewing rawPBM image files.\n"
"Syntax: bmv [options] file\n"
"  options: -v<number>     VGA mode number for VGAlib, see <vga.h>\n"
"           -m<number>     initial magnification, can be negative\n"
"           -b             display PBM file instead of PS file\n"
"           -g<string>     full path name of GhostScript executable\n"
"           -I<string>     include directory- same as -I option for GS \n"
"           -r<string>     resolution- same as -r option for GhostScript\n"
"           -p<string>     papersize- same values as GhostScript -sPAPERSIZE=\n"
"           -w<number>     wait timeout limit for GhostScript (in seconds)\n"
"           -h             displays this screen\n"
"           -t<number>     set picture threshold in percents\n"
"While you are viewing picture you can use the following keys:\n"
"  h,j,k,l     to move left/down/up/right\n"
"  H,J,K,L     to move to the boundary\n"
"  f,r         to increase/decrease step size\n"
"  +,-         to increase/decrease magnification\n"
"  n,p         to go to the next/previous page (only DSC documents)\n"
"  N,P         to go to the first/last/page (only DSC documents)\n" 
"  g<n>        to go to page number <n>, <n> consists of exactly 3 digits\n"
"  s<n>        to switch to virtual console <n>, <n> is a digit\n"
"  q           to quit\n" ;




/* printerr is used for printing error messages */

void printerr(const char *e)
{ printf("!!! BMV: %s\r\n",e) ;
}


/* leave prints all the strings it is passed via printerr, does all the
   cleanup and exits with status stat. It expects pointers to strings to be
   printed, the last one being NULL */

void leave(int stat,...)
{ va_list ptr ; char *e ;

  if (leaving) { /* puts("Recursive call to leave ignored") ; */ return ; }

 /* now free the buffers */
 if (inbuff!=NULL) { free(inbuff) ; inbuff = NULL ; }
 if (outbuff!=NULL) { free(outbuff) ; outbuff = NULL ; }
 if (readbuff!=NULL) { free(readbuff) ; readbuff = NULL ; }
 if (gray_inbuff!=NULL) { free(gray_inbuff) ; gray_inbuff = NULL ; }
 if (pix_inbuff!=NULL) { free(pix_inbuff) ; pix_inbuff = NULL ; }
 if (filebuff!=NULL) { free(filebuff) ; filebuff = NULL ; }

 /* call close... to do the rest of the cleaning */
 closepsfile() ;
 closedisplay() ;

 /* print messages, after closedisplay in case mode changes clear the screen */
 va_start(ptr,stat) ;

 for(va_start(ptr,stat);(e=va_arg(ptr,char *))!=NULL;) printerr(e) ;
     /* print all the error messages */
 va_end(ptr) ;

 puts("Goodbye. Try it again !") ;
 exit(stat) ;
}


/* and here we have main */

void main(int argc,const char *argv[])
{
  int curopt,temp ; char *tail ; const char *s ;

  /* install handlers - we want to terminate cleanly on SIGTERM,SIGINT
     do nothing for SIGREADY (we us it for communication) and
     on SIGCHLD we want to notice if GhostScript has had any troubles */

  signal(SIGTERM,sigtermhandler) ;
  signal(SIGINT,sigtermhandler) ;
  signal(SIGREADY,emptyhandler) ;
  signal(SIGCHLD,sigchldhandler) ;

  puts(initmsg) ;                    /* greetings */
  if (argc<=1) puts(helpmsg) ;

  for(curopt=1;curopt<argc;curopt++) /* process options */
    if (*argv[curopt]=='-')
      switch (*(argv[curopt]+1))
        { case 'b': psview=0 ; break ;

          case 'g': gsname = &argv[curopt][2] ;
		    if (*gsname == '\0')
			leave(1, "Name required for -g option", NULL) ;
		    if (access(gsname, X_OK) != 0)
			leave(1, "Name specified by -g must be executable", NULL) ;
		    break ;

          case 'I': gsincdir = argv[curopt] ;
		    if (gsincdir[2] == '\0')
			leave(1, "Path required for -I option", NULL) ;
		    break ;

          case 'p': gspaper = &argv[curopt][2] ;
		    if (*gspaper == '\0')
			leave(1, "Papersize required for -p option", NULL) ;
		    break ;

          case 'r': gsres = argv[curopt] ;
		    s = &gsres[2];
		    if (*s == '\0')
			leave(1, "Resolution required for -r option", NULL) ;
		    while (*s >= '0' && *s <= '9') s++;
		    if (*s == 'x') {
			s++;
			while (*s >= '0' && *s <= '9') s++;
		    }
		    if (*s != '\0')
			leave(1, "Illegal resolution - must be a number + 'x' + a number", NULL) ;
		    break ;

          case 'v': temp=(int) strtol(argv[curopt]+2,&tail,0) ;
                    if (tail==argv[curopt]+2)
                       leave(1,"Wrong VGA mode - must be a number",NULL) ;
                    vidmode=temp ;
                    break ;

          case 'm': temp=(int) strtol(argv[curopt]+2,&tail,0) ;
                    if (tail==argv[curopt]+2)
                       leave(1,"Wrong magnification - must be a number",NULL) ;
                    if (temp==0 || temp>MAXIMBITS || temp<-MAXIMBITS)
                       leave(1,"Magnification must be within -" STRMAXIMBITS
                               "..." STRMAXIMBITS " excluding 0.",NULL) ;
                    mag=temp ;
                    break ;

          case 't': temp=(int) strtol(argv[curopt]+2,&tail,0) ;
                    if (tail==argv[curopt]+2)
                       leave(1,"Wrong pixel threshold - must be a number",NULL)
 ;
                    if (temp<=0 || temp>=100)
                       leave(1,"Pixel threshold must be between 1 and 99",NULL)
 ;
                    pixthresh = temp ;
                    break ;

          case 'w': temp=(int) strtol(argv[curopt]+2,&tail,0) ;
                    if (tail==argv[curopt]+2)
                       leave(1,"Wrong wait limit - must be a number",NULL) ;
                    if (temp<0 || temp>=10000)
                       leave(1,"Wait limit must be between 0 and 9999",NULL) ;
                    gstimeout = temp ;
                    break ;

          case 'h': puts(helpmsg) ; exit(0) ; break ;

          default:  leave(1,"Wrong option -",argv[curopt],
                          "Try bmv -h.",NULL) ;
        }
    /* argv[curopt] is probably a filename */
    else { leaving = 0;

	   opendisplay() ; /* init display */

           /* call the appropriate display function */
           if (!psview) bkgdisplayfile(argv[curopt]) ;
                   else displaypsfile(argv[curopt]) ;

           closedisplay() ;  /* back to text */

           break ;   /* leave this out if you want to display more files
                        in one run, but I do not think it is necessary */
         }

  puts("Bye.") ; exit(0) ;
}



/* openpbm expect filename of a rawPBM file to open. It opens the file
   reads the header and sets globals: file,imagew,imageh,offset
   if it encounters any error it exits, except when file is too short
   and testpbm=1, then it just silently resets testpbm=0. This is used
   by makegsdisply
   See PBM(5) for description of a format */

/* changed to read colour pixmaps too */

void openpbm(const char *filename)
{ char c ;
  long length ; /* expected length of a file */
  int fno ;
  struct stat stbuf ;

  if (!testpbm)
     printf("Opening PBM file %s for reading...\n",filename) ;

  if ((file=fopen(filename,"r"))==0)
   leave(1,"Cannot open PBMfile for reading",strerror(errno),NULL) ;

#ifdef BMV_BUFSIZ
  if (!testpbm) {
    if (setvbuf(file, NULL, _IOFBF, BMV_BUFSIZ))
      printf("Could not increase %s buffer to %d\n", filename, BMV_BUFSIZ);
  }
#endif

  if ((fno=fileno(file))<0)
    leave(1,"Cannot get PBMfile descriptor",strerror(errno),NULL) ;

  if (fstat(fno,&stbuf))
     leave(1,"Cannot stat PBM file",strerror(errno),NULL) ;

  /* If file is shorter than PBMFILEMIN do not bother to read it
     This is to prevent the test from fatal failure when GS has
     not written the header yet in full */

  if ((long)stbuf.st_size<PBMFILEMIN)
     if (!testpbm) leave(1,"PBM file is very short",NULL) ;
              else { testpbm=0 ; fclose(file) ; return ; }

  /* Check magic numbers */
  if (fgetc(file)=='P') {
   c = fgetc(file);
   if (c == PBMRAW_MAGIC || c == PGMRAW_MAGIC || c == PPMRAW_MAGIC) {
     image_magic = c;
     image_num_components =
	((image_magic == PPMRAW_MAGIC)? NUM_PIX_COMPONENTS: 1);
     image_pixels_per_byte = ((image_magic == PBMRAW_MAGIC)? 8: 1);

     if (isspace(fgetc(file)))
       { /* skip comments */
         while ((c=fgetc(file))=='#')
            for (;c!=EOF && c!='\n';c=fgetc(file)) ;

         ungetc(c,file) ; /* we read too much */

	 /* read dimensions */
         if (fscanf(file,"%d %d",&imagew,&imageh)!=2)
            leave(1,"Wrong format of PBM file",NULL) ;

         fgetc(file) ; /* skip separator */

         /* read maximum component value */
         if (image_magic == PBMRAW_MAGIC) {
	   image_max_component = 1;
	 } else {
           if (fscanf(file,"%d",&image_max_component) != 1)
             leave(1,"Missing max component value in PPM file",NULL) ;
           if (image_max_component <= 0 || image_max_component > 255)
             leave(1,"Invalid max component value in PPM file",NULL) ;
           fgetc(file) ; /* skip separator */
	 }

         offset=ftell(file) ;  /* tell where data begin */

         /* try to guess the correct length
            !!! This assumes ftell returns number of bytes from the
                begining of file */

 	 /* round imagew up to a full number of bytes */
         imagew = ((imagew + image_pixels_per_byte - 1) /
			image_pixels_per_byte) * image_pixels_per_byte ;

         length = offset + (imagew * (long) imageh *
			image_num_components) / image_pixels_per_byte ;

         if (fstat(fno,&stbuf))
               leave(1,"Cannot stat PBM file",strerror(errno),NULL) ;

         /* Now compare the actual and expected length */
         if ((long)stbuf.st_size<length)
           if (!testpbm) leave(1,"PBM file is shorter than expected",NULL) ;
               else  { /* if this was just a test, fail silently */
                       testpbm=0 ; fclose(file) ; return ; }

         /* If testing, the file should not stay open */
         if (testpbm) fclose(file) ;
         return ;
       }
   }
  }
  leave(1,"Invalid preamble in PBM file",NULL) ;
}


#ifdef M_UNIX
char *strsignal(int sig);

char *strsignal(int sig)
{
  static char buf[10];

  sprintf(buf, "SIG%d", (sig % 100));
  return(buf);
}
#endif

/* sigtermhandler handles SIGTERM and SIGINT. It simply calls leave
   to do the cleaning. */		

void sigtermhandler(int sig)
{ static volatile sig_atomic_t busy=0 ;

  /* the purpose of busy is to make sure we will not be called twice.
     It does not do any harm, but it does not look nice */

  if (busy>0) return ;
  busy=1 ;
  tcsetpgrp(STDOUT_FILENO,getpgrp()) ;
  leave(1,"I received a fatal signal",strsignal(sig),NULL) ;
}


/* sigchldhandler checks if the child that exited was GhostScript.
   If it returned with nonzero status (I assume this means error),
   we leave, otherwise we would have to wait till timeout */


void sigchldhandler(int sig)
{ int status ;
  int wait_return;
  if (gspid == -1) {
    leave(1,"Received SIGCHLD, GhostScript pid is -1",NULL) ;
    signal(SIGCHLD,SIG_IGN) ;
    return;
  }
  if (gspid == 0) {
    signal(SIGCHLD,SIG_IGN) ;
    return;
  }
  while (1) {
    status = 0;
    wait_return = waitpid(-1, &status, WNOHANG);
    if (wait_return == -1) {
      if (errno == EINTR)
	leave(1,"Wait with WNOHANG for GhostScript failed with EINTR",NULL) ;
      else if (errno == EINVAL)
	leave(1,"Wait for GhostScript failed with EINVAL",NULL) ;
      else if (errno == ECHILD)
	leave(1,"Wait for GhostScript failed with ECHILD",NULL) ;
      else
	leave(1,"Wait for GhostScript failed",NULL) ;
      break;
    }
    if (wait_return == 0) {
      if (gspid	> 0 && kill(gspid,0) == -1)
	leave(1,"GhostScript exited, without returning a status",NULL) ;
      break;
    }
    if (wait_return == gspid) {
      if (WIFEXITED(status) == 0)
        leave(1,"GhostScript was aborted",NULL) ;
      else if (WEXITSTATUS(status) == 0)
        leave(1,"GhostScript exited with OK status",NULL) ;
      else
        leave(1,"GhostScript exited, returning nonzero",NULL) ;
    }
    if (leaving) {
      break;
    }
  }
  if (leaving) {
    signal(SIGCHLD,SIG_IGN) ;
  } else {
    signal(SIGCHLD,sigchldhandler) ;
  }
}

/* ************** end of bmv.c *************** */
