/* xloadimage.c:
 *
 * generic image loader for X11
 *
 * jim frost 09.27.89
 *
 * Copyright 1989, 1990, 1991 Jim Frost.
 * See included file "copyright.h" for complete copyright information.
 */

#include "copyright.h"
#include "xloadimage.h"
#include "patchlevel"
#include <signal.h>

extern double atof();
extern int _Xdebug;

/* if an image loader needs to have our display and screen, it will get
 * them from here.  this is done to keep most of the image routines
 * clean
 */

Display      *Disp= NULL;
int           Scrn= 0;

/* used for the -default option.  this is the root weave bitmap with
 * the bits in the order that xloadimage likes.
 */

#define root_weave_width 4
#define root_weave_height 4
static byte root_weave_bits[] = {
  0xe0, 0xb0, 0xd0, 0x70
};

/* the real thing
 */

main(argc, argv)
     int argc;
     char *argv[];
{ char         *border;
  char         *dname;
  Image        *dispimage;      /* image that will be sent to the display */
  Image        *newimage;       /* new image we're loading */
  Image        *tmpimage;
  Display      *disp;           /* display we're sending to */
  int           scrn;           /* screen we're sending to */
  XColor        xcolor;         /* color for border option */
  ImageOptions  images[MAXIMAGES + 1]; /* list of image w/ options to load */
  int           a;
  unsigned int  imagecount;     /* number of images in ImageName array */
  char         *user_geometry;	/* -geometry passed by user */
  unsigned int  dest_window;    /* window id to put image onto */
  unsigned int  do_fork;
  unsigned int  fit;
  unsigned int  fullscreen;
  unsigned int  identify;
  unsigned int  install;
  unsigned int  onroot;
  unsigned int  private_cmap;
  unsigned int  set_default;
  unsigned int  use_pixmap;
  unsigned int  verbose;
  int           visual_class;    /* user-defined visual class */
  unsigned int  winwidth, winheight; /* geometry of image */
  unsigned int  global_bright= 0; /* options which are propagated to all */
  unsigned int  global_colors= 0; /* remaining images */
  unsigned int  global_delay= 0;
  unsigned int  global_dither= 0;
  float         global_gamma= 1.0;
  unsigned int  global_normalize= 0;
  unsigned int  global_smooth= 0;
  unsigned int  global_xzoom= 0;
  unsigned int  global_yzoom= 0;

  /* set up internal error handlers
   */

  signal(SIGSEGV, internalError);
  signal(SIGBUS, internalError);
  signal(SIGFPE, internalError);
  signal(SIGILL, internalError);
#if defined(_AIX) && defined(_IBMR2)
  /* the RS/6000 (AIX 3.1) has a new signal, SIGDANGER, which you get
   * when memory is exhausted.  since malloc() can overcommit, it's a good
   * idea to trap this one.
   */
  signal(SIGDANGER, memoryExhausted);
#endif

  if (argc < 2)
    usage(argv[0]);

  /* defaults and other initial settings.  some of these depend on what
   * our name was when invoked.
   */

  loadPathsAndExts();
  onroot= 0;
  verbose= 1;
  if (!strcmp(tail(argv[0]), "xview")) {
    onroot= 0;
    verbose= 1;
  }
  else if (!strcmp(tail(argv[0]), "xsetbg")) {
    onroot= 1;
    verbose= 0;
  }
  border= NULL;
  dname= NULL;
  fit= 0;
  fullscreen= 0;
  do_fork= 0;
  identify= 0;
  install= 0;
  private_cmap= 0;
  use_pixmap= 0;
  set_default= 0;
  dest_window= 0;
  user_geometry = NULL;
  winwidth= winheight= 0;
  visual_class= -1;

  imagecount= 0;
  for (a= 0; a < MAXIMAGES; a++) {
    images[a].name= NULL;
    images[a].atx= images[a].aty= 0;
    images[a].bright= 0;
    images[a].center= 0;
    images[a].clipx= images[a].clipy= 0;
    images[a].clipw= images[a].cliph= 0;
    images[a].colors= 0;
    images[a].delay= 0;
    images[a].dither= 0;
    images[a].gamma= 1.0;
    images[a].go_to= NULL;
    images[a].gray= 0;
    images[a].merge= 0;
    images[a].normalize= 0;
    images[a].rotate= 0;
    images[a].smooth= 0;
    images[a].xzoom= images[a].yzoom= 0;
    images[a].fg= images[a].bg= NULL;
  }
  for (a= 1; a < argc; a++) {
    switch (optionNumber(argv[a])) {
    case OPT_BADOPT:
      printf("%s: Bad option\n", argv[a]);
      usage(argv[0]);
      /* NOTREACHED */

    case OPT_NOTOPT:
      if (imagecount == MAXIMAGES)
	printf("%s: Too many images (ignoring)\n", argv[++a]);
      else {
	images[imagecount++].name= argv[a];
	if (imagecount < MAXIMAGES) {
	  images[imagecount].bright= global_bright;
	  images[imagecount].colors= global_colors;
	  images[imagecount].delay= global_delay;
	  images[imagecount].dither= global_dither;
	  images[imagecount].gamma= global_gamma;
	  images[imagecount].normalize= global_normalize;
	  images[imagecount].smooth= global_smooth;
	  images[imagecount].xzoom= global_xzoom;
	  images[imagecount].yzoom= global_yzoom;
	}
      }
      break;

    case OPT_SHORTOPT:
      printf("%s: Not enough characters to identify option\n", argv[a]);
      usage(argv[0]);
      /* NOTREACHED */

    /* process options global to everything
     */

    case ONROOT:
      onroot= 1;
      fit= 1; /* assume -fit */
      break;

    case BORDER:
      if (argv[++a])
	border= argv[a];
      break;

    case DBUG:
      _Xdebug= True;
      break;

    case DEFAULT:
      set_default= 1;
      break;

    case DELAY:
      if (!argv[++a])
	  break;
      global_delay= atoi(argv[a]);
      if (global_delay <= 0) {
	  global_delay= 0;
	  printf("Bad argument to -delay\n");
	  break;
      }
      images[imagecount].delay= global_delay;
      break;

    case DISPLAY:
      if (argv[++a])
	dname= argv[a];
      break;

    case FIT:
      fit= 1;
      break;

    case FORK:
      do_fork= 1;
      verbose= 0; /* background processes should be seen but not heard */
      break;

    case FULLSCREEN:
      fullscreen= 1;
      break;

    case GEOMETRY:
      if (argv[++a])
	user_geometry = argv[a];
      break;

    case HELP:
      if (argv[++a])
	do {
	  help(argv[a++]);
	} while (argv[a]); 
      else
	help(NULL);
      exit(0);

    case IDENTIFY:
      identify= 1;
      break;

    case LIST:
      listImages();
      exit(0);

    case INSTALL:
      install= 1;
      break;

    case PATH:
      showPath();
      break;

    case PIXMAP:
      use_pixmap= 1;
      break;

    case PRIVATE:
      private_cmap= 1;
      break;

    case QUIET:
      verbose= 0;
      break;

    case SUPPORTED:
      supportedImageTypes();
      break;

    case VERBOSE:
      verbose= 1;
      break;

    case VER_NUM:
      version();
      break;

    case VIEW:
      onroot= 0;
      break;

    case VISUAL:
      if (argv[++a])
	visual_class= visualClassFromName(argv[a]);
      break;

    case WINDOWID:
      if (!argv[++a])
	break;
      if (sscanf(argv[a], "0x%x", &dest_window) != 1) {
	printf("Bad argument to -windowid\n");
	usage(argv[0]);
	/* NOTREACHED */
      }
      onroot= 1; /* this means "on special root" */
      fit= 1; /* assume -fit */
      break;

    /* process options local to an image
     */

    case AT:
      if (!argv[++a])
	break;
      if (sscanf(argv[a], "%d,%d",
		 &images[imagecount].atx, &images[imagecount].aty) != 2) {
	printf("Bad argument to -at\n");
	usage(argv[0]);
	/* NOTREACHED */
      }
      break;

    case BACKGROUND:
      if (argv[++a])
	images[imagecount].bg= argv[a];
      break;

    case BRIGHT:
      if (argv[++a]) {
	images[imagecount].bright= atoi(argv[a]);
	global_bright= images[imagecount].bright;
      }
      break;

    case GAMMA:
      if (argv[++a]) {
	images[imagecount].gamma= atof(argv[a]);
	global_gamma= images[imagecount].gamma;
      }
      break;

    case GOTO:
      if (argv[++a])
	images[imagecount].go_to= argv[a];
      break;

    case GRAY:
      images[imagecount].gray= 1;
      break;

    case CENTER:
      images[imagecount].center= 1;
      break;

    case CLIP:
      if (!argv[++a])
	break;
      if (sscanf(argv[a], "%d,%d,%d,%d",
		 &images[imagecount].clipx, &images[imagecount].clipy,
		 &images[imagecount].clipw, &images[imagecount].cliph) != 4) {
	printf("Bad argument to -clip\n");
	usage(argv[0]);
	/* NOTREACHED */
      }
      break;

    case COLORS:
      if (!argv[++a])
	break;
      images[imagecount].colors= atoi(argv[a]);
      if (images[imagecount].colors < 2) {
	printf("Argument to -colors is too low (ignored)\n");
	images[imagecount].colors= 0;
      }
      else if (images[imagecount].colors > 65536) {
	printf("Argument to -colors is too high (ignored)\n");
	images[imagecount].colors= 0;
      }
      global_colors= images[imagecount].colors;
      break;

    case DITHER:
      images[imagecount].dither= 1;
      global_dither= 1;
      break;

    case FOREGROUND:
      if (argv[++a])
	images[imagecount].fg= argv[a];
      break;

    case HALFTONE:
      images[imagecount].dither= 2;
      global_dither= 2;
      break;

    case IDELAY:
      if (!argv[++a])
	break;
      images[a].delay= atoi(argv[a]);
      if (images[a].delay <= 0) {
	printf("Bad argument to -idelay\n");
	images[a].delay= 0;
      }
      break;

    case INVERT:
      images[imagecount].fg= "white";
      images[imagecount].bg= "black";
      break;

    case MERGE:
      images[imagecount].merge= 1;
      break;

    case NAME:
      if (imagecount == MAXIMAGES)
	printf("%s: Too many images (ignoring)\n", argv[++a]);
      else
	images[imagecount++].name= argv[++a];
      break;

    case NEWOPTIONS:
      global_bright= 0;
      global_colors= 0;
      global_delay= 0;
      global_dither= 0;
      global_gamma= 1.0;
      global_normalize= 0;
      global_smooth= 0;
      global_xzoom= 0;
      global_yzoom= 0;
      break;

    case NORMALIZE:
      images[imagecount].normalize= 1;
      global_normalize= images[imagecount].normalize;
      break;

    case ROTATE:
      if (!argv[++a])
	break;
      images[imagecount].rotate = atoi(argv[a]);
      if ((images[imagecount].rotate % 90) != 0)
	{ printf("Argument to -rotate must be a multiple of 90 (ignored)\n");
	  images[imagecount].rotate = 0;
	}
      else 
	while (images[imagecount].rotate < 0)
	  images[imagecount].rotate += 360;
      break;

    case SMOOTH:
      global_smooth++;
      images[imagecount].smooth= global_smooth;
      break;

    case XZOOM:
      if (argv[++a]) {
	if (atoi(argv[a]) < 0) {
	  printf("Zoom argument must be positive (ignored).\n");
	  continue;
	}
	images[imagecount].xzoom= atoi(argv[a]);
	global_xzoom= images[imagecount].xzoom;
      }
      break;

    case YZOOM:
      if (argv[++a]) {
	if (atoi(argv[a]) < 0) {
	  printf("Zoom argument must be positive (ignored).\n");
	  continue;
	}
	images[imagecount].yzoom= atoi(argv[a]);
	global_yzoom= images[imagecount].yzoom;
      }
      break;

    case ZOOM:
      if (argv[++a]) {
	if (atoi(argv[a]) < 0) {
	  printf("Zoom argument must be positive (ignored).\n");
	  continue;
	}
	images[imagecount].xzoom= images[imagecount].yzoom= atoi(argv[a]);
	global_xzoom= global_yzoom= images[imagecount].xzoom;
      }
      break;

    default:

      /* this should not happen!
       */

      printf("%s: Internal error parsing arguments\n", argv[0]);
      exit(1);
    }
  }

  if (fit && (visual_class != -1)) {
    printf("-fit and -visual options are mutually exclusive (ignoring -visual)\n");
    visual_class= -1;
  }

  if (!imagecount && !set_default) /* NO-OP from here on */
    exit(0);

  if (identify) {                    /* identify the named image(s) */
    for (a= 0; a < imagecount; a++)
      identifyImage(images[a].name);
    exit(0);
  }

  /* start talking to the display
   */

  if (! (Disp= disp= XOpenDisplay(dname))) {
    printf("%s: Cannot open display\n", XDisplayName(dname));
    exit(1);
  }
  Scrn= scrn= DefaultScreen(disp);
  XSetErrorHandler(errorHandler);

  /* background ourselves if the user asked us to
   */

  if (do_fork)
    switch(fork()) {
    case -1:
      perror("fork");
      /* FALLTHRU */
    case 0:
      break;
    default:
      exit(0);
    }

  /* handle -default option.  this resets the colormap and loads the
   * default root weave.
   */

  if (set_default) {
    byte *old_data;

    dispimage= newBitImage(root_weave_width, root_weave_height);
    old_data= dispimage->data;
    dispimage->data= root_weave_bits;
    imageOnRoot(disp, scrn, dest_window, dispimage, 0);
    dispimage->data= old_data;
    freeImage(dispimage);
    if (!imagecount) /* all done */
      exit(0);
  }

  dispimage= NULL;

  if (onroot && (winwidth || winheight || images[0].center ||
      images[0].atx || images[0].aty || fullscreen)) {
    if (!winwidth)
	winwidth= DisplayWidth(disp, scrn);
    if (!winheight)
      winheight= DisplayHeight(disp, scrn);
    if (DefaultDepth(disp, scrn) == 1)
      dispimage= newBitImage(winwidth, winheight);
    else {
      dispimage= newRGBImage(winwidth, winheight, DefaultDepth(disp, scrn));
      dispimage->rgb.used= 1;
    }
    *(dispimage->rgb.red)= 65535;   /* default border value is white */
    *(dispimage->rgb.green)= 65535;
    *(dispimage->rgb.blue)= 65535;
    if (border) {
      XParseColor(disp, DefaultColormap(disp, scrn), border, &xcolor);
      *dispimage->rgb.red= xcolor.red;
      *dispimage->rgb.green= xcolor.green;
      *dispimage->rgb.blue= xcolor.blue;
    }

    /* bitmap needs both black and white
     */

    if (DefaultDepth(disp, scrn) == 1) {
	if (*(dispimage->rgb.red)) {
	    *(dispimage->rgb.red + 1)= 0;
	    *(dispimage->rgb.green + 1)= 0;
	    *(dispimage->rgb.blue + 1)= 0;
	}
	else {
	    *(dispimage->rgb.red + 1)= 65535;
	    *(dispimage->rgb.green + 1)= 65535;
	    *(dispimage->rgb.blue + 1)= 65535;
	}
    }
    fill(dispimage, 0, 0, winwidth, winheight, 0);
  }

  /* load in each named image
   */

  for (a= 0; a < imagecount; a++) {
    if (! (newimage= loadImage(images[a].name, verbose)))
      continue;
    if (!images[a].dither &&
	((dispimage && BITMAPP(dispimage)) || (DefaultDepth(disp, scrn) == 1)))
      images[a].dither= 1;

    /* if this is the first image and we're putting it on the root window
     * in fullscreen mode, set the zoom factors and
     * location to something reasonable.
     */

    if ((a == 0) && onroot && fullscreen &&
	(images[0].xzoom == 0.0) && (images[0].yzoom == 0.0) &&
	(images[0].atx == 0) && (images[0].aty == 0) &&
	(images[0].center == 0)) {
      XWindowAttributes wa;
      images[0].center= 1;
    
      if ((newimage->width > DisplayWidth(disp, scrn)) ||
	  (newimage->height > DisplayHeight(disp, scrn))) {
	images[0].xzoom= images[0].yzoom=
	  (newimage->width - DisplayWidth(disp, scrn) >
	   newimage->height - DisplayHeight(disp, scrn) ?
	   (float)DisplayWidth(disp, scrn) / (float)newimage->width * 100.0 :
	   (float)DisplayHeight(disp, scrn) / (float)newimage->height * 100.0);
      }
      else {
	images[0].xzoom= images[0].yzoom=
	  (DisplayWidth(disp, scrn) - newimage->width <
	   DisplayHeight(disp, scrn) - newimage->height ?
	   (float)DisplayWidth(disp, scrn) / (float)newimage->width * 100.0 :
	   (float)DisplayHeight(disp, scrn) / (float)newimage->height * 100.0);
      }
    }

    newimage= processImage(disp, scrn, newimage, &images[a], verbose);

    if (images[a].center && dispimage) {
      images[a].atx= (int)(dispimage->width - newimage->width) / 2;
      images[a].aty= (int)(dispimage->height - newimage->height) / 2;
    }
    if (dispimage) {
      if (! dispimage->title)
	dispimage->title= dupString(newimage->title);
      tmpimage= merge(dispimage, newimage, images[a].atx, images[a].aty,
		      verbose);
      if (dispimage != tmpimage) {
	freeImage(dispimage);
	dispimage= tmpimage;
      }
      freeImage(newimage);
    }
    else
      dispimage= newimage;

    /* if next image is to be merged onto this one, do it now
     */

    if (onroot || ((a < imagecount) && (images[a + 1].merge)))
      continue;

    switch(imageInWindow(disp, scrn, dispimage, user_geometry,
			 fullscreen, install, private_cmap, fit, use_pixmap,
			 images[a].delay, visual_class, argc, argv, verbose)) {
    case '\0': /* window got nuked by someone */
      XCloseDisplay(disp);
      exit(1);
    case '\003':
    case 'q':  /* user quit */
      cleanUpWindow(disp);
      XCloseDisplay(disp);
      exit(0);
    case ' ':
    case 'n':  /* next image */
      if (images[a + 1].go_to) {
	int b;
	for (b= 0; b < imagecount; b++)
	  if (!strcmp(images[b].name, images[a + 1].go_to)) {
	    a= b - 1;
	    goto next_image;
	  }
	fprintf(stderr, "Target for -goto %s was not found\n",
		images[a + 1].go_to);
      next_image:
	;
      }
      break;
    case 'p':  /* previous image */
      if (a > 0)
	a -= 2;
      else
	a--;
      break;
    }
    freeImage(dispimage);
    dispimage= NULL;
  }

  if (onroot)
    imageOnRoot(disp, scrn, dest_window, dispimage, verbose);
  XCloseDisplay(disp);
  exit(0);
}
