/* Hey, emacs, this is -*- C -*- code.  */

/* main.c - set global options and step along maps

Copyright (C) 1992 D P Gymer

This is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
   Foundation; either version 2 of the License, or (at your option) any later
   version.

This program is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
   more details.  */

/* $Id: main.c,v 0.3.2.0 1992/06/11 22:30:07 dpg Alpha dpg $ */

#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "view.h"

/* There is an assumption in the terminal handling code that handles 0 and 1
   are connected to the same terminal.  This is because we can only muck with
   handle 1 (because when we start off, the standard input may be coming from
   a pipe), but we must read the keypress from handle 0 (which is closed and
   reconnected to /dev/tty after a map is read from it).  I should probably
   just open /dev/tty and muck with it.  Hmm.  */

/* Flag which gets set to the number of the last non-terminating, caught
   signal.  */
int last_sig = 0;

/* exit() status.  */
int rc = 0;

/* Size of the physical screen (only the ratio is significant).  This is
   about 1.3 (width to height) on my SVGA monitor.  */
static int physical_width = 13;
static int physical_height = 10;

/* Size of the display, in pixels, and the number of colors it has.  */
static int display_width;
static int display_height;
static int display_colors;

/* As seen in argv[0].  */
static const char *program_name;

/* Percentage border.  */
static int border = 0;

/* Scaling mode.  */
enum
{
  SCALING_NONE,
  SCALING_AUTO,
  SCALING_FULL
};

/* I would have left this as part of the enum, but gcc produced a warning
   when I used it in the long options.  Grrr.  */
static int scaling_mode = SCALING_AUTO;

/* Don't invert the map.  */
static int invert_flag = 0;

/* Print an error message and/or a system error string.  FMT should represent
   the source of the error, and msg the actual error.  */

void
view_message (const char *msg, const char *fmt,...)
{
  fputs (program_name, stderr);

  if (fmt)
    {
      va_list args;

      fputc (':', stderr);
      va_start (args, fmt);
      vfprintf (stderr, fmt, args);
      va_end (args);
    }

  if (!msg)
    if (errno > 0 && errno <= sys_nerr)
      msg = sys_errlist[errno];
    else
      msg = "unknown error";

  fprintf (stderr, ": %s\n", msg);
}

/* Call malloc(), and check the result.  */
static void *
xmalloc (size_t n)
{
  void *p = malloc (n);

  if (!p)
    {
      view_message (0, "xmalloc");
      exit (1);
    }
  return p;
}

/* Allocate a palette element.  */
PALETTE *
alloc_palette (void)
{
  return xmalloc (sizeof (PALETTE));
}

/* Allocate an image in a map.  */
void
alloc_image (MAP *map, int width, int height)
{
  if (map->image)
    free_image (map);

  map->image = xmalloc (height * sizeof (COLOR *));
  map->height = height;
  map->width = width;

  while (height--)
    map->image[height] = xmalloc (width * sizeof (COLOR));
}

/* Free an image.  */
void
free_image (MAP *map)
{
  if (map->image)
    {
      while (map->height--)
	free (map->image[map->height]);
      free (map->image);
    }
}

/* Allocate a map.  */
MAP *
alloc_map (void)
{
  MAP *map;

  map = xmalloc (sizeof (MAP));
  map->image = 0;
  map->palette = 0;
  return map;
}

/* Free a map.  */
void
free_map (MAP *map)
{
  PALETTE *p;

  free_image (map);

  p = map->palette;
  while (p)
    {
      PALETTE *next = p->next;

      free (p);
      p = next;
    }
  free (map);
}

/* Plot a pixmap.  */
static int
drawit (MAP *map, const char *file)
{
  int off_x;
  int off_y;
  int width = display_width;
  int height = display_height;

  off_x = width * border / 200;
  off_y = height * border / 200;
  width = width * (100 - border) / 100;
  height = height * (100 - border) / 100;

  switch (scaling_mode)
    {
    case SCALING_NONE:
      if (width < map->width || height < map->height)
	{
	  view_message ("resolution too low to not scale", "`%s'", file);
	  return 1;
	}
      off_x += (width - map->width) / 2;
      off_y += (height - map->height) / 2;
      width = map->width;
      height = map->height;
      break;
    case SCALING_AUTO:
      if (map->width * physical_height > map->height * physical_width)
	{
	  int nheight = (height * map->height * physical_width) / (map->width * physical_height);

	  off_y += (height - nheight) / 2;
	  height = nheight;
	}
      else
	{
	  int nwidth = (width * map->width * physical_height) / (map->height * physical_width);

	  off_x += (width - nwidth) / 2;
	  width = nwidth;
	}
      break;
    case SCALING_FULL:
      /* Nothing to do here.  */
      break;
    default:
      view_message ("(internal) invalid scaling mode", 0);
    }

  return dither (map, width, height, off_x, off_y, display_colors, file);
}

/* Plot a bitmap.  */
static void
view (MAP *map, const char *file)
{
  if (invert_flag)
    {
      PALETTE *pal;

      pal = map->palette;
      while (pal)
	{
	  pal->color.red = MAXRGBVAL - pal->color.red;
	  pal->color.green = MAXRGBVAL - pal->color.green;
	  pal->color.blue = MAXRGBVAL - pal->color.blue;
	  pal = pal->next;
	}
    }
  if (!drawit (map, file))
    readkey ();
}

/* Plot or update the status bar.  */
#ifdef USE_STATUS_BAR
void
status_bar (int done, int total)
{
  int y, yb;

  if (!total)
    done = total = 1;
  else if (done < 0)
    done = 0;
  else if (done > total)
    done = total;

  yb = (done - 1) * display_height / total;
  if (yb < 0)
    yb = 0;
  y = done * display_height / total;
  srand (time (0));
  while (y-- > yb)
    plot_pixel (0, y, 1);
}

#endif /* USE_STATUS_BAR */

/* Handle signals.  */
static void
catch_sig (int sig)
{
  switch (sig)
    {
      case SIGINT:
      last_sig = sig;
      signal (SIGINT, catch_sig);
      return;
    case SIGQUIT:
      exit (rc);
    default:
      exit (1);
    }
}

static void
view_image (FILE *fp, const char *file)
{
  MAP *map;

#ifdef USE_STATUS_BAR
  PALETTE pal[2] =
  {
    {
      {0, 0, 0}, 0, 1, pal + 1
    },
    {
      {MAXRGBVAL, MAXRGBVAL, MAXRGBVAL}, 1, 1, 0
    }
  };

  set_colors (pal);
#endif /* USE_STATUS_BAR */

  map = read_map (fp, file);
  if (!map)
    rc = 1;
  else
    {
      if (!last_sig)
	view (map, file);
      free_map (map);
    }
  last_sig = 0;
}

/* Long argument list.  */
static const struct option long_opts[]=
{
  "border", required_argument, 0, 'b',
  "display", required_argument, 0, 'S',
  "fullscreen", no_argument, &scaling_mode, SCALING_FULL,
  "invert", no_argument, &invert_flag, 1,
  "physical-ratio", required_argument, 0, 'p',
  "unscaled", no_argument, &scaling_mode, SCALING_NONE,
  "version", no_argument, 0, 'v',
  0, 0, 0, 0
};

/* Print usage message and exit.  */
static void
usage (void)
{
  const struct option *lopt;
  /* Strings used in usage message for arguments.  Must match the options in
     long_opts.  */
  static char *lopt_args[]=
  {
    "PERCENTAGE", "DEVICE", 0, 0, "WIDTHxHEIGHT", 0, 0, 0
  };
  char **arg = lopt_args;

  fprintf (stderr, "Usage: %s", program_name);
  lopt = long_opts;
  while (lopt->name)
    {
      fprintf (stderr, " [-%s", lopt->name);
      if (lopt->has_arg == required_argument)
	fprintf (stderr, "=%s", *arg);
#if 0
      /* We don't have any of these yet.  */
      else if (lopt->has_arg == optional_argument)
	fprintf (stderr, "[=%s]", *arg);
#endif
      fputc (']', stderr);
      ++lopt;
      ++arg;
    }
  fputs (" image...\n", stderr);
  exit (1);
}

/* Parse arguments, read bitmaps, and view them.  */
int
main (int argc, char **argv)
{
  int c;
  int lopt_index;
  int signal_num;
  const char *screen_dev;

  setvbuf (stdout, 0, _IONBF, 0);
  program_name = argv[0];

  screen_dev = 0;
  while ((c = getopt_long_only (argc, argv, "S:b:p:v", long_opts, &lopt_index)) != EOF)
    switch (c)
      {
      case 0:			/* Long option dealt with elsewhere.  */
	break;
      case 'S':		/* Screen mode.  */
	screen_dev = optarg;	/* Error check? */
	break;
      case 'b':		/* Percentage border.  */
	border = strtol (optarg, &optarg, 10);
	if (*optarg || border < 0 || border > 100)
	  {
	    view_message ("invalid border percentage", 0);
	    return 1;
	  }
	break;
      case 'p':		/* Physical screen size.  */
	sscanf (optarg, "%dx%d", &physical_width, &physical_height);	/* Error check? */
	break;
      case 'v':		/* Print version number and exit.  */
	fprintf (stderr, "%s: version %d.%d.%d\n", program_name, VERSION, RELEASE, UPDATE);
	return 0;
      default:
	usage ();
      }

  atexit (tidy_display);
  signal_num = 0;
  while (++signal_num < _NSIG)
    signal (signal_num, catch_sig);

  if (init_display (&display_width, &display_height, &display_colors, screen_dev))
    view_message ("can't open display", 0);

  if (argc == optind)
    view_image (stdin, "-");
  else
    while (optind < argc)
      {
	const char *file = argv[optind++];
	FILE *fp = fopen (file, "rb");

	if (!fp)
	  {
	    perror (file);
	    rc = 1;
	  }
	else
	  {
	    view_image (fp, file);
	    fclose (fp);
	  }
      }

  return rc;
}
