/*****************************************************************************
   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 "multivector.h"

/*********************************************************************
  Routines to handle the multivector data type: a collection of
  1D arrays of strings or floats, all the same length.

  RWCox - May 1999
**********************************************************************/

/*-------------------------------------------------------------------
   Check 2 strings for equivalence, regardless of case
---------------------------------------------------------------------*/

static int my_strequiv( char * s1 , char * s2 )
{
   int ii , ll ;

   if( s1 == NULL && s2 == NULL ) return 1 ;
   if( s1 == NULL || s2 == NULL ) return 0 ;
   ii = strlen(s1) ; ll = strlen(s2) ; if( ii != ll ) return 0 ;
   for( ii=0 ; ii < ll ; ii++ )
      if( toupper(s1[ii]) != toupper(s2[ii]) ) return 0 ;
   return 1 ;
}

/*--------------------------------------------------------------------
  Throw away a multivector
----------------------------------------------------------------------*/

void multivector_free( multivector * mv )
{
   int ii ;

   if( mv == NULL ) return ;

   if( mv->name != NULL ) free(mv->name) ;
   if( mv->type != NULL ) free(mv->type) ;
   if( mv->label != NULL )
      for( ii=0 ; ii < mv->nvec ; ii++ ) free(mv->label[ii]) ;
   if( mv->vec != NULL )
      for( ii=0 ; ii < mv->nvec ; ii++ ) free(mv->vec[ii]) ;

   free(mv) ; return ;
}

/*-------------------------------------------------------------------
  Read a multivector from disk
---------------------------------------------------------------------*/

#define NVMAX 128
#define LBUF  2048
#define SEPCH " \t\n"

#define MERR(ss) \
   fprintf(stderr,"*** multivector_read error; file=%s: %s\n",fname,ss)

multivector * multivector_read( char * fname )
{
   FILE * fp ;
   char buf[LBUF] ;
   char * ptr , * pp[NVMAX] ;
   multivector * mv ;
   int ii , ll , nvec,ndim , first=0 ;
   float val ;

   /*-- sanity check --*/

   if( fname == NULL || fname[0] == '\0' ) return NULL ;

   fp = fopen( fname , "r" ) ;
   if( fp == NULL ){ MERR("can't open file"); return NULL; }

   mv = (multivector *) malloc( sizeof(multivector) ) ;
   nvec = ndim = mv->nvec = mv->ndim = 0 ;
   mv->name = strdup(fname) ;
   mv->type = NULL ; mv->label = NULL ; mv->vec = NULL ;

   /*-- read and process any header comments --*/

   while(1){
      ptr = afni_fgets( buf , LBUF , fp ) ;
      if( ptr == NULL ){
         fclose(fp); multivector_free(mv); MERR("no data"); return NULL;
      }

      ll = strlen(buf) ;
      for( ii=ll-1 ; ii >= 0 ; ii-- ) if( !isspace(buf[ii]) ) break ;
      if( ii < 0 ) continue ;       /* was all blanks; goto next line */

      if( buf[0] != '#' ){ first=1; break; }   /* not a header line ==> done */

      ptr = strtok( buf , SEPCH ) ;

      /* handle #NAME */

      if( my_strequiv(ptr,"#NAME") ){
         ptr = strtok( NULL , SEPCH ) ;
         if( ptr != NULL ){
            free(mv->name) ; mv->name = strdup(ptr) ;
         }
         continue ;  /* goto next line */
      }

      /* handle #TYPE */

      if( my_strequiv(ptr,"#TYPE") ){
         int ntyp=0 , typ[NVMAX] ;

         if( mv->type != NULL ){
            fclose(fp); multivector_free(mv); MERR("second #TYPE"); return NULL;
         }

         /* scan tokens for type strings */

         while(1){
            ptr = strtok( NULL , SEPCH ) ;
            if( ptr == NULL ) break ;

            if( ntyp >= NVMAX ){
               fclose(fp); multivector_free(mv); MERR("oversize #TYPE"); return NULL;
            }

                 if( my_strequiv(ptr,"STRING") ) typ[ntyp++] = MV_STRING ;
            else if( my_strequiv(ptr,"FLOAT")  ) typ[ntyp++] = MV_FLOAT  ;
            else {
               fclose(fp); multivector_free(mv); MERR("illegal #TYPE"); return NULL;
            }
         }

         if( ntyp == 0 ){
            fclose(fp); multivector_free(mv); MERR("illegal #TYPE"); return NULL;
         }

         if( mv->nvec > 0 && ntyp != mv->nvec ){
            fclose(fp); multivector_free(mv); MERR("illegal #TYPE count"); return NULL;
         }

         if( mv->nvec == 0 ) nvec = mv->nvec = ntyp ;
         mv->type = (int *) malloc( sizeof(int) * ntyp ) ;
         for( ii=0 ; ii < ntyp ; ii++ ) mv->type[ii] = typ[ii] ;
         continue ;  /* goto next line */
      }

      /* handle #LABEL */

      if( my_strequiv(ptr,"#LABEL") ){
         int nlab=0 ; char * lab[NVMAX] ;

         if( mv->label != NULL ){
            fclose(fp); multivector_free(mv); MERR("second #LABEL"); return NULL;
         }

         /* scan tokens for label strings */

         while(1){
            ptr = strtok( NULL , SEPCH ) ;
            if( ptr == NULL ) break ;

            if( nlab >= NVMAX ){
               for( ii=0 ; ii < nlab ; ii++ ) free( lab[ii] ) ;
               fclose(fp); multivector_free(mv); MERR("oversize #LABEL"); return NULL;
            }

            lab[nlab++] = strdup(ptr) ;
         }

         if( nlab == 0 ){
            fclose(fp); multivector_free(mv); MERR("illegal #LABEL"); return NULL;
         }

         if( mv->nvec > 0 && nlab != mv->nvec ){
            for( ii=0 ; ii < nlab ; ii++ ) free( lab[ii] ) ;
            fclose(fp); multivector_free(mv); MERR("illegal #LABEL count"); return NULL;
         }

         if( mv->nvec == 0 ) nvec = mv->nvec = nlab ;
         mv->label = (char **) malloc( sizeof(char *) * nlab ) ;
         for( ii=0 ; ii < nlab ; ii++ ) mv->label[ii] = lab[ii] ;
         continue ;  /* goto next line */
      }

      /* otherwise, just ignore the line (it's a comment, maybe) */

   } /* end of scan over header lines */

   /*-- read and store data lines --*/

   while(1){
      if( !first ) ptr = afni_fgets( buf , LBUF , fp ) ;
      if( ptr == NULL ) break ;        /* end of input */
      first = 0 ;

      ll = strlen(buf) ;
      for( ii=ll-1 ; ii >= 0 ; ii-- ) if( !isspace(buf[ii]) ) break ;
      if( ii < 0 ) continue ;         /* was all blanks; goto next line */
      if( buf[0] == '#' ) continue ;  /* a comment line; goto next line */

      /* extract tokens from this line */

      pp[0] = strtok(buf,SEPCH) ; if( pp[0] == NULL ) continue ;
      ll = 1 ;
      while(1){
         pp[ll] = strtok(NULL,SEPCH) ; if( pp[ll] == NULL ) break ;
         ll++ ;
      }

      /* check count */

      if( nvec == 0 ){
          mv->nvec = nvec = ll ;
          if( nvec > NVMAX ) MERR("too many columns") ;
      }
      if( ll > nvec ) ll = nvec ;

      /* make type, if needed */

      if( mv->type == NULL ){
         mv->type = (int *) malloc( sizeof(int) * nvec ) ;
         for( ii=0 ; ii < ll ; ii++ ){
            val = strtod( pp[ii] , &ptr ) ;
            if( *ptr != '\0' ) mv->type[ii] = MV_STRING ;
            else               mv->type[ii] = MV_FLOAT  ;
         }
         for( ; ii < nvec ; ii++ )    /* this can only happen if #LABEL  */
            mv->type[ii] = MV_FLOAT ; /* is used and has too many labels */
      }

      /* initialize vector space, if needed */

      if( mv->vec == NULL ){
         mv->vec = (void **) malloc( sizeof(void *) * nvec ) ;
         for( ii=0 ; ii < nvec ; ii++ )
            mv->vec[ii] = (void *) malloc( sizeof(float)*16 ) ;
      }

      /* expand vector space for new row of data,
         convert tokens to values and store them in this space */

      for( ii=0 ; ii < nvec ; ii++ ){
         switch( mv->type[ii] ){
            case MV_FLOAT:{
               float * fpt ;
               mv->vec[ii] = (void *) realloc( mv->vec[ii], sizeof(float)*(ndim+1) );
               fpt = (float *) mv->vec[ii] ;
               fpt[ndim] = (ii < ll) ? strtod( pp[ii] , NULL ) : 0.0 ;
            }
            break ;

            case MV_STRING:{
               char ** cpt ;
               mv->vec[ii] = (void *) realloc( mv->vec[ii], sizeof(char *)*(ndim+1) );
               cpt = (char **) mv->vec[ii] ;
               cpt[ndim] = (ii < ll) ? strdup(pp[ii]) : strdup("\0") ;
            }
            break ;
          }
      }
      ndim++ ;   /* just added a new element! */

   } /* end of processing this line */

   /*-- done --*/

   mv->ndim = ndim ; return mv ;
}

/*-------------------------------------------------------------------
   (Re)set the name stored in a multivector.
   nname can be NULL t{o clear the name.
---------------------------------------------------------------------*/

void multivector_set_name( multivector * mv , char * nname )
{
   if( mv->name != NULL ){ free(mv->name); mv->name = NULL; }

   if( nname != NULL ) mv->name = strdup(nname) ;
   return ;
}


/*-------------------------------------------------------------------
  Write a multivector to disk.
  Returns 0 if it fails, 1 if it succeeds.
---------------------------------------------------------------------*/

int multivector_write( char * fname , multivector * mv )
{
   int nvec,ndim , ii,kk,ll , width[NVMAX] ;
   char buf[LBUF] , fbuf[32] ;
   FILE * fp ;
   float * fpt ;
   char ** cpt ;

   /*-- sanity checks --*/

   if( !THD_filename_ok(fname) || mv == NULL ) return 0 ;

   nvec = mv->nvec ; ndim = mv->ndim ;
   if( nvec < 1 || ndim < 1 ) return 0 ;

   if( mv->type == NULL || mv->vec == NULL ) return 0 ;

   /*-- open file, write headers --*/

   if( strcmp(fname,"-") == 0 ){
      fp = stdout ;
   } else {
      fp = fopen( fname , "w" ) ; if( fp == NULL ) return 0 ;
   }

   if( mv->name != NULL ) fprintf(fp,"#NAME %s\n",mv->name) ;

   if( mv->label != NULL ){
      sprintf(buf,"#LABEL") ;
      for( ii=0 ; ii < nvec ; ii++ ){
         ll = strlen(buf) ;
         if( mv->label[ii] != NULL )
            sprintf(buf+ll," %s",mv->label[ii]) ;
         else
            sprintf(buf+ll," -none-") ;
      }
      fprintf(fp,"%s\n",buf) ;
   }

   sprintf(buf,"#TYPE") ;
   for( ii=0 ; ii < nvec ; ii++ ){
      ll = strlen(buf) ;
      switch( mv->type[ii] ){
         case MV_FLOAT:  sprintf(buf+ll," FLOAT" ) ; break ;
         case MV_STRING: sprintf(buf+ll," STRING") ; break ;
      }
      width[ii] = 1 ;
   }
   fprintf(fp,"%s\n",buf) ;

   /*-- scan vectors to determine maximum column widths --*/

   for( kk=0 ; kk < ndim ; kk++ ){
      for( ii=0 ; ii < nvec ; ii++ ){
         switch( mv->type[ii] ){
            case MV_FLOAT:
               fpt = (float *) mv->vec[ii] ;
               MV_fval_to_char( fpt[kk] , fbuf ) ; ll = strlen(fbuf) ;
               width[ii] = MAX( width[ii] , ll ) ;
            break ;

            case MV_STRING:
               cpt = (char **) mv->vec[ii] ; ll = strlen(cpt[kk]) ;
               width[ii] = MAX( width[ii] , ll ) ;
            break ;
         }
      }
   }

   /*-- write data in columns --*/

   for( kk=0 ; kk < ndim ; kk++ ){
      buf[0] = '\0' ;
      for( ii=0 ; ii < nvec ; ii++ ){
         ll = strlen(buf) ;
         switch( mv->type[ii] ){
            case MV_FLOAT:
               fpt = (float *) mv->vec[ii] ;
               MV_fval_to_char( fpt[kk] , fbuf ) ;
               sprintf(buf+ll," %*s",width[ii],fbuf) ;
            break ;

            case MV_STRING:
               cpt = (char **) mv->vec[ii] ;
               sprintf(buf+ll," %*s",width[ii],cpt[kk]) ;
            break ;
         }
      }
      fprintf(fp,"%s\n",buf) ;
   }

   /*-- done --*/

   if( fp != stdout ) fclose(fp) ;
   return 1 ;
}

/*----------------------------------------------------------------
   Adapted from AV_fval_to_char
------------------------------------------------------------------*/

#define MV_NCOL 12

void MV_fval_to_char( float qval , char *buf )
{
   float aval = fabs(qval) ;
   int lv ;
   char lbuf[32] ;
   int il ;

   /* special case if the value is an integer */

   if( qval == 0.0 ){ strcpy(buf,"0"); return; }

   lv = (fabs(qval) < 99999999.0) ? (int)qval : 100000001 ;

   if( qval == lv && abs(lv) < 100000000 ){
      sprintf( buf, "%d" , lv ) ; return ;
   }

/* macro to strip trailing zeros from output */

#undef  BSTRIP
#define BSTRIP for( il=strlen(lbuf)-1 ;                        \
                    il>1 && (lbuf[il]=='0' || lbuf[il]==' ') ; \
                    il-- ) lbuf[il] = '\0'

   /* noninteger: choose floating format based on magnitude */

   lv = (int) (10.0001 + log10(aval)) ;

   switch( lv ){

      default:
         if( qval > 0.0 ) sprintf( lbuf , "%-12.6e" , qval ) ;
         else             sprintf( lbuf , "%-12.5e" , qval ) ;
      break ;

      case  6:  /* 0.0001-0.001 */
      case  7:  /* 0.001 -0.01  */
      case  8:  /* 0.01  -0.1   */
      case  9:  /* 0.1   -1     */
      case 10:  /* 1     -9.99  */
         sprintf( lbuf , "%-9.6f" , qval ) ; BSTRIP ; break ;

      case 11:  /* 10-99.9 */
         sprintf( lbuf , "%-9.5f" , qval ) ; BSTRIP ; break ;

      case 12:  /* 100-999.9 */
         sprintf( lbuf , "%-9.4f" , qval ) ; BSTRIP ; break ;

      case 13:  /* 1000-9999.9 */
         sprintf( lbuf , "%-9.3f" , qval ) ; BSTRIP ; break ;

      case 14:  /* 10000-99999.9 */
         sprintf( lbuf , "%-9.2f" , qval ) ; BSTRIP ; break ;

      case 15:  /* 100000-999999.9 */
         sprintf( lbuf , "%-9.1f" , qval ) ; BSTRIP ; break ;

      case 16:  /* 1000000-9999999.9 */
         sprintf( lbuf , "%-9.0f" , qval ) ; break ;
   }

   strcpy(buf,lbuf) ; return ;
}

/*!
   \sa MV_format_fval2  ZSS May 28 04
*/
char * MV_format_fval( float fval )
{
   static char buf[32] ;
   MV_fval_to_char( fval , buf ) ;
   return buf ;
}

/*!
   \brief s = MV_format_fval2( fval, len);
   same as fval, but will attempt to keep
   the number len characters long. That's done
   by truncating digits to the right of the decimal
   point, if one exists.
   \sa MV_fval_to_char
   \sa MV_format_fval      ZSS, RickR May 28 04
*/
char * MV_format_fval2( float fval, int len)
{
   static char bufm[10][32];
   static int icall=0;
   char *buf=NULL;
   int wid;
   char *pos = NULL;

   ++icall;
   if (icall > 9) icall = 0;
   buf = (char *)bufm[icall]; /* This way we can make multiple calls
                                 formatting function inside one *printf*
                                 call  ZSS Jan 2014 */
   
   MV_fval_to_char( fval , buf ) ;
   if (len < 1) return (buf);
   if (strlen(buf) < len) return (buf);

   /* trim it down */
   pos = strchr (buf, 'e');
   if (pos) return(buf); /* scientific notation, get out 
                           (ZSS, thanks to tip by Ben Singer Dec 12 05) */
   pos = strchr (buf, '.');
   if (!pos) return(buf);  /* can't do no'in */
   wid = pos - buf;
   if (wid < len) buf[len] = '\0';
   if (buf[len-1] == '.') buf[len-1] = '\0'; /* remove trailing period */
   return buf ;

}
