#include "mrilib.h"

static int acorr_demean = 1 ;

#undef  PHI
#define PHI(x) (1.0-qg(x))       /* CDF of N(0,1) */

#undef  PHINV
#define PHINV(x) qginv(1.0-(x))  /* inverse CDF of N(0,1) */

/*----------------------------------------------------------------------------*/
/*! atanh() of Pearson correlation of x[] and y[] */

static INLINE float acorrfun( int n, float *x, float *y )
{
   float xv=0.0f , yv=0.0f , xy=0.0f , vv,ww ;
   float xm=0.0f , ym=0.0f ;
   register int jj ;

   if( acorr_demean ){
     for( jj=0 ; jj < n ; jj++ ){ xm += x[jj]; ym += y[jj]; }
     xm /= n ; ym /= n ;
   }
   for( jj=0 ; jj < n ; jj++ ){
     vv = x[jj]-xm; ww = y[jj]-ym; xv += vv*vv; yv += ww*ww; xy += vv*ww;
   }

   if( xv <= 0.0f || yv <= 0.0f ) return 0.0f ;
   return atanhf(xy/sqrtf(xv*yv)) ;
}

/*----------------------------------------------------------------------------*/
/*! atanh() of Correlation of x[] and y[], omitting index qq.
    For doing jackknife statistics on the correlation. */

static INLINE float acorrfun_jack( int n, float *x, float *y, int qq )
{
   float xv=0.0f , yv=0.0f , xy=0.0f , vv,ww ;
   float xm=0.0f , ym=0.0f ;
   register int jj ;

   if( acorr_demean ){
     for( jj=0 ; jj < n ; jj++ ){ if( jj != qq ){ xm += x[jj]; ym += y[jj]; } }
     xm /= (n-1.0f) ; ym /= (n-1.0f) ;
   }
   for( jj=0 ; jj < n ; jj++ ){
     if( jj != qq ){
       vv = x[jj]-xm; ww = y[jj]-ym; xv += vv*vv; yv += ww*ww; xy += vv*ww;
     }
   }

   if( xv <= 0.0f || yv <= 0.0f ) return 0.0f ;
   return atanhf(xy/sqrtf(xv*yv)) ;
}

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

static float bca_alpha_1 = 0.05f ;
static float bca_phial_1 = 1.64485 ;

static float bca_alpha_2 = 0.1f ;
static float bca_phial_2 = 1.28155f ;

void THD_bstrap_set_bca_alphas( float al1 , float al2 )
{
   if( al1 <  0.01f    ) al1 = 0.01f    ; else if( al1 > 0.1f ) al1 = 0.1f ;
   if( al2 <= 1.2f*al1 ) al2 = 2.0f*al1 ; else if( al2 > 0.2f ) al2 = 0.2f ;
   bca_alpha_1 = al1 ; bca_phial_1 = (float)qginv(al1) ;
   bca_alpha_2 = al2 ; bca_phial_2 = (float)qginv(al2) ;
}

/*----------------------------------------------------------------------------*/
/* Bias-Corrected accelerated (BCa) bootstrap estimation of correlation
   and confidence intervals: M Mudelsee, Mathematical Geology 35:651-665.
*//*--------------------------------------------------------------------------*/

float_triple THD_bstrap_corr( int npt, float *xx, float *yy, float tau, int nboot )
{
   float_triple retval = {0.0f,0.0f,0.0f} ;
   int kb , ii , qq ;
   float rxy , pboot , *rboot , *xb , *yb ; int qboot ;
   float rjbar , ahat , anum,aden , val , z0hat , alpha_b ;
   float rb_al1 , rb_al2 , rb_au1 , rb_au2 , sig1,sig2,sig , med ;

ENTRY("THD_bstrap_corr") ;

   if( npt < 19 || xx == NULL || yy == NULL ) RETURN(retval) ;

   if( nboot < 666 ) nboot = 666 ;

#pragma omp critical (MALLOC)
   { rboot = (float *)malloc(sizeof(float)*(nboot+2*npt)) ; }
   xb = rboot + nboot ;
   yb = xb + npt ;

   rxy = acorrfun( npt , xx , yy ) ;  /* "raw" correlation */

   pboot = (tau <= 0.5f) ? 0.5f : 0.25f/tau ;  /* prob of random jump */
   qboot = (int)(pboot * ((1u << 31)-1u)) ;    /* scaled to int range */

   /** bootstrap work **/

   for( kb=0 ; kb < nboot ; kb++ ){
     qq = (lrand48() >> 3) % npt ;      /* random starting point */
     xb[0] = xx[qq] ; yb[0] = yy[qq] ;
     for( ii=1 ; ii < npt ; ii++ ){
       if( lrand48() < qboot ) qq = (lrand48() >> 3) % npt ; /* random jump */
       else                    qq = (qq+1) % npt ;           /* next point */
       xb[ii] = xx[qq] ; yb[ii] = yy[qq] ;
     }
     rboot[kb] = acorrfun( npt , xb , yb ) ;
   }

   /* find location of rxy in the sorted rboot array,
      then use that to estimate the bias correction parameter z0hat */

   qsort_float( nboot , rboot ) ;
   for( kb=0 ; kb < nboot && rboot[kb] < rxy ; kb++ ) ; /*nada*/
   z0hat = PHINV(kb/(double)nboot) ;  /* kb = number of rboot < rxy */
   if( z0hat < -1.0f ) z0hat = -1.0f ; else if( z0hat > 1.0f ) z0hat = 1.0f ;

   /** jackknife work to get "acceleration" parameter ahat **/

   rjbar = 0.0f ;
   for( kb=0 ; kb < npt ; kb++ ){
     xb[kb] = acorrfun_jack( npt, xx,yy , kb ) ; rjbar += xb[kb] ;
   }
   rjbar /= npt ;
   anum = aden = 0.0f ;
   for( kb=0 ; kb < npt ; kb++ ){
     val = rjbar - xb[kb] ; anum += val*val*val ; aden += val*val ;
   }
   ahat = (aden > 0.0f ) ? anum / (6.0f*aden*sqrtf(aden)) : 0.0f ;
   if( ahat < -0.2f ) ahat = -0.2f ; else if( ahat > 0.2f ) ahat = 0.2f ;

   /* compute bootstrap value at alpha_1 level, adjusted */

   val = z0hat + (z0hat - bca_phial_1) / ( 1.0f - ahat*(z0hat - bca_phial_1) ) ;
   val = nboot * PHI(val) ; kb  = (int)val ; val = val-kb ;
   rb_al1 = (1.0f-val)*rboot[kb] + val*rboot[kb+1] ;

   /* compute bootstrap value at alpha_2 level, adjusted */

   val = z0hat + (z0hat - bca_phial_2) / ( 1.0f - ahat*(z0hat - bca_phial_2) ) ;
   val = nboot * PHI(val) ; kb  = (int)val ; val = val-kb ;
   rb_al2 = (1.0f-val)*rboot[kb] + val*rboot[kb+1] ;

   /* compute bootstrap value at 1-alpha_2 level, adjusted */

   val = z0hat + (z0hat + bca_phial_2) / ( 1.0f - ahat*(z0hat + bca_phial_2) ) ;
   val = nboot * PHI(val) ; kb  = (int)val ; val = val-kb ;
   rb_au2 = (1.0f-val)*rboot[kb] + val*rboot[kb+1] ;

   /* compute bootstrap value at 1-alpha_1 level, adjusted */

   val = z0hat + (z0hat + bca_phial_1) / ( 1.0f - ahat*(z0hat + bca_phial_1) ) ;
   val = nboot * PHI(val) ; kb  = (int)val ; val = val-kb ;
   rb_au1 = (1.0f-val)*rboot[kb] + val*rboot[kb+1] ;

   /* compute bootstrap value at alpha_1 level, adjusted */

   val = z0hat + (z0hat + bca_alpha_1) / ( 1.0f - ahat*(z0hat + bca_alpha_1) ) ;
   val = nboot * PHI(val) ; kb  = (int)val ; val = val-kb ;
   rb_al1 = (1.0f-val)*rboot[kb] + val*rboot[kb+1] ;

   /* compute bootstrap value at alpha_2 level, adjusted */

   val = z0hat + (z0hat + bca_alpha_2) / ( 1.0f - ahat*(z0hat + bca_alpha_2) ) ;
   val = nboot * PHI(val) ; kb  = (int)val ; val = val-kb ;
   rb_al2 = (1.0f-val)*rboot[kb] + val*rboot[kb+1] ;

   /* compute bootstrap value at 1-alpha_1 level, adjusted */

   val = z0hat + (z0hat + 1.0f-bca_alpha_1) / ( 1.0f - ahat*(z0hat + 1.0f-bca_alpha_1) ) ;
   val = nboot * PHI(val) ; kb  = (int)val ; val = val-kb ;
   rb_au1 = (1.0f-val)*rboot[kb] + val*rboot[kb+1] ;

   /* compute bootstrap value at 1-alpha_2 level, adjusted */

   val = z0hat + (z0hat + 1.0f-bca_alpha_2) / ( 1.0f - ahat*(z0hat + 1.0f-bca_alpha_2) ) ;
   val = nboot * PHI(val) ; kb  = (int)val ; val = val-kb ;
   rb_au2 = (1.0f-val)*rboot[kb] + val*rboot[kb+1] ;

   /* now estimate sigma both ways, then combine them */

   sig1 = (rb_au1 - rb_al1) / (2.0f*bca_phial_1) ;
   sig2 = (rb_au2 - rb_al2) / (2.0f*bca_phial_2) ;
   sig  = 0.5f * (sig1+sig2) ;

   /* and estimate middle of distribution */

   val = z0hat + (z0hat + 0.5f) / ( 1.0f - ahat*(z0hat + 0.5f) ) ;
   val = nboot * PHI(val) ; kb  = (int)val ; val = val-kb ;
   med = (1.0f-val)*rboot[kb] + val*rboot[kb+1] ;

   retval.a = med ; retval.b = sig ; retval.c = rxy ;

#if 0
   /* convert rboot into alpha values */

   for( kb=0 ; kb < nboot ; kb++ ){
     phab = PHINV( (kb+0.5f)/nboot ) - z0hat ;
     val  = phab / ( 1.0f + aa*phab ) - z0hat ;
     if( val < -5.0f ) val = -5.0f ; else if ( val > 5.0f ) val = 5.0f ;
     phab = PHI(val) ;
     printf("%f %f\n",rboot[kb],val) ;
   }
#endif

   /***/

#pragma omp critical (MALLOC)
   { free(rboot) ; }
   RETURN(retval) ;
}

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

#undef  A
#define A(i,j) asym[(i)+(j)*nsym]
#undef  XPT
#define XPT(q) ( (xtyp<=0) ? xx+(q)*nn : xar[q] )

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

static void mean_vector( int n , int m , int xtyp , void *xp , float *uvec )
{
   int nn=n , mm=m , jj ; register int ii ;
   register float *xj , fac ; float *xx=NULL , **xar=NULL ;

   if( nn < 1 || mm < 1 || xp == NULL || uvec == NULL ) return ;

   if( xtyp <= 0 ) xx  = (float * )xp ;
   else            xar = (float **)xp ;

   for( ii=0 ; ii < nn ; ii++ ) uvec[ii] = 0.0f ;

   for( jj=0 ; jj < mm ; jj++ ){
     xj = XPT(jj) ;
     for( ii=0 ; ii < nn ; ii++ ) uvec[ii] += xj[ii] ;
   }

   fac = 1.0f / nn ;
   for( ii=0 ; ii < nn ; ii++ ) uvec[ii] *= fac ;
   return ;
}

/*----------------------------------------------------------------------------*/
/*! Compute the principal singular vector of a set of m columns, each
    of length n, stored in array xx[i+j*n] for i=0..n-1, j=0..m-1.
   * The singular value is returned, and the vector is stored into uvec[].
   * tvec is a vector so that the sign of uvec dot tvec will be non-negative.
   * If the return value is not positive, something bad happened.
*//*--------------------------------------------------------------------------*/

static float principal_vector( int n , int m , int xtyp , void *xp ,
                               float *uvec , float *tvec            )
{
   int nn=n , mm=m , nsym , ii,jj,kk,qq ;
   double *asym , *deval ;
   register double sum , qsum ; register float *xj , *xk ;
   float sval , fsum , *xx=NULL , **xar=NULL ;

   nsym = MIN(nn,mm) ;  /* size of the symmetric matrix to create */

   if( nsym < 1 || xp == NULL || uvec == NULL ) return -666.0f ;

#pragma omp critical (MALLOC)
   { asym  = (double *)malloc(sizeof(double)*nsym*nsym) ;  /* symmetric matrix */
     deval = (double *)malloc(sizeof(double)*nsym) ;       /* its eigenvalues */
   }

   if( xtyp <= 0 ) xx  = (float * )xp ;
   else            xar = (float **)xp ;

   /** setup matrix to eigensolve: choose smaller of [X]'[X] and [X][X]' **/
   /**     since [X] is n x m, [X]'[X] is m x m and [X][X]' is n x n     **/

   if( nn > mm ){                       /* more rows than columns:  */
                                        /* so [A] = [X]'[X] = m x m */
     int n1 = nn-1 ;
     for( jj=0 ; jj < mm ; jj++ ){
       xj = XPT(jj) ;
       for( kk=0 ; kk <= jj ; kk++ ){
         sum = 0.0 ; xk = XPT(kk) ;
         for( ii=0 ; ii < n1 ; ii+=2 ) sum += xj[ii]*xk[ii] + xj[ii+1]*xk[ii+1];
         if( ii == n1 ) sum += xj[ii]*xk[ii] ;
         A(jj,kk) = sum ; if( kk < jj ) A(kk,jj) = sum ;
       }
     }

   } else {                             /* more columns than rows:  */
                                        /* so [A] = [X][X]' = n x n */
     float *xt ; int m1=mm-1 ;
#pragma omp critical (MALLOC)
     xt = (float *)malloc(sizeof(float)*nn*mm) ;
     for( jj=0 ; jj < mm ; jj++ ){      /* form [X]' into array xt */
       if( xtyp <= 0 )
         for( ii=0 ; ii < nn ; ii++ ) xt[jj+ii*mm] = xx[ii+jj*nn] ;
       else
         for( ii=0 ; ii < nn ; ii++ ) xt[jj+ii*mm] = xar[jj][ii] ;
     }

     for( jj=0 ; jj < nn ; jj++ ){
       xj = xt + jj*mm ;
       for( kk=0 ; kk <= jj ; kk++ ){
         sum = 0.0 ; xk = xt + kk*mm ;
         for( ii=0 ; ii < m1 ; ii+=2 ) sum += xj[ii]*xk[ii] + xj[ii+1]*xk[ii+1];
         if( ii == m1 ) sum += xj[ii]*xk[ii] ;
         A(jj,kk) = sum ; if( kk < jj ) A(kk,jj) = sum ;
       }
     }

#pragma omp critical (MALLOC)
     free(xt) ;  /* don't need this no more */
   }

   /** compute the first eigenvector corresponding to largest eigenvalue **/
   /** this eigenvector is stored on top of first column of asym         **/

   ii = symeig_irange( nsym, asym, deval, nsym-1, nsym-1, 0 ) ;

   if( ii != 0 || deval[0] < 0.0 ){
#pragma omp critical (MALLOC)
     { free(deval) ; free(asym) ; }
     return -999.0f ;  /* eigensolver failed!? */
   }

   /** Store singular value (sqrt of eigenvalue) **/

   sval = (float)sqrt(deval[0]) ;

   /** SVD is [X] = [U] [S] [V]', where [U] = desired output vectors

       case n <= m: [A] = [X][X]' = [U] [S][S]' [U]'
                    so [A][U] = [U] [S][S]'
                    so eigenvectors of [A] are just [U]

       case n > m:  [A] = [X]'[X] = [V] [S]'[S] [V]'
                    so [A][V] = [V] [S'][S]
                    so eigenvectors of [A] are [V], but we want [U]
                    note that [X][V] = [U] [S]
                    so pre-multiplying each column vector in [V] by matrix [X]
                    will give the corresponding column in [U], but scaled;
                    below, just L2-normalize the column to get output vector **/

	if( nn <= mm ){                    /* copy eigenvector into output directly */
                                      /* (e.g., more vectors than time points) */

     for( ii=0 ; ii < nn ; ii++ ) uvec[ii] = (float)asym[ii] ;

   } else {  /* n > m: transform eigenvector to get left singular vector */
             /* (e.g., more time points than vectors) */

     qsum = 0.0 ;
     for( ii=0 ; ii < nn ; ii++ ){
       sum = 0.0 ;
       if( xtyp <= 0 )
         for( kk=0 ; kk < mm ; kk++ ) sum += xx[ii+kk*nn] * asym[kk] ;
       else
         for( kk=0 ; kk < mm ; kk++ ) sum += xar[kk][ii]  * asym[kk] ;
       uvec[ii] = sum ; qsum += sum*sum ;
     }
     if( qsum > 0.0 ){       /* L2 normalize */
       register float fac ;
       fac = (float)(1.0/sqrt(qsum)) ;
       for( ii=0 ; ii < nn ; ii++ ) uvec[ii] *= fac ;
     }
   }

   /** make it so that uvec dotted into the mean vector is positive **/

   fsum = 0.0f ;
   for( ii=0 ; ii < nn ; ii++ ) fsum += tvec[ii]*uvec[ii] ;
   if( fsum < 0.0f ){
     for( ii=0 ; ii < nn ; ii++ ) uvec[ii] = -uvec[ii] ;
   }

   /** free at last!!! **/

#pragma omp critical (MALLOC)
   { free(deval) ; free(asym) ; }

   return sval ;
}

#undef XPT
#undef A

/*----------------------------------------------------------------------------*/
/* Return bootstrap BCa estimates for atanh(correlation) and stdev of that,
   plus the un-corrected 'raw' correlation, in a triple of floats.
*//*--------------------------------------------------------------------------*/

static int use_pca = 0 ;

float_triple THD_bstrap_vectcorr( int nlen , int nboot ,
                                  int xnum , float *xx , int ynum , float *yy )
{
   float_triple retval = {0.0f,0.0f,0.0f} ;
   float **xar, **yar, *rboot, *rjack, *xbar, *ybar, *uvec, *vvec, *zvec ;
   float rxy , rjbar , anum,aden,ahat , val , z0hat , zval ;
   float bmed , bsig , bcor ;
   int kb,ii,jj , njack ;

ENTRY("THD_bstrap_vectcorr") ;

   if( nlen < 3 || xnum < 1 || ynum < 1 || xx == NULL || yy == NULL )
     RETURN(retval) ;

   /* trivial case */

   if( xnum == 1 && ynum == 1 ){
     retval = THD_bstrap_corr( nlen , xx , yy , 0.0f , nboot ) ;
     RETURN(retval) ;
   }

   /* compute mean vectors */

#pragma omp critical (MALLOC)
   { xbar = (float *)malloc(sizeof(float)*nlen) ;
     ybar = (float *)malloc(sizeof(float)*nlen) ; }

   mean_vector( nlen , xnum , 0 , xx , xbar ) ;
   mean_vector( nlen , ynum , 0 , yy , ybar ) ;

   /* almost trivial case */

   if( xnum < 7 || ynum < 7 ){
     retval = THD_bstrap_corr( nlen , xbar , ybar , 0.0f , nboot ) ;
#pragma omp critical (MALLOC)
     { free(ybar) ; free(xbar) ; }
     RETURN(retval) ;
   }

   /** allocate memory for bootstrapping **/

   if( nboot < 66 ) nboot = 66 ;
   njack = xnum + ynum ;

#pragma omp critical (MALLOC)
   { xar   = (float **)malloc(sizeof(float *)*xnum ) ;
     yar   = (float **)malloc(sizeof(float *)*ynum ) ;
     rboot = (float * )malloc(sizeof(float)  *nboot) ;
     rjack = (float * )malloc(sizeof(float)  *njack) ;
     uvec  = (float * )malloc(sizeof(float)  *nlen ) ;
     vvec  = (float * )malloc(sizeof(float)  *nlen ) ;
     zvec  = (float * )malloc(sizeof(float)  *nlen ) ;
   }

   /* correlation with no resampling at all */

   if( use_pca ){
     (void)principal_vector( nlen , xnum , 0 , xx , uvec , xbar ) ;
     (void)principal_vector( nlen , ynum , 0 , yy , vvec , ybar ) ;
   } else {
#pragma omp critical (MEMCPY)
     { memcpy(uvec,xbar,sizeof(float)*nlen) ;
       memcpy(vvec,ybar,sizeof(float)*nlen) ; }
   }
   rxy = acorrfun( nlen , uvec , vvec ) ;
INFO_message("rxy = %g",rxy) ;

   /* bootstrap correlations */

INFO_message("bootstrap %d times",nboot) ;
   for( kb=0 ; kb < nboot ; kb++ ){
     for( ii=0 ; ii < xnum ; ii++ ){  /* resample xx vectors */
       jj = (lrand48() >> 3) % xnum ; xar[ii] = xx + jj*nlen ;
     }
     for( ii=0 ; ii < ynum ; ii++ ){  /* resample yy vectors */
       jj = (lrand48() >> 3) % ynum ; yar[ii] = yy + jj*nlen ;
     }
     if( use_pca ){
       (void)principal_vector( nlen , xnum , 1 , xar , uvec , xbar ) ;
       (void)principal_vector( nlen , ynum , 1 , yar , vvec , ybar ) ;
     } else {
       mean_vector( nlen , xnum , 1 , xar , uvec ) ;
       mean_vector( nlen , ynum , 1 , yar , vvec ) ;
     }
     rboot[kb] = acorrfun( nlen , uvec , vvec ) ;
   }

   /* statistics */

   qmedmadbmv_float( nboot , rboot , &bmed , NULL , &bsig ) ;

   bcor = 2*rxy - bmed ;

   val  = fabsf(rxy)            ; if( val < 0.1f ) val = 0.1f ;
   val  = fabsf(bcor-rxy) / val ; if( val > 1.0f ) val = 1.0f ;
   bsig = bsig * sqrtf( 1.0f + 4.0f*val*val ) ;

   /* cleanup the trash */

#pragma omp critical (MALLOC)
   { free(zvec); free(vvec); free(uvec); free(rjack); free(rboot);
     free(yar); free(xar);
   }

   retval.a = bcor ; retval.b = bsig ; retval.c = rxy ;

   RETURN(retval) ;
}

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

int main( int argc , char *argv[] )
{
   int iarg=1 , nboot=666 ;
   MRI_IMAGE *aim , *bim ;
   float *aar , *bar ;
   float_triple rval ;

   if( argc < 3 ) ERROR_exit("need 2 file args") ;

   if( AFNI_yesenv("NO_DEMEAN") ) acorr_demean = 0 ;
   SET_RAND_SEED ;
   THD_bstrap_set_bca_alphas( 0.1f , 0.2f ) ;

   aim = mri_read_1D(argv[1]) ; if( aim == NULL ) ERROR_exit("Can't read aim") ;
   bim = mri_read_1D(argv[2]) ; if( bim == NULL ) ERROR_exit("Can't read bim") ;
   if( aim->nx != aim->nx ) ERROR_exit("nx not same") ;

   if( argc > 3 ){
     nboot = (int)strtod(argv[3],NULL) ;
     if( nboot < 66 || nboot > 999999 ) nboot = 666 ;
   }

   use_pca = 0 ;
   fprintf(stderr,"----------- MEAN ---------\n") ;
   rval = THD_bstrap_vectcorr( aim->nx , nboot,
                               aim->ny , MRI_FLOAT_PTR(aim) ,
                               bim->ny , MRI_FLOAT_PTR(bim)  ) ;
   fprintf(stderr,"final = %.5f  %.5f  %.5f\n",rval.a,rval.b,rval.c) ;

#if 1
   fprintf(stderr,"----------- SVD ---------\n") ;
   use_pca = 1 ;
   rval = THD_bstrap_vectcorr( aim->nx , nboot,
                               aim->ny , MRI_FLOAT_PTR(aim) ,
                               bim->ny , MRI_FLOAT_PTR(bim)  ) ;
   fprintf(stderr,"final = %.5f  %.5f  %.5f\n",rval.a,rval.b,rval.c) ;
#endif
   exit(0) ;
}
