/* readpnm.c */

#include "pnm.h"
#include "pnm_private.h"
#include "common.h"
#include "pools.h"

/* parses the header of a PPM file - see the pbm(5) manpage.
 * returns nonzero if errors occur, zero if succesful */
static int ReadPBMHeader(FILE *fp, IMAGE *pnm)
{
#ifdef DEBUG
  fprintf(stderr, "ReadPBMHeader:\n");
#endif /*DEBUG*/

  skipspace(fp);

  if ((pnm->width = getnatural(fp)) < 0)
    return 1;

  skipspace(fp);
  
  if ((pnm->height = getnatural(fp)) < 0)
    return 2;
  
  skip_comment(fp);
  if (skip_one_space(fp) != 0)
    return 3;
  
  pnm->nrchannels = 1;
  pnm->maxval = 1;
  
  return 0;
}

/* parses the header of a PGM file - see the pgm(5) manpage.
 * returns nonzero if errors occur, zero if succesful */
static int ReadPGMHeader(FILE *fp, IMAGE *pnm)
{
#ifdef DEBUG
  fprintf(stderr, "ReadPGMHeader:\n");
#endif /*DEBUG*/

  skipspace(fp);
  
  if ((pnm->width = getnatural(fp)) < 0)
    return 1;
  
  skipspace(fp);
  
  if ((pnm->height = getnatural(fp)) < 0)
    return 2;
  
  skipspace(fp);
  
  if ((pnm->maxval = getnatural(fp)) < 0)
    return 3;
  if (pnm->maxval > MAXCVAL) {
    PnmError(NULL, "Can't process this pgm file: max. grayvalue too large");
    return 4;
  }
  
  skip_comment(fp);
  if (skip_one_space(fp) != 0)
    return 5;
  
  pnm->nrchannels = 1;
  
  return 0;
}

/* parses the header of a PPM file - see the ppm(5) manpage.
 * returns nonzero if errors occur, zero if succesful */
static int ReadPPMHeader(FILE *fp, IMAGE *pnm)
{
#ifdef DEBUG
  fprintf(stderr, "ReadPPMHeader:\n");
#endif /*DEBUG*/

  skipspace(fp);
  
  if ((pnm->width = getnatural(fp)) < 0)
    return 1;
  
  skipspace(fp);
  
  if ((pnm->height = getnatural(fp)) < 0)
    return 2;
  
  skipspace(fp);
  
  if ((pnm->maxval = getnatural(fp)) < 0)
    return 3;
  if (pnm->maxval > MAXCVAL) {
    PnmError(NULL, "Can't process this ppm file: max. colorvalue too large");
    return 4;
  }
  
  skip_comment(fp);
  if (skip_one_space(fp) != 0)
    return 5;
  
  pnm->nrchannels = 3;
  
  return 0;
}

/*
 * Portable Bit Map: 
 *	1 channel 
 *	1 bit per pixel (ascii data: '0' = white, '0' = black)
 *
 * Return the filled in IMAGE struct if succesfull, a NULL-pointer if not
 */
static IMAGE *ReadPBM(FILE *fp, IMAGE *pnm)
{
  int row, col, bit;
  
  if (ReadPBMHeader(fp, pnm) != 0 ||
      AllocPixmap(pnm) != 0) 
    return (IMAGE *)NULL;
  
  for (row = 0; row < pnm->height; row++)
    for (col = 0; col < pnm->width; col++) {
      skipspace(fp);
      if ((bit = getbit(fp)) < 0)
	return (IMAGE *)NULL;
      pnm->pix[0][row][col] = bit;
    }
  
  return pnm;
}

/*
 * Portable Gray Map:
 *	1 channel
 * 	gray values between 0 and a maximum value found in the header of the file
 *
 * Return the filled in IMAGE struct if succesfull, a NULL-pointer if not
 */
static IMAGE *ReadPGM(FILE *fp, IMAGE *pnm)
{
  int row, col, gray;
  
  if (ReadPGMHeader(fp, pnm) != 0 ||
      AllocPixmap(pnm) != 0) 
    return (IMAGE *)NULL;
  
  for (row = 0; row < pnm->height; row++)
    for (col = 0; col < pnm->width; col++) {
      skipspace(fp);
      if ((gray = getnatural(fp)) < 0)
	return (IMAGE *)NULL;
      pnm->pix[0][row][col] = gray;
    }
  
  return pnm;
}

/*
 * Portable Pix Map:
 *	3 channels (red, green, blue)
 * 	colorvalues between 0 and a maximum value found in the header of the file
 *
 * Return the filled in PPM struct if succesfull, a NULL-pointer if not
 */
static IMAGE *ReadPPM(FILE *fp, IMAGE *pnm)
{
  int row, col, red, green, blue;
  
  if (ReadPPMHeader(fp, pnm) != 0 ||
      AllocPixmap(pnm) != 0) 
    return (IMAGE *)NULL;
  
  for (row = 0; row < pnm->height; row++)
    for (col = 0; col < pnm->width; col++) {
      skipspace(fp);
      if ((red = getnatural(fp)) < 0)
	return (IMAGE *)NULL;
      pnm->pix[0][row][col] = red;
      
      skipspace(fp);
      if ((green = getnatural(fp)) < 0)
	return (IMAGE *)NULL;
      pnm->pix[1][row][col] = green;
			
      skipspace(fp);
      if ((blue = getnatural(fp)) < 0)
	return (IMAGE *)NULL;
      pnm->pix[2][row][col] = blue;
    }

  return pnm;
}

/* same as above, but raw versions: */
static IMAGE *ReadRawPBM(FILE *fp, IMAGE *pnm)
{
  int row, col, count, c=0;
  
  if (ReadPBMHeader(fp, pnm) != 0 ||
      AllocPixmap(pnm) != 0) 
    return (IMAGE *)NULL;
  
  count = 0;
  for (row = 0; row < pnm->height; row++)
    for (col = 0; col < pnm->width; col++) {
      if ((count & 7) == 0) { /* there are 8 bits stored per byte */
	if ((c = myfgetc(fp)) == EOF) {
	  PnmError(NULL, "Unexpected end of input");
	  return (IMAGE *)NULL;
	}
	count = 0;
      }
      
      pnm->pix[0][row][col] = (c & 0x80) ? 0 : 1;
      c <<= 1; count++;
    }
  
  return pnm;
}

static IMAGE *ReadRawPGM(FILE *fp, IMAGE *pnm)
{
  int row, col, c;
  
  if (ReadPGMHeader(fp, pnm) != 0 ||
      AllocPixmap(pnm) != 0) 
    return (IMAGE *)NULL;
  
  for (row = 0; row < pnm->height; row++)
    for (col = 0; col < pnm->width; col++) {
      if ((c = myfgetc(fp)) == EOF) {
	PnmError(NULL, "Unexpected end of input");
	return (IMAGE *)NULL;
      }
      pnm->pix[0][row][col] = c;
    }
  
  return pnm;
}

static IMAGE *ReadRawPPM(FILE *fp, IMAGE *pnm)
{
  int row, col=0, red=0, green=0, blue=0;
  
  if (ReadPPMHeader(fp, pnm) != 0 ||
      AllocPixmap(pnm) != 0) 
    return (IMAGE *)NULL;
  
  for (row = 0; row < pnm->height; row++)
    for (col = 0; col < pnm->width; col++) {
      if ((red = myfgetc(fp)) == EOF ||
	  (green = myfgetc(fp)) == EOF ||
	  (blue = myfgetc(fp)) == EOF) {
	PnmError(NULL, "Unexpected end of input");
	return (IMAGE *)NULL;
      }
      pnm->pix[0][row][col] = red;
      pnm->pix[1][row][col] = green;
      pnm->pix[2][row][col] = blue;
    }
  
  return pnm;
}

/*
 * Input: pointer to a Portable aNy Map file.
 *
 * Output: pointer to a filled in IMAGE struct if file was succesfully read
 *         NULL pointer if errors occur while reading the file.
 */
IMAGE *PnmRead(FILE *fp)
{
  IMAGE *pnm = (IMAGE *)NULL; 
  int c;
  
  pnm = (IMAGE *)Alloc(sizeof(IMAGE));
  
  /* read the file type */
  c = myfgetc(fp);
  if (c != 'P') {
    PnmError(NULL, "Input not a pnm file");
    return (IMAGE *)NULL;
  }
  
  switch ((c = myfgetc(fp))) {
  case '1':
    return ReadPBM(fp, pnm);
  case '2':
    return ReadPGM(fp, pnm);
  case '3':
    return ReadPPM(fp, pnm);
  case '4':
    return ReadRawPBM(fp, pnm);
  case '5':
    return ReadRawPGM(fp, pnm);
  case '6':
    return ReadRawPPM(fp, pnm);
  default:
    PnmError(NULL, "Input not a pnm file");
  }
  
  return (IMAGE *)NULL;
}
