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

/* read-pnm.c - read an anymap

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: read-pnm.c,v 0.3.2.0 1992/06/11 22:30:07 dpg Alpha dpg $ */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#include "view.h"

/* Make a palette suitable for a bitmap.  Return the maximum color number.  */
static COLOR
make_pbm_palette (PALETTE **pal)
{
  *pal = alloc_palette ();
  (*pal)->color.red = 0;
  (*pal)->color.green = 0;
  (*pal)->color.blue = 0;
  (*pal)->number = 1;
  (*pal)->count = 0;
  pal = &(*pal)->next;

  *pal = alloc_palette ();
  (*pal)->color.red = MAXRGBVAL;
  (*pal)->color.green = MAXRGBVAL;
  (*pal)->color.blue = MAXRGBVAL;
  (*pal)->number = 0;
  (*pal)->count = 0;
  (*pal)->next = 0;

  return 1;
}

/* Make a palette suitable for a graymap.  Return the maximum color number.  */
static COLOR
make_pgm_palette (PALETTE **pal, int maxval)
{
  int i = maxval;

  while (i--)
    {
      *pal = alloc_palette ();
      (*pal)->color.red = (*pal)->color.green = (*pal)->color.blue = i * MAXRGBVAL / maxval;
      (*pal)->number = i;
      (*pal)->count = 0;
      pal = &(*pal)->next;
    }
  *pal = 0;
  return maxval;
}

/* Build up an array of pointers to palette values, so that we can increase
   the usage count of colors quickly when reading bitmaps and graymaps.  We
   should really be careful about unused numbers, but if we generate any then
   something's gone seriously wrong anyhow.  */
static PALETTE **
make_color_array (PALETTE **array, PALETTE *pal)
{
  while (pal)
    {
      array[pal->number] = pal;
      pal = pal->next;
    }
  return array;
}

/* Search the palette for a particular (RGB-specified) color, and add it to
   the palette if necessary.  Then return the color number, -1 if the number
   of colors already found exceeds MAXCOLOR.  */
static int
do_ppm_palette (PALETTE **pal, rgbval red, rgbval green, rgbval blue, int *max_color, int max)
{
  struct color palval =
  {
    red * MAXRGBVAL / max,
    green * MAXRGBVAL / max,
    blue * MAXRGBVAL / max
  };
  PALETTE **palstart = pal;
  PALETTE *this;

  while (this = *pal)
    {
      if (COLOREQ (this->color, palval))
	{
	  /* Optimization: we shift the color to the head of the list.  It
	     seems likely that it will be used again soon, so it makes sense
	     to put it where we will find it quickly.  */
	  if (pal != palstart)
	    {
	      *pal = this->next;
	      this->next = *palstart;
	      *palstart = this;
	    }
	  ++this->count;
	  return this->number;
	}
      pal = &this->next;
    }

  if (*max_color > MAXCOLOR)
    return -1;

  this = alloc_palette ();
  this->color = palval;
  this->count = 1;
  this->next = *palstart;
  *palstart = this;
  return this->number = (*max_color)++;
}

/* Read a positive integer, ignoring leading whitespace.  Returns -1 if we
   hit EOF.  */
static int
read_int (FILE *fp)
{
  int n = 0;
  int digit_flag = 0;
  int c;

  while ((c = fgetc (fp)) != EOF && isspace (c))
    /* Not much to do.  */
    digit_flag = 1;

  if (digit_flag)
    {
      digit_flag = 0;
      ungetc (c, fp);

      /* If we just got EOF, we can expect to get it again here.  */
      while ((c = fgetc (fp)) != EOF && isdigit (c))
	{
	  /* Nasty hacks: assumes 0-9 are consequtive,a dn ignores overflow.
	     The latter we can live with; the former is a serious problem.  */
	  n = 10 * n + (c - '0');
	  digit_flag = 1;
	}
    }

  if (!digit_flag)
    return -1;
  ungetc (c, fp);
  return n;
}

/* Used for counting the bits in a raw bitmap.  */
static int pbm_raw_count;
static unsigned char pbm_raw_bits;

/* Try to read it as a p[bgp]m file.  If we succeed, return 0, else return 1.  */
int
maybe_read_pnm (FILE *fp, const char *file, MAP *map)
{
  enum
  {
    BITMAP, GRAYMAP, PIXMAP
  } format;
  int rawbits;
  int max_level;
  int max_color;
  PALETTE **pal_array = 0;
  int y;
  COLOR **linep;
  int c;
  int (*read_func) (FILE *);

  /* Check to see if it's a pnm file.  We assume it is (for now) if the first
     character is a `P' and the second is a digit.  */
  c = fgetc (fp);
  format = fgetc (fp);
  if (c == EOF || c != 'P' || format == EOF || !isdigit (format))
    return 1;

  map->width = read_int (fp);
  map->height = read_int (fp);
  if (map->width == -1 || map->height == -1)
    return 1;

  /* Work out what kind of map it is, and reset format to a symbolic value.
     Set rawbits if necessary, and initialize the map.  */
  rawbits = 0;
  switch (format)
    {
    case '4':			/* Bitmaps.  */
      rawbits = 1;
      pbm_raw_count = 0;	/* Reset bit count.  */
    case '1':
      max_level = 0;
      max_color = make_pbm_palette (&map->palette);
      format = BITMAP;
      break;

    case '5':			/* Graymaps.  */
      rawbits = 1;
    case '2':
      max_level = read_int (fp);
      if (max_level == -1)
	return 1;
      max_color = make_pgm_palette (&map->palette, max_level);
      format = GRAYMAP;
      break;

    case '6':			/* Pixmaps.  */
      rawbits = 1;
    case '3':
      max_level = read_int (fp);
      if (max_level == -1)
	return 1;
      format = PIXMAP;
      max_color = 0;
      break;

    default:
      return 1;
    }

  if (rawbits)
    {
      c = fgetc (fp);
      if (c == EOF || !isspace (c))
	return 1;
      read_func = fgetc;
    }
  else
    read_func = read_int;

  if (format == BITMAP || format == GRAYMAP)
    {
      pal_array = alloca ((max_color + 1) * sizeof (PALETTE *));
      if (!pal_array)
	{
	  view_message (0, "alloca");
	  exit (1);
	}
      make_color_array (pal_array, map->palette);
    }

  alloc_image (map, map->width, map->height);

  y = map->height;
  linep = map->image;
  while (y-- && !last_sig)
    {
      COLOR *line = *linep++;
      int x = map->width;

      while (x-- && !last_sig)
	{
	  COLOR color;

	  switch (format)
	    {
	    case BITMAP:
	      {
		if (rawbits)
		  {
		    if (!pbm_raw_count--)
		      {
			c = fgetc (fp);
			if (c == EOF)
			  return 1;
			pbm_raw_bits = c;
			pbm_raw_count = 7;
		      }
		    color = !!(pbm_raw_bits & 0x80);
		    pbm_raw_bits <<= 1;
		  }
		else
		  {
		    c = read_func (fp);
		    if (c == EOF)
		      return 1;
		    color = c;
		  }
		++pal_array[color]->count;
	      }
	      break;
	    case GRAYMAP:
	      c = read_func (fp);
	      if (c == EOF)
		return 1;
	      color = c;
	      ++pal_array[color]->count;
	      break;
	    case PIXMAP:
	      {
		rgbval red;
		rgbval green;
		rgbval blue;

		c = read_func (fp);
		if (c == EOF)
		  return 1;
		red = c;
		c = read_func (fp);
		if (c == EOF)
		  return 1;
		green = c;
		c = read_func (fp);
		if (c == EOF)
		  return 1;
		blue = c;

		c = do_ppm_palette (&map->palette, red, green, blue, &max_color, max_level);
		if (c == -1)
		  return 1;
		color = c;
	      }
	      break;
	    default:
	      view_message ("internal error (invalid pnm type)", "source file %s line %d", __FILE__, __LINE__);
	      exit (1);
	    }
	  *line++ = color;
	}
      status_bar (map->height - y, map->height);
    }

  return last_sig;
}
