/****************************************************************
 *
 * ppm.c
 *
 * Read and write PPM files.  Only works for "raw" format.
 *
 * AF970205
 *
 ****************************************************************/


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


/************************ private functions ****************************/

/* die gracelessly */

static void
die(char *message)
{
  fprintf(stderr, "ppm: %s\n", message);
  exit(1);
}


/* check a dimension (width or height) from the image file for reasonability */

static void
checkDimension(int dim)
{
  if (dim < 1 || dim > 4000) 
    die("file contained unreasonable width or height");
}


/* read a header: verify format and get width and height */

static void
readPPMHeaderP2(FILE *fp, int *width, int *height)
{
  char ch;
  int  maxval;

  if (fscanf(fp, "P%c\n", &ch) != 1 || ch != '2')
    die("file is not in ppm raw format; cannot read");

  /* skip comments */
  ch = getc(fp);
  while (ch == '#')
    {
      do {
	ch = getc(fp);
      } while (ch != '\n');	/* read to the end of the line */
      ch = getc(fp);            /* thanks, Elliot */
    }

  if (!isdigit(ch)) die("cannot read header information from ppm file");

  ungetc(ch, fp);		/* put that digit back */

  /* read the width, height, and maximum value for a pixel */
  fscanf(fp, "%d%d%d\n", width, height, &maxval);

  if (maxval != 255) die("image is not true-color (24 bit); read failed");
  
  checkDimension(*width);
  checkDimension(*height);
}

static void
readPPMHeader(FILE *fp, int *width, int *height)
{
  char ch;
  int  maxval;

  if (fscanf(fp, "P%c\n", &ch) != 1 || ch != '6')
    die("file is not in ppm raw format; cannot read");

  /* skip comments */
  ch = getc(fp);
  while (ch == '#')
    {
      do {
	ch = getc(fp);
      } while (ch != '\n');	/* read to the end of the line */
      ch = getc(fp);            /* thanks, Elliot */
    }

  if (!isdigit(ch)) die("cannot read header information from ppm file");

  ungetc(ch, fp);		/* put that digit back */

  /* read the width, height, and maximum value for a pixel */
  fscanf(fp, "%d%d%d\n", width, height, &maxval);

  if (maxval != 255) die("image is not true-color (24 bit); read failed");
  
  checkDimension(*width);
  checkDimension(*height);
}

static void
readPPMHeaderP3(FILE *fp, int *width, int *height)
{
  char ch;
  int  maxval;

  if (fscanf(fp, "P%c\n", &ch) != 1 || ch != '3')
    die("file is not in ppm raw format; cannot read");

  /* skip comments */
  ch = getc(fp);
  while (ch == '#')
    {
      do {
	ch = getc(fp);
      } while (ch != '\n');	/* read to the end of the line */
      ch = getc(fp);            /* thanks, Elliot */
    }

  if (!isdigit(ch)) die("cannot read header information from ppm file");

  ungetc(ch, fp);		/* put that digit back */

  /* read the width, height, and maximum value for a pixel */
  fscanf(fp, "%d%d%d\n", width, height, &maxval);

  if (maxval != 255) die("image is not true-color (24 bit); read failed");
  
  checkDimension(*width);
  checkDimension(*height);
}

/************************ exported functions ****************************/

Image *
ImageCreate(int width, int height)
{
  Image *image = (Image *) malloc(sizeof(Image));

  if (!image) die("cannot allocate memory for new image");

  image->width  = width;
  image->height = height;
  image->data   = (u_char *) malloc(width * height * 3);
  image->fdata   = (double *) malloc(sizeof (double) * width * height * 3); // double just for one channel

  if (!image->data) die("cannot allocate memory for new image");
  if (!image->fdata) die("cannot allocate memory for new image");

  return image;
}
  
void ImageDestroy(Image *image)
{
	free (image->data);
	free (image->fdata);
	//free (image);
}

Image *
ImageReadP2(char *filename) // PGM ASCII
{
  int i, width, height, num, size;

  Image *image = (Image *) malloc(sizeof(Image));
  FILE  *fp    = fopen(filename, "r");

  if (!image) die("cannot allocate memory for new image");
  if (!fp)    die("cannot open file for reading");

  readPPMHeaderP2(fp, &width, &height);

  size          = width * height;
  image->data   = (u_char *) malloc(size);
  image->width  = width;
  image->height = height;

  if (!image->data) die("cannot allocate memory for new image");

  num = 0;
  for (i = 0; i < size; i++) {
	  num++;
	  fscanf(fp, "%d ", &(image->data[i]));
  }

  if (num != size) die("cannot read image data from file");

  fclose(fp);

  return image;
}

Image *
ImageRead(char *filename)
{
  int width, height, num, size;

  Image *image = (Image *) malloc(sizeof(Image));
  FILE  *fp    = fopen(filename, "r");

  if (!image) die("cannot allocate memory for new image");
  if (!fp)    die("cannot open file for reading");

  readPPMHeader(fp, &width, &height);

  size          = width * height * 3;
  image->data   = (u_char *) malloc(size);
  image->width  = width;
  image->height = height;

  if (!image->data) die("cannot allocate memory for new image");

  num = fread((void *) image->data, 1, (size_t) size, fp);

  if (num != size) die("cannot read image data from file");

  fclose(fp);

  return image;
}

Image *
ImageReadP3(char *filename)
{
  int i, width, height, num, size;

  Image *image = (Image *) malloc(sizeof(Image));
  FILE  *fp    = fopen(filename, "r");

  if (!image) die("cannot allocate memory for new image");
  if (!fp)    die("cannot open file for reading");

  readPPMHeaderP3(fp, &width, &height);

  size          = width * height * 3;
  image->data   = (u_char *) malloc(size);
  image->width  = width;
  image->height = height;

  if (!image->data) die("cannot allocate memory for new image");

  num=0;

  for (i = 0; i < size; i++) {
	  num++;
	  fscanf(fp, "%d ", &(image->data[i]));
  }

  //num = fread((void *) image->data, 1, (size_t) size, fp);

  if (num != size) die("cannot read image data from file");

  fclose(fp);

  return image;
}

// ASCII PGM (greyscale) unsigned char PPM format
void ImageWriteP2(Image *image, char *filename)
{
  int i, count;
  int size = image->width * image->height;

  FILE *fp = fopen(filename, "w");

  if (!fp) die("cannot open file for writing");

  fprintf(fp, "P2\n%d %d\n%d\n", image->width, image->height, 255);

  count = 0;
  for (i = 0; i < size; i++) {
	  count++;
	  fprintf(fp, "%d ", image->data[i]);
	  if (count > 10) {
		  fprintf(fp, "\n");
		  count = 0;
	  }
  }

  fclose(fp);
}  

// Binary Grey scale unsigned char PPM format
void ImageWriteP5(Image *image, char *filename)
{
  int num;
  int size = image->width * image->height;

  FILE *fp = fopen(filename, "wb");

  if (!fp) die("cannot open file for writing");

  fprintf(fp, "P5\n%d %d\n%d\n", image->width, image->height, 255);

  num = fwrite((void *) image->data, 1, (size_t) size, fp);

  if (num != size) die("cannot write image data to file");

  fclose(fp);
}  

// ASCII unsigned char PPM format
void ImageWriteP3(Image *image, char *filename)
{
  int i, count;
  int size = image->width * image->height * 3;

  FILE *fp = fopen(filename, "w");

  if (!fp) die("cannot open file for writing");

  fprintf(fp, "P3\n%d %d\n%d\n", image->width, image->height, 255);

  count = 0;
  for (i = 0; i < size; i++) {
	  count++;
	  fprintf(fp, "%d ", image->data[i]);
	  if (count > 10) {
		  fprintf(fp, "\n");
		  count = 0;
	  }
  }

  fclose(fp);
}  


// Binary color unsigned char PPM format (the most standard)
void ImageWrite(Image *image, char *filename)
{
  int num;
  int size = image->width * image->height * 3;

  FILE *fp = fopen(filename, "w");

  if (!fp) die("cannot open file for writing");

  fprintf(fp, "P6\n%d %d\n%d\n", image->width, image->height, 255);

  num = fwrite((void *) image->data, 1, (size_t) size, fp);

  if (num != size) die("cannot write image data to file");

  fclose(fp);
}  

// Binary grey2color unsigned char PPM format
void ImageWriteP6grey2color(Image *image, char *filename)
{
  int i, count = 0, num;
  int size = image->width * image->height * 3;

  FILE *fp = fopen(filename, "wb");

  if (!fp) die("cannot open file for writing");

  u_char *fdata   = (u_char *) malloc(sizeof (u_char) * size); // float just for one channel

  fprintf(fp, "P6\n%d %d\n%d\n",  image->width, image->height, 255);

  for (i = 0; i < size/3; i++) {
	  fdata[count++] = image->data[i];
	  fdata[count++] = image->data[i];
	  fdata[count++] = image->data[i];
	  }

  num = fwrite((u_char *) fdata, sizeof(u_char), (size_t) count, fp);
  if (num != count) die("cannot write image data to file");

  fclose(fp);
}  

// floating point binary PPM format
void ImageWriteP7grey(Image *image, char *filename)
{
  int i, num;
  int size = image->width * image->height;

  FILE *fp = fopen(filename, "w");

  if (!fp) die("cannot open file for writing");

  fprintf(fp, "PF\n%d %d\n%f\n", image->width, image->height, -1.0);

  float *fdata   = (float *) malloc(sizeof (float) * size); // float just for one channel

  for (i = 0; i < size; i++) {
	  fdata[i] = (float) image->data[i];
	  }

  num = fwrite((float *) image->fdata, sizeof(float), (size_t) size, fp);

  //if (num != size) die("cannot write image data to file");

  fclose(fp);
}  

// for now writing in just .float format
void ImageWriteP7greyfloat2color(double **image, int width, int height, char *filename)
{
  int i, j, count = 0, num;
  int size = width * height;

  FILE *fp = fopen(filename, "wb");

  if (!fp) die("cannot open file for writing");

  float *fdata   = (float *) malloc(sizeof (float) * 3 * width * height); // float just for one channel

  fprintf(fp, "PF\n%d %d\n%f\n",  width , height , -1.0);

  // index starts from 1 to n
  for (j=1; j <= height; j++) {
	  for (i=1; i <= width; i++) {
		  fdata[count++] = (float) (image[i][j]);
		  fdata[count++] = (float) (image[i][j]);
		  fdata[count++] = (float) (image[i][j]);
		  //fprintf(fp, "%f", (float) (255.0 * image[i][j]) );
	  }
  }
  printf("Written %s , with count = %d (%d %d)\n", filename, count, width, height);
  num = fwrite((float *) fdata, sizeof(float), (size_t) count, fp);
  //if (num != size) die("cannot write image data to file");

  fclose(fp);
}  

int
ImageWidth(Image *image)
{
  return image->width;
}


int
ImageHeight(Image *image)
{
  return image->height;
}


void   
ImageClear(Image *image, u_char red, u_char green, u_char blue)
{
  int i;
  int pix = image->width * image->height;

  u_char *data = image->data;

  for (i = 0; i < pix; i++)
    {
      *data++ = red;
      *data++ = green;
      *data++ = blue;
    }
}

void
ImageSetPixel(Image *image, int x, int y, int chan, u_char val, int format)
{
  int offset = (y * image->width + x) * format + chan;

  image->data[offset] = val;
}


u_char 
ImageGetPixel(Image *image, int x, int y, int chan, int format)
{
  int offset = (y * image->width + x) * format + chan;

  return image->data[offset];
}

void
ImageSetPixelf(Image *image, int x, int y, int chan, double val, int format)
{
  int offset = (y * image->width + x) * format + chan;

  image->fdata[offset] = val;
}


double ImageGetPixelf(Image *image, int x, int y, int chan, int format)
{
  int offset = (y * image->width + x) * format + chan;

  return image->fdata[offset];
}

