#include "../include/lass.h"
#define CHECK

/**
 *
 *  @file dsss_test_dgtsv.c
 *
 *  @brief LASs-DSSs dsss_test_dgtsv routine.
 *
 *  LASs-DSSs is a software package provided by:
 *  Barcelona Supercomputing Center - Centro Nacional de Supercomputacion
 *
 * 	@author Sandra Catalán sandra.catalan@bsc.es 
 *  @author Pedro Valero-Lara pedro.valero@bsc.es
 * 	@date 2019-05-30
 *  @reviewer 
 *  @modified 
 *
 **/

/**
 *  
 *  @ingroup DDSs
 *   
 *  main:
 *  Performs the testing of the ddss_dgtsv routine. 
 *
 **/

/**
 *
 *  @sa ddss_dspmv
 *  @sa kdspmv
 *
 **/

// Read matrix in coordinate format and store it in CSR format
void coo2csr ( int n, int nz, double *a, int *i_idx, int *j_idx,
		double *csr_a, int *col_idx, int *row_start )
{
	int i, l;

	for ( i = 0; i <= n; i++ ) row_start[i] = 0;
	for ( i = 0; i < nz; i++ ) row_start[i_idx[i]+1]++;
	for ( i = 0; i < n; i++ )  row_start[i+1] += row_start[i];
	for ( l = 0; l < nz; l++ )
	{
		i = row_start[i_idx[l]];  
		csr_a[i] = a[l];
		col_idx[i] = j_idx[l];
		row_start[i_idx[l]]++;
	}
	for ( i = n; i > 0; i-- ) row_start[i] = row_start[i-1];

	row_start[0] = 0;
	for ( i = 0; i < n; i++ )
	{
		sort( col_idx, csr_a, row_start[i], row_start[i+2] );
	}
}

// Sort a row in column index
void sort ( int *col_idx, double *a, int start, int end )
{
	int i, j, it;
	double dt;

	for ( i = end-1; i > start; i-- )
	{
		for( j = start; j < i; j++ )
		{
			if ( col_idx[j] > col_idx[j+1] )
			{
				if (a)
				{
					dt = a[j];
					a[j] = a[j+1];
					a[j+1] = dt;
				}
				it = col_idx[j];
				col_idx[j] = col_idx[j+1];
				col_idx[j+1] = it;
			}
		}
	}
}

int main( int argc, char const *argv[] )
{

	if ( argc < 4 || argc > 4 )
	{
		fprintf( stderr, "Usage: ./test_dspmv ALPHA BETA MATRIX.mtx\n" );
		return NoSuccess;
	}


	// Local variables
	int i;
	int j;
	int row_elements;
	int processed_elements;
	int m;
	int n;
	int nnz;
	double alpha;
	double beta;
	double *CSR_VAL_A;
	int *CSR_ROW_PTR_A;
	int *CSR_COL_IND_A;
	double *X;
	double *Y;
	double *CSR_VAL_A_ref;
	int *CSR_ROW_PTR_A_ref;
	int *CSR_COL_IND_A_ref;
	double *X_ref;
	double *Y_ref;
	double *CSR_VAL_A_test;
	int *CSR_ROW_PTR_A_test;
	int *CSR_COL_IND_A_test;
	double *COO_VAL_A;
	int *COO_ROW_PTR_A;
	int *COO_COL_IND_A;
	double *X_test;
	double *Y_test;
	double error;
	double max_error;
	double count_error;	
	struct timeval start, end;
	double time_ref;
	double time;
	double flops;
	double flops_dsss; 
	double flops_ref; 
	enum LASS_RETURN ret;

	char banner[64];
	char mtx[64];
	char crd[64];
	char data_type[64];
	char storage_scheme[64];

	MM_typecode matcode;

	sparse_matrix_t matrix;
	struct matrix_descr descr;

	descr.type = SPARSE_MATRIX_TYPE_GENERAL;

	//char *file = "in-2004.mtx";
	char file[100];

	alpha = atoi( argv[1] );
	beta  = atoi( argv[2] );

	strcpy( file, argv[3] );

	//Check if matrix can be processed
	max_error = 0.0;
	count_error = 0.0;

	mm_read_mtx_crd( file, &m, &n, &nnz, 
			&COO_ROW_PTR_A, &COO_COL_IND_A, 
			&COO_VAL_A, &matcode);

	if( mm_is_pattern( matcode ) )
	{
		COO_VAL_A = (double *) malloc(nnz * sizeof(double));
		for( i = 0; i < nnz; i++ ) COO_VAL_A[i] =- 1.0;
	}

	for( i = 0; i < nnz; i++ )
	{
		COO_ROW_PTR_A[i]--;
		COO_COL_IND_A[i]--;
	}

	// Vector allocation
	CSR_VAL_A       = ( double * ) malloc( sizeof( double ) * nnz );
	CSR_ROW_PTR_A   = ( int * ) malloc( sizeof( int ) * (m+1) );
	CSR_COL_IND_A   = ( int * ) malloc( sizeof( int ) * nnz );
	X               = ( double * ) malloc( sizeof( double ) * m );
	Y               = ( double * ) malloc( sizeof( double ) * m );

	CSR_VAL_A_ref       = ( double * ) malloc( sizeof( double ) * nnz );
	CSR_ROW_PTR_A_ref   = ( int * ) malloc( sizeof( int ) * (m+1) );
	CSR_COL_IND_A_ref   = ( int * ) malloc( sizeof( int ) * nnz );
	X_ref               = ( double * ) malloc( sizeof( double ) * m );
	Y_ref               = ( double * ) malloc( sizeof( double ) * m );

	CSR_VAL_A_test       = ( double * ) malloc( sizeof( double ) * nnz );
	CSR_ROW_PTR_A_test   = ( int * ) malloc( sizeof( int ) * (m+1) );
	CSR_COL_IND_A_test   = ( int * ) malloc( sizeof( int ) * nnz );
	X_test               = ( double * ) malloc( sizeof( double ) * m );
	Y_test               = ( double * ) malloc( sizeof( double ) * m );

	coo2csr(n, nnz, COO_VAL_A, COO_ROW_PTR_A , COO_COL_IND_A, 
			CSR_VAL_A, CSR_COL_IND_A, CSR_ROW_PTR_A);

	for ( i = 0; i < m; i++ )
	{
		X[i] = 1.0; //(double) ( rand() + DBL_MIN );
		Y[i] = 1.0; //(double) ( rand() + DBL_MIN );
	}

	memcpy( CSR_VAL_A_ref, CSR_VAL_A, nnz * sizeof( double ) );
	memcpy( CSR_ROW_PTR_A_ref, CSR_ROW_PTR_A, ( m + 1 ) * sizeof( int ) );
	memcpy( CSR_COL_IND_A_ref, CSR_COL_IND_A, nnz * sizeof( int ) );
	memcpy( X_ref, X, m * sizeof( double ) );
	memcpy( Y_ref, Y, m * sizeof( double ) );

	memcpy( CSR_VAL_A_test, CSR_VAL_A, nnz * sizeof( double ) );
	memcpy( CSR_ROW_PTR_A_test, CSR_ROW_PTR_A, ( m + 1 ) * sizeof( int ) );
	memcpy( CSR_COL_IND_A_test, CSR_COL_IND_A, nnz * sizeof( int ) );
	memcpy( X_test, X, m * sizeof( double ) );
	memcpy( Y_test, Y, m * sizeof( double ) );

	// DGEMM FLOPS
	flops = FLOPS_DSPMV( n ); 

#ifdef LASs_WITH_MKL    	
	mkl_sparse_d_create_csr (&matrix, SPARSE_INDEX_BASE_ZERO, m, n, 
			CSR_ROW_PTR_A_ref, CSR_ROW_PTR_A_ref + 1, 
			CSR_COL_IND_A_ref, CSR_VAL_A_ref);


	mkl_sparse_d_mv( SPARSE_OPERATION_NON_TRANSPOSE, (double) alpha, matrix,
			descr, X, (double) beta, Y );
#else

	dspmvseq( m, n, nnz, alpha,
			CSR_VAL_A_ref, CSR_ROW_PTR_A_ref,
			CSR_COL_IND_A_ref, X_ref,
			beta, Y_ref );
#endif


	gettimeofday( &start, NULL );
#ifdef LASs_WITH_MKL    	
	mkl_sparse_d_mv( SPARSE_OPERATION_NON_TRANSPOSE, (double) alpha, 
			matrix, descr, X, 
			(double) beta, Y );
#else
	dspmvseq( m, n, nnz, alpha,
			CSR_VAL_A_ref, CSR_ROW_PTR_A_ref,
			CSR_COL_IND_A_ref, X_ref,
			beta, Y_ref );
#endif
	gettimeofday( &end, NULL );

	// TIME consumed by ref. spmv routine	
	time_ref = ( double ) (((end.tv_sec * 1e6 + end.tv_usec)
				- (start.tv_sec * 1e6 + start.tv_usec)) / 1e6);

	// FLOPS achieved by the ref. spmv routine
	flops_ref = (double) flops / time_ref;

	ret = dsss_dspmv( m, n, alpha,
			CSR_VAL_A_test, CSR_ROW_PTR_A_test,
			CSR_COL_IND_A_test, X_test,
			beta, Y_test );

	if ( ret == NoSuccess )
		return ret;	

	gettimeofday( &start, NULL );

	ret = dsss_dspmv( m, n, alpha,
			CSR_VAL_A, CSR_ROW_PTR_A,
			CSR_COL_IND_A, X,
			beta, Y );

	gettimeofday( &end, NULL );

	if ( ret == NoSuccess )
		return ret;	

	// TIME consumed by dsss_dspmv	
	time = ( double ) (((end.tv_sec * 1e6 + end.tv_usec)
				- (start.tv_sec * 1e6 + start.tv_usec)) / 1e6);

	// FLOPS achieved by the dsss_dspmv routine
	flops_dsss = flops / time;

#ifdef CHECK
	printf("Check on\n");
	// Error computation
	for ( i = 0; i < m; i++ )
	{
		error = fabs( Y_ref[i] - Y[i] );

		if ( max_error < error )
		{
			max_error = error;
		}
		count_error += error;
	}
#endif

	fprintf( stdout, "Max. error = %1.2f\n", max_error );
	fprintf( stdout, "Av. error = %1.2f\n", count_error / m );

	fprintf( stdout, "DDSs_DSPMV TIME(s) = %1.2f\n", time );
	fprintf( stdout, "REF. DSPMV TIME(s) = %1.2f\n", time_ref );
	fprintf( stdout, "DDSs_DSPMV GFLOPS = %1.2f\n", flops_dsss / 1e9 );
	fprintf( stdout, "REF. DSPMV GFLOPS = %1.2f\n", flops_ref / 1e9 );

	free( CSR_VAL_A );
	free( CSR_ROW_PTR_A );
	free( CSR_COL_IND_A );
	free( X );
	free( Y );
	free( CSR_VAL_A_test );
	free( CSR_ROW_PTR_A_test );
	free( CSR_COL_IND_A_test );
	free( X_test );
	free( Y_test );
	free( CSR_VAL_A_ref );
	free( CSR_ROW_PTR_A_ref );
	free( CSR_COL_IND_A_ref );
	free( X_ref );
	free( Y_ref );

	return Success;

}
