#include "hip/hip_runtime.h"
/*
    -- MAGMA (version 2.6.2) --
       Univ. of Tennessee, Knoxville
       Univ. of California, Berkeley
       Univ. of Colorado, Denver
       @date March 2022

       @generated from magmablas_hip/zlarfgx-v2.hip.cpp, normal z -> d, Mon Mar 21 16:51:43 2022

*/
#include "magma_internal.h"
#include "commonblas_d.h"

// 512 is maximum number of threads for CUDA capability 1.x
#define BLOCK_SIZE 512

#define REAL


/******************************************************************************/
__global__
void magma_dlarfgx_gpu_kernel( int n, double* dx0, double* dx,
                               double *dtau, double *dxnorm,
                               double *dA, int it)
{
    const int i = threadIdx.x;
    const int j = i + BLOCK_SIZE * blockIdx.x;
    __shared__ double scale;
    __shared__ double xnorm;
  
    double dxi;

    if ( j < n-1 )
        dxi = dx[j];
  
    if ( i == 0 ) {
        xnorm = *dxnorm;
        #ifdef REAL
        double alpha = *dx0;
        double alphai = MAGMA_D_ZERO;
        if ( (xnorm == 0 && alphai == MAGMA_D_ZERO ) || n == 1 )
        #else
        double alpha = *dx0;
        double alphar =  MAGMA_D_REAL(alpha), alphai = MAGMA_D_IMAG(alpha);
        if ( (xnorm == 0 && alphai == MAGMA_D_ZERO ) || n == 0 )
        #endif
        {
            *dtau = MAGMA_D_ZERO;
            *dA   = *dx0;
        }
        else {
            #ifdef REAL
                // no need to compute the norm as it is passed as input
                double beta  = xnorm; // sqrt( alpha*alpha + xnorm*xnorm );
                beta  = -copysign( beta, alpha );
     
                // todo: deal with badly scaled vectors (see lapack's larfg)
                if (j == 0) {
                    *dtau = (beta - alpha) / beta;
                    //*dx0  = 1.; //cannot be done here because raise condition all threadblock need to read it for alpha
                    *dA   = beta;
                }
    
                scale = 1. / (alpha - beta);
            #else
                // no need to compute the norm as it is passed as input
                double beta  = xnorm; // sqrt( alphar*alphar + alphai*alphai + xnorm*xnorm );
                beta  = -copysign( beta, alphar );
    
                // todo: deal with badly scaled vectors (see lapack's larfg)
                if (j == 0) {
                    *dtau = MAGMA_D_MAKE((beta - alphar)/beta, -alphai/beta);
                    //*dx0  = MAGMA_D_MAKE(  1., 0.); //cannot be done here because raise condition all threadblock need to read it for alpha
                    *dA   = MAGMA_D_MAKE(beta, 0.);
                }
    
                alpha = MAGMA_D_MAKE( MAGMA_D_REAL(alpha) - beta, MAGMA_D_IMAG(alpha));
                scale = MAGMA_D_DIV( MAGMA_D_ONE, alpha);
            #endif
        }
    }

    // scale x
    __syncthreads();
    if ( xnorm != 0 && j < n-1)
        dx[j] = MAGMA_D_MUL(dxi, scale);

    if (j < it) {
        *( dA-it+j) = *(dx0-it+j);
        *(dx0-it+j) = MAGMA_D_MAKE(0., 0.);
    }
}


/***************************************************************************//**
    Generates Householder elementary reflector H = I - tau v v^T to reduce
        H [ dx0 ] = [ beta ]
          [ dx  ]   [ 0    ]
    with |beta| = norm( [dx0, dx] ) = dxnorm[0].
    Stores v over dx; first element of v is 1 and is not stored.
    Stores beta over dx0.
    Stores tau.
    
    The difference with LAPACK's dlarfg is that the norm of dx, and hance beta,
    are computed outside the routine and passed to it in dxnorm (array on the GPU).
*******************************************************************************/
extern "C" void
magma_dlarfgx_gpu(
    magma_int_t n,
    magmaDouble_ptr dx0,
    magmaDouble_ptr dx,
    magmaDouble_ptr dtau,
    magmaDouble_ptr        dxnorm,
    magmaDouble_ptr dA, magma_int_t iter, 
    magma_queue_t queue )
{
    dim3 blocks( magma_ceildiv( n, BLOCK_SIZE ) );
    dim3 threads( BLOCK_SIZE );
 
    hipLaunchKernelGGL(magma_dlarfgx_gpu_kernel, dim3(blocks), dim3(threads), 0, queue->hip_stream() ,  n, dx0, dx, dtau, dxnorm, dA, iter);
}


/***************************************************************************//**
    Generates Householder elementary reflector H = I - tau v v^T to reduce
        H [ dx0 ] = [ beta ]
          [ dx  ]   [ 0    ]
    with |beta| = norm( [dx0, dx] ) = dxnorm[0].
    Stores v over dx; first element of v is 1 and is not stored.
    Stores beta over dx0.
    Stores tau.
    
    The difference with LAPACK's dlarfg is that the norm of dx, and hance beta,
    are computed outside the routine and passed to it in dxnorm (array on the GPU).
*******************************************************************************/
extern "C" void
magma_dlarfgtx_gpu(
    magma_int_t n,
    magmaDouble_ptr dx0,
    magmaDouble_ptr dx,
    magmaDouble_ptr dtau,
    magmaDouble_ptr        dxnorm,
    magmaDouble_ptr dA, magma_int_t iter,
    magmaDouble_ptr V,  magma_int_t ldv,
    magmaDouble_ptr T,  magma_int_t ldt,
    magmaDouble_ptr dwork,
    magma_queue_t queue )
{
    /*  Generate the elementary reflector H(iter)  */
    magma_dlarfgx_gpu(n, dx0, dx, dtau, dxnorm, dA, iter, queue);
    
    if (iter == 0) {
        double tt = MAGMA_D_ONE;
        magmablas_dlacpy( MagmaFull, 1, 1, dtau, 1, T+iter+iter*ldt, 1, queue );
        magma_dsetmatrix( 1, 1, &tt, 1, dx0, 1, queue );
    }
    else {
        /* Compute the iter-th column of T */
        hipLaunchKernelGGL(magma_dgemv_kernel3, dim3(iter), dim3(BLOCK_SIZE), 0, queue->hip_stream() ,  n, V, ldv, dx0, dwork, dtau );
        
        hipLaunchKernelGGL(magma_dtrmv_kernel2, dim3(iter), dim3(iter), 0, queue->hip_stream() ,  T, ldt, dwork, T+iter*ldt, dtau );
    }
}
