#include "superlu_ddefs.h"
#include "scatter.h"
//#include "compiler.h"

#define ISORT

#if 0 /**** Sherry: this routine is moved to util.c ****/
void
arrive_at_ublock (int_t j,      //block number
                  int_t *iukp,  // output
                  int_t *rukp, int_t *jb,   /* Global block number of block U(k,j). */
                  int_t *ljb,   /* Local block number of U(k,j). */
                  int_t *nsupc,     /*supernode size of destination block */
                  int_t iukp0,  //input
                  int_t rukp0, int_t *usub,     /*usub scripts */
                  int_t *perm_u,    /*permutation matrix */
                  int_t *xsup,  /*for SuperSize and LBj */
                  gridinfo_t *grid)
{
    int_t jj;
    *iukp = iukp0;
    *rukp = rukp0;

#ifdef ISORT
    for (jj = 0; jj < perm_u[j]; jj++)
#else
    for (jj = 0; jj < perm_u[2 * j + 1]; jj++)
#endif
    {

        *jb = usub[*iukp];      /* Global block number of block U(k,j). */
        *nsupc = SuperSize (*jb);
        *iukp += UB_DESCRIPTOR; /* Start fstnz of block U(k,j). */
        *rukp += usub[*iukp - 1];   /* Move to block U(k,j+1) */
        *iukp += *nsupc;
    }

    /* reinitilize the pointers to the begining of the */
    /* kth column/row of L/U factors                   */
    *jb = usub[*iukp];          /* Global block number of block U(k,j). */
    *ljb = LBj (*jb, grid);     /* Local block number of U(k,j). */
    *nsupc = SuperSize (*jb);
    *iukp += UB_DESCRIPTOR;     /* Start fstnz of block U(k,j). */
}
#endif
/*--------------------------------------------------------------*/

void
block_gemm_scatter( int_t lb, int_t j,
                    Ublock_info_t *Ublock_info,
                    Remain_info_t *Remain_info,
                    double *L_mat, int_t ldl,
                    double *U_mat, int_t ldu,
                    double *bigV,
                    // int_t jj0,
                    int_t knsupc,  int_t klst,
                    int_t *lsub, int_t *usub, int_t ldt,
                    int_t thread_id,
                    int_t *indirect,
                    int_t *indirect2,
                    int_t **Lrowind_bc_ptr, double **Lnzval_bc_ptr,
                    int_t **Ufstnz_br_ptr, double **Unzval_br_ptr,
                    int_t *xsup, gridinfo_t *grid,
                    SuperLUStat_t *stat
#ifdef SCATTER_PROFILE
                    , double *Host_TheadScatterMOP, double *Host_TheadScatterTimer
#endif
                  )
{
    // return ;
    thread_id = omp_get_thread_num();
    int_t *indirect_thread = indirect + ldt * thread_id;
    int_t *indirect2_thread = indirect2 + ldt * thread_id;
    double *tempv1 = bigV + thread_id * ldt * ldt;

    /* Getting U block information */

    int_t iukp =  Ublock_info[j].iukp;
    int_t jb   =  Ublock_info[j].jb;
    int_t nsupc = SuperSize(jb);
    int_t ljb = LBj (jb, grid);
    int_t st_col;
    int_t ncols;
    // if (j > jj0)
    if (j > 0)
    {
        ncols  = Ublock_info[j].full_u_cols - Ublock_info[j - 1].full_u_cols;
        st_col = Ublock_info[j - 1].full_u_cols;
    }
    else
    {
        ncols  = Ublock_info[j].full_u_cols;
        st_col = 0;
    }

    /* Getting L block information */
    int_t lptr = Remain_info[lb].lptr;
    int_t ib   = Remain_info[lb].ib;
    int_t temp_nbrow = lsub[lptr + 1];
    lptr += LB_DESCRIPTOR;
    int_t cum_nrow = (lb == 0 ? 0 : Remain_info[lb - 1].FullRow);
    /* Getting L block information */
    // int_t lptr = Remain_info[lb].lptr;
    // int_t ib   = Remain_info[lb].ib;
    // int_t temp_nbrow = lsub[lptr + 1];
    // lptr += LB_DESCRIPTOR;
    // int_t cum_nrow =  Remain_info[lb].StRow;

    double alpha = 1.0;
    double beta = 0.0;


    /* calling DGEMM */
    // printf(" m %d n %d k %d ldu %d ldl %d st_col %d \n",temp_nbrow,ncols,ldu,ldl,st_col );
    // dgemm("N", "N", &temp_nbrow, &ncols, &ldu, &alpha,
    //       &L_mat[(knsupc - ldu)*ldl + cum_nrow], &ldl,
    //       &U_mat[st_col * ldu], &ldu, &beta, tempv1, &temp_nbrow);


    // printf("%d %d %d %d  %d %d %d %d\n", temp_nbrow, ncols, ldu,  ldl,st_col,(knsupc - ldu)*ldl + cum_nrow,cum_nrow,st_col);

    cblas_dgemm(CblasColMajor, CblasNoTrans, CblasNoTrans,
                temp_nbrow, ncols, ldu, alpha,
                &L_mat[(knsupc - ldu)*ldl + cum_nrow], ldl,
                &U_mat[st_col * ldu], ldu,
                beta, tempv1, temp_nbrow);

    // printf("SCU update: (%d, %d)\n",ib,jb );
#ifdef SCATTER_PROFILE
    unsigned long long ttx = __rdtsc();
#endif
    /*Now scattering the block*/
    if (ib < jb)
    {

        SCATTER_U_CPU (
            ib, jb,
            nsupc, iukp, xsup,
            klst, temp_nbrow,
            lptr, temp_nbrow, lsub,
            usub, tempv1,
            indirect_thread,
            Ufstnz_br_ptr,
            Unzval_br_ptr,
            grid
        );
    }
    else
    {
        scatter_l (
            ib, ljb, nsupc, iukp, xsup, klst, temp_nbrow, lptr,
            temp_nbrow, usub, lsub, tempv1,
            indirect_thread, indirect2_thread,
            Lrowind_bc_ptr, Lnzval_bc_ptr, grid
        );

    }

    // #pragma omp atomic
    // stat->ops[FACT] += 2*temp_nbrow*ncols*ldu + temp_nbrow*ncols;

#ifdef SCATTER_PROFILE
    double t_s = (double) __rdtsc() - ttx;
    Host_TheadScatterMOP[thread_id * ((192 / 8) * (192 / 8)) + ((CEILING(temp_nbrow, 8) - 1)   +  (192 / 8) * (CEILING(ncols, 8) - 1))]
    += 3.0 * (double ) temp_nbrow * (double ) ncols;
    Host_TheadScatterTimer[thread_id * ((192 / 8) * (192 / 8)) + ((CEILING(temp_nbrow, 8) - 1)   +  (192 / 8) * (CEILING(ncols, 8) - 1))]
    += t_s;
#endif
} /* block_gemm_scatter */

/*this version uses a lock to prevent multiple thread updating the same block*/
void
block_gemm_scatter_lock( int_t lb, int_t j,
                         omp_lock_t* lock,
                         Ublock_info_t *Ublock_info,
                         Remain_info_t *Remain_info,
                         double *L_mat, int_t ldl,
                         double *U_mat, int_t ldu,
                         double *bigV,
                         // int_t jj0,
                         int_t knsupc,  int_t klst,
                         int_t *lsub, int_t *usub, int_t ldt,
                         int_t thread_id,
                         int_t *indirect,
                         int_t *indirect2,
                         int_t **Lrowind_bc_ptr, double **Lnzval_bc_ptr,
                         int_t **Ufstnz_br_ptr, double **Unzval_br_ptr,
                         int_t *xsup, gridinfo_t *grid
#ifdef SCATTER_PROFILE
                         , double *Host_TheadScatterMOP, double *Host_TheadScatterTimer
#endif
                       )
{
    int_t *indirect_thread = indirect + ldt * thread_id;
    int_t *indirect2_thread = indirect2 + ldt * thread_id;
    double *tempv1 = bigV + thread_id * ldt * ldt;

    /* Getting U block information */

    int_t iukp =  Ublock_info[j].iukp;
    int_t jb   =  Ublock_info[j].jb;
    int_t nsupc = SuperSize(jb);
    int_t ljb = LBj (jb, grid);
    int_t st_col = Ublock_info[j].StCol;
    int_t ncols = Ublock_info[j].ncols;


    /* Getting L block information */
    int_t lptr = Remain_info[lb].lptr;
    int_t ib   = Remain_info[lb].ib;
    int_t temp_nbrow = lsub[lptr + 1];
    lptr += LB_DESCRIPTOR;
    int_t cum_nrow =  Remain_info[lb].StRow;

    double alpha = 1.0;
    double beta = 0.0;


    /* calling DGEMM */
    // printf(" m %d n %d k %d ldl %d st_col %d \n",temp_nbrow,ncols,ldu,ldl,st_col );
    // dgemm("N", "N", &temp_nbrow, &ncols, &ldu, &alpha,
    //       &L_mat[(knsupc - ldu)*ldl + cum_nrow], &ldl,
    //       &U_mat[st_col * ldu], &ldu, &beta, tempv1, &temp_nbrow);

    cblas_dgemm(CblasColMajor, CblasNoTrans, CblasNoTrans,
                temp_nbrow, ncols, ldu, alpha,
                &L_mat[(knsupc - ldu)*ldl + cum_nrow], ldl,
                &U_mat[st_col * ldu], ldu,
                beta, tempv1, temp_nbrow);

    /*try to get the lock for the block*/
    if (lock)       /*lock is not null*/
        while (!omp_test_lock(lock))
        {

        }

#ifdef SCATTER_PROFILE
    unsigned long long ttx = __rdtsc();
#endif
    /*Now scattering the block*/
    if (ib < jb)
    {

        SCATTER_U_CPU (
            ib, jb,
            nsupc, iukp, xsup,
            klst, temp_nbrow,
            lptr, temp_nbrow, lsub,
            usub, tempv1,
            indirect_thread,
            Ufstnz_br_ptr,
            Unzval_br_ptr,
            grid
        );
    }
    else
    {
        scatter_l (
            ib, ljb, nsupc, iukp, xsup, klst, temp_nbrow, lptr,
            temp_nbrow, usub, lsub, tempv1,
            indirect_thread, indirect2_thread,
            Lrowind_bc_ptr, Lnzval_bc_ptr, grid
        );

    }

    if (lock)
        omp_unset_lock(lock);

#ifdef SCATTER_PROFILE
    double t_s = (double) __rdtsc() - ttx;
    Host_TheadScatterMOP[thread_id * ((192 / 8) * (192 / 8)) + ((CEILING(temp_nbrow, 8) - 1)   +  (192 / 8) * (CEILING(ncols, 8) - 1))]
    += 3.0 * (double ) temp_nbrow * (double ) ncols;
    Host_TheadScatterTimer[thread_id * ((192 / 8) * (192 / 8)) + ((CEILING(temp_nbrow, 8) - 1)   +  (192 / 8) * (CEILING(ncols, 8) - 1))]
    += t_s;
#endif
} /* block_gemm_scatter_lock */

// there are following three variations of block_gemm_scatter call
/*
+---------------------------------------+
|          ||                           |
|  CPU     ||          CPU+TopRight     |
|  Top     ||                           |
|  Left    ||                           |
|          ||                           |
+---------------------------------------+
+---------------------------------------+
|          ||        |                  |
|          ||        |                  |
|          ||        |                  |
|  CPU     ||  CPU   |Accelerator       |
|  Bottom  ||  Bottom|                  |
|  Left    ||  Right |                  |
|          ||        |                  |
|          ||        |                  |
+--------------------+------------------+
                  jj_cpu
*/

int_t block_gemm_scatterTopLeft( int_t lb, /* block number in L */
				 int_t j,  /* block number in U */
                                 double* bigV, int_t knsupc,  int_t klst,
				 int_t* lsub, int_t * usub, int_t ldt,
				 int_t* indirect, int_t* indirect2, HyP_t* HyP,
                                 LUstruct_t *LUstruct,
                                 gridinfo_t* grid,
                                 SCT_t*SCT, SuperLUStat_t *stat
                               )
{
    Glu_persist_t *Glu_persist = LUstruct->Glu_persist;
    LocalLU_t *Llu = LUstruct->Llu;
    int_t* xsup = Glu_persist->xsup;
    int_t** Lrowind_bc_ptr = Llu->Lrowind_bc_ptr;
    int_t** Ufstnz_br_ptr = Llu->Ufstnz_br_ptr;
    double** Lnzval_bc_ptr = Llu->Lnzval_bc_ptr;
    double** Unzval_br_ptr = Llu->Unzval_br_ptr;
    volatile int_t thread_id = omp_get_thread_num();
    
//    printf("Thread's ID %lld \n", thread_id);
    unsigned long long t1 = _rdtsc();
    block_gemm_scatter( lb, j, HyP->Ublock_info, HyP->lookAhead_info,
			HyP->lookAhead_L_buff, HyP->Lnbrow,
                        HyP->bigU_host, HyP->ldu,
                        bigV, knsupc,  klst, lsub,  usub, ldt, thread_id,
			indirect, indirect2,
                        Lrowind_bc_ptr, Lnzval_bc_ptr, Ufstnz_br_ptr, Unzval_br_ptr,
			xsup, grid, stat
#ifdef SCATTER_PROFILE
                        , SCT->Host_TheadScatterMOP, SCT->Host_TheadScatterTimer
#endif
                      );
    unsigned long long t2 = _rdtsc();
    SCT->SchurCompUdtThreadTime[thread_id * CACHE_LINE_SIZE] += (double) (t2 - t1);
    return 0;
} /* block_gemm_scatterTopLeft */

int_t block_gemm_scatterTopRight( int_t lb,  int_t j,
                                  double* bigV, int_t knsupc,  int_t klst, int_t* lsub,
                                  int_t * usub, int_t ldt,  int_t* indirect, int_t* indirect2,
                                  HyP_t* HyP,
                                  LUstruct_t *LUstruct,
                                  gridinfo_t* grid,
                                  SCT_t*SCT, SuperLUStat_t *stat
                                )
{
    Glu_persist_t *Glu_persist = LUstruct->Glu_persist;
    LocalLU_t *Llu = LUstruct->Llu;
    int_t* xsup = Glu_persist->xsup;
    int_t** Lrowind_bc_ptr = Llu->Lrowind_bc_ptr;
    int_t** Ufstnz_br_ptr = Llu->Ufstnz_br_ptr;
    double** Lnzval_bc_ptr = Llu->Lnzval_bc_ptr;
    double** Unzval_br_ptr = Llu->Unzval_br_ptr;
   volatile  int_t thread_id = omp_get_thread_num();
    unsigned long long t1 = _rdtsc();
    block_gemm_scatter( lb, j, HyP->Ublock_info_Phi, HyP->lookAhead_info, HyP->lookAhead_L_buff, HyP->Lnbrow,
                        HyP->bigU_Phi, HyP->ldu_Phi,
                        bigV, knsupc,  klst, lsub,  usub, ldt, thread_id, indirect, indirect2,
                        Lrowind_bc_ptr, Lnzval_bc_ptr, Ufstnz_br_ptr, Unzval_br_ptr, xsup, grid, stat
#ifdef SCATTER_PROFILE
                        , SCT->Host_TheadScatterMOP, SCT->Host_TheadScatterTimer
#endif
                      );
    unsigned long long t2 = _rdtsc();
    SCT->SchurCompUdtThreadTime[thread_id * CACHE_LINE_SIZE] += (double) (t2 - t1);
    return 0;
} /* block_gemm_scatterTopRight */


int_t block_gemm_scatterBottomLeft( int_t lb,  int_t j,
                                    double* bigV, int_t knsupc,  int_t klst, int_t* lsub,
                                    int_t * usub, int_t ldt,  int_t* indirect, int_t* indirect2,
                                    HyP_t* HyP,
                                    LUstruct_t *LUstruct,
                                    gridinfo_t* grid,
                                    SCT_t*SCT, SuperLUStat_t *stat
                                  )
{
    Glu_persist_t *Glu_persist = LUstruct->Glu_persist;
    LocalLU_t *Llu = LUstruct->Llu;
    int_t* xsup = Glu_persist->xsup;
    int_t** Lrowind_bc_ptr = Llu->Lrowind_bc_ptr;
    int_t** Ufstnz_br_ptr = Llu->Ufstnz_br_ptr;
    double** Lnzval_bc_ptr = Llu->Lnzval_bc_ptr;
    double** Unzval_br_ptr = Llu->Unzval_br_ptr;
    volatile int_t thread_id = omp_get_thread_num();
    //printf("Thread's ID %lld \n", thread_id);
    unsigned long long t1 = _rdtsc();
    block_gemm_scatter( lb, j, HyP->Ublock_info, HyP->Remain_info, HyP->Remain_L_buff, HyP->Rnbrow,
                        HyP->bigU_host, HyP->ldu,
                        bigV, knsupc,  klst, lsub,  usub, ldt, thread_id, indirect, indirect2,
                        Lrowind_bc_ptr, Lnzval_bc_ptr, Ufstnz_br_ptr, Unzval_br_ptr, xsup, grid, stat
#ifdef SCATTER_PROFILE
                        , SCT->Host_TheadScatterMOP, SCT->Host_TheadScatterTimer
#endif
                      );
    unsigned long long t2 = _rdtsc();
    SCT->SchurCompUdtThreadTime[thread_id * CACHE_LINE_SIZE] += (double) (t2 - t1);
    return 0;

} /* block_gemm_scatterBottomLeft */

int_t block_gemm_scatterBottomRight( int_t lb,  int_t j,
                                     double* bigV, int_t knsupc,  int_t klst, int_t* lsub,
                                     int_t * usub, int_t ldt,  int_t* indirect, int_t* indirect2,
                                     HyP_t* HyP,
                                     LUstruct_t *LUstruct,
                                     gridinfo_t* grid,
                                     SCT_t*SCT, SuperLUStat_t *stat
                                   )
{
    Glu_persist_t *Glu_persist = LUstruct->Glu_persist;
    LocalLU_t *Llu = LUstruct->Llu;
    int_t* xsup = Glu_persist->xsup;
    int_t** Lrowind_bc_ptr = Llu->Lrowind_bc_ptr;
    int_t** Ufstnz_br_ptr = Llu->Ufstnz_br_ptr;
    double** Lnzval_bc_ptr = Llu->Lnzval_bc_ptr;
    double** Unzval_br_ptr = Llu->Unzval_br_ptr;
   volatile  int_t thread_id = omp_get_thread_num();
   // printf("Thread's ID %lld \n", thread_id);
    unsigned long long t1 = _rdtsc();
    block_gemm_scatter( lb, j, HyP->Ublock_info_Phi, HyP->Remain_info, HyP->Remain_L_buff, HyP->Rnbrow,
                        HyP->bigU_Phi, HyP->ldu_Phi,
                        bigV, knsupc,  klst, lsub,  usub, ldt, thread_id, indirect, indirect2,
                        Lrowind_bc_ptr, Lnzval_bc_ptr, Ufstnz_br_ptr, Unzval_br_ptr, xsup, grid, stat
#ifdef SCATTER_PROFILE
                        , SCT->Host_TheadScatterMOP, SCT->Host_TheadScatterTimer
#endif
                      );

    unsigned long long t2 = _rdtsc();
    SCT->SchurCompUdtThreadTime[thread_id * CACHE_LINE_SIZE] += (double) (t2 - t1);
    return 0;

} /* block_gemm_scatterBottomRight */

/******************************************************************
 * SHERRY: The following routines may conflict with dscatter.c
 ******************************************************************/
#if 1
void
scatter_l (int_t ib,
           int_t ljb,
           int_t nsupc,
           int_t iukp,
           int_t *xsup,
           int_t klst,
           int_t nbrow,
           int_t lptr,
           int_t temp_nbrow,
           int_t *usub,
           int_t *lsub,
           double *tempv,
           int_t *indirect_thread, int_t *indirect2,
           int_t **Lrowind_bc_ptr, double **Lnzval_bc_ptr, gridinfo_t *grid)
{

    int_t rel, i, segsize, jj;
    double *nzval;
    int_t *index = Lrowind_bc_ptr[ljb];
    int_t ldv = index[1];       /* LDA of the dest lusup. */
    int_t lptrj = BC_HEADER;
    int_t luptrj = 0;
    int_t ijb = index[lptrj];

    while (ijb != ib)
    {
        luptrj += index[lptrj + 1];
        lptrj += LB_DESCRIPTOR + index[lptrj + 1];
        ijb = index[lptrj];
    }


    /*
     * Build indirect table. This is needed because the
     * indices are not sorted for the L blocks.
     */
    int_t fnz = FstBlockC (ib);
    int_t dest_nbrow;
    lptrj += LB_DESCRIPTOR;
    dest_nbrow = index[lptrj - 1];

    for (i = 0; i < dest_nbrow; ++i)
    {
        rel = index[lptrj + i] - fnz;
        indirect_thread[rel] = i;

    }

    /* can be precalculated */
    for (i = 0; i < temp_nbrow; ++i)
    {
        rel = lsub[lptr + i] - fnz;
        indirect2[i] = indirect_thread[rel];
    }


    nzval = Lnzval_bc_ptr[ljb] + luptrj;
    for (jj = 0; jj < nsupc; ++jj)
    {

        segsize = klst - usub[iukp + jj];
        if (segsize)
        {
            for (i = 0; i < temp_nbrow; ++i)
            {
                nzval[indirect2[i]] -= tempv[i];
            }
            tempv += nbrow;
        }
        nzval += ldv;
    }

}

void   // SHERRY: NOT CALLED!!
scatter_u (int_t ib,
           int_t jb,
           int_t nsupc,
           int_t iukp,
           int_t *xsup,
           int_t klst,
           int_t nbrow,
           int_t lptr,
           int_t temp_nbrow,
           int_t *lsub,
           int_t *usub,
           double *tempv,
           int_t *indirect,
           int_t **Ufstnz_br_ptr, double **Unzval_br_ptr, gridinfo_t *grid)
{
#ifdef PI_DEBUG
    printf ("A(%d,%d) goes to U block \n", ib, jb);
#endif
    int_t jj, i, fnz;
    int_t segsize;
    double *ucol;
    int_t ilst = FstBlockC (ib + 1);
    int_t lib = LBi (ib, grid);
    int_t *index = Ufstnz_br_ptr[lib];

    /* reinitialize the pointer to each row of U */
    int_t iuip_lib, ruip_lib;
    iuip_lib = BR_HEADER;
    ruip_lib = 0;

    int_t ijb = index[iuip_lib];
    while (ijb < jb)            /* Search for dest block. */
    {
        ruip_lib += index[iuip_lib + 1];

        iuip_lib += UB_DESCRIPTOR + SuperSize (ijb);
        ijb = index[iuip_lib];
    }
    /* Skip descriptor.  Now point_t to fstnz index of
       block U(i,j). */

    for (i = 0; i < temp_nbrow; ++i)
    {
        indirect[i] = lsub[lptr + i] ;
    }


    iuip_lib += UB_DESCRIPTOR;

    ucol = &Unzval_br_ptr[lib][ruip_lib];
    for (jj = 0; jj < nsupc; ++jj)
    {
        segsize = klst - usub[iukp + jj];
        fnz = index[iuip_lib++];
        ucol -= fnz;
        if (segsize)            /* Nonzero segment in U(k.j). */
        {
            for (i = 0; i < temp_nbrow; ++i)
            {
                ucol[indirect[i]] -= tempv[i];
            }                   /* for i=0..temp_nbropw */
            tempv += nbrow;

        } /*if segsize */
        ucol += ilst ;

    } /*for jj=0:nsupc */

}

#endif // comment out

