/*!
  contains code shamelessly stolen from Rick who stole it from Bob. 
*/
SUMA_Boolean SUMA_Get_isosurface_datasets (
                     SUMA_GENERIC_PROG_OPTIONS_STRUCT * Opt)
{
   static char FuncName[]={"SUMA_Get_isosurface_datasets"};
   int i;
   SUMA_Boolean LocalHead = NOPE;
   
   SUMA_ENTRY;
   
   if (!Opt->in_vol && !Opt->in_name) {
      SUMA_S_Err("NULL input");
      SUMA_RETURN(NOPE);
   } 
   if (!Opt->in_vol) { /* open it */
     Opt->in_vol = THD_open_dataset( Opt->in_name );
   }
   if (!Opt->in_vol) {
      SUMA_S_Err("No volume could be had");
      SUMA_RETURN(NOPE);
   } 
   
   if (!ISVALID_DSET(Opt->in_vol)) {
      if (!Opt->in_name) {
         SUMA_SL_Err("NULL input volume.");
         SUMA_RETURN(NOPE);
      } else {
         SUMA_SL_Err("invalid volume.");
         SUMA_RETURN(NOPE);
      }
   } else if ( DSET_BRICK_TYPE(Opt->in_vol, 0) == MRI_complex) {
      SUMA_SL_Err("Can't do complex data.");
      SUMA_RETURN(NOPE);
   }
   
   Opt->nvox = DSET_NVOX( Opt->in_vol );
   if (DSET_NVALS( Opt->in_vol) != 1) {
      SUMA_SL_Err("Input volume can only have one sub-brick in it.\n"
                  "Use [.] selectors to choose sub-brick needed.");
      SUMA_RETURN(NOPE);
   }
   
   
   Opt->mcdatav = (double *)SUMA_malloc(sizeof(double)*Opt->nvox);
   if (!Opt->mcdatav) {
      SUMA_SL_Crit("Failed to allocate for maskv");
      SUMA_RETURN(NOPE);
   }
   if (Opt->xform == SUMA_ISO_XFORM_MASK) {
      SUMA_LH("SUMA_ISO_XFORM_MASK");
      switch (Opt->MaskMode) {
         case SUMA_ISO_CMASK:
            SUMA_LH("SUMA_ISO_CMASK");
            if (Opt->cmask) {
               /* here's the second order grand theft */
               int    clen = strlen( Opt->cmask );
	            char * cmd;
               byte *bmask;

	            cmd = (char *)malloc((clen + 1) * sizeof(char));
	            strcpy( cmd,  Opt->cmask);

	            bmask = EDT_calcmask( cmd, &Opt->ninmask, 0 );
               SUMA_LHv("Have %d\n", Opt->ninmask);
	            free( cmd );			   /* free EDT_calcmask() string */

	            if ( bmask == NULL ) {
	               SUMA_SL_Err("Failed to compute mask from -cmask option");
	               SUMA_free(Opt->mcdatav); Opt->mcdatav=NULL;
                  SUMA_RETURN(NOPE);
	            } 
	            if ( Opt->ninmask != Opt->nvox ) {
	               SUMA_SL_Err("Input and cmask datasets do not have "
		                        "the same dimensions\n" );
	               SUMA_free(Opt->mcdatav); Opt->mcdatav=NULL;
	               SUMA_RETURN(NOPE);
	            }
	            Opt->ninmask = THD_countmask( Opt->ninmask, bmask );
               SUMA_LHv("Have %d\n", Opt->ninmask);
               for (i=0; i<Opt->nvox; ++i) 
                  if (bmask[i]) Opt->mcdatav[i] = (double)bmask[i]; 
                  else Opt->mcdatav[i] = -1;
               free(bmask);bmask=NULL;
            } else {
               SUMA_SL_Err("NULL cmask"); SUMA_RETURN(NOPE);
            }
            break;
         case SUMA_ISO_VAL:
         case SUMA_ISO_RANGE:
            SUMA_LH("SUMA_ISO_VAL, SUMA_ISO_RANGE");
            /* load the dset */
            DSET_load(Opt->in_vol);
            Opt->dvec = (double *)SUMA_malloc(sizeof(double) * Opt->nvox);
            if (!Opt->dvec) {
               SUMA_SL_Crit("Faile to allocate for dvec.\nOh misery.");
               SUMA_RETURN(NOPE);
            }
            EDIT_coerce_scale_type( 
               Opt->nvox , DSET_BRICK_FACTOR(Opt->in_vol,0) ,
               DSET_BRICK_TYPE(Opt->in_vol,0), DSET_ARRAY(Opt->in_vol, 0) , 
               MRI_double               , Opt->dvec  ) ;  
            /* no need for data in input volume anymore */
            PURGE_DSET(Opt->in_vol);

            Opt->ninmask = 0;
            if (Opt->MaskMode == SUMA_ISO_VAL) {
               for (i=0; i<Opt->nvox; ++i) {
                  if (Opt->dvec[i] == Opt->v0) { 
                     Opt->mcdatav[i] = 1; ++ Opt->ninmask; 
                  }  else Opt->mcdatav[i] = -1;
               }
            } else if (Opt->MaskMode == SUMA_ISO_RANGE) {
               for (i=0; i<Opt->nvox; ++i) {
                  if (Opt->dvec[i] >= Opt->v0 && Opt->dvec[i] < Opt->v1) { 
                     Opt->mcdatav[i] = 1; ++ Opt->ninmask; 
                  }  else Opt->mcdatav[i] = -1;
               }
            } else {
               SUMA_SL_Err("Bad Miracle.");
               SUMA_RETURN(NOPE);
            }
            SUMA_free(Opt->dvec); Opt->dvec = NULL; 
                  /* this vector is not even created in SUMA_ISO_CMASK mode ...*/
            break;
         default:
            SUMA_SL_Err("Unexpected value of MaskMode");
            SUMA_RETURN(NOPE);
            break;   
      }
   } else if (Opt->xform == SUMA_ISO_XFORM_SHIFT) {
      /* load the dset */
      DSET_load(Opt->in_vol);
      Opt->dvec = (double *)SUMA_malloc(sizeof(double) * Opt->nvox);
      if (!Opt->dvec) {
         SUMA_SL_Crit("Failed to allocate for dvec.\nOh misery.");
         SUMA_RETURN(NOPE);
      }
      EDIT_coerce_scale_type( 
         Opt->nvox , DSET_BRICK_FACTOR(Opt->in_vol,0) ,
         DSET_BRICK_TYPE(Opt->in_vol,0), DSET_ARRAY(Opt->in_vol, 0) ,      
         MRI_double               , Opt->dvec  ) ;   
      /* no need for data in input volume anymore */
      PURGE_DSET(Opt->in_vol);
      Opt->ninmask = Opt->nvox;
      for (i=0; i<Opt->nvox; ++i) { 
         Opt->mcdatav[i] = Opt->dvec[i] - Opt->v0;
      }
      SUMA_free(Opt->dvec); Opt->dvec = NULL; 
   } else if (Opt->xform == SUMA_ISO_XFORM_NONE) {
      /* load the dset */
      DSET_load(Opt->in_vol);
      Opt->dvec = (double *)SUMA_malloc(sizeof(double) * Opt->nvox);
      if (!Opt->dvec) {
         SUMA_SL_Crit("Faile to allocate for dvec.\nOh misery.");
         SUMA_RETURN(NOPE);
      }
      EDIT_coerce_scale_type( 
         Opt->nvox , DSET_BRICK_FACTOR(Opt->in_vol,0) ,
         DSET_BRICK_TYPE(Opt->in_vol,0), DSET_ARRAY(Opt->in_vol, 0) , 
         MRI_double               , Opt->dvec  ) ;   
      /* no need for data in input volume anymore */
      PURGE_DSET(Opt->in_vol);
      Opt->ninmask = Opt->nvox;
      for (i=0; i<Opt->nvox; ++i) { 
         Opt->mcdatav[i] = Opt->dvec[i];
      }
      SUMA_free(Opt->dvec); Opt->dvec = NULL; 
   } else {
      SUMA_SL_Err("Bad Opt->xform.");
      SUMA_RETURN(NOPE);
   }
   
   if ( Opt->ninmask  <= 0 ) {
	   if (Opt->ninmask == 0) {
         SUMA_SL_Err("A negative value!\nNothing to do." );
      } else {
         SUMA_SL_Err("An empty mask!\n Nothing to do." );
	   }
      SUMA_RETURN(NOPE);
	}
   
   if (Opt->debug > 0) {
      fprintf( SUMA_STDERR, 
               "%s:\nInput dset %s has nvox = %d, nvals = %d",
		         FuncName, SUMA_CHECK_NULL_STR(Opt->in_name), 
               Opt->nvox, DSET_NVALS(Opt->in_vol) );
	   fprintf( SUMA_STDERR, " (%d voxels in mask)\n", Opt->ninmask );
   }
   
   SUMA_RETURN(YUP);
}
int main (int argc,char *argv[])
{   /* Main */
    static char FuncName[]= {"SurfQual"};
    char *OutName = NULL, ext[5], *prefix = NULL, *shist=NULL;
    SUMA_SURFQUAL_OPTIONS *Opt;
    int SO_read = -1;
    int i, cnt, trouble, consistent = -1, eu = -1, nsi = -1, N_Spec=0;
    SUMA_SurfaceObject *SO = NULL;
    SUMA_SurfSpecFile *Spec=NULL;
    void *SO_name = NULL;
    SUMA_Boolean DoConv = NOPE, DoSphQ = NOPE, DoSelfInt = NOPE;
    int N_bad_nodes, N_bad_facesets;
    SUMA_GENERIC_ARGV_PARSE *ps=NULL;
    SUMA_Boolean LocalHead = NOPE;

    SUMA_STANDALONE_INIT;
    SUMA_mainENTRY;

    ps = SUMA_Parse_IO_Args(argc, argv, "-i;-t;-spec;-s;-sv;");

    /* Allocate space for DO structure */
    SUMAg_DOv = SUMA_Alloc_DisplayObject_Struct (SUMA_MAX_DISPLAYABLE_OBJECTS);

    Opt = SUMA_SurfQual_ParseInput (argv, argc, ps);
    if (argc < 2)
    {
        SUMA_S_Err("Too few options");
        usage_SUMA_SurfQual(ps, 0);
        exit (1);
    }

    /* read all surfaces */
    Spec = SUMA_IO_args_2_spec(ps, &N_Spec);
    if (N_Spec == 0) {
        SUMA_S_Err("No surfaces found.");
        exit(1);
    }

    if (N_Spec > 1 ) {
        SUMA_S_Err( "Mike, you cannot mix -spec with -i or -t options "
                    "for specifying surfaces.");
        exit(1);
    }

    if (Spec->N_Surfs < 1) {
        SUMA_S_Err("No surfaces");
        exit(1);
    }

    if (Opt->self_intersect) DoSelfInt = YUP;

    DoConv = NOPE;
    DoSphQ = NOPE;
    if (Opt->surftype) {
        if (!strcmp(Opt->surftype, "-sphere")) {
            DoSphQ = YUP;
        } else {
            /* Don't complain anymore, maybe winding checking is all users need */
        }
    }

    for (i=0; i < Spec->N_Surfs; ++i) {/* loop to read in surfaces */
        /* now identify surface needed */
        if (!(SO = SUMA_Load_Spec_Surf_with_Metrics(Spec, i, ps->sv[0], 0))) {
            SUMA_S_Err("Failed to load surface .\n");
            exit(1);
        }
        fprintf(SUMA_STDERR,"\nReport for Surface %s\n", SO->Label);
        /* do the quality thing based on the Opt->surftype */
        if (!Opt->out_prefix) prefix = SUMA_copy_string(SO->Label);
        else prefix = SUMA_copy_string (Opt->out_prefix);

        /* check the winding */
        if (!SUMA_MakeConsistent (SO->FaceSetList, SO->N_FaceSet,
                                  SO->EL, 0, &trouble)) {
            SUMA_S_Warn(
                "Failed to make sure surface's mesh is consistently wound.\n"
                "You should fix the mesh.\n");
            consistent = 0;
        }

        {
            int iii=0, isbad=0, ht0;
            int *badedge=(int*)SUMA_calloc(SO->N_Node, sizeof(int));
            /* check on troubled edges */
            while (iii < SO->EL->N_EL) {
                ht0 = SO->EL->ELps[iii][1];
                /* make sure edge is not part of three triangles, if it is,skip it*/
                if (SO->EL->ELps[iii][2] > 2) {
                    ++iii;
                    fprintf( SUMA_STDERR,
                             "%s: Bad edge (#%d: %d--%d), \n"
                             " part of more than 2 triangles, skip it\n",
                             FuncName, i, SO->EL->EL[iii][0], SO->EL->EL[iii][1]);
                    ++badedge[SO->EL->EL[iii][0]];
                    ++badedge[SO->EL->EL[iii][1]];
                    isbad = 1;
                    continue;
                }
                ++iii;
            }
            if (isbad) {
                if (Spec->N_Surfs > 1) {
                    sprintf(ext,"_%c", 65+i);
                    OutName = SUMA_append_replace_string (
                                  prefix,
                                  "_BadEdgeNodes.1D.dset",
                                  ext, 0);
                } else {
                    OutName = SUMA_append_string (prefix, "_BadEdgeNodes.1D.dset");
                }
                SUMA_WRITE_ARRAY_1D(badedge,SO->N_Node,1,OutName);
                if (OutName) SUMA_free(OutName);
                OutName = NULL;
            }
            SUMA_free(badedge);
            badedge = NULL;
        }

        if (DoConv) {
            float *Cx = NULL;
            if (Spec->N_Surfs > 1) {
                sprintf(ext,"_%c", 65+i);
                OutName = SUMA_append_replace_string
                          (prefix, "_Conv_detail.1D.dset", ext, 0);
            } else {
                OutName = SUMA_append_string (prefix, "_Conv_detail.1D.dset");
            }
            Cx = SUMA_Convexity_Engine ( SO->NodeList, SO->N_Node,
                                         SO->NodeNormList, SO->FN, OutName, NULL);
            if (Cx) SUMA_free(Cx);
            Cx = NULL;
            if (OutName) SUMA_free(OutName);
            OutName = NULL;
        }

        if (DoSphQ) {
            if (Spec->N_Surfs > 1) {
                sprintf(ext,"_%c", 65+i);
                OutName = SUMA_append_string (prefix, ext);
            } else {
                OutName = SUMA_copy_string (prefix);
            }
            shist = SUMA_HistString (NULL, argc, argv, NULL);
            SUMA_SphereQuality (SO, OutName, shist, &N_bad_nodes, &N_bad_facesets);
            if (shist) SUMA_free(shist);
            shist = NULL;
            if (OutName) SUMA_free(OutName);
            OutName = NULL;
        }

        if (trouble) { /* put winding problem here to make it visible */
            fprintf (SUMA_STDERR,"\n");
            SUMA_S_Warn(
                "Mesh is not consistent, use ConvertSurface's -make_consistent \n"
                "option to fix the problem before proceeding further.\n"
                "Other results reported by this and other programs\n"
                "may be incorrect if mesh is not consistently wound.\n" );
            consistent = 0;
        } else {
            consistent = 1;
            fprintf (SUMA_STDERR,"\n");
            fprintf (SUMA_STDERR,"Surface is consistently wound\n");
        }
        {
            SUMA_EULER_SO(SO, eu);
            fprintf (SUMA_STDERR,"\n");
            fprintf(SUMA_STDERR,"Surface Euler Characteristic is: %d\n", eu);
        }
        if ((SO->EL->min_N_Hosts == 1 || SO->EL->max_N_Hosts == 1)) {
            fprintf (SUMA_STDERR,"\n");
            fprintf(SUMA_STDERR,
                    "Warning %s:\n"
                    " Min/Max number of edge hosting triangles: [%d/%d] \n",
                    FuncName, SO->EL->min_N_Hosts, SO->EL->max_N_Hosts);
            fprintf( SUMA_STDERR,
                     " You have edges that form a border in the surface.\n");
        }
        if (SO->EL->min_N_Hosts == 2 && SO->EL->max_N_Hosts == 2) {
            fprintf (SUMA_STDERR,"\n");
            fprintf(SUMA_STDERR,"Surface is closed and is a 2-manifold.");
        }
        if (SO->EL->min_N_Hosts > 2 || SO->EL->max_N_Hosts > 2) {
            fprintf (SUMA_STDERR,"\n");
            fprintf( SUMA_STDERR,
                     "Warning %s:\n"
                     "Min/Max number of edge hosting triangles: [%d/%d] \n",
                     FuncName, SO->EL->min_N_Hosts, SO->EL->max_N_Hosts);
            fprintf(SUMA_STDERR,
                    "Warning %s:\n"
                    " You have edges that belong to more than two triangles.\n"
                    " Bad for analysis assuming surface is a 2-manifold.\n",
                    FuncName);
            if (1) {
                int iii=0;
                fprintf( SUMA_STDERR,
                         " These edges are formed by the following nodes:\n");
                for (iii = 0; iii < SO->EL->N_EL; ++iii) {
                    if (SO->EL->ELps[iii][2] > 2)
                        fprintf (SUMA_STDERR,
                                 " %d: Edge [%d %d] shared by %d triangles.\n",
                                 iii+1, SO->EL->EL[iii][0], SO->EL->EL[iii][1] ,
                                 SO->EL->ELps[iii][2] );
                }
            }
        }

        if (DoSelfInt) {
            int iii;
            FILE *fout=NULL;
            byte *report = (byte *)SUMA_calloc(SO->N_Node, sizeof(byte));
            if (!report) {
                SUMA_SL_Crit("Failed to allocate for report");
                report = NULL;
            }
            fprintf( SUMA_STDERR, "\n\nChecking for intersections...:\n");
            nsi = SUMA_isSelfIntersect(SO, 500, report);
            if (nsi) {
                fprintf( SUMA_STDERR,
                         " Surface is self intersecting.\n"
                         "%d segments were found to intersect the surface.\n", nsi);
                if (nsi >= 500) {
                    fprintf( SUMA_STDERR,
                             " It is possible that you have additional segments"
                             " intersecting the surface.\n");
                }
                if (report) {
                    if (Spec->N_Surfs > 1) {
                        sprintf(ext,"_%c", 65+i);
                        OutName = SUMA_append_replace_string ( prefix,
                                                               "_IntersNodes.1D.dset",
                                                               ext, 0);
                    } else {
                        OutName = SUMA_append_string (prefix, "_IntersNodes.1D.dset");
                    }
                    fout = fopen(OutName, "w");
                    if (fout) {
                        fprintf(fout,
                                "#List of nodes that are part of segments which intersect "
                                "the surface\n"
                                "#%s\n"
                                "#A total of %d segments (search limit is 500) were found to "
                                "intersect the surface.\n"
                                "#Col.1 : Node index\n"
                                "#Col.2 : Dummy flag, always 1\n",
                                SUMA_CHECK_NULL_STR(SO->Label), nsi );
                        for (iii=0; iii<SO->N_Node; ++iii)
                            if (report[iii]) fprintf(fout, "%d\t1\n", iii);
                        fclose(fout);
                        fout = NULL;
                    } else {
                        SUMA_SL_Err("Failed to open file for output.");
                    }
                    if (OutName) SUMA_free(OutName);
                }
            } else {
                fprintf(SUMA_STDERR, " Surface is not self intersecting.\n");
            }
            if (report) SUMA_free(report);
            report = NULL;
        }

        fprintf (SUMA_STDERR,"\n");

        if (Opt->DoSum) {   /* do not change syntax, scripts depend on this */
            fprintf(stdout,"Summary for %s:\n", SO->Label);
            fprintf(stdout,"Euler_Charac. %d\n", eu);
            fprintf(stdout,"Consistent_Winding %d\n", consistent);
            if (DoSphQ) {
                fprintf(stdout,"Folding_Triangles %d\n", N_bad_facesets);
                fprintf(stdout,"Sketchy_nodes %d\n", N_bad_nodes);
            }
            if (DoSelfInt)
                fprintf(stdout,"Self_Intersections %d\n", nsi);
            fprintf(stdout,"\n");
        }

    }



    SUMA_LH("clean up");
    if (!SUMA_FreeSpecFields(Spec)) {
        SUMA_S_Err("Failed to free spec fields");
    }
    SUMA_free(Spec);
    Spec = NULL;
    if (prefix) SUMA_free(prefix);
    prefix = NULL;
    if (Opt->out_prefix) SUMA_free(Opt->out_prefix);
    Opt->out_prefix = NULL;
    if (Opt) SUMA_free(Opt);
    if (!SUMA_Free_Displayable_Object_Vect (SUMAg_DOv, SUMAg_N_DOv)) {
        SUMA_SL_Err("DO Cleanup Failed!");
    }
    if (!SUMA_Free_CommonFields(SUMAg_CF))
        SUMA_error_message(FuncName,"SUMAg_CF Cleanup Failed!",1);

    SUMA_RETURN(0);
}