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

/**
 *
 * 	@file ddss_test_dtrmm.c
 *
 * 	@brief LASs-DDSs ddss_test_dtrmm routine.
 *
 * 	LASs-DDSs is a software package provided by:
 * 	Barcelona Supercomputing Center - Centro Nacional de Supercomputacion
 *
 * 	@author Pedro Valero-Lara   pedro.valero@bsc.es
 * 	@author Boro Sofranac       boro.sofranac@bsc.es
 * 	@date 2018-03-19
 * 	@reviewer 
 * 	@modified 
 *
 **/

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

/**
 *
 *	@sa ddss_dtrmm
 *	@sa kdtrmm
 *
 **/

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

	if ( argc < 7 || argc > 7 ) 
	{
		fprintf( stderr, "Usage: ./test_dtrmm M N TRANS_A DIAG UPLO SIDE\n" );
		return NoSuccess;
	}

	// Local variables
	int i, j;
	int m, n, k;
	int ldA, ldB;
	enum DDSS_SIDE side;
	enum DDSS_UPLO uplo;
	enum DDSS_DIAG diag;
	enum DDSS_TRANS transA;
	const char *transA_input = NULL;
	const char *diag_input = NULL;
	const char *uplo_input = NULL;
	const char *side_input = NULL;
	double alpha;
	double *A;
	double *A_ref;
	double *A_test;
	double *B;
	double *B_ref;
	double *B_test;
	double error;
	double max_error;
	double count_error;
	struct timeval start, end;
	double time;
	double flops;
	double flops_ddss;
	double flops_ref;
	int seed[] = {0, 0, 0, 1};
	enum LASS_RETURN ret;
	lapack_int retval;

	m = atoi( argv[1] );
	n = atoi( argv[2] );

	if ( strlen( argv[3] ) != 1 ) 
	{
		fprintf( stderr, "Illegal value of TRANS_A, TRANS_A can be T or N\n" );
		return NoSuccess;
	}
	transA_input = argv[3];

	if ( strlen( argv[4] ) != 1 ) 
	{
		fprintf( stderr, "Illegal value of DIAG, DIAG can be U or N\n" );
		return NoSuccess;
	}
	diag_input = argv[4];

	if ( strlen(argv[5] ) != 1 ) 
	{
		fprintf( stderr, "Illegal value of UPLO, UPLO can be U or L\n" );
		return NoSuccess;
	}
	uplo_input = argv[5];

	if ( strlen( argv[6] ) != 1 ) 
	{
		fprintf( stderr, "Illegal value of SIDE, SIDE can be L or R\n" );
		return NoSuccess;
	}
	side_input = argv[6];

	// Checking input
	if ( m < 0 ) 
	{
		fprintf( stderr, "Illegal value of M, M must be >= 0\n" );
		return NoSuccess;
	}
	if ( n < 0 ) 
	{
		fprintf( stderr, "Illegal value of N, N must be >= 0\n" );
		return NoSuccess;
	}

	if (transA_input[0] == 'T') 
	{
		transA = Trans;
	} 
	else if (transA_input[0] == 'N') 
	{
		transA = NoTrans;
	} 
	else 
	{
		fprintf( stderr, "Illegal value of TRANS_A, TRANS_A can be T or N\n" );
		return NoSuccess;
	}

	if ( diag_input[0] == 'U' ) 
	{
		diag = Unit;
	} 
	else if ( diag_input[0] == 'N' ) 
	{
		diag = NonUnit;
	} 
	else 
	{
		fprintf( stderr, "Illegal value of DIAG, DIAG can be U or N\n" );
		return NoSuccess;
	}

	if ( uplo_input[0] == 'U' ) 
	{
		uplo = Upper;
	} 
	else if ( uplo_input[0] == 'L' ) 
	{
		uplo = Lower;
	} 
	else 
	{
		fprintf( stderr, "Illegal value of UPLO, UPLO can be U or L\n" );
		return NoSuccess;
	}

	if ( side_input[0] == 'L' ) 
	{
		side = Left;
		ldA = k = m;
	} 
	else if ( side_input[0] == 'R' ) 
	{
		side = Right;
		ldA = k = n;
	} 
	else 
	{
		fprintf( stderr, "Illegal value of SIDE, SIDE can be L or R\n" );
		return NoSuccess;
	}

	ldB = n;
	max_error = 0.0;
	count_error = 0.0;

	// Matrices allocation
    A = ( double * ) malloc( sizeof( double ) * k * ldA );
    A_ref = ( double * ) malloc( sizeof( double ) * k * ldA );
    A_test = ( double * ) malloc( sizeof( double ) * k * ldA );
	B = ( double * ) malloc( sizeof( double ) * m * ldB );
	B_ref = ( double * ) malloc( sizeof( double ) * m * ldB );
	B_test = ( double *) malloc( sizeof( double ) * m * ldB );
	
	// Alpha initialization
	alpha = ( double ) rand() / ( double ) rand() + DBL_MIN;

	// Matrices initialization
	retval = LAPACKE_dlarnv( 1, seed, k * ldA, A );
	assert( retval == 0 );
		
	retval = LAPACKE_dlarnv( 1, seed, m * ldB, B );
	assert( retval == 0 );
	
	memcpy( A_test, A, k * ldA * sizeof( double ) );
	memcpy( A_ref, A, k * ldA * sizeof( double ) );
	
	memcpy( B_test, B, m * ldB * sizeof( double ) );
	memcpy( B_ref, B, m * ldB * sizeof( double ) );
	
	// DTRMM FLOPS
	flops = FLOPS_DTRMM( side, m, n );

	gettimeofday( &start, NULL );

	cblas_dtrmm( CblasRowMajor, 
				 ( CBLAS_SIDE ) side, 		 ( CBLAS_UPLO ) uplo,
				 ( CBLAS_TRANSPOSE ) transA, ( CBLAS_DIAG ) diag, 
				 m, n, 
				 alpha, A_ref, ldA,
					    B_ref, ldB );

	gettimeofday( &end, NULL );

	// FLOPS achieved by the (reference) cblas_dtrmm routine
	flops_ref = ( double ) flops / ((( end.tv_sec * 1e6 + end.tv_usec )
					- ( start.tv_sec * 1e6 + start.tv_usec )) / 1e6 );
	
	ret = ddss_dtrmm( side, uplo, 
					  transA, diag, 
					  m, n, 
					  alpha, A_test, ldA, 
					   	     B_test, ldB );
	
	gettimeofday( &start, NULL );

	ret = ddss_dtrmm( side, uplo, 
					  transA, diag, 
					  m, n, 
					  alpha, A, ldA, 
					   	     B, ldB );

	gettimeofday( &end, NULL );
	if (ret == NoSuccess)
		return ret;

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

	// FLOPS achieved by the ddss_dtrmm routine
	flops_ddss = flops / time;
	
	// Error computation
	for ( i = 0; i < m; i++ ) 
	{
		for (  j = 0; j < n; j++ ) 
		{
			error = fabs( B[i * n + j] - B_ref[i * n + j] );
			if ( error > max_error )
				max_error = error;
			count_error += error;
		}
	}
	
	fprintf( stdout, "Max. error = %1.2f\n", max_error );
	fprintf( stdout, "Av. error = %1.2f\n", count_error / (m * n) );

	fprintf( stdout, "DDSs_DTRMM TIME(s) = %1.2f\n", time );
	fprintf( stdout, "DDSs_DTRMM GFLOPS = %1.2f\n", flops_ddss / 1e9 );
	fprintf( stdout, "REF. DTRMM GFLOPS = %1.2f\n", flops_ref / 1e9 );

	// Matrices free 
	free( A );
	free( A_ref );
	free( A_test );
	free( B );
	free( B_ref );
	free( B_test );
	
	return Success;

}
