#include "mrilib.h"
#include "extrema.h"

#define ALLOW_INEDGE

#ifdef ALLOW_INEDGE
#include "mri_intedge.c"  /* Jul 2018 */
#endif

int main( int argc , char * argv[] )
{
   THD_3dim_dataset * inset , * outset ;
   int nx,ny,nz,nxyz,nval , ii,kk , nopt=1, nsum=0 ;
   char * prefix = "edge3" , *insetname=NULL;
   int datum=-1 , verb=0 , do_sd=0, do_sum=0 , do_sqr=0, firstds=0 ;
   float ** sum , fsum, ffac = 0.0;
   float ** sd;
   int border[3]={0,0,0};
   int indims[3]={0,0,0};
   int fscale=0 , gscale=0 , nscale=0 ;
   float filterCoefs[3] = {1.0, 1.0, 1.0};
   recursiveFilterType filterType = ALPHA_DERICHE;
   /* recursiveFilterType filterType = GAUSSIAN_DERICHE; */

   int do_inedge=0 ;  /* Jul 2018 */

   /* [PT: Feb 1, 2022] for automasking, with infl */
   int do_automask = 0;  
   int amask_ndil  = 0; 
   char *auto_tstring = NULL;
   byte *mask_arr = NULL;                      // what mask becomes

   /*-- help? --*/

   if( argc < 2 || strcmp(argv[1],"-help") == 0 ){
      printf(
"Usage: 3dedge3 [options] dset dset ...\n"
 "Does 3D Edge detection using the library 3DEdge by;\n"
 "by Gregoire Malandain (gregoire.malandain@sophia.inria.fr)\n"
 "\n"
 "Options :\n"
 "\n"
 "  -input iii  = Input dataset\n"
 "\n"
 "  -verbose    = Print out some information along the way.\n"
 "\n"
 "  -prefix ppp = Sets the prefix of the output dataset.\n"
 "\n"
 "  -datum ddd  = Sets the datum of the output dataset.\n"
 "\n"
 "  -fscale     = Force scaling of the output to the maximum integer range.\n"
 "  -gscale     = Same as '-fscale', but also forces each output sub-brick to\n"
 "                  to get the same scaling factor.\n"
 "  -nscale     = Don't do any scaling on output to byte or short datasets.\n"
 "  -scale_floats VAL = Multiply input by VAL, but only if the input datum is\n"
 "                      float. This is needed when the input dataset\n"
 "                      has a small range, like 0 to 2.0 for instance.\n"
 "                      With such a range, very few edges are detected due to\n"
 "                      what I suspect to be truncation problems.\n"
 "                      Multiplying such a dataset by 10000 fixes the problem\n"
 "                      and the scaling is undone at the output.\n"
 "\n"
 "  -automask   = For automatic, internal calculation of a mask in the usual\n"
 "                AFNI way. Again, this mask is only applied after all calcs\n"
 "                (so using this does not speed up the calc or affect\n"
 "                distance values).\n"
 "                ** Special note: you can also write '-automask+X', where\n"
 "                X is some integer; this will dilate the initial automask\n"
 "                number of times (as in 3dAllineate); must have X>0.\n"
 "\n"
#if 0
 "\n"
 "  -inedge     = Process the #0 sub-brick like the '-inedge' option of\n"
 "                3dQwarp and write the result out. Mostly for checking\n"
 "                things out, not for any other practical purpose. [Jul 2018]\n"
 "\n"
#endif
 "\n"
 "References for the algorithms:\n"
 " -  Optimal edge detection using recursive filtering\n"
 "    R. Deriche, International Journal of Computer Vision,\n"
 "    pp 167-187, 1987.\n"
 " -  Recursive filtering and edge tracking: two primary tools\n"
 "    for 3-D edge detection, O. Monga, R. Deriche, G. Malandain\n"
 "    and J.-P. Cocquerez, Image and Vision Computing 4:9, \n"
 "    pp 203-214, August 1991.\n"
 "\n"
            ) ;
      PRINT_COMPILE_DATE ; exit(0) ;
   }

   /*-- 20 Apr 2001: addto the arglist, if user wants to [RWCox] --*/

   mainENTRY("3dedge3 main"); machdep() ; PRINT_VERSION("3dedge3") ;

   { int new_argc ; char ** new_argv ;
     addto_args( argc , argv , &new_argc , &new_argv ) ;
     if( new_argv != NULL ){ argc = new_argc ; argv = new_argv ; }
   }

   AFNI_logger("3dedge3",argc,argv) ;

   /*-- command line options --*/

   while( nopt < argc ){


      if( strcmp(argv[nopt],"-prefix") == 0 ){
         if( ++nopt >= argc ){
            fprintf(stderr,
               "** ERROR: need an argument after -prefix!\n"); exit(1);
         }
         prefix = argv[nopt] ;
         nopt++ ; continue ;
      }
      if( strcmp(argv[nopt],"-input") == 0 ){
         if( ++nopt >= argc ){
            fprintf(stderr,"** ERROR: need an argument after -input!\n"); 
            exit(1);
         }
         insetname = argv[nopt] ;
         nopt++ ; continue ;
      }

      if( strcmp(argv[nopt],"-inedge") == 0 ){  /* Jul 2018 */
#ifndef ALLOW_INEDGE
        WARNING_message("-inedge is disabled - ignoring this option :(") ;
#endif
        do_inedge = 1 ; nopt++ ; continue ;
      }
      
      if( strcmp(argv[nopt],"-scale_floats") == 0 ){
         if( ++nopt >= argc ){
            fprintf(stderr,"** ERROR: a factor after -scale_floats!\n"); 
            exit(1);
         }
         ffac = (float)strtod(argv[nopt],NULL) ;
         nopt++ ; continue ;
      }
      
      if( strncmp(argv[nopt],"-verbose",5) == 0 ){
         verb++ ; nopt++ ; continue ;
      }

      if( strcmp(argv[nopt],"-datum") == 0 ){
         if( ++nopt >= argc ){
            fprintf(stderr,"** ERROR: need an argument after -datum!\n"); 
            exit(1);
         }
         if( strcmp(argv[nopt],"short") == 0 ){
            datum = MRI_short ;
         } else if( strcmp(argv[nopt],"float") == 0 ){
            datum = MRI_float ;
         } else if( strcmp(argv[nopt],"byte") == 0 ){
            datum = MRI_byte ;
         } else {
            fprintf(stderr,
                  "** ERROR -datum of type '%s' not supported in 3dedge3!\n",
                    argv[nopt] ) ;
            exit(1) ;
         }
         nopt++ ; continue ;  /* go to next arg */
      }

      if( strncmp(argv[nopt],"-nscale",6) == 0 ){
         gscale = fscale = 0 ; nscale = 1 ;
         nopt++ ; continue ;
      }

      if( strncmp(argv[nopt],"-fscale",6) == 0 ){
         fscale = 1 ; nscale = 0 ;
         nopt++ ; continue ;
      }

      if( strncmp(argv[nopt],"-gscale",6) == 0 ){
         gscale = fscale = 1 ; nscale = 0 ;
         nopt++ ; continue ;
      }

      if( strncmp(argv[nopt],"-automask", 9) == 0 ) {
         do_automask = 1;

         auto_tstring = argv[nopt];
         if ( auto_tstring[9] == '+' && auto_tstring[10] != '\0' ) {
            amask_ndil = (int) strtod(auto_tstring+10, NULL);
            if ( amask_ndil <= 0 )
               ERROR_exit("Cannot have number of dilations (%d) <=0.",
                          amask_ndil);
         }
         
         nopt++ ; continue ;
      }

      fprintf(stderr,"** ERROR: unknown option %s\n",argv[nopt]) ;
      exit(1) ;
   }

   if (!insetname) {
      fprintf(stderr,"** ERROR: no input dset specified\n") ;
      exit(1) ;
   }

   {

      /*-- input dataset header --*/

      inset = THD_open_dataset( insetname ) ;
      if( !ISVALID_DSET(inset) ){
         fprintf(stderr,"** ERROR: can't open dataset %s\n",argv[nopt]) ;
         exit(1) ;
      }

      /*-- make workspace and empty output dataset --*/

      if( nsum == 0 ){

         nx   = DSET_NX(inset) ;
         ny   = DSET_NY(inset) ;
         nz   = DSET_NZ(inset) ; nxyz= nx*ny*nz;
         nval = DSET_NVALS(inset) ;

         sum = (float **) malloc( sizeof(float *)*nval ) ;    
                                          /* array of sub-bricks */
         for( kk=0 ; kk < nval ; kk++ ){
           sum[kk] = (float *) malloc(sizeof(float)*nxyz) ;  
                                             /* kk-th sub-brick */ 
         }

         outset = EDIT_empty_copy( inset ) ;

         if( datum < 0 ) datum = DSET_BRICK_TYPE(inset,0) ;

         tross_Copy_History( inset , outset ) ;
         tross_Make_History( "3dedge3" , argc,argv , outset ) ;

         EDIT_dset_items( outset ,
                             ADN_prefix    , prefix ,
                             ADN_datum_all , datum ,
                          ADN_none ) ;

         if( THD_deathcon() && THD_is_file(outset->dblk->diskptr->header_name) ){
            fprintf(stderr,
                    "*** Output file %s already exists -- cannot continue!\n",
                    outset->dblk->diskptr->header_name ) ;
            exit(1) ;
         }

      } 
      

      /*-- read data from disk --*/

      DSET_mallocize(inset);
      DSET_load(inset) ; CHECK_LOAD_ERROR(inset) ;

      if( verb ) fprintf(stderr,"  ++ read in dataset %s\n",insetname) ;

      /*-- Do the -inedge option now [Jul 2018] --*/


#ifdef ALLOW_INEDGE
      if( do_inedge ){
        MRI_IMAGE *bim = THD_extract_float_brick(0,inset) ;
        if( bim == NULL ) ERROR_exit("-inedge: Cannot get sub-brick #0") ;
        mri_interior_edgeize(bim,4,0.222f) ;

        EDIT_dset_items( outset, ADN_nvals,1 , ADN_datum_all,MRI_float , ADN_none ) ;
        EDIT_substitute_brick(outset, 0, MRI_float, MRI_FLOAT_PTR(bim) ) ;
        DSET_write(outset) ;
        INFO_message("wrote out -inedge dataset: %s",DSET_BRIKNAME(outset)) ;
        exit(0) ;
      }
#endif
      
      indims[0] = DSET_NX(inset);
      indims[1] = DSET_NY(inset);
      indims[2] = DSET_NZ(inset);
      border[0] = 50;
      border[1] = 50;
      border[2] = 50;

      if ( do_automask) {
         int i, j, nvox;
         int nmask= -1; 

         nvox = DSET_NVOX(inset);

         mask_arr = THD_automask( inset );
         if( mask_arr == NULL )
            ERROR_exit("Can't create -automask from input dataset?");

         if( amask_ndil ){
            if( verb )
               INFO_message("Inflate automask %d times", amask_ndil);

            // a very odd recipe for inflating a mask... mirroring
            // default 3dAllineate behavior (slightly different than that
            // of 3dAutomask; commented out)
            for( i=0 ; i < amask_ndil ; i++ ){
               j = THD_mask_dilate( indims[0], indims[1], indims[2], 
                                    mask_arr, 3, 2 );
               j = THD_mask_fillin_once( indims[0], indims[1], indims[2],
                                         mask_arr, 2 );
            }
         }
      
         nmask = THD_countmask( nvox, mask_arr );
         if( verb )
            INFO_message("Number of voxels in automask = %d / %d", nmask, nvox);
         if( nmask < 1 )
            ERROR_exit("Automask is too small to process");
      }

      /*-- Edge detect each sub-brik --*/

      for( kk=0 ; kk < nval ; kk++ ){

         if( verb )
            fprintf(stderr,"   + sub-brick %d [%s]\n",
                    kk,MRI_TYPE_name[DSET_BRICK_TYPE(inset,kk)] ) ;
         if (DSET_BRICK_TYPE(inset,kk) != DSET_BRICK_TYPE(inset,0)) {
            fprintf(stderr,"ERROR: Sub-bricks of different types.\n"
                           "This is not splenda\n") ;
              exit(1) ;
         }
         switch( DSET_BRICK_TYPE(inset,kk) ){
            default:
              fprintf(stderr,"ERROR: illegal input sub-brick datum\n") ;
              exit(1) ;

           case MRI_float:{
             float *pp = (float *) DSET_ARRAY(inset,kk) ;
             float fac = DSET_BRICK_FACTOR(inset,kk)  ;
             if(ffac) {
               if (fac) fac = fac*ffac;
               else fac = ffac;
             }
             if( fac ) {
               for( ii=0 ; ii < nxyz ; ii++ ) { pp[ii] *= fac; }
             }
              if ( Extract_Gradient_Maxima_3D( (void *)pp, FLOAT,
				               sum[kk], FLOAT,
				               indims,
				               border,
				               filterCoefs,
				               filterType ) == 0 ) {
                fprintf( stderr, "ERROR: gradient extraction failed.\n" );
                exit( 1 );
              }
             
           }
           break ;

            case MRI_short:{
               short *pp = (short *) DSET_ARRAY(inset,kk) ;
               float fac = DSET_BRICK_FACTOR(inset,kk)  ;
               
               if( fac && verb) {
                  INFO_message(
                     "Ignoring brick factor of %f for Gradient Extraction", 
                                 fac);
               }
               if (verb) INFO_message("Going to Extract_Gradient_Maxima_3D\n");
               if ( Extract_Gradient_Maxima_3D( (void *)pp, SSHORT,
				               sum[kk], FLOAT,
				               indims,
				               border,
				               filterCoefs,
				               filterType ) == 0 ) {
                fprintf( stderr, "ERROR: gradient extraction failed.\n" );
                exit( 1 );
               }
            }
            break ;

            case MRI_byte:{
               byte *pp = (byte *) DSET_ARRAY(inset,kk) ;
               float fac = DSET_BRICK_FACTOR(inset,kk)  ;
               
               if( fac && verb) {
                  INFO_message(
                     "Ignoring brick factor of %f for Gradient Extraction", 
                                 fac);
               }
               if ( Extract_Gradient_Maxima_3D( (void *)pp, UCHAR,
				               sum[kk], FLOAT,
				               indims,
				               border,
				               filterCoefs,
				               filterType ) == 0 ) {
                fprintf( stderr, "ERROR: gradient extraction failed.\n" );
                exit( 1 );
               }
            }
            break ;
         }
      }

      DSET_delete(inset) ;
   }

      

   switch( datum ){

      default:
         fprintf(stderr,
                 "*** Fatal Error ***\n"
                 "*** Somehow ended up with datum = %d\n",datum) ;
         exit(1) ;

      case MRI_float:{
         for( kk=0 ; kk < nval ; kk++ ){
             EDIT_substitute_brick(outset, kk, MRI_float, sum[kk]);
             /* Undo ffac scaling.*/
             if (ffac != 0.0) {
               DSET_BRICK_FACTOR(outset, kk) = 1.0/ffac;
             } else {
               DSET_BRICK_FACTOR(outset, kk) = 0.0;
             }
         }
      }
      break ;

      case MRI_byte:
      case MRI_short:{
         void ** dfim ;
         float gtop=0.0 , fimfac , gtemp ;

         if( verb )
            fprintf(stderr,"  ++ Scaling output to type %s brick(s)\n",
                    MRI_TYPE_name[datum] ) ;

         dfim = (void **) malloc(sizeof(void *)*nval) ;

         if( gscale ){   /* allow global scaling */
            gtop = 0.0 ;
            for( kk=0 ; kk < nval ; kk++ ){
               gtemp = MCW_vol_amax( nxyz , 1 , 1 , MRI_float, sum[kk] ) ;
               gtop  = MAX( gtop , gtemp ) ;
               if( gtemp == 0.0 )
                  fprintf(stderr,"  -- Warning: output sub-brick %d is all zeros!\n",kk) ;
            }
         }

         for (kk = 0 ; kk < nval ; kk ++ ) {

            if( ! gscale ){
               gtop = MCW_vol_amax( nxyz , 1 , 1 , MRI_float, sum[kk] ) ;
               if( gtop == 0.0 )
                  fprintf(stderr,"  -- Warning: output sub-brick %d is all zeros!\n",kk) ;
            }

            if( fscale ){
               fimfac = (gtop > 0.0) ? MRI_TYPE_maxval[datum] / gtop : 0.0 ;
            } else if( !nscale ){
               fimfac = (gtop > MRI_TYPE_maxval[datum] || (gtop > 0.0 && gtop <= 1.0) )
                        ? MRI_TYPE_maxval[datum]/ gtop : 0.0 ;
            } else {
               fimfac = 0.0 ;
            }

            if( verb ){
               if( fimfac != 0.0 )
                  fprintf(stderr,"  ++ Sub-brick %d scale factor = %f\n",kk,fimfac) ;
               else
                  fprintf(stderr,"  ++ Sub-brick %d: no scale factor\n" ,kk) ;
            }

            dfim[kk] = (void *) malloc( mri_datum_size(datum) * nxyz ) ;
            if( dfim[kk] == NULL ){ fprintf(stderr,"*** malloc fails at output\n");exit(1); }

            EDIT_coerce_scale_type( nxyz , fimfac ,
                                    MRI_float, sum[kk] , datum,dfim[kk] ) ;
            free( sum[kk] ) ;
            EDIT_substitute_brick(outset, kk, datum, dfim[kk] );

            DSET_BRICK_FACTOR(outset,kk) = (fimfac != 0.0) ? 1.0/fimfac : 0.0 ;
          }
      }
      break ;
   }

   if ( mask_arr ) {
      if( verb )
         INFO_message("Apply mask");
      THD_3dim_dataset *dset_tmp = NULL;
      
      dset_tmp = thd_apply_mask( outset, mask_arr, 
                                 prefix );
      DSET_delete( outset ); 
      outset = dset_tmp;
      dset_tmp = NULL;
   }



   if( verb ) fprintf(stderr,"  ++ Computing output statistics\n") ;
   THD_load_statistics( outset ) ;

   THD_write_3dim_dataset( NULL,NULL , outset , True ) ;
   if( verb ) fprintf(stderr,"  ++ Wrote output: %s\n",DSET_BRIKNAME(outset)) ;

   if( mask_arr )
      free(mask_arr);

   exit(0) ;
}
