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

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

/**
 *  
 *	@ingroup DDSS
 *   
 *	Performs one of the matrix-matrix operations:
 *
 *		C = ALPHA * A * B + BETA * C
 *	or
 *
 *		C = ALPHA * B * A + BETA * C
 *	
 *
 *	where op( X ) is one of:
 *
 *		op( X ) = X      or
 *		op( X ) = X**T 
 *	
 *	ALPHA and BETA are scalars, A is a symmetric matrix, and B and C 
 *	are M by N matrices.
 *
**/

/**
 *  
 * 	@param[in]
 *	SIDE	enum DDSS_SIDE.
 *	        UPLO specifies the position of the symmetric A matrix in 
 *			the operation:
 *       	- Left:  C = ALPHA *  A *  B + BETA * C
 *      	- Right: C = ALPHA *  B *  A + BETA * C
 *  
 * 	@param[in]
 *	UPLO	enum DDSS_UPLO.
 *	        UPLO specifies the form of A is stored:
 *       	- Lower: Lower triangle of A is stored. The upper traingular part is
 *       	not referenced.
 *      	- Upper: Upper triangle of A is stored. The lower triangular part is
 *      	not referenced.
 *  
 *	@param[in]
 *	M       int.
 *			M specifies the number of rows of the matrix C. 
 *			M must be equal or greater than zero.
 *  
 *	@param[in]
 *	N       int.
 *			N specifies the number of columns of the matrix C. 
 *			N must be equal or greater than zero.
 *   
 *	@param[in]
 *	ALPHA   double.
 *   
 *	@param[in]
 *	A  		double *.
 *          A is a pointer to a matrix of dimension Ma ( rows ) by Na 
 *			(columns), where Ma is M and Na is M when SIDE = Left, and
 *			Ma is N and Na is N when SIDE = Right
 *
 *
 *  @param[in]
 *	LDA     int.
 *          LDA specifies the number of columns of A ( row-major order ). 
 *			LDA must be at least max( 1, Na ). 
 *   
 *	@param[in]
 *	B  		double *.
 *          B is a pointer to a matrix of dimension M by N. 
 *   
 *  @param[in]
 *	LDB     int.
 *          LDB specifies the number of columns of B ( row-major order ). 
 *			LDB must be at least max( 1, N ). 
 *
 *	@param[in]
 *	BETA    double.
 *   
 *	@param[in,out]
 *	C  		double *.
 *	    	C is a pointer to a matrix of dimension M by N.
 *          On exit, C is overwritten by the M by N matrix. 
 *   
 *	@param[in]
 *	LDC     int.
 * 	    	LDC specifies the number of columns of C ( row-major order ).
 * 	    	LDC must be at least max( 1, N ).
 *   
 **/

/**
 *
 *	@retval Success sucessful exit
 *	@retval NoSuccess unsucessful exit
 *
 **/

/**
 *
 *	@sa ddss_dgemm
 *	@sa ddss_tile
 *	@sa ddss_flat2tiled
 *	@sa ddss_symflat2tiled
 *	@sa ddss_tiled2flat
 *	@sa ddss_symtiled2flat
 *
 **/

enum LASS_RETURN kdsymm( enum DDSS_SIDE SIDE, enum DDSS_UPLO UPLO, 
		int M, int N,
		const double ALPHA, double *A, int LDA,
					  		double *B, int LDB,
		const double BETA,  double *C, int LDC )
{

	// Local variables
	int mt, kt, nt;
	int mi, ki, ni;
	int Am, An;
	int Amt, Ant;
	int tile_size_m;
	int tile_size_n;
	int tile_size_k;
	int k_check;
	double betat;
	
	// Number of tiles
	if ( M % TILE_SIZE == 0 )
	{
		mt = M / TILE_SIZE;
	}
	else
	{
		mt = ( M / TILE_SIZE ) + 1;
	}

	if ( N % TILE_SIZE == 0 )
	{
		nt = N / TILE_SIZE;
	}
	else
	{
		nt = ( N / TILE_SIZE ) + 1;
	}
	
	if ( SIDE == Left )
	{
		if ( M % TILE_SIZE == 0 )
		{
			kt = M / TILE_SIZE;
		}
		else
		{
			kt = ( M / TILE_SIZE ) + 1;
		}
	}
	else
	{	
		if ( N % TILE_SIZE == 0 )
		{
			kt = N / TILE_SIZE;
		}
		else
		{
			kt = ( N / TILE_SIZE ) + 1;
		}
	}

	/****************************
	--Tile matrices declaration--
	****************************/

	if ( SIDE == Left )
	{
		Am = M;
		An = M;
		Amt = mt;
		Ant = mt;
	}
	else
	{
		Am = N;
		An = N;
		Amt = nt;
		Ant = nt;
	}

	/***************************
	--Tile matrices allocation--
	***************************/
	
	double (*TILE_A)[Ant][TILE_SIZE * TILE_SIZE] = malloc ( Amt * Ant * 
		TILE_SIZE * TILE_SIZE * sizeof( double ) );

	if ( TILE_A == NULL)
	{
		fprintf( stderr, "Failure in kdsymm for matrix TILE_A\n" );
		return NoSuccess;
	}

	double (*TILE_B)[nt][TILE_SIZE * TILE_SIZE] = malloc ( mt * nt * 
		TILE_SIZE * TILE_SIZE * sizeof( double ) );
    
	if ( TILE_B == NULL)
	{
		fprintf( stderr, "Failure in kdsymm for matrix TILE_B\n" );
		return NoSuccess;
	}

	double (*TILE_C)[nt][TILE_SIZE * TILE_SIZE] = malloc ( mt * nt * 
		TILE_SIZE * TILE_SIZE * sizeof( double ) );

	if ( TILE_C == NULL)
	{
		fprintf( stderr, "Failure in kdsymm for matrix TILE_C\n" );
		return NoSuccess;
	}

	/************************************************
	// --From flat data layout to tiled data layout--	
	************************************************/

	// From flat and symmetric matrix A to tile matrix TILE_A
	ddss_dsymflat2tiled( Am, An, A, LDA, Amt, Ant, TILE_A, UPLO );
	
	// From flat matrix B to tile matrix TILE_B 
	ddss_dflat2tiled( M, N, B, LDB, mt, nt, TILE_B );
	
	// From flat matrix C to tile matrix TILE_C 
	ddss_dflat2tiled( M, N, C, LDC, mt, nt, TILE_C );
	
	/*************
	--DSYMM tile--
	*************/	

	for ( mi = 0; mi < mt; mi++ )
	{
		tile_size_m  = ddss_tile_size( M, mi );
		for ( ni = 0; ni < nt; ni++ )
		{
			tile_size_n  = ddss_tile_size( N, ni );
			if ( SIDE == Left )
			{
				// --SIDE = Left & UPLO = Lower--	
				if ( UPLO == Lower)
				{
					for ( ki = 0; ki < kt; ki++ )
					{
						if (ki == 0)
						{
							betat = BETA;
						}
						else 
						{
							betat = 1.0;
						}
						tile_size_k  = ddss_tile_size( M, ki );
					
						if ( ki < mi )
						{
							#pragma oss task in( TILE_A[mi][ki] ) \
                     			in( TILE_B[ki][ni] ) \
                     			inout( TILE_C[mi][ni] ) \
								shared( TILE_A, TILE_B, TILE_C ) \
 								firstprivate( mi, ni, ki, betat )
							cblas_dgemm( CblasRowMajor, 
										( CBLAS_TRANSPOSE ) NoTrans,
										( CBLAS_TRANSPOSE ) NoTrans,
										tile_size_m, 
										tile_size_n, 
									   	tile_size_k,
										ALPHA, TILE_A[mi][ki], tile_size_k, 
								     		   TILE_B[ki][ni], tile_size_n, 
							 			betat, TILE_C[mi][ni], tile_size_n );
						}
						else
						{
							if ( ki == mi )
							{
								#pragma oss task in( TILE_A[ki][ki] ) \
                     				in( TILE_B[ki][ni] ) \
                     				inout( TILE_C[mi][ni] ) \
									shared( TILE_A, TILE_B, TILE_C ) \
 									firstprivate( mi, ni, ki, betat )
								cblas_dsymm( CblasRowMajor,
									   		( CBLAS_SIDE ) SIDE,
									   		( CBLAS_UPLO ) UPLO,
									   		tile_size_m, tile_size_n,
									   		ALPHA, TILE_A[ki][ki], tile_size_k,
										      	   TILE_B[ki][ni], tile_size_n,
									   		betat, TILE_C[mi][ni], 
											tile_size_n );
							}	
							else
							{
								#pragma oss task in( TILE_A[ki][mi] ) \
                     				in( TILE_B[ki][ni] ) \
                     				inout( TILE_C[mi][ni] ) \
									shared( TILE_A, TILE_B, TILE_C ) \
 									firstprivate( mi, ni, ki, betat )
								cblas_dgemm( CblasRowMajor, 
											( CBLAS_TRANSPOSE ) Trans,
											( CBLAS_TRANSPOSE ) NoTrans,
											tile_size_m, 
											tile_size_n, 
									   		tile_size_k,
											ALPHA, TILE_A[ki][mi], tile_size_m, 
								     		   	   TILE_B[ki][ni], tile_size_n, 
							 				betat, TILE_C[mi][ni], 
											tile_size_n );
							}
						}	
					} 
				}
				// --SIDE = Left & UPLO = Upper--	
				else
				{
					for ( ki = 0; ki < kt; ki++ )
					{
						if (ki == 0)
						{
							betat = BETA;
						}
						else 
						{
							betat = 1.0;
						}
						tile_size_k  = ddss_tile_size( M, ki );
					
						if ( ki < mi )
						{
							#pragma oss task in( TILE_A[ki][mi] ) \
                     			in( TILE_B[ki][ni] ) \
                     			inout( TILE_C[mi][ni] ) \
								shared( TILE_A, TILE_B, TILE_C ) \
 								firstprivate( mi, ni, ki, betat )
							cblas_dgemm( CblasRowMajor, 
										( CBLAS_TRANSPOSE ) Trans,
										( CBLAS_TRANSPOSE ) NoTrans,
										tile_size_m, 
										tile_size_n, 
									   	tile_size_k,
										ALPHA, TILE_A[ki][mi], tile_size_m, 
								     		   TILE_B[ki][ni], tile_size_n, 
							 			betat, TILE_C[mi][ni], tile_size_n );
						}
						else
						{
							if ( ki == mi )
							{
								#pragma oss task in( TILE_A[ki][ki] ) \
                     				in( TILE_B[ki][ni] ) \
                     				inout( TILE_C[mi][ni] ) \
									shared( TILE_A, TILE_B, TILE_C ) \
 									firstprivate( mi, ni, ki, betat )
								cblas_dsymm( CblasRowMajor,
									   		( CBLAS_SIDE ) SIDE,
									   		( CBLAS_UPLO ) UPLO,
									   		tile_size_m, tile_size_n,
									   		ALPHA, TILE_A[ki][ki], tile_size_k,
										      	   TILE_B[ki][ni], tile_size_n,
									   		betat, TILE_C[mi][ni], 
											tile_size_n );
							}	
							else
							{
								#pragma oss task in( TILE_A[mi][ki] ) \
                     				in( TILE_B[ki][ni] ) \
                     				inout( TILE_C[mi][ni] ) \
									shared( TILE_A, TILE_B, TILE_C ) \
 									firstprivate( mi, ni, ki, betat )
								cblas_dgemm( CblasRowMajor, 
											( CBLAS_TRANSPOSE ) NoTrans,
											( CBLAS_TRANSPOSE ) NoTrans,
											tile_size_m, 
											tile_size_n, 
									   		tile_size_k,
											ALPHA, TILE_A[mi][ki], tile_size_k, 
								     		   	   TILE_B[ki][ni], tile_size_n, 
							 				betat, TILE_C[mi][ni], 
											tile_size_n );
							}
						}	
					}
				}
			}
			else 
			{
				// --SIDE = Right & UPLO = Lower--	
				if ( UPLO == Lower)
				{
					for ( ki = 0; ki < kt; ki++ )
					{
						if (ki == 0)
						{
							betat = BETA;
						}
						else 
						{
							betat = 1.0;
						}
						tile_size_k  = ddss_tile_size( N, ki );
					
						if ( ki < ni )
						{
							#pragma oss task in( TILE_B[mi][ki] ) \
                     			in( TILE_A[ni][ki] ) \
                     			inout( TILE_C[mi][ni] ) \
								shared( TILE_A, TILE_B, TILE_C ) \
 								firstprivate( mi, ni, ki, betat )
							cblas_dgemm( CblasRowMajor, 
										( CBLAS_TRANSPOSE ) NoTrans,
										( CBLAS_TRANSPOSE ) Trans,
										tile_size_m, 
										tile_size_n, 
									   	tile_size_k,
										ALPHA, TILE_B[mi][ki], tile_size_k, 
								     		   TILE_A[ni][ki], tile_size_k, 
							 			betat, TILE_C[mi][ni], tile_size_n );
						}
						else
						{
							if ( ki == ni )
							{
								#pragma oss task in( TILE_A[ki][ki] ) \
                     				in( TILE_B[mi][ki] ) \
                     				inout( TILE_C[mi][ni] ) \
									shared( TILE_A, TILE_B, TILE_C ) \
 									firstprivate( mi, ni, ki, betat )
								cblas_dsymm( CblasRowMajor,
									   		( CBLAS_SIDE ) SIDE,
									   		( CBLAS_UPLO ) UPLO,
									   		tile_size_m, tile_size_n,
									   		ALPHA, TILE_A[ki][ki], tile_size_k,
										      	   TILE_B[mi][ki], tile_size_k,
									   		betat, TILE_C[mi][ni], 
											tile_size_n );
							}	
							else
							{
								#pragma oss task in( TILE_B[mi][ki] ) \
                     				in( TILE_A[ki][ni] ) \
                     				inout( TILE_C[mi][ni] ) \
									shared( TILE_A, TILE_B, TILE_C ) \
 									firstprivate( mi, ni, ki, betat )
								cblas_dgemm( CblasRowMajor, 
											( CBLAS_TRANSPOSE ) NoTrans,
											( CBLAS_TRANSPOSE ) NoTrans,
											tile_size_m, 
											tile_size_n, 
									   		tile_size_k,
											ALPHA, TILE_B[mi][ki], tile_size_k, 
								     		   	   TILE_A[ki][ni], tile_size_n, 
							 				betat, TILE_C[mi][ni], 
											tile_size_n );
							}
						}	
					} 
				}
				// --SIDE = Right & UPLO = Upper--	
				else
				{
					for ( ki = 0; ki < kt; ki++ )
					{
						if (ki == 0)
						{
							betat = BETA;
						}
						else 
						{
							betat = 1.0;
						}
						tile_size_k  = ddss_tile_size( N, ki );
					
						if ( ki < ni )
						{
							#pragma oss task in( TILE_B[mi][ki] ) \
                     			in( TILE_A[ki][ni] ) \
                     			inout( TILE_C[mi][ni] ) \
								shared( TILE_A, TILE_B, TILE_C ) \
 								firstprivate( mi, ni, ki, betat )
							cblas_dgemm( CblasRowMajor, 
										( CBLAS_TRANSPOSE ) NoTrans,
										( CBLAS_TRANSPOSE ) NoTrans,
										tile_size_m, 
										tile_size_n, 
									   	tile_size_k,
										ALPHA, TILE_B[mi][ki], tile_size_k, 
								     		   TILE_A[ki][ni], tile_size_n, 
							 			betat, TILE_C[mi][ni], tile_size_n );
						}
						else
						{
							if ( ki == ni )
							{
								#pragma oss task in( TILE_A[ki][ki] ) \
                     				in( TILE_B[ki][ni] ) \
                     				inout( TILE_C[mi][ni] ) \
									shared( TILE_A, TILE_B, TILE_C ) \
 									firstprivate( mi, ni, ki, betat )
								cblas_dsymm( CblasRowMajor,
									   		( CBLAS_SIDE ) SIDE,
									   		( CBLAS_UPLO ) UPLO,
									   		tile_size_m, tile_size_n,
									   		ALPHA, TILE_A[ki][ki], tile_size_k,
										      	   TILE_B[mi][ki], tile_size_k,
									   		betat, TILE_C[mi][ni], 
											tile_size_n );
							}	
							else
							{
								#pragma oss task in( TILE_B[mi][ki] ) \
                     				in( TILE_A[ni][ki] ) \
                     				inout( TILE_C[mi][ni] ) \
									shared( TILE_A, TILE_B, TILE_C ) \
 									firstprivate( mi, ni, ki, betat )
								cblas_dgemm( CblasRowMajor, 
											( CBLAS_TRANSPOSE ) NoTrans,
											( CBLAS_TRANSPOSE ) Trans,
											tile_size_m, 
											tile_size_n, 
									   		tile_size_k,
											ALPHA, TILE_B[mi][ki], tile_size_k, 
								     		   	   TILE_A[ni][ki], tile_size_k, 
							 				betat, TILE_C[mi][ni], 
											tile_size_n );
							}
						}	
					}
				}
			}
		}
	}

	// From tile matrix TILE_C to flat matrix C 
	ddss_dtiled2flat( M, N, C, LDC, mt, nt, TILE_C );

	// --Tile matrices free--
	free( TILE_A );
	free( TILE_B );
	free( TILE_C );

	return Success;

}
 
