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

#define ASSERT_ALLOC(cmd) if(NULL == (cmd)) { printf("Not enough memory\n"); exit(EXIT_FAILURE); }

/* external modules */
extern int load_tiff_into_double(double***,int*,int*,int*,char*,char); // see source file 'tiffread.c'
extern int save_monopage_striped_tiff_from_double(char*,double*,int,int,char,int,char*); // see source file 'tiffwrite.c'

/* usage displayer */
static void display_usage()
{
  printf("\nAdd a white Gaussian noise to a TIFF (monopage or mutlipage) image.\n\n");
  printf("Usage: tiffaddnoise [-ftype type] [-g std] [-s seed] in out\n\n");
  printf(" -ftype type : (default float) datatype of the output TIFF images, possible\n");
  printf("               choices are {uint8,int8,uint16,int16,uint32,int32,float}\n");
  printf(" -g std      : (default 2.) standard deviation of the Gaussian noise\n");
  printf(" -s seed     : use a specified seed for the random number generator\n");
  printf(" in          : input TIFF image\n");
  printf(" out         : output TIFF image\n\n");
}

/* command line interface */
int main(int argc, char **argv)
{
  char *datatype=NULL,*fname_in=NULL,*fname_out=NULL,*std_value=NULL,*seed_value=NULL;
  double **u,std,a,b;
  long int seed;
  int err,k,type,nx,ny,nimages,adr;

  /*************************/
  /* parse input arguments */
  /*************************/
  for(k=1; k<argc; k++) {
    if (strcmp(argv[k],"-ftype") == 0) {
      if(k==argc-1) { display_usage(); printf("Error: option -ftype requires an argument\n\n"); return EXIT_FAILURE; }
      else { datatype = argv[k+1]; k++; }
    }
    else if (strcmp(argv[k],"-g") == 0) {
      if(k==argc-1) { display_usage(); printf("Error: option -g requires an argument\n\n"); return EXIT_FAILURE; }
      else {
	std_value = argv[k+1]; err = sscanf(std_value,"%lf",&std); k++;
	if(err != 1) { display_usage(); printf("Error: could not retrieve properly the argument of option -g\n\n"); return EXIT_FAILURE; }
      }
    }
    else if (strcmp(argv[k],"-s") == 0) {
      if(k==argc-1) { display_usage(); printf("Error: option -s requires an argument\n\n"); return EXIT_FAILURE; }
      else {
	seed_value = argv[k+1]; err = sscanf(seed_value,"%ld",&seed); k++;
	if(err != 1) { display_usage(); printf("Error: could not retrieve properly the argument of option -s\n\n"); return EXIT_FAILURE; }
      }
    }
    else if (NULL == fname_in) fname_in = argv[k];
    else if (NULL == fname_out) fname_out = argv[k];
    else { display_usage(); printf("Error: could not retrieve properly input arguments.\n\n"); return EXIT_FAILURE; }
    }

  /**********************/
  /* set default values */
  /**********************/
  if(NULL == std_value) std = 2.;

  /*********************/
  /* check consistency */
  /*********************/
  if ((NULL == fname_in) || (NULL == fname_out)) {
    display_usage();
    if (NULL == fname_in) printf("Error: input 'in' is missing\n\n");
    else printf("Error: output 'out' is missing\n\n");
    return EXIT_FAILURE;
  }

  if((NULL == datatype)||(strcmp(datatype,"float")==0)) type = 0;
  else if (strcmp(datatype,"uint8")==0) type = 8;
  else if (strcmp(datatype,"int8")==0) type = -8;
  else if (strcmp(datatype,"uint16")==0) type = 16;
  else if (strcmp(datatype,"int16")==0) type = -16;
  else if (strcmp(datatype,"uint32")==0) type = 32;
  else if (strcmp(datatype,"int32")==0) type = -32;
  else { display_usage(); printf("Error: unrecognized value for option -ftype.\n\n"); return EXIT_FAILURE; }

  /*************************/
  /* load input TIFF image */
  /*************************/
  if(EXIT_FAILURE == load_tiff_into_double(&u,&nx,&ny,&nimages,fname_in,(char)0)){
    printf("Error: failed to open input image '%s'\n",fname_in);
    return EXIT_FAILURE;
  }

  /**********************/
  /* CORE OF THE MODULE */
  /**********************/
  srand48((NULL!=seed_value) ? seed : (long int) time (NULL) + (long int) getpid());
  for(k=0;k<nimages;k++)
    for (adr=0;adr<nx*ny;adr++) {
      a = drand48();
      b = drand48();
      u[k][adr] = u[k][adr] + std*sqrt(-2.0*log(a))*cos(2.0*M_PI*b);
    }

  /**************************/
  /* Save output TIFF image */
  /**************************/
  for(k=0;k<nimages;k++) {
    if(EXIT_FAILURE == save_monopage_striped_tiff_from_double(fname_out,u[k],nx,ny,0,type,((k==0)?"w":"a"))) {
      printf("Error: failed to save output image '%s'\n",fname_out);
      for(adr=0;adr<nimages;adr++) free(u[adr]);
      free(u);
      return EXIT_FAILURE;
    }
  }

  /***************/
  /* free memory */
  /***************/
  for(k=0;k<nimages;k++) free(u[k]);
  free(u);

  return EXIT_SUCCESS;
}
