/* This code is ripped from Autotrace-0.29. Small modifications by pts. */

/* input-pnm.ci:
 * The pnm reading and writing code was written from scratch by Erik Nygren
 * (nygren@mit.edu) based on the specifications in the man pages and
 * does not contain any code from the netpbm or pbmplus distributions.
 */

#ifdef __GNUC__
#ifndef __clang__
#pragma implementation
#endif
#endif

/* #include "types.h" */
#include "at_bitmap.h"
/* #include "input-pnm.h" */
/* #include "message.h" */
/* #include "xstd.h" */

/* #include <math.h> -- ceil(); emulated */
#include <stdlib.h> /* atoi(...) */

/**** pts ****/
/* #include <ctype.h> */
#define isspace(c) ((c)=='\0' || (c)==' ' || ((unsigned char)((c)-011)<=(unsigned char)(015-011)))
/* ^^^ not strictly POSIX C locale */
#define isdigit(c) ((unsigned char)(c-'0')<=(unsigned char)('9'-'0'))

#if 0
#  define PNMFILE FILE
#  define fread_PNMFILE(s,slen,f) fread(s, 1, slen, f)
#else
#  define PNMFILE /*Filter::UngetFILED*/GenBuffer::Readable
#  define fread_PNMFILE(s,slen,f) f->vi_read(s, slen)
#endif

/* Declare local data types
 */

typedef struct _PNMScanner
{
  PNMFILE   *fd;		      /* The file descriptor of the file being read */
  char   cur;		      /* The current character in the input stream */
  int    eof;		      /* Have we reached end of file? */
} PNMScanner;

typedef struct _PNMInfo
{
  unsigned int       xres, yres;	/* The size of the image */
  int       maxval;		/* For ascii image files, the max value
				 * which we need to normalize to */
  int       np;		/* Number of image planes (0 for pbm) */
  int       asciibody;		/* 1 if ascii body, 0 if raw body */
  /* Routine to use to load the pnm body */
  void    (* loader) (PNMScanner *, struct _PNMInfo *, unsigned char *);
} PNMInfo;

/* Contains the information needed to write out PNM rows */
typedef struct _PNMRowInfo
{
  PNMFILE   *fd;		/* File descriptor */
  char  *rowbuf;	/* Buffer for writing out rows */
  int    xres;		/* X resolution */
  int    np;		/* Number of planes */
  unsigned char *red;		/* Colormap red */
  unsigned char *grn;		/* Colormap green */
  unsigned char *blu;		/* Colormap blue */
} PNMRowInfo;

/* Save info  */
typedef struct
{
  int  raw;  /*  raw or ascii  */
} PNMSaveVals;

typedef struct
{
  int  run;  /*  run  */
} PNMSaveInterface;

#define PNM_BUFLEN 512		/* The input buffer size for data returned
				 * from the scanner.  Note that lines
				 * aren't allowed to be over 256 characters
				 * by the spec anyways so this shouldn't
				 * be an issue. */

#define SAVE_COMMENT_STRING "# CREATOR: The GIMP's PNM Filter Version 1.0\n"

/* Declare some local functions.
 */

static void   pnm_load_ascii           (PNMScanner *scan,
					PNMInfo    *info,
					unsigned char  *pixel_rgn);
static void   pnm_load_raw             (PNMScanner *scan,
					PNMInfo    *info,
					unsigned char  *pixel_rgn);
static void   pnm_load_rawpbm          (PNMScanner *scan,
					PNMInfo    *info,
					unsigned char  *pixel_rgn);

static void   pnmscanner_destroy       (PNMScanner *s);
#if 0
static void   pnmscanner_createbuffer  (PNMScanner *s,
					unsigned int bufsize);
static void   pnmscanner_getchar       (PNMScanner *s);
static void   pnmscanner_getsmalltoken (PNMScanner *s, unsigned char *buf);
#endif
static void   pnmscanner_eatwhitespace (PNMScanner *s);
static void   pnmscanner_gettoken      (PNMScanner *s,
					unsigned char *buf,
					unsigned int bufsize);
static unsigned pnmscanner_getint(PNMScanner *s);

static PNMScanner * pnmscanner_create  (PNMFILE *fd);


#define pnmscanner_eof(s) ((s)->eof)
#define pnmscanner_fd(s) ((s)->fd)

#if 0
/* pnmscanner_getchar ---
 *    Reads a character from the input stream
 */
static void
pnmscanner_getchar (PNMScanner *s)
{
  if (s->inbuf)
    {
      s->cur = s->inbuf[s->inbufpos++];
      if (s->inbufpos >= s->inbufvalidsize)
	{
	  if (s->inbufsize > s->inbufvalidsize)
	    s->eof = 1;
	  else
	    s->inbufvalidsize = fread(s->inbuf, 1, s->inbufsize, s->fd);
	  s->inbufpos = 0;
	}
    }
  else
    s->eof = !fread(&(s->cur), 1, 1, s->fd);
}
#endif

#define pnmscanner_getchar(s) do { s->eof = !fread_PNMFILE(&(s->cur), 1, s->fd); } while(0)

/* pnmscanner_eatwhitespace ---
 *    Eats up whitespace from the input and returns when done or eof.
 *    Also deals with comments.
 */
static inline void pnmscanner_eatwhitespace(PNMScanner *s) { /**** pts ****/
  while (1) {
    if (s->cur=='#') {
      do pnmscanner_getchar(s); while (s->cur!='\n');
    } else if (!isspace(s->cur)) {
      break;
    }
    pnmscanner_getchar(s);
  }
}


static struct struct_pnm_types
{
  char   name;
  int    np;
  int    asciibody;
  int    maxval;
  void (* loader) (PNMScanner *, struct _PNMInfo *, unsigned char *pixel_rgn);
} pnm_types[] =
{
  { '1', 0, 1,   1, pnm_load_ascii },  /* ASCII PBM */
  { '2', 1, 1, 255, pnm_load_ascii },  /* ASCII PGM */
  { '3', 3, 1, 255, pnm_load_ascii },  /* ASCII PPM */
  { '4', 0, 0,   1, pnm_load_rawpbm }, /* RAW   PBM */
  { '5', 1, 0, 255, pnm_load_raw },    /* RAW   PGM */
  { '6', 3, 0, 255, pnm_load_raw },    /* RAW   PPM */
  {  0 , 0, 0,   0, NULL}
};

#if PTS_SAM2P
bitmap_type pnm_load_image (PNMFILE* filename)
#else
bitmap_type pnm_load_image (at_string filename)
#endif
{
  char buf[PNM_BUFLEN];		/* buffer for random things like scanning */
  PNMInfo *pnminfo;
  PNMScanner * volatile scan;
  int ctr;
  PNMFILE* fd;
  bitmap_type bitmap;

  #if PTS_SAM2P /**** pts ****/
    fd=filename;
  #else
  /* open the file */
  fd = xfopen (filename, "rb");
  if (fd == NULL)
    {
      FATAL("PNM: can't open file\n");
      BITMAP_BITS (bitmap) = NULL;
      BITMAP_WIDTH (bitmap) = 0;
      BITMAP_HEIGHT (bitmap) = 0;
      BITMAP_PLANES (bitmap) = 0;
      return (bitmap);
    }
  #endif


  /* allocate the necessary structures */
  /* pnminfo = (PNMInfo *) malloc (sizeof (PNMInfo)); */
  XMALLOCT(pnminfo, PNMInfo*, sizeof(PNMInfo));

  scan = NULL;
  /* set error handling */

  scan = pnmscanner_create(fd);

  /* Get magic number */
  pnmscanner_gettoken (scan, (unsigned char *)buf, PNM_BUFLEN);
  if (pnmscanner_eof(scan))
    FATALP ("PNM: premature end of file");
  if (buf[0] != 'P' || buf[2])
    FATALP ("PNM: is not a valid file");

  /* Look up magic number to see what type of PNM this is */
  for (ctr=0; pnm_types[ctr].name; ctr++)
    if (buf[1] == pnm_types[ctr].name)
      {
	pnminfo->np        = pnm_types[ctr].np;
	pnminfo->asciibody = pnm_types[ctr].asciibody;
	pnminfo->maxval    = pnm_types[ctr].maxval;
	pnminfo->loader    = pnm_types[ctr].loader;
      }
  if (!pnminfo->loader)
      FATALP ("PNM: file not in a supported format");

  pnmscanner_gettoken(scan, (unsigned char *)buf, PNM_BUFLEN);
  if (pnmscanner_eof(scan))
    FATALP ("PNM: premature end of file");
  pnminfo->xres = isdigit(*buf)?atoi(buf):0;
  if (pnminfo->xres<=0)
    FATALP ("PNM: invalid xres while loading");

  pnmscanner_gettoken(scan, (unsigned char *)buf, PNM_BUFLEN);
  if (pnmscanner_eof(scan))
    FATALP ("PNM: premature end of file");
  pnminfo->yres = isdigit(*buf)?atoi(buf):0;
  if (pnminfo->yres<=0)
    FATALP ("PNM: invalid yres while loading");

  if (pnminfo->np != 0)		/* pbm's don't have a maxval field */
    {
      pnmscanner_gettoken(scan, (unsigned char *)buf, PNM_BUFLEN);
      if (pnmscanner_eof(scan))
        FATALP ("PNM: premature end of file");

      pnminfo->maxval = isdigit(*buf)?atoi(buf):0;
      if ((pnminfo->maxval<=0)
		|| (pnminfo->maxval>255 && !pnminfo->asciibody))
        FATALP ("PNM: invalid maxval while loading");
    }

  BITMAP_WIDTH (bitmap) = (at_dimen_t) pnminfo->xres;
  BITMAP_HEIGHT (bitmap) = (at_dimen_t) pnminfo->yres;

  BITMAP_PLANES (bitmap) = (pnminfo->np)?(pnminfo->np):1;
  /* BITMAP_BITS (bitmap) = (unsigned char *) malloc (pnminfo->yres * pnminfo->xres * BITMAP_PLANES (bitmap)); */
  XMALLOCT(BITMAP_BITS (bitmap), unsigned char *, pnminfo->yres * pnminfo->xres * BITMAP_PLANES (bitmap));
  pnminfo->loader (scan, pnminfo, BITMAP_BITS (bitmap));
  /* vvv Dat: We detect truncation late truncated files will just have garbage :-( */
  if (pnmscanner_eof(scan))
    FATALP ("PNM: truncated image data");

  /* Destroy the scanner */
  pnmscanner_destroy (scan);

  /* free the structures */
  /* free (pnminfo); */
  XFREE(pnminfo);

  /* close the file */
  /* xfclose (fd, filename); */

  return (bitmap);
}

static void
pnm_load_ascii (PNMScanner *scan,
		PNMInfo    *info,
		unsigned char *data)
{
  register unsigned char *d, *dend;
  unsigned u, s;
  #if 0 /**** pts ****/
    /* Buffer reads to increase performance */
    /* !! convert(1) is faster -- maybe buffering helps? */
    pnmscanner_createbuffer(scan, 4096);
  #endif
  d = data;
  if (info->np==0) { /* PBM */
    dend=d+info->xres*info->yres;
    while (d!=dend) {
      /* pnmscanner_getsmalltoken(scan, (unsigned char *)buf); */
      pnmscanner_eatwhitespace(scan);
      *d++=-(scan->cur=='0');
      pnmscanner_getchar(scan);
    }
  } else { /* PGM or PPM */ /**** pts ****/
    dend=d+info->xres*info->yres*info->np;
    switch (s=info->maxval) {
     case 255:
      while (d!=dend) {
        *d++=pnmscanner_getint(scan); /* Dat: removed isdigit() */
      }
      break;
     case 15:
      while (d!=dend) {
        *d++ = pnmscanner_getint(scan)*17;
      }
      break;
     case 3:
      while (d!=dend) {
        *d++ = pnmscanner_getint(scan)*85;
      }
      break;
     case 0: /* avoid division by 0 */
     case 1:
      while (d!=dend) {
        *d++ = -(0==pnmscanner_getint(scan)); /* (*buf=='0')?0xff:0x00; */
      }
     default:
      while (d!=dend) {
	u=pnmscanner_getint(scan);
	*d++ = (0UL+u*255UL+(s>>1))/s; /* always <= 255 */
      }
    }
  }
}

static void
pnm_load_raw (PNMScanner *scan,
	      PNMInfo    *info,
	      unsigned char  *data)
{
  unsigned char *d, *dend;
  unsigned s=info->maxval;
  slen_t delta, scanlines;
  PNMFILE *fd=pnmscanner_fd(scan);

  scanlines = info->yres;
  d = data;
  delta=info->xres * info->np;
  dend=d+delta*scanlines;
  while (d!=dend) {
    if (info->xres*info->np != fread_PNMFILE((char*)d, delta, fd)) return;
    d+=delta;
  }
  d=data;
  switch (s=info->maxval) { /**** pts ****/
   case 1: case 0:
    for (; d!=dend; d++) if (*d!=0) *d=255;
    break;
   case 3:
    while (d!=dend) *d++*=85;
    break;
   case 15:
    while (d!=dend) *d++*=17;
    break;
   case 255:
    break;
   default:
    for (; d!=dend; d++) *d=(0UL+*d*255UL+(s>>1))/s; /* always <= 255 */
    break;
  }
}

static void
pnm_load_rawpbm (PNMScanner *scan,
		 PNMInfo    *info,
		 unsigned char  *data)
{
  unsigned char *buf;
  unsigned char  curbyte;
  unsigned char *d;
  unsigned int   x, i;
  unsigned int   start, end, scanlines;
  PNMFILE          *fd;
  unsigned int            rowlen, bufpos;

  fd = pnmscanner_fd(scan);
  /****pts****/ /* rowlen = (unsigned int)ceil((double)(info->xres)/8.0);*/
  rowlen=(info->xres+7)>>3;
  /* buf = (unsigned char *)malloc(rowlen*sizeof(unsigned char)); */
  XMALLOCT(buf, unsigned char*, rowlen*sizeof(unsigned char));

      start = 0;
      end = info->yres;
      scanlines = end - start;
      d = data;

      for (i = 0; i < scanlines; i++)
	{
	  if (rowlen != fread_PNMFILE((char*)buf, rowlen, fd))
            FATALP ("PNM: error reading file");
	  bufpos = 0;
	  curbyte = buf[0];

	  for (x = 0; x < info->xres; x++)
	    {
	      if ((x % 8) == 0) {
		curbyte = buf[bufpos++];
		/* // if (curbyte!=0) printf("%d <%u>\n", x, curbyte); */
              }
	      /* // if (curbyte!=0) printf("[%u]\n", curbyte); */
	      d[x] = (curbyte&0x80) ? 0x00 : 0xff;
	      curbyte <<= 1;
	    }

	  d += info->xres;
	}

  XFREE(buf);
}

/**************** FILE SCANNER UTILITIES **************/

/* pnmscanner_create ---
 *    Creates a new scanner based on a file descriptor.  The
 *    look ahead buffer is one character initially.
 */
static PNMScanner *
pnmscanner_create (PNMFILE *fd)
{
  PNMScanner *s;

  XMALLOCT (s, PNMScanner*, sizeof(PNMScanner));
  s->fd = fd;
  s->eof = !fread_PNMFILE(&(s->cur), 1, s->fd);
  return s;
}

/* pnmscanner_destroy ---
 *    Destroys a scanner and its resources.  Doesn't close the fd.
 */
static void
pnmscanner_destroy (PNMScanner *s)
{
  XFREE(s);
}

#if 0 /**** pts ****/
/* pnmscanner_createbuffer ---
 *    Creates a buffer so we can do buffered reads.
 */
static void
pnmscanner_createbuffer (PNMScanner *s,
			 unsigned int bufsize)
{
  /* s->inbuf = (char *)malloc(sizeof(char)*bufsize); */
  XMALLOCT(s->inbuf, char*, sizeof(char)*bufsize);
  s->inbufsize = bufsize;
  s->inbufpos = 0;
  s->inbufvalidsize = fread(s->inbuf, 1, bufsize, s->fd);
}
#endif

/* pnmscanner_gettoken ---
 *    Gets the next token, eating any leading whitespace.
 */
static void pnmscanner_gettoken (PNMScanner *s,
		     unsigned char *buf,
		     unsigned int bufsize)
{
  unsigned char *bufend=buf+bufsize-1;
  pnmscanner_eatwhitespace(s);
  while (!pnmscanner_eof(s) && !isspace(s->cur) && s->cur!='#') {
    if (buf!=bufend) *buf++=s->cur;
    pnmscanner_getchar(s);
  }
  *buf='\0';
}

static unsigned pnmscanner_getint(PNMScanner *s) {
  unsigned ret=0;
  pnmscanner_eatwhitespace(s);
  while (!pnmscanner_eof(s)) {
    if (isdigit(s->cur)) ret=10*ret+s->cur-'0';
    else if (isspace(s->cur) || s->cur=='#') break;
    pnmscanner_getchar(s);
  }
  return ret;
}