/*****************************************************************************
   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"

/*** 7D SAFE ***/
/*-----------------------------------------------------------------*/
/* Check ref vectors for all zero components [13 Mar 2017]
   Return value
    *   0 if all are OK.
    *  -1 if inputs are invalid
    * > 0 is count of all zero ref vectors
*//*---------------------------------------------------------------*/

int check_ref_vectors( int veclen , int nref , float *ref[] , char *funcname )
{
   int ii,jj , nbad=0 ; float *vv ;

   if( veclen < 1 || nref < 1 || ref == NULL ){
     if( funcname != NULL )
       ERROR_message("%s :: bad inputs for data fitting",funcname) ;
     return -1 ;
   }

   for( jj=0 ; jj < nref ; jj++ ){
     vv = ref[jj] ;
     for( ii=0 ; ii < veclen && vv[ii] == 0.0f ; ii++ ) ; /*nada*/
     if( ii == veclen ) nbad++ ;
   }

   if( nbad > 0 && funcname != NULL )
     ERROR_message("%s :: %d vector%s are all 0 for data fitting",
                   funcname , nbad , (nbad > 1) ? "s" : "\0" ) ;

   return nbad ;
}

/*-----------------------------------------------------------------
   Routine to compute the least squares fit of one image
   (fitim) to a linear combination of other images (refim).
   If not NULL, wtim is an image of weighting factors.
   Returns a malloc-ed array of floats (the fit coefficients).
-------------------------------------------------------------------*/

float * mri_lsqfit( MRI_IMAGE * fitim , MRI_IMARR * refim , MRI_IMAGE * wtim )
{
   float *fit = NULL ;                 /* will be the output */
   MRI_IMAGE *ffitim , *tim , *wim ;   /* local versions of inputs */
   MRI_IMARR *frefim ;

   int ii , jj , npix,nref ;
   float **refar , *fitar , *war ;

ENTRY("mri_lsqfit") ;

   /****---- check inputs, convert to float type if needed ----****/

   if( fitim == NULL ){
     ERROR_message("mri_lsqfit has NULL fitim"); RETURN(NULL);
   }

   if( fitim->kind == MRI_float ) ffitim = fitim ;
   else                           ffitim = mri_to_float( fitim ) ;
   npix  = ffitim->nvox ;
   fitar = mri_data_pointer(ffitim) ;

   if( wtim == NULL ){
      wim = NULL ;
      war = NULL ;
   } else if( wtim->kind == MRI_float ){
      wim = wtim ;
      war = mri_data_pointer( wim ) ;
      if( wim->nvox != npix ){
         ERROR_message("mri_lsqfit: MISMATCH wtim->nvox=%d npix=%d",
                       wtim->nvox , npix );
         RETURN(NULL);
      }
   } else {
      wim = mri_to_float( wtim ) ;
      war = mri_data_pointer( wim ) ;
      if( wim->nvox != npix ){
         ERROR_message("mri_lsqfit: MISMATCH wtim->nvox=%d npix=%d",
                       wtim->nvox , npix );
         RETURN(NULL);
      }
   }

   if( refim == NULL || refim->num < 1 ){
      ERROR_message("mri_lsqfit: NULL refim"); RETURN(NULL);
   }

   nref = refim->num ;

   INIT_IMARR(frefim) ;
   refar = (float **) malloc( sizeof(float *) * nref ) ;
   if( refar == NULL ){
     fprintf(stderr,"** mri_lsqfit: malloc failure for refar"); RETURN(NULL);
   }

   for( ii=0 ; ii < nref ; ii++ ){
      if( refim->imarr[ii] == NULL ){
         ERROR_message("mri_lsqfit: NULL refim[%d]!",ii); RETURN(NULL);
      }
      if( refim->imarr[ii]->nvox != npix ){
         ERROR_message("mri_lsqfit: MISMATCH refim[%d]!",ii); RETURN(NULL);
      }
      if( refim->imarr[ii]->kind == MRI_float ) tim = refim->imarr[ii] ;
      else                                      tim = mri_to_float(refim->imarr[ii]) ;
      ADDTO_IMARR(frefim,tim) ;
      refar[ii] = mri_data_pointer(tim) ;
   }

   /****---- get coefficients ----****/

   fit = lsqfit( npix , fitar , war , nref , refar ) ;

   /****---- clean up and exit ----****/

   if( ffitim != fitim ) mri_free( ffitim ) ;
   if( wim != NULL && wim != wtim ) mri_free( wim ) ;
   for( ii=0 ; ii < nref ; ii++ ){
      if( frefim->imarr[ii] != refim->imarr[ii] ) mri_free( frefim->imarr[ii] ) ;
   }
   FREE_IMARR(frefim) ;
   free(refar) ;

   RETURN(fit) ;
}

/*----------------------------------------------------------------
     Routine to compute the weighted least square fit of
     a data vector to a bunch of reference vectors:

 veclen = length of vectors
 data   = array holding data vector (length veclen)
 wt     = array holding weight for each data point (length veclen)
          [if NULL, then a vector of all 1's is used]
 nref   = number of reference vectors, each of length veclen;
          must have 1 <= nref <= veclen
 ref    = array of pointers to reference vectors, so that
          ref[i][k] is the k-th component of the i-th reference,
          for i=0..nref-1, k=0..veclen-1

 Return value is a pointer to a vector of length nref with the
 weights of each reference;  if NULL is returned, there was an
 error.  The array is allocated by malloc and so should be freed
 when the caller is finished with it.

 Input and output vectors are floats.  Internal calculations
 are done with doubles.
------------------------------------------------------------------*/

float * lsqfit( int veclen ,
                float *data , float *wt , int nref , float *ref[] )
{
   int    ii , jj , kk ;
   float  *alpha = NULL ;
   double *cc = NULL , *rr = NULL ;
   double sum ;

   /** macros will be used in routines below, as well! **/

#define DBLEVEC(ll) (double *) malloc( sizeof(double) * (ll) )
#define DISCARD(xx) if( xx != NULL ){free(xx); xx = NULL;}
#define CLEANUP     {DISCARD(cc); DISCARD(rr);}

   if( nref < 1 || veclen < nref || data == NULL || ref == NULL ) return NULL ;

   /* 13 Mar 2017 */
   { int nbad = check_ref_vectors( veclen , nref , ref , "lsqfit" ) ;
     if( nbad ) return NULL ;
   }

   /*** form RHS vector into rr ***/

   rr = DBLEVEC(nref) ;
   cc = DBLEVEC(nref*nref) ;
   if( rr == NULL || cc == NULL ){ CLEANUP ; return NULL ; }

   if( wt != NULL ){
      for( ii=0 ; ii < nref ; ii++ ){
         sum = 0.0 ;
         for( jj=0 ; jj < veclen ; jj++ ) sum += ref[ii][jj] * wt[jj] * data[jj] ;
         rr[ii] = sum ;
      }
   } else {
      for( ii=0 ; ii < nref ; ii++ ){
         sum = 0.0 ;
         for( jj=0 ; jj < veclen ; jj++ ) sum += ref[ii][jj] * data[jj] ;
         rr[ii] = sum ;
      }
   }

   /*** form coefficient matrix into cc */

#define CC(i,j) cc[(i)+(j)*nref]

   if( wt != NULL ){
      for( jj=0 ; jj < nref ; jj++ ){
         for( ii=0 ; ii <= jj ; ii++ ){
            sum = 0.0 ;
            for( kk=0 ; kk < veclen ; kk++ ) sum += ref[ii][kk] * ref[jj][kk] * wt[kk] ;
            CC(ii,jj) = CC(jj,ii) = sum ;
         }
      }
   } else {
      for( jj=0 ; jj < nref ; jj++ ){
         for( ii=0 ; ii <= jj ; ii++ ){
            sum = 0.0 ;
            for( kk=0 ; kk < veclen ; kk++ ) sum += ref[ii][kk] * ref[jj][kk] ;
            CC(ii,jj) = CC(jj,ii) = sum ;
         }
      }
   }

   /*** Choleski decompose cc ***/

   for( ii=0 ; ii < nref ; ii++ ){
      for( jj=0 ; jj < ii ; jj++ ){
         sum = CC(ii,jj) ;
         for( kk=0 ; kk < jj ; kk++ ) sum -= CC(ii,kk) * CC(jj,kk) ;
         CC(ii,jj) = sum / CC(jj,jj) ;
      }
      sum = CC(ii,ii) ;
      for( kk=0 ; kk < ii ; kk++ ) sum -= CC(ii,kk) * CC(ii,kk) ;
      if( sum <= 0.0 ){ CLEANUP ; return NULL ; }
      CC(ii,ii) = sqrt(sum) ;
   }

   /*** forward solve ***/

   for( ii=0 ; ii < nref ; ii++ ){
      sum = rr[ii] ;
      for( jj=0 ; jj < ii ; jj++ ) sum -= CC(ii,jj) * rr[jj] ;
      rr[ii] = sum / CC(ii,ii) ;
   }

   /*** backward solve ***/

   for( ii=nref-1 ; ii >= 0 ; ii-- ){
      sum = rr[ii] ;
      for( jj=ii+1 ; jj < nref ; jj++ ) sum -= CC(jj,ii) * rr[jj] ;
      rr[ii] = sum / CC(ii,ii) ;
   }

   /*** put result into alpha ***/

   alpha = (float *) malloc( sizeof(float) * nref ) ;
   for( ii=0 ; ii < nref ; ii++ ) alpha[ii] = rr[ii] ;

   /*** cleanup and exit ***/

   CLEANUP ; return alpha ;
}

/*----------------------------------------------------------------
   Similar to above, but only produce the Choleski decomposition
   and weight scaled ref vectors for later use in delayed_lsqfit.
   This is to be used when fitting several data vectors to the
   same references with the same weight factors.

 veclen = length of vectors
 wt     = array holding weight for each data point (length veclen)
          [if NULL, then a vector of all 1's is used]
 nref   = number of reference vectors, each of length veclen;
          must have 1 <= nref <= veclen
 ref    = array of pointers to reference vectors, so that
          ref[i][k] is the k-th component of the i-th reference,
          for i=0..nref-1, k=0..veclen-1

   Return value is a (double *) which points to malloc-ed memory
   for the Choleksi decomposition.  It should later be passed
   to delayed_lsqfit.  If wt != NULL, then ref[ii][jj] is
   scaled by wt[jj] as well.

   If NULL is returned, an error occurred.
------------------------------------------------------------------*/

double * startup_lsqfit( int veclen ,
                         float *wt , int nref , float *ref[] )
{
   int    ii , jj , kk ;
   double *cc = NULL ;
   double sum ;

   if( nref < 1 || veclen < nref || ref == NULL ){
      ERROR_message("startup_lsqfit: nref=%d veclen=%d ref=%p",
                    nref , veclen , (void *)ref ) ;
      return NULL ;
   }

   /* 13 Mar 2017 */
   { int nbad = check_ref_vectors( veclen , nref , ref , "lsqfit" ) ;
     if( nbad ) return NULL ;
   }

   /*** form coefficient matrix into cc */

   cc = DBLEVEC(nref*nref) ;
   if( cc == NULL ){
      fprintf(stderr,"Can't malloc workspace in startup_lsqfit\n") ;
      return NULL ;
   }

   if( wt != NULL ){
      for( jj=0 ; jj < nref ; jj++ ){
         for( ii=0 ; ii <= jj ; ii++ ){
            sum = 0.0 ;
            for( kk=0 ; kk < veclen ; kk++ ) sum += ref[ii][kk] * ref[jj][kk] * wt[kk] ;
            CC(ii,jj) = CC(jj,ii) = sum ;
         }
      }
   } else {
      for( jj=0 ; jj < nref ; jj++ ){
         for( ii=0 ; ii <= jj ; ii++ ){
            sum = 0.0 ;
            for( kk=0 ; kk < veclen ; kk++ ) sum += ref[ii][kk] * ref[jj][kk] ;
            CC(ii,jj) = CC(jj,ii) = sum ;
         }
      }
   }

   /*** Choleski decompose cc ***/

   for( ii=0 ; ii < nref ; ii++ ){
      for( jj=0 ; jj < ii ; jj++ ){
         sum = CC(ii,jj) ;
         for( kk=0 ; kk < jj ; kk++ ) sum -= CC(ii,kk) * CC(jj,kk) ;
         CC(ii,jj) = sum / CC(jj,jj) ;
      }
      sum = CC(ii,ii) ;
      for( kk=0 ; kk < ii ; kk++ ) sum -= CC(ii,kk) * CC(ii,kk) ;
      if( sum <= 0.0 ){
         free(cc) ;
         ERROR_message(
           "Choleski factorization failure in startup_lsqfit [row=%d sum=%g]",ii,sum) ;
         return NULL ;
      }
      CC(ii,ii) = sqrt(sum) ;
   }

   /*** scale ref by wt, if desired ***/

   if( wt != NULL ){
      for( ii=0 ; ii < nref ; ii++ )
         for( jj=0 ; jj < veclen ; jj++ ) ref[ii][jj] *= wt[jj] ;
   }

   return cc ;
}

/**------------------------------------------------------------------
  Given the data from startup_lsqfit, finish the job.
------------------------------------------------------------------**/

float * delayed_lsqfit( int veclen ,
                        float * data , int nref , float *ref[] , double * cc )
{
   int    ii , jj ;
   float  *alpha = NULL ;
   double *rr = NULL ;
   register double sum ;

   if( nref < 1 || veclen < nref ||
       data == NULL || ref == NULL || cc == NULL ) return NULL ;

   /* 13 Mar 2017 */
   { int nbad = check_ref_vectors( veclen , nref , ref , "lsqfit" ) ;
     if( nbad ) return NULL ;
   }

   /*** form RHS vector into rr ***/

   rr = DBLEVEC(nref) ; if( rr == NULL ) return NULL ;

   for( ii=0 ; ii < nref ; ii++ ){
      sum = 0.0 ;
      for( jj=0 ; jj < veclen ; jj++ ) sum += ref[ii][jj] * data[jj] ;
      rr[ii] = sum ;
   }

   /*** forward solve ***/

   for( ii=0 ; ii < nref ; ii++ ){
      sum = rr[ii] ;
      for( jj=0 ; jj < ii ; jj++ ) sum -= CC(ii,jj) * rr[jj] ;
      rr[ii] = sum / CC(ii,ii) ;
   }

   /*** backward solve ***/

   for( ii=nref-1 ; ii >= 0 ; ii-- ){
      sum = rr[ii] ;
      for( jj=ii+1 ; jj < nref ; jj++ ) sum -= CC(jj,ii) * rr[jj] ;
      rr[ii] = sum / CC(ii,ii) ;
   }

   /*** put result into alpha ***/

   alpha = (float *) malloc( sizeof(float) * nref ) ; if( alpha == NULL ) return NULL ;
   for( ii=0 ; ii < nref ; ii++ ) alpha[ii] = rr[ii] ;

   /*** cleanup and exit ***/

   free(rr) ;
   return alpha ;
}

/*-----------------------------------------------------------------
   'startup' and 'delayed' versions for image fitting.
   N.B.: unlike mri_lsqfit, all the images in refim and wtim must
         be of MRI_float kind.
-------------------------------------------------------------------*/

double * mri_startup_lsqfit( MRI_IMARR * refim , MRI_IMAGE * wtim )
{
   double *cc = NULL ;                 /* will be the output */
   int ii , npix,nref ;
   float * wtar , ** refar ;

ENTRY("mri_startup_lsqfit") ;

   /****---- check inputs ----****/

   if( wtim != NULL && wtim->kind != MRI_float ){
      ERROR_message("mri_startup_lsqfit: non-float wtim") ; RETURN(NULL);
   }
   wtar = MRI_FLOAT_PTR(wtim) ;

   if( refim == NULL || refim->num < 1 ){
      ERROR_message("mri_startup_lsqfit: NULL refim") ; RETURN(NULL);
   }

   nref  = refim->num ;
   npix  = refim->imarr[0]->nvox ;
   refar = (float **) malloc( sizeof(float *) * nref ) ;
   if( refar == NULL ){
      fprintf(stderr,"mri_startup_lsqfit: malloc failure for refar") ;
      RETURN(NULL);
   }

   for( ii=0 ; ii < nref ; ii++ ){
      if( refim->imarr[ii] == NULL ){
         ERROR_message("mri_startup_lsqfit: NULL refim[%d]",ii) ; RETURN(NULL);
      }
      if( refim->imarr[ii]->nvox != npix ){
         ERROR_message("mri_startup_lsqfit: MISMATCH refim[%d]",ii) ; RETURN(NULL);
      }
      if( refim->imarr[ii]->kind != MRI_float ){
         ERROR_message("mri_startup_lsqfit: non-float refim[%d]",ii) ; RETURN(NULL);
      }
      refar[ii] = MRI_FLOAT_PTR(refim->imarr[ii]) ;
   }

   /****---- get Choleski, send it out ----****/

   cc = startup_lsqfit( npix , wtar , nref , refar ) ;
   if( cc == NULL ){
     ERROR_message("mri_startup_lsqfit: bad call to startup_lsqfit!\a\n") ;
     RETURN(NULL);
   }
   free(refar) ;
   RETURN(cc) ;
}

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

float * mri_delayed_lsqfit( MRI_IMAGE * fitim , MRI_IMARR * refim , double * cc )
{
   int ii , npix,nref ;
   float * fit ;
   static float ** refar = NULL ;
   static int     nrefar = -1 ;

ENTRY("mri_delayed_lsqfit") ;

   nref = refim->num ;
   npix = refim->imarr[0]->nvox ;

   if( nrefar < nref ){
      if( refar != NULL ) free(refar) ;
      refar  = (float **) malloc( sizeof(float *) * nref ) ;
      nrefar = nref ;
   }
   if( refar == NULL ){
     fprintf(stderr,"mri_delayed_lsqfit: malloc failure for refar");
     RETURN(NULL);
   }

   for( ii=0 ; ii < nref ; ii++ )
      refar[ii] = MRI_FLOAT_PTR(refim->imarr[ii]) ;

   fit = delayed_lsqfit( npix , MRI_FLOAT_PTR(fitim) , nref , refar , cc ) ;
   RETURN(fit) ;
}
