/*****************************************************************************
   Major portions of this software are copyrighted by the Medical College
   of Wisconsin, 1994-2000, and are released under the Gnu General Public
   License, Version 2.  See the file README.Copyright for details.
******************************************************************************/

#include "mrilib.h"

#define MAXIM 1024
int main( int argc , char * argv[] )
{
   int nim , ii , jj , kk , nx, narg, oform ;
   MRI_IMAGE **inim ;  int num_inim=0 ;
   NI_element **inel ; int num_inel=0 ;
   int first_nx=0 ;
   float *far;

   char *formatstr=NULL, *sel=NULL, *fname=NULL, *qname=NULL , *qpt ;
   int nonconst=0 , ncol,ncold , cc , nonfixed=0 , stack=0, okempty=0;
   intvec *ncv=NULL ;
   char *hline=NULL ;
   int do_tsvout = 0 , do_csvout = 0 ;
   static struct stat buf;

   /*-- paperwork --*/

   mainENTRY("1dcat:main");

   /*-- help? --*/

   if( argc < 2 || strcmp(argv[1],"-help") == 0 ){
     printf(
"\n"
"Usage: 1dcat [options] a.1D b.1D ...\n"
"\n"
"  where each file a.1D, b.1D, etc. is a 1D file.\n"
"  In the simplest form, a 1D file is an ASCII file of numbers\n"
"  arranged in rows and columns.\n"
"\n"
"1dcat takes as input one or more 1D files, and writes out a 1D file\n"
"containing the side-by-side concatenation of all or a subset of the\n"
"columns from the input files.\n"
"\n"
"* Output goes to stdout (the screen); redirect (e.g., '>') to save elsewhere.\n"
"* All files MUST have the same number of rows!\n"
"* Any header lines (i.e., lines that start with '#') will be lost.\n"
"* For generic 1D file usage help and information, see '1dplot -help'\n"
"\n"
"-----------\n"
" TSV files: [Sep 2018]\n"
"-----------\n"
"* 1dcat can now also read .tsv files, which are columns of values separated\n"
"  by tab characters (tsv = tab separated values). The first row of a .tsv \n"
"  file is a set of column labels. After the header row, each column is either\n"
"  all numbers, or is a column of strings. For example\n"
"     Col 1	Col 2	Col 3\n"
"     3.2	7.2	Elvis\n"
"     8.2	-1.2	Sinatra\n"
"     6.66 	33.3	20892\n"
"  In this example, the column labels contain spaces, which are NOT separators;\n"
"  the only column separator used in a .tsv file is the tab character.\n"
"  The first and second columns are converted to number columns, since every\n"
"  value (after the label/header row) is a numeric string. The third column\n"
"  is stored as strings, since some of the entries are not valid numbers.\n"
"\n"
"* 1dcat can deal with a mix of .1D and .tsv files. The .tsv file header\n"
"  rows are NOT output by default, since .1D files don't have such headers.\n"
"\n"
"* The usual output from 1dcat is NOT a .tsv file - blanks are used for\n"
"  separators. You can use the '-tsvout' option to get TSV formatted output.\n"
"\n"
"* If you mix .1D and .tsv files, the number of data rows in each file\n"
"  must be the same. Since the header row in a .tsv file is NOT used here,\n"
"  the total number of lines in a .tsv file must be 1 more than the number\n"
"  of lines in a .1D file for the two files to match in this program.\n"
"\n"
"* The purpose of supporting .tsv files is for eventual compatibility with\n"
"  the BIDS format http://bids.neuroimaging.io - which uses .tsv files\n"
"  extensively to provide auxiliary information for (F)MRI datasets.\n"
"\n"
"* Column selectors (like '[0,3]') can be used on .tsv files, but row selectors\n"
"  (like '{0,3..5}') cannot be used on .tsv files - at this time :(\n"
"\n"
"* You can also select a column in a .tsv file by using the label at the top of\n"
"  of the column. A BIDS-related example:\n"
"    1dcat sub-666_task-XXX_events.tsv'[onset,duration,trial_type,reaction_time]'\n"
"  A similar example, which outputs a list of the trial types in an imaging run:\n"
"    1dcat sub-666_task-XXX_events.tsv'[trial_type]' | sort | uniq\n"
"\n"
"* Since .1D files don't have headers, the label method of column selection\n"
"  doesn't work with such inputs; you must use integer column selectors\n"
"  on .1D files.\n"
"\n"
"* NOTE WELL: The string 'N/A' or 'n/a' in a column that is otherwise numeric\n"
"             will be considered to be a number, and will be replaced on input\n"
"             with the mean of the \"true\" numbers in the column -- there is\n"
"             no concept of missing data in an AFNI .1D file.\n"
"           ++ If you don't like this, well ... too bad for you.\n"
"\n"
"* NOTE WELL: 1dcat now also allows comma separated value (.csv) files. These\n"
"             are treated the same as .tsv files, with a header line, et cetera.\n"
"\n"
"--------\n"
"OPTIONS:\n"
"--------\n"
"  -tsvout   = Output in a TSV (.tsv) format, where the values in each row\n"
"              are separated by tabs, not blanks. Also, a header line will\n"
"              be provided, as TSV files require.\n"
"\n"
"  -csvout   = Output in a CSV (.csv) format, where the values in each row\n"
"              are separated by commas, not blanks. Also, a header line will\n"
"              be provided, as CSV files require.\n"
"\n"
"  -nonconst = Columns that are identically constant should be omitted\n"
"              from the output.\n"
"\n"
"  -nonfixed = Keep only columns that are marked as 'free' in the \n"
"              3dAllineate header from '-1Dparam_save'.\n"
"              If there is no such header, all columns are kept.\n"
"           * NOTE: -nconst and -nonfixed don't have any effect on\n"
"                   .tsv/.csv files, and the use of these options\n"
"                   has NOT been tested at all when the inputs\n"
"                   are mixture of .tsv/.csv and .1D files.\n"
"\n"
"  -form FORM = Format of the numbers to be output.\n"
"               You can also substitute -form FORM with shortcuts such \n"
"               as -i, -f, or -c.\n"
"               For help on -form's usage, and its shortcut versions\n"
"               see ccalc's help for the option of the same name. \n"
"\n"
"  -stack = Stack the columns of the resultant matrix in the output.\n"
"             You can't use '-stack' with .tsv/.csv files :(\n"
"\n"
"  -sel SEL = Apply the same column/row selection string to all filenames\n"
"             on the command line.\n"
"             For example:\n"
"               1dcat -sel '[0,2]' f1.1D f2.1D\n"
"             is the same as: 1dcat f1.1D'[1,2]' f2.1D'[1,2]'\n"
"             The advantage of the option is that it allows wildcard use\n"
"             in file specification so that you can run something like:\n"
"               1dcat -sel '[0,2]' f?.1D\n"
"\n"
"  -OKempty: Exit quietly when encountering an empty file on disk.\n"
"            Note that if the file is poorly formatted, it might be\n"
"            considered empty.\n"
"\n"
"EXAMPLE:\n"
"--------\n"
"  Input file 1:\n   1\n   2\n   3\n   4\n"
"  Input file 2:\n   5\n   6\n   7\n   8\n"
"\n"
"  1dcat data1.1D data2.1D > catout.1D\n"
"  Output file: \n   1 5\n   2 6\n   3 7\n   4 8\n"
"\n"
           ) ;
      PRINT_COMPILE_DATE ; exit(0) ;
   }

   machdep() ;

   /* do we have any options? */
   oform = CCALC_NOT_SET;
   sel = NULL;
   stack = 0;
   okempty = 0;
   narg = 1;
   while (narg < argc && argv[narg][0] == '-') {

      if( strcmp(argv[narg],"-tsvout") == 0 ){  /* 13 Sep 2018 */
        do_tsvout = 1 ; do_csvout = 0 ; narg++ ; continue ;
      }

      if( strcmp(argv[narg],"-csvout") == 0 ){  /* 15 Apr 2019 */
        do_csvout = 1 ; do_tsvout = 0 ; narg++ ; continue ;
      }

      if( strncmp(argv[narg],"-nonconst",7) == 0 ){  /* 04 Dec 2010 */
        nonconst++ ; narg++ ; continue ;
      }

      if( strncmp(argv[narg],"-nonfixed",7) == 0 ){  /* 06 Dec 2010 */
        nonfixed++ ; narg++ ; continue ;
      }
 
      if( strncmp(argv[narg],"-OKempty",7) == 0 ){  /* March 2017 */
        okempty++ ; narg++ ; continue ;
      }

      if( strncmp(argv[narg],"-stack",6) == 0 ){  /* 05 Sep 2013 */
        stack = 1 ; narg++ ; continue ;
      }

      if (strcmp(argv[narg],"-form") == 0) {
         ++narg;
         if (narg >= argc) ERROR_exit("need argument after -form") ;
         if (strcmp(argv[narg],"double") == 0 ) oform = CCALC_DOUBLE;
         else if (strcmp(argv[narg],"nice") == 0 ) oform = CCALC_NICE;
         else if (strcmp(argv[narg],"int") == 0 ) oform = CCALC_INT;
         else if (strcmp(argv[narg],"rint") == 0 ) oform = CCALC_INT;
         else if (strcmp(argv[narg],"fint") == 0 ) oform = CCALC_FINT;
         else if (strcmp(argv[narg],"cint") == 0 ) oform = CCALC_CINT;
         else if (strlen(argv[narg])<=256) {
            oform = CCALC_CUSTOM;
            formatstr = argv[narg];
         }
         else {
            ERROR_exit("Format type '%s' not supported :(",argv[narg]) ;
         }
         ++narg;
      } else if (strcmp(argv[narg],"-sel") == 0) {
         ++narg;
         if (narg >= argc)  {
           ERROR_exit("need argument after -sel ");
         }
         sel = argv[narg]; ++narg;
      } else if (strncmp(argv[narg],"-d",2) == 0) {
         oform = CCALC_DOUBLE; ++narg;
      } else if (strncmp(argv[narg],"-n",2) == 0) {
         oform = CCALC_NICE; ++narg;
      } else if (strncmp(argv[narg],"-i",2) == 0) {
         oform = CCALC_INT; ++narg;
      } else if (strncmp(argv[narg],"-r",2) == 0) {
         oform = CCALC_INT; ++narg;
      } else if (strncmp(argv[narg],"-f",2) == 0) {
         oform = CCALC_FINT; ++narg;
      } else if (strncmp(argv[narg],"-c",2) == 0) {
         oform = CCALC_CINT; ++narg;
      } else { /* break if option is not recognized */
         ++narg;
         break;
      }
   }

   /* read input files */

   nim  = argc-narg ;
   inim = (MRI_IMAGE **)  malloc( sizeof(MRI_IMAGE *)  * nim ) ;
   inel = (NI_element **) malloc( sizeof(NI_element *) * nim ) ;
   ncol = 0 ;
   if( nonconst || nonfixed ) MAKE_intvec(ncv,1) ;
   for( jj=0 ; jj < nim ; jj++ ){

#if 0                                   /** for testing only **/
      if( AFNI_yesenv("ragged") ){
        MRI_IMAGE *qim ;
        qim      = mri_read_ascii_ragged( argv[jj+narg] , 3.e+33 ) ;
        fprintf(stderr,"qim: nx=%d ny=%d\n",qim->nx,qim->ny) ;
        inim[jj] = mri_transpose(qim) ; mri_free(qim) ;
      } else
#endif

      if (sel) {
         fname = (char *)
                  calloc((strlen(argv[jj+narg])+strlen(sel)+1), sizeof(char));
         strcat(fname, argv[jj+narg]); strcat(fname, sel);
      } else {
         fname = argv[jj+narg];
      }

      ncold = ncol ;

      qname = strdup(fname) ;
      qpt   = strchr(qname,'[') ; if( qpt != NULL ) *qpt = '\0' ;
      qpt   = strchr(qname,'{') ; if( qpt != NULL ) *qpt = '\0' ;

      if( STRING_HAS_SUFFIX(qname,".tsv") ){  /* Sep 2018 */
        inel[jj] = THD_read_tsv(fname) ; inim[jj] = NULL ;
        if( inel[jj] == NULL )
          ERROR_exit("Can't read input file '%s'",fname) ;
        if( jj == 0 ) first_nx = inel[jj]->vec_len ;
        else if( inel[jj]->vec_len != first_nx )
          ERROR_exit("Input file %s doesn't match first file %s in length!",
                     fname,argv[narg]) ;
        ncol += inel[jj]->vec_num ; num_inel++ ;

      } else if( STRING_HAS_SUFFIX(qname,".csv") ){  /* Apr 2019 */
        inel[jj] = THD_read_csv(fname) ; inim[jj] = NULL ;
        if( inel[jj] == NULL )
          ERROR_exit("Can't read input file '%s'",fname) ;
        if( jj == 0 ) first_nx = inel[jj]->vec_len ;
        else if( inel[jj]->vec_len != first_nx )
          ERROR_exit("Input file %s doesn't match first file %s in length!",
                     fname,argv[narg]) ;
        ncol += inel[jj]->vec_num ; num_inel++ ;

#if 0
fprintf(stderr,"%s : ",fname) ;
for( kk=0; kk < inel[jj]->vec_num ; kk++ )
  fprintf(stderr," %d",inel[jj]->vec_typ[kk]) ;
fprintf(stderr,"\n") ;
#endif

      } else {  /* standard read of 1D file */
        inim[jj] = mri_read_1D( fname ) ; inel[jj] = NULL ;
        if( inim[jj] == NULL ) {
            if ( ! okempty || stat(qname, &buf)) {
               ERROR_exit("Can't read input file '%s'",fname) ;
            }
            /* continuing will cause trouble, exit politely now */
            exit(0);
        }
        if( jj == 0 ) first_nx = inim[jj]->nx ;
        else if( inim[jj]->nx != first_nx )
          ERROR_exit("Input file %s doesn't match first file %s in length!",
                     fname,argv[narg]) ;
        ncol += inim[jj]->ny ; num_inim++ ;
      }

      if( ncv != NULL && inim[jj] != NULL ){  /* check for constant columns [04 Dec 2010] */
        RESIZE_intvec(ncv,ncol) ;
        for( kk=0 ; kk < inim[jj]->ny ; kk++ ) ncv->ar[ncold+kk] = 1 ;
        far = MRI_FLOAT_PTR(inim[jj]) ; nx = inim[jj]->nx ;
        if( nonconst ){
          for( kk=0 ; kk < inim[jj]->ny ; kk++ ){ /* loop over columns */
            for( ii=1 ; ii < nx ; ii++ ){         /* loop down column */
              if( far[ii+kk*nx] != far[kk*nx] ) break ;
            }
            if( ii == nx ) ncv->ar[ncold+kk] = 0 ; /* constant */
          }
        }
        if( nonfixed ){
          char *hl = mri_read_1D_headerlines( fname ) ;
          if( hl != NULL && *hl == '#' ){
            char *spt = strchr(hl,'\n') ;
            if( spt != NULL ) spt = strchr(spt,'#') ; /* start of line 2 */
            if( spt != NULL ){
              NI_str_array *sar = NI_decode_string_list( spt+1 , "~" ) ;
              if( sar != NULL && sar->num >= inim[jj]->ny ){
                for( kk=0 ; kk < inim[jj]->ny ; kk++ ){
                  spt = strchr(sar->str[kk],'$') ;
                  if( spt != NULL && spt[1] == '\0' ) ncv->ar[ncold+kk] = 0 ;
                  else {
                    if( hline == NULL ) hline = strdup("#") ;
                    hline = THD_zzprintf( hline , " %s" , sar->str[kk] ) ;
                  }
                }
              }
              NI_delete_str_array(sar) ;
            }
          }
        }
      } /* end of computing ncv = array marking non-constant vectors */
      if (sel) {
         free(fname); fname = NULL;
      }
      if( qname != NULL ){ free(qname) ; qname = NULL ; }
   } /* end of input loop */

   if( stack && num_inel > 0 )
     ERROR_exit("-stack and .tsv/.csv inputs are not compatible at this time :(") ;
   if( stack && do_tsvout )
     ERROR_exit("-stack and -tsvout are not compatible at this time :(") ;
   if( stack && do_csvout )
     ERROR_exit("-stack and -csvout are not compatible at this time :(") ;

   /* now do the output */

   if( hline != NULL ) printf("%s\n",hline) ;

   nx = first_nx ;

   if (stack) {    /* no TSV files are here at this time coordinate */
      if (oform == CCALC_NOT_SET) {
         for( cc=jj=0 ; jj < nim ; jj++ ){
            far = MRI_FLOAT_PTR(inim[jj]) ;
            for( kk=0 ; kk < inim[jj]->ny ; kk++,cc++ ){
               for( ii=0 ; ii < nx ; ii++ ){
                  if( ncv == NULL || ncv->ar[cc] )
                    printf(" %g\n", far[ii+kk*nx] ) ;
               }
            }
         }
      } else {
         for( cc=jj=0 ; jj < nim ; jj++ ){
            far = MRI_FLOAT_PTR(inim[jj]) ;
            for( kk=0 ; kk < inim[jj]->ny ; kk++,cc++ ){
               for( ii=0 ; ii < nx ; ii++ ){
                  if( ncv == NULL || ncv->ar[cc] )
                    printf(" %s\n",
                        format_value_4print(far[ii+kk*nx], oform, formatstr ));
               }
            }
         }
      }
   } else {  /* not stacked, might have TSV files */
      char sep ; char ssep[2] ; int notfirst ;
      sep =  (do_tsvout) ? '\t'
           : (do_csvout) ? ','
           :               ' ' ;
      ssep[1] = '\0' ;
#define SEPCHAR ( (notfirst++) ? sep : '\0' )
      if( do_tsvout || do_csvout ){  /* write a header line */
        for( notfirst=cc=jj=0 ; jj < nim ; jj++ ){
          if( inim[jj] != NULL ){  /* make up .1D header */
            for( kk=0 ; kk < inim[jj]->ny ; kk++,cc++ ){
              ssep[0] = SEPCHAR ;
              printf("%sCol#%d",ssep,cc) ;
            }
          } else {                 /* TSV/CSV file header */
            for( kk=0 ; kk < inel[jj]->vec_num ; kk++,cc++ ){
              ssep[0] = SEPCHAR ;
              printf("%s%s",ssep,inel[jj]->vec_lab[kk]) ;
            }
          }
        }
        printf("\n") ;
      }
      if (oform == CCALC_NOT_SET) {
         for( ii=0 ; ii < nx ; ii++ ){
            for( notfirst=cc=jj=0 ; jj < nim ; jj++ ){
               if( inim[jj] != NULL ){
                 far = MRI_FLOAT_PTR(inim[jj]) ;
                 for( kk=0 ; kk < inim[jj]->ny ; kk++,cc++ ){
                    if( ncv == NULL || ncv->ar[cc] ){
                      ssep[0] = SEPCHAR ;
                      printf("%s%g", ssep,far[ii+kk*nx] ) ;
                    }
                 }
               } else {  /* write inel instead */
                 for( kk=0 ; kk < inel[jj]->vec_num ; kk++,cc++ ){
                   ssep[0] = SEPCHAR ;
                   if( inel[jj]->vec_typ[kk] == NI_FLOAT ){
                     far = (float *)inel[jj]->vec[kk] ;
                     printf("%s%g",ssep,far[ii]) ;
                   } else if( inel[jj]->vec_typ[kk] == NI_STRING ){
                     char **cpt = (char **)inel[jj]->vec[kk] ;
                     printf("%s%s" , ssep , (cpt[ii] != NULL) ? cpt[ii] : "(null)" ) ;
                   }
                 }
               }
            }
            printf("\n") ;
         }
      } else {
         for( ii=0 ; ii < nx ; ii++ ){
            for( notfirst=cc=jj=0 ; jj < nim ; jj++ ){
               if( inim[jj] != NULL ){
                 far = MRI_FLOAT_PTR(inim[jj]) ;
                 for( kk=0 ; kk < inim[jj]->ny ; kk++,cc++ ){
                    if( ncv == NULL || ncv->ar[cc] ){
                      ssep[0] = SEPCHAR ;
                      printf("%s%s",ssep,
                          format_value_4print(far[ii+kk*nx], oform, formatstr ));
                    }
                 }
               } else { /* write inel instead */
                 for( kk=0 ; ii < inel[jj]->vec_num ; kk++,cc++ ){
                   ssep[0] = SEPCHAR ;
                   if( inel[jj]->vec_typ[jj] == NI_FLOAT ){
                     far = (float *)inel[jj]->vec[kk] ;
                     printf("%s%s",ssep,
                          format_value_4print(far[ii], oform, formatstr ));
                   } else if( inel[jj]->vec_typ[jj] == NI_STRING ){
                     char **cpt = (char **)inel[jj]->vec[kk] ;
                     printf("%s%s" , ssep , (cpt[ii] != NULL) ? cpt[ii] : "(null)" ) ;
                   }
                 }
               }
            }
            printf("\n") ;
         }
      }
   }
   exit(0) ;
}
