#define EXPONENT 4.7

/*
 * cement.c  adds uchar image into total.pdm (double) file
 * to compile: gcc -Wall cement.c -o cement -lm
 * to run: cement tot1.pdm v080.ppm .9 .5 .1 -o tot2.pdm   # to cement in

 * later this code will be modified to permit in-place cementing
 * and uncementing:
 *  future syntax: cement v080.ppm .9 .5 .1    # to add to total.pdm
 *         ucement v080.ppm .9 .5 .1           # to take out
 *
 * steve; from ndf's diff.c, with help of sbeck on parsing input images
 */

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

struct image_params {

  char * filename;
  int width;
  int height;
  /* max_val is the maximum colour component value - often 255 */
  int max_val;
  /* type is either '5' to indicate a P5 file or '6' to
     indicate a P6 file */
  int type;
};

double pow(double x, double y);

void
use(void)
{
  fprintf(stderr, "Use: cement tot1.pdm v002.ppm 0.9 0.8 0.1 [-o tot2.pdm]\n");
  exit(EXIT_SUCCESS);
}

void
parse_commandline(int argc, char ** argv,
                  struct image_params * a_params,
                  struct image_params * b_params,
                  double *R,
                  double *G,
                  double *B,
                  char ** output_filename)
{
  int i;

  if (argc<3) {
    fprintf(stderr,"number of input arguments was only %d\n",argc);
    use();
  }

  for (i=1;i<argc;i++)
    {
      if (!strcasecmp(argv[i], "-o"))
	{
	  if ((i+1)>=argc) use(); /* e.g. if last argument not output file */
	  *output_filename=(char *)strdup(argv[++i]);
	}
      else if (a_params->filename==NULL)
	a_params->filename=(char *)strdup(argv[i]); /* if file a still null */
      else if (b_params->filename==NULL)
	b_params->filename=(char *)strdup(argv[i]);
      /* else use(); */
//*R: scanf(...R)    double must be a pointer, choice exists only with interger
      sscanf(argv[3],"%lf",R); /* sscanf does not recognize %g, must be %lf */
//R:  sscanf(argv[3],"%lf",&R);
      sscanf(argv[4],"%lf",G); /* sscanf does not recognize %g, must be %lf */
      sscanf(argv[5],"%lf",B); /* sscanf does not recognize %g, must be %lf */
    }

  if ((a_params->filename==NULL) || (b_params->filename==NULL)) use();
}

void
get_image_params(FILE * ifs, struct image_params * params)
{
  int c;
  int dims[3];
  int dims_scanned;
  if( ifs == NULL ) { /* check for file pointers to non existant files */
    fprintf(stderr,"get_frame_params: non-existant file\n");
    exit(EXIT_FAILURE);
  }

  if (fgetc(ifs)!='P') /* pnm file must begin with P, e.g. P5 or P6 */
    {
      fprintf(stderr, "get_frame_params: input does not begin with ``P''"
	              " thus not a pnm image.\n");
      exit(EXIT_FAILURE);
    }
  c=fgetc(ifs);
  if (c!='5' && c!='6'  && c!='7' && c!='8')
    {
      fprintf(stderr, "get_frame_params: input file must begin with "
             " P5, P6, P7, or P8.\n");
      exit(EXIT_FAILURE);
    }
  params->type=c;

  /* enter a loop
   * skip comments which start with #
   * and look for the dimensions
   */
  dims_scanned = 0;
  do {
    char line[80];
    fgets( line, sizeof(line), ifs );
    fprintf(stderr,"got line [%s]\n", line );
    if( *line != '#' ) {
      char *p = strtok(line," \n" );
      while( p && (*p != '#') ) {
        sscanf(p,"%d", &dims[dims_scanned] );
        dims_scanned++;
        p = strtok( NULL, " \n" );
      }
    }
    fprintf(stderr,"dims_scanned:%d\n", dims_scanned);
  } while( dims_scanned < 3 );

  params->width = dims[0];
  params->height = dims[1];
  params->max_val = dims[2];

  if( params->max_val != 255 ){
    fprintf(stderr,"get_frame_params: max value must be 255\n");
    exit( EXIT_FAILURE );
  }

}

int
main(int argc, char ** argv)
{
  struct image_params a_params={NULL,0,0,0,0};
  struct image_params b_params={NULL,0,0,0,0};
  char * dest_filename=NULL;
  FILE * a, * b, * dest;
  double p=EXPONENT;  /* exponent for pow */
  double p_inv;  /* 1.0/p */
  //double scale_factor; /* to make the result on same interval is input */
  double        a_pixel[3];
  unsigned char b_pixel[3];
  double out_pixel[3];
  int i;
  int channels_per_pixel;
  double weight, R, G, B;  /* weights for cement */

  p_inv=1.0/p;
  //scale_factor = pow(2.0,p_inv);

  parse_commandline(argc, argv, &a_params, &b_params,
                    &R, &G, &B, &dest_filename);

  if ((a=fopen(a_params.filename, "r"))==NULL)
    {
      fprintf(stderr, "Unable to open %s.\n", a_params.filename);
      exit(EXIT_FAILURE);
    }

  if ((b=fopen(b_params.filename, "r"))==NULL)
    {
      fprintf(stderr, "Unable to open %s.\n", b_params.filename);
      exit(EXIT_FAILURE);
    }

  if (dest_filename==NULL) dest=stdout;
  else if ((dest=fopen(dest_filename, "w"))==NULL)
    {
      fprintf(stderr, "Unable to open %s.\n", dest_filename);
      exit(EXIT_FAILURE);
    }

  get_image_params(a, &a_params);
  get_image_params(b, &b_params);

  if ((a_params.width!=b_params.width) || (a_params.height!=b_params.height)
      || (a_params.max_val!=b_params.max_val))
    {
      fprintf(stderr, "Images must have the same dimensions and maximum "
	      "values.\n");
      exit(EXIT_FAILURE);
    }

/*   images will not be the same type; typically a is double, b is uchar
     e.g. image a is type '8' and b is '6'
  if (a_params.type!=b_params.type)
    {
      fprintf(stderr, "%s is a P%c but %s is a P%c.\n", argv[1],
	      a_params.type, argv[2], b_params.type);
      exit(EXIT_FAILURE);
    }
*/

  if (a_params.type=='7')      channels_per_pixel=1;
  else if (a_params.type=='8') channels_per_pixel=3;
  else {
         fprintf(stderr, "first input image must be type 7 or 8\n");
         exit(EXIT_FAILURE);
       }

/* output is of same type as image a input */
  fprintf(dest, "P%c\n%d %d\n%d\n", a_params.type, a_params.width,
	  a_params.height, a_params.max_val);

  //fprintf(stderr,"R=%lf\n",R);  /*gives warning in gcc -Wall*/
  fprintf(stderr,"R=%g\n",R);
  fprintf(stderr,"G=%g\n",G);
  fprintf(stderr,"B=%g\n",B);

  while (!feof(a))
    {
      fread(a_pixel, sizeof(double),        channels_per_pixel, a);
      fread(b_pixel, sizeof(unsigned char), channels_per_pixel, b);

      for (i=0;i<channels_per_pixel;i++) {
        if (i==0) weight=R; if (i==1) weight=G; if (i==2) weight=B;
        out_pixel[i]=
          (
            pow(
              pow(a_pixel[i],p)
                +
              weight
                *
              pow((double)b_pixel[i],p)
                ,p_inv)   //             /scale_factor
          );
        // pdm file may exceed 255; it's up to double2uchar to fix that
        //if (out_pixel[i] > 255.0) out_pixel[i]=255.0;
        //if (out_pixel[i] < 0.0) out_pixel[i]=0.0;
      }
      fwrite(out_pixel, sizeof(double), channels_per_pixel, dest);
    }

  fclose(a); fclose(b); fclose(dest);
  exit(EXIT_SUCCESS);
}

