#include "../include/lass.h"

int int_pow( int base, int exp )
{
    int result = 1;
    while (exp)
    {
        if (exp & 1)
            result *= base;
        exp /= 2;
        base *= base;
    }
    return result;
}

int switch_point( int NUMCORES )
{
	//Local variables
	int switchpoint;
	int num_operations;
	int ref_size;
	int pcr;
	int tmp;
	int red;

	/*
	The switch point computation depends on the number of cores available, not 
	on the size of the problems to be computed. It is because of this that we 
	use a reference size (ref_size), instead of the proper size of the 
	tridiagonal system. For big problems, we can have overflow problems to 
	compute the switch point, using the size of the system.
	*/
	ref_size = 100000 * NUMCORES;

	//Number of operation for one PCR iteration
	pcr = ( 12 * ref_size ) / NUMCORES;
	
	switchpoint = 1;
	
	num_operations = ( pcr * switchpoint ) 
						+ ( 8 * ( ref_size / int_pow( 2, switchpoint ) ) ); 
	
	red = 100;
	
	while ( red > 20 )
	{
		tmp = (int) num_operations;
		switchpoint++;
		num_operations = ( pcr * switchpoint ) 
							+ ( 8 * ( ref_size / int_pow( 2, switchpoint ) ) );
		red = 100 - ( num_operations * 100 / tmp ); 

	} 

	return switchpoint-=1;

}

/**
 *
 *  @file kdgtsv.c
 *
 *  @brief LASs-DSSs kdgtsv routine.
 *
 *  LASs-DSSs is a software package provided by:
 *  Barcelona Supercomputing Center - Centro Nacional de Supercomputacion
 *
 *  @author Pedro Valero-Lara pedro.valero@bsc.es
 *  @date 2017-01-02
 *  @reviewer 
 *  @modified 
 *
 **/

/**
 *  
 *  @ingroup DSSS
 *   
 *  Solves the system of equations, where A is an N-by-N tridiagonal matrix: 
 *
 *      A * X = RHS
 *
**/

/**
 *  
 *  @param[in]
 *  N       int.
 *          N specifies the size of the vectors (DL, D, DU), which composed the
 *			tridiagonal matrix A, and the size of the vector RHS.
 *          N must be greater than zero.
 *    
 *  @param[in]
 *  DL      double *.
 *          DL is a pointer to the lower vector of the tridiagonal matrix A.  
 *
 *  @param[in]
 *  D       double *.
 *          D is a pointer to the diagonal vector of the tridiagonal matrix A.  
 *
 *  @param[in]
 *  DU      double *.
 *          DU is a pointer to the upper vector of the tridiagonal matrix A.  
 *  
 *	@param[in,out]
 *  RHS     double *.
 *          RHS is a pointer to the right-hand side vector of the equation.
 *          On exit, RHS is overwritten by the vector solution X.  
 *   
**/

/**
 *
 *  @retval Success successful exit
 *  @retval NoSuccess unsuccessful exit
 *
 **/

/**
 *
 *  @sa dsss_dgtsv
 *
 **/

enum LASS_RETURN kdgtsv( int N,
                         double *DL, double *D, double *DU,
                         double *RHS )
{
    //Local variables
    int i;
    int j;
    int offset;
    int nThomas;
    int sizeThomas;
    int offsetThomas;
	int switchpoint;
	int writeon;
	int readfrom;
    int idThomas;
    int posThomas;
    double alpha;
    double beta;
    double auxL;
    double auxR;
    double DLL;
    double DLR;
    double DUL;
    double DUR;

    int grain; 

    double *tmpDL[2];
    tmpDL[0] = malloc ( N * sizeof( double ) );
    tmpDL[1] = malloc ( N * sizeof( double ) );
    double *tmpD[2];
    tmpD[0] = malloc ( N * sizeof( double ) );
    tmpD[1] = malloc ( N * sizeof( double ) );
    double *tmpDU[2];
    tmpDU[0] = malloc ( N * sizeof( double ) );
    tmpDU[1] = malloc ( N * sizeof( double ) );
    double *tmpRHS[2];
    tmpRHS[0] = malloc ( N * sizeof( double ) );
    tmpRHS[1] = malloc ( N * sizeof( double ) );

	//Computing switch point
	switchpoint = switch_point( NUM_CORES );
    nThomas = int_pow( 2, switchpoint );
    sizeThomas = N / nThomas;
    offsetThomas = nThomas;

    double(*tmpThomasDL)[sizeThomas]  = malloc ( sizeThomas * nThomas 
													* sizeof( double ) );
    double(*tmpThomasD)[sizeThomas]   = malloc ( sizeThomas * nThomas 
													* sizeof( double) );
    double(*tmpThomasDU)[sizeThomas]  = malloc ( sizeThomas * nThomas 
													* sizeof( double ) );
    double(*tmpThomasRHS)[sizeThomas] = malloc ( sizeThomas * nThomas 
													* sizeof( double ) );

    for ( i = 1; i < switchpoint; i++ )
    {
        offset = int_pow( 2, i - 1 );
	grain = N;
	if ( N/ NUM_CORES > 0)
		grain = N/NUM_CORES;		
		#pragma omp taskloop private( writeon, readfrom, \
                                  alpha, auxL, DLL, DUL, \
                                  beta, auxR, DLR, DUR ) \
				shared( tmpDL, tmpD, tmpDU, tmpRHS ) \
				grainsize( grain ) \
						 label( PCR )
        for ( j = 0; j < N; j++ )
    	{
            if ( i == 1 ) //Read from input parameters
            {
                if ( j - offset >= 0 )
                {
                    alpha = -DL[j] / D[j - offset];
                    auxL = alpha * RHS[j - offset];
                    DLL = DL[j - offset];
                    DUL = DU[j - offset];
                }
                else
                {
                    alpha = 0.0;
                    auxL = 0.0;
                    DLL = 0.0;
                    DUL = 0.0;
                }

                if ( j + offset < N )
                {
                    beta = -DU[j] / D[j + offset];
                    auxR = beta * RHS[j + offset];
                    DLR = DL[j + offset];
                    DUR = DU[j + offset];
                }
                else
                {
                    beta = 0.0;
                    auxR = 0.0;
                    DLR  = 0.0;
                    DUR  = 0.0;
                }

                tmpRHS[1][j] = RHS[j] + auxL + auxR;
                tmpD[1][j]  = D[j] + ( alpha * DUL ) + ( beta * DLR );
                tmpDL[1][j] = alpha * DLL;
                tmpDU[1][j] = beta * DUR;
            }
			/*
			Read from [1][x] and write on [0][x] or viceversa 
			depending on PCR iteration
			*/
            else             
			{
                writeon = i % 2;
            	readfrom = !writeon;

                if ( j - offset >= 0 )
                {
                    alpha = -tmpDL[readfrom][j] / tmpD[readfrom][j - offset];
                    auxL  = alpha * tmpRHS[readfrom][j - offset];
                    DLL   = tmpDL[readfrom][j - offset];
                    DUL   = tmpDU[readfrom][j - offset];
                }
                else
                {
                    alpha = 0.0;
                    auxL  = 0.0;
                    DLL   = 0.0;
                    DUL   = 0.0;
                }

                if ( j + offset < N )
                {
                    beta = -tmpDU[readfrom][j] / tmpD[readfrom][j + offset];
                    auxR = beta * tmpRHS[readfrom][j + offset];
                    DLR  = tmpDL[readfrom][j + offset];
                    DUR  = tmpDU[readfrom][j + offset];
                }
                else
                {
                    beta = 0.0;
                    auxR = 0.0;
                    DLR  = 0.0;
                    DUR  = 0.0;
                }

                tmpRHS[writeon][j] = tmpRHS[readfrom][j] + auxL + auxR;
                tmpD[writeon][j]   = tmpD[readfrom][j] + ( alpha * DUL ) 
												       + ( beta * DLR );
                tmpDL[writeon][j]  = alpha * DLL;
                tmpDU[writeon][j]  = beta * DUR;
            }
        }
		#pragma oss taskwait
    }

    //Last PCR iteration 
    offset = int_pow( 2, switchpoint - 1 );

    grain = N;
    if( N/NUM_CORES > 0)
    	grain = N/NUM_CORES;
    #pragma omp taskloop private( writeon, readfrom, \
                              idThomas, posThomas, \
                              alpha, auxL, DLL, DUL, \
                              beta, auxR, DLR, DUR ) \
                    shared( tmpThomasDL, tmpThomasD, \
                            tmpThomasDU, tmpThomasRHS ) \
		    grainsize( grain ) \
                    label( last PCR )


    for ( j = 0; j < N; j++ )
    {
		idThomas = j % nThomas;
        posThomas = j / nThomas;

		if ( i == 1 ) //Read from input parameters
        {
            if ( j - offset >= 0 )
            {
                alpha = -DL[j] / D[j - offset];
                auxL  = alpha * RHS[j - offset];
                DLL   = DL[j - offset];
                DUL   = DU[j - offset];
            }
            else
            {
                alpha = 0.0;
                auxL  = 0.0;
                DLL   = 0.0;
                DUL   = 0.0;
            }

            if ( j + offset < N )
            {
                beta = -DU[j] / D[j + offset];
                auxR = beta * RHS[j + offset];
                DLR  = DL[j + offset];
                DUR  = DU[j + offset];
            }
            else
            {
                beta = 0.0;
                auxR = 0.0;
                DLR  = 0.0;
                DUR  = 0.0;
            }

            tmpThomasRHS[idThomas][posThomas] = RHS[j] + auxL + auxR;
            
            tmpThomasD[idThomas][posThomas]   = D[j] + ( alpha * DUL ) 
											     + ( beta * DLR );
            tmpThomasDL[idThomas][posThomas]  = alpha * DLL;
            tmpThomasDU[idThomas][posThomas]  = beta * DUR;
        } 
		/*
		Read from [1][x] and write on [0][x] or viceversa 
		depending on PCR iteration
        */
		else
        {
            writeon = i % 2;
            readfrom = !writeon;

            if ( j - offset >= 0 )
            {
                alpha = -tmpDL[readfrom][j] / tmpD[readfrom][j - offset];
                auxL  = alpha * tmpRHS[readfrom][j - offset];
                DLL   = tmpDL[readfrom][j - offset];
                DUL   = tmpDU[readfrom][j - offset];
            }
            else
            {
                alpha = 0.0;
                auxL  = 0.0;
                DLL   = 0.0;
                DUL   = 0.0;
            }

            if ( j + offset < N )
            {
                beta = -tmpDU[readfrom][j] / tmpD[readfrom][j + offset];
                auxR = beta * tmpRHS[readfrom][j + offset];
                DLR  = tmpDL[readfrom][j + offset];
                DUR  = tmpDU[readfrom][j + offset];
            }
            else
            {
                beta = 0.0;
                auxR = 0.0;
                DLR  = 0.0;
                DUR  = 0.0;
            }

            tmpThomasRHS[idThomas][posThomas] = tmpRHS[readfrom][j] 
												+ auxL + auxR;
            tmpThomasD[idThomas][posThomas]   = tmpD[readfrom][j] 
												+ ( alpha * DUL ) 
												+ ( beta * DLR );
            tmpThomasDL[idThomas][posThomas]  = alpha * DLL;
            tmpThomasDU[idThomas][posThomas]  = beta * DUR;
        } 
    }
	#pragma oss taskwait    

    #pragma omp taskloop private( j, posThomas ) \
                     shared( tmpThomasDL, tmpThomasD, \
                             tmpThomasDU, tmpThomasRHS) \
		    grainsize( 1 ) \
                    label( Thomas & copyback )
	
    for ( idThomas = 0; idThomas < nThomas; idThomas++ )
    {          
        dgtsvseq( sizeThomas, tmpThomasDL[idThomas], tmpThomasD[idThomas], 
							  tmpThomasDU[idThomas], tmpThomasRHS[idThomas] );

        posThomas=0;
        for( j = idThomas; j < N; j += offsetThomas ) 
		{
            RHS[j] = tmpThomasRHS[idThomas][posThomas];
            posThomas++;
        }

    }
	#pragma oss taskwait
	
	free( tmpDL[0] );
    free( tmpD[0] );
    free( tmpDU[0] );
    free( tmpRHS[0] );
	free( tmpDL[1] );
    free( tmpD[1] );
    free( tmpDU[1] );
    free( tmpRHS[1] );
	
	free( tmpThomasDL );
    free( tmpThomasD );
    free( tmpThomasDU );
    free( tmpThomasRHS );
	
	return Success;
    
}
