/*------------------------------------------------------------------------------------

  File        : inrcast.cpp

  Description : 1D/2D/3D Image converter and visualizer based on command line arguments

  Author      : David Tschumperlé
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
		
  -----------------------------------------------------------------------------------*/

#include "../CImg.h"
using namespace cimg_library;
// The lines below should be not necessary in your own code, it simply allows 
// the source compilation with compilers that do not respect the C++ standart.
#if ( defined(_MSC_VER) && _MSC_VER<=1200 ) || defined(__DMC__)
#define std
#endif

//---------------------------------------------
// Convert to output image, save and visualize
//---------------------------------------------
template<typename T,typename t> void output_file(int argc,const char **argv,CImgl<T>& src,const t& pixel_type) {

  // Read command line arguments
  const char* typei = cimg_option("-i","float","Input pixel type (can be : char,uchar,ushort,short,uint,int,ulong,long,float,double)");
  cimg_option("-t",typei,"Output pixel type (can be : char,uchar,ushort,short,uint,int,ulong,long,float,double)");
  const bool a_orient   = cimg_option("-orient",false,"Compute vector orientations");
  const bool a_norm     = cimg_option("-norm",false,"Compute vector norms");
  const float a_rotate  = (float)cimg_option("-angle",0.0f,"Rotate image(s) (angle in degrees)");
  const char  a_flip    = cimg_option("-flip",'\0',"Flip image(s) along axis (can be 'x','y','z' or 'v')");
  const float a_blur    = (float)cimg_option("-blur",0.0f,"Blur image(s) (variance)");
  const float a_noiseg  = (float)cimg_option("-ng",0.0f,"Add gaussian noise (variance)");
  const float a_noiseu  = (float)cimg_option("-nu",0.0f,"Add uniform noise (variance)");
  const float a_noises  = (float)cimg_option("-ns",0.0f,"Add salt&pepper noise (percentage)");
  const char *a_cut     = cimg_option("-cut",(const char*)NULL,"Pixel value cutting (ex : '0,255')");
  const char *a_vnorm   = cimg_option("-val",(const char*)NULL,"Pixel value normalization (ex : '0,255')");
  const int a_quantify  = cimg_option("-quantify",0,"Uniform quantification (number of levels)");
  const char *a_bcrop   = cimg_option("-beg",(const char*)NULL,"Upper-left coordinates crop (2D,3D or 4D coordinates)");
  const char *a_ecrop   = cimg_option("-end",(const char*)NULL,"Lower-right coordinates crop (2D,3D or 4D coordinates");
  const char *a_resize  = cimg_option("-g",(const char*)NULL,"Resize image(s) (ex : '100x100' or '50%x30%')");
  const bool a_equalize = cimg_option("-equalize",false,"Equalize histogram");
  const float a_mul     = (float)cimg_option("-mul",1.0f,"Multiply pixel values by a float constant");
  const float a_add     = (float)cimg_option("-add",0.0f,"Add a float constant to pixel values");
  const int a_erode     = cimg_option("-erode",0,"Erode image(s) (number of steps)");
  const int a_dilate    = cimg_option("-dilate",0,"Dilate image(s) (number of steps)");
  const int a_histo     = cimg_option("-histogram",0,"Compute image histogram (number of levels)");
  const char a_break    = cimg_option("-b",'\0',"Break image along axis (can be 'z' or 'v')");
  const char a_append   = cimg_option("-a",'\0',"Append images along axis (can be 'x','y','z' or 'v')");
  const bool a_ucrop    = cimg_option("-crop",false,"User-selected crop");
  const char *file_o    = cimg_option("-o",(const char*)NULL,"Output file");
  const bool a_visu     = cimg_option("-visu",false,"Image visualization");
  if (!src.size) exit(0);

  // Display image statistics
  std::fprintf(stderr,"\n- Input image(s) statistics :\n");
  src.print("\tInput");

  // Vector orientation computation
  if (a_orient) {
    std::fprintf(stderr,"\n- Vector orientation computation\t"); std::fflush(stderr);
    cimgl_map(src,l) src[l].orientation_pointwise();
  }

  // Vector norm computation
  if (a_norm) {
    std::fprintf(stderr,"\n- Vector norm computation\t"); std::fflush(stderr);
    cimgl_map(src,l) src[l].norm_pointwise();
  }

  // Image rotation
  if (a_rotate!=0) {
    std::fprintf(stderr,"\n- Image rotation ( %g degrees )\t",a_rotate); std::fflush(stderr);
    cimgl_map(src,l) src[l].rotate(a_rotate,3);
  }
  
  // Flip Image
  if (a_flip!='\0') {
    std::fprintf(stderr,"\n- Flip image along axis '%c'\t",a_flip); std::fflush(stderr);
    cimgl_map(src,l) src[l].flip(a_flip);
  }

  // Image blur
  if (a_blur!=0) {
    std::fprintf(stderr,"\n- Image blur ( sigma = %g )\t",a_blur); std::fflush(stderr);
    cimgl_map(src,l) src[l].blur(a_blur);
  }

  // Image noise
  if (a_noiseg) {
    std::fprintf(stderr,"\n- Add gaussian noise ( variance = %g )\t",a_noiseg); std::fflush(stderr);
    cimgl_map(src,l) src[l].noise(a_noiseg,0);
  }
  if (a_noiseu) {
    std::fprintf(stderr,"\n- Add uniform noise ( variance = %g )\t",a_noiseu); std::fflush(stderr);
    cimgl_map(src,l) src[l].noise(a_noiseu,1);
  }
  if (a_noises) {
    std::fprintf(stderr,"\n- Add uniform noise ( variance = %g )\t",a_noises); std::fflush(stderr);
    cimgl_map(src,l) src[l].noise(a_noises,2);
  }

  // Pixel value cutting
  if (a_cut) {
    std::fprintf(stderr,"\n- Pixel cutting in [%s]\t",a_cut); std::fflush(stderr);
    cimgl_map(src,l) {
      CImg<T>& img=src[l];
      CImgStats stats(img);
      double vmin=stats.min, vmax=stats.max;
      std::sscanf(a_cut,"%lg%*c%lg",&vmin,&vmax);
      img.cut((T)vmin,(T)vmax);
    }
  }
  
  // Pixel value normalization
  if (a_vnorm) {
    std::fprintf(stderr,"\n- Pixel normalization in [%s]\t",a_vnorm); std::fflush(stderr);
    cimgl_map(src,l) {
      CImg<T>& img=src[l];
      CImgStats stats(img);
      double vmin=stats.min, vmax=stats.max;
      std::sscanf(a_vnorm,"%lg%*c%lg",&vmin,&vmax);
      img.normalize((T)vmin,(T)vmax);
    }
  }

  // Image quantification
  if (a_quantify) {
    std::fprintf(stderr,"\n- Quantify image in %d levels\t",a_quantify); std::fflush(stderr);
    cimgl_map(src,l) src[l].quantify(a_quantify);
  }

  // Image cropping
  if (a_bcrop || a_ecrop) {
    std::fprintf(stderr,"\n- Crop image(s) to %s-%s\t",a_bcrop?a_bcrop:"*",a_ecrop?a_ecrop:"*"); std::fflush(stderr);
    cimgl_map(src,l) {
      CImg<T> &img=src[l];
      int x0=0,y0=0,z0=0,v0=0,x1=img.dimx()-1,y1=img.dimy()-1,z1=img.dimz()-1,v1=img.dimv()-1;
      char buf[16];
      if (a_bcrop) std::sscanf(a_bcrop,"%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]",&x0,buf+0,&y0,buf+1,&z0,buf+2,&v0,buf+3);
      if (a_ecrop) std::sscanf(a_ecrop,"%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]",&x1,buf+4,&y1,buf+5,&z1,buf+6,&v1,buf+7);
      if (buf[0]=='%') x0 = -x0*img.width/100;
      if (buf[1]=='%') y0 = -y0*img.height/100;
      if (buf[2]=='%') z0 = -z0*img.depth/100;
      if (buf[3]=='%') v0 = -v0*img.dim/100;
      if (buf[4]=='%') x1 = -x1*img.width/100;
      if (buf[5]=='%') y1 = -y1*img.height/100;
      if (buf[6]=='%') z1 = -z1*img.depth/100;
      if (buf[7]=='%') v1 = -v1*img.dim/100;
      if (x0>x1) cimg::swap(x0,x1);
      if (y0>y1) cimg::swap(y0,y1);
      if (z0>z1) cimg::swap(z0,z1);
      if (v0>v1) cimg::swap(v0,v1);
      if (x0>=img.dimx()) x0=img.dimx()-1;
      if (y0>=img.dimy()) y0=img.dimy()-1;
      if (z0>=img.dimz()) z0=img.dimz()-1;
      if (v0>=img.dimv()) v0=img.dimv()-1;
      if (x1>=img.dimx()) x1=img.dimx()-1;
      if (y1>=img.dimy()) y1=img.dimy()-1;
      if (z1>=img.dimz()) z1=img.dimz()-1;
      if (v1>=img.dimv()) v1=img.dimv()-1;
      img.crop(x0,y0,z0,v0,x1,y1,z1,v1);
    }
  }
  
  // Image resizing
  if (a_resize) {
    std::fprintf(stderr,"\n- Resize image(s) to '%s'\t",a_resize); std::fflush(stderr);
    int dimx=-100,dimy=-100,dimz=-100,dimv=-100;
    char buf[16];
    std::sscanf(a_resize,"%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]",&dimx,buf,&dimy,buf+1,&dimz,buf+2,&dimv,buf+3);
    if (buf[0]=='%') dimx=-dimx;
    if (buf[1]=='%') dimy=-dimy;
    if (buf[2]=='%') dimz=-dimz;
    if (buf[3]=='%') dimv=-dimv;
    cimgl_map(src,l) src[l].resize(dimx,dimy,dimz,dimv,3);
  }

  if (a_equalize) {
    std::fprintf(stderr,"\n- Equalize histogram\t"); std::fflush(stderr);
    cimgl_map(src,l) src[l].equalize_histogram(4096);
  }
  
  // Multiplication of pixel values
  if (a_mul!=1) {
    std::fprintf(stderr,"\n- Multiply pixel values by %g\t",a_mul); std::fflush(stderr);
    cimgl_map(src,l) src[l]*=a_mul;
  }
  
  // Add a constant to pixel values
  if (a_add!=0) {
    std::fprintf(stderr,"\n- Add %g to pixel values\t",a_add); std::fflush(stderr);
    cimgl_map(src,l) src[l]+=(T)a_add;
  }

  // Erode image
  if (a_erode) {
    std::fprintf(stderr,"\n- Erode image(s) %d times",a_erode); std::fflush(stderr);
    cimgl_map(src,l) src[l].erode(a_erode);
  }

  // Dilate image
  if (a_dilate) {
    std::fprintf(stderr,"\n- Dilate image(s) %d times",a_dilate); std::fflush(stderr);
    cimgl_map(src,l) src[l].dilate(a_dilate);
  }

  // Compute image histogram
  if (a_histo) {
    std::fprintf(stderr,"\n- Compute histogram(s)\t"); std::fflush(stderr);
    cimgl_map(src,l) src[l] = src[l].get_histogram(a_histo);
  }

  // Image break
  if (a_break!='\0') {
    std::fprintf(stderr,"\n- Break image along axis '%c'\t",a_break); std::fflush(stderr);
    CImgl<T> tmp;
    cimgl_map(src,l) tmp.insert(src[l].get_split(a_break));
    src = tmp;
  }
  
  // Images append
  if (a_append!='\0') {
    std::fprintf(stderr,"\n- Append images along axis '%c'\t",a_append); std::fflush(stderr);
    src = CImgl<T>(src.get_append(a_append,'p'));    
  }

  // User-selected crop
  if (a_ucrop) {
    std::fprintf(stderr,"\n- User crop : Please select the image region to crop.\t"); std::fflush(stderr);
    int selection[6] = {-1,-1,-1,-1,-1,-1};
    src[0].feature_selection(selection,2);
    if (selection[0]<0 || selection[1]<0 || selection[2]<0 ||
	selection[3]<0 || selection[4]<0 || selection[5]<0) std::fprintf(stderr,".. Aborted..");
    else {
      std::fprintf(stderr,"-> Crop [%d,%d,%d]-[%d,%d,%d]",
		   selection[0],selection[1],selection[2],
		   selection[3],selection[4],selection[5]);
      cimgl_map(src,l) src[l].crop(selection[0],selection[1],selection[2],selection[3],selection[4],selection[5]);
    }
  }

  // Convert image to destination type
  std::fprintf(stderr,"\n- Conversion to '%s' pixels\t",cimg::get_type(pixel_type)); std::fflush(stderr);
  CImgl<t> dest(src);

  // Save destination image
  if (file_o) {    
    std::fprintf(stderr,"\n- Save image(s) to '%s'\t",file_o); std::fflush(stderr);
    dest.save(file_o);
    dest.print("\tOutput image(s)");
  }  

  // Display destination image
  if (a_visu || !file_o) {
    std::fprintf(stderr,"\n- Visualize image(s)\n"); std::fflush(stderr);
    if ( dest.size==1 &&
	 ( dest[0].height==1 && dest[0].depth==1  ) ||
	 ( dest[0].width==1  && dest[0].depth==1  ) ||
	 ( dest[0].width==1  && dest[0].height==1 ) ||
	 ( dest[0].width==1  && dest[0].height==1 && dest[0].depth==1)) { // 1D plot representation
      const CImg<t>& img = dest[0];
      img.print("\tOutput image");
      CImgStats stats(img,false);
      const CImg<unsigned char> palette = CImg<unsigned char>(3,img.dim,1,1,0).noise(300,1);
      const unsigned char gray[3]={128,128,128},white[3]={255,255,255};
      unsigned long trame = 0x0F0F0F0F;
      char message[1024];
      palette[0]=255; palette[1]=0; palette[2]=0;
      if (img.dim>1) { palette[3]=0; palette[4]=255; palette[5]=0; }
      if (img.dim>2) { palette[6]=0; palette[7]=0; palette[8]=255; }
      CImgDisplay disp(500,300,"1D plot of the output image",0);
      CImg<unsigned char> visu0;
      do {
	if (!visu0.data || disp.resized) { // plot graphics
	  disp.resize();
	  visu0 = CImg<unsigned char>(disp.width,disp.height,1,3,0);
	  if (img.dim==1) visu0.draw_graph(img,palette.ptr(),1,stats.min,stats.max,0.4f);
	  cimg_mapV(img,k) visu0.draw_graph(img.ref_channel(k),&palette(0,k),0,stats.min,stats.max);
	  visu0.draw_axeXY(0,img.size(),stats.max,stats.min,gray);
	}
	CImg<unsigned char> visu(visu0);
	if (disp.mousex>=0) {
	  visu.draw_line(disp.mousex,0,disp.mousex,visu.height-1,gray,trame,0.5);
	  trame = cimg::rol(trame);
	  const unsigned x = disp.mousex*img.size()/(img.dim*disp.width);
	  std::sprintf(message,"x=%d\n[ ",x);
	  cimg_mapV(img,k) std::sprintf(message+std::strlen(message),"%s%g%s",k==0?"":"  ",
					(float)img(x,0,0,k),k==img.dimv()-1?"":"\n");
	  std::sprintf(message+std::strlen(message)," ]");
	  visu.draw_text(message,disp.mousex-20,10,white,NULL,1);
	} else visu=visu0;	
	disp.display(visu).wait(40);	
      } while (!disp.closed && !disp.key);
    }
    else dest.display("\tOutput image(s)");
  }
}

//---------------------------------------------------------
// Read input image and do conversion to output pixel type
//---------------------------------------------------------
template<typename T> void input_file(int argc,const char **argv,const T& pixel_type) {
  
  // Read input image list
  std::fprintf(stderr,"- Load images...\t"); std::fflush(stderr);
  CImgl<T> src;
  bool opt = false;
  for (int k=1; k<argc; k++)
    if (argv[k][0]=='-' && argv[k][1]!='\0') opt=true;
    else {
      if (!opt ||
	  !cimg::strcasecmp(argv[k-1],"-h") ||
	  !cimg::strcasecmp(argv[k-1],"-orient") ||
	  !cimg::strcasecmp(argv[k-1],"-norm") ||
	  !cimg::strcasecmp(argv[k-1],"-equalize") ||
	  !cimg::strcasecmp(argv[k-1],"-crop") ||
	  !cimg::strcasecmp(argv[k-1],"-plot") ||
	  !cimg::strcasecmp(argv[k-1],"-visu")
	  ) {
	std::fprintf(stderr,"\n\t> Loading [%d]='%s'\t",k-1,argv[k]); std::fflush(stderr);
	src.insert(CImgl<T>::load(argv[k])); }
      opt=false;
    }
  std::fputc('\n',stderr);
  if (!src.size) { 
    std::fprintf(stderr,"\n** You must specify at least one input image\n** Try '%s -h' to get help\n**\n",cimg::basename(argv[0]));
    unsigned char x=0;
    CImgl<unsigned char> tmp;
    output_file(argc,argv,tmp,x); 
  }
  else {
    // Convert to output pixel type (if specified)
    const char *typei = cimg_option("-i","float",NULL);
    const char *typeo = cimg_option("-t",typei,NULL);
    bool saved = false;
    if (!saved && !cimg::strcasecmp(typeo,"char")) { char t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"uchar")) { unsigned char t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"ushort")) { unsigned short t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"short")) { short t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"uint")) { unsigned int t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"int")) { int t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"ulong")) { unsigned long t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"long")) { long t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"float")) { float t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"double")) { float t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved) throw CImgException("Cannot save image with pixel type '%s' (see help)",typeo);
  }
}

//----------------
// Main procedure
//----------------
int main(int argc,const char **argv) {
  cimg_usage("1D/2D/3D Image(s) converter and visualizer.\n\t\tusage : inrcast [options] image_in1 [image_in2] [...] [-o image_out]");
  if (argc==1) { std::fprintf(stderr,"Try '%s -h' to get help\n",cimg::basename(argv[0])); exit(0); }
  
  // Read input image and call converter
  bool casted = false;
  const char *typei = cimg_option("-i","float",NULL);
  if (!casted && !cimg::strcasecmp(typei,"char")) { char t=0; input_file(argc,argv,t); casted=true; }
  if (!casted && !cimg::strcasecmp(typei,"uchar")) { unsigned char t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"ushort")) { unsigned short t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"short")) { short t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"uint")) { unsigned int t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"int")) { int t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"ulong")) { unsigned long t=0; input_file(argc,argv,t); casted=true; }
  if (!casted && !cimg::strcasecmp(typei,"long")) { long t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"float")) { float t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"double")) { float t=0; input_file(argc,argv,t); casted=true;}
  if (!casted) throw CImgException("Cannot load image pixel type '%s' (see help)",typei);

  // Return to shell
  std::fprintf(stderr,"Done !\n");
  exit(0);
  return 0;
}
