/*

    Copyright (C) 2014, The University of Texas at Austin

    This file is part of libflame and is available under the 3-Clause
    BSD license, which can be found in the LICENSE file at the top-level
    directory, or at http://opensource.org/licenses/BSD-3-Clause

*/

#include "FLAME.h"

#define FLA_ALG_REFERENCE     0
#define FLA_ALG_UNBLOCKED     1
#define FLA_ALG_UNB_OPT       2
#define FLA_ALG_BLOCKED       3

void fill_singularvalues( FLA_Obj s );

void time_Bsvd_v(
               int variant, int type, int n_repeats, int m, int n, int n_iter_max, int k_accum, int b_alg,
               FLA_Obj A_orig, FLA_Obj d, FLA_Obj e, FLA_Obj s, FLA_Obj G, FLA_Obj H, FLA_Obj RG, FLA_Obj RH, FLA_Obj W, FLA_Obj U, FLA_Obj V,
               double *dtime, double *diff1, double* diff2, double *gflops );


int main(int argc, char *argv[])
{
  int 
    m_input, n_input,
    m, n,
    p_first, p_last, p_inc,
    p,
    k_accum,
    b_alg,
    n_iter_max,
    min_m_n,
    variant,
    n_repeats,
    i,
    n_variants = 2;
  
  char *colors = "brkgmcbrkg";
  char *ticks  = "o+*xso+*xs";
  char m_dim_desc[14];
  char m_dim_tag[10];
  char n_dim_desc[14];
  char n_dim_tag[10];

  double max_gflops=6.0;

  double
    dtime,
    gflops,
    diff1,
    diff2;

  FLA_Datatype datatype, dt_real, dt_comp;

  FLA_Obj
    A, A_orig, d, e, s, G, H, U, V, Vh, TU, TV, rU, rV, alpha;
  FLA_Obj RG, RH, W;
  

  FLA_Init();


  fprintf( stdout, "%c number of repeats:", '%' );
  scanf( "%d", &n_repeats );
  fprintf( stdout, "%c %d\n", '%', n_repeats );

  fprintf( stdout, "%c enter n_iter_max:", '%' );
  scanf( "%d", &n_iter_max );
  fprintf( stdout, "%c %d\n", '%', n_iter_max );

  fprintf( stdout, "%c enter number of Givens rotations to accumulate:", '%' );
  scanf( "%d", &k_accum );
  fprintf( stdout, "%c %d\n", '%', k_accum );

  fprintf( stdout, "%c enter blocking size:", '%' );
  scanf( "%d", &b_alg );
  fprintf( stdout, "%c %d\n", '%', b_alg );

  fprintf( stdout, "%c enter problem size first, last, inc:", '%' );
  scanf( "%d%d%d", &p_first, &p_last, &p_inc );
  fprintf( stdout, "%c %d %d %d\n", '%', p_first, p_last, p_inc );

  fprintf( stdout, "%c enter m n (-1 means bind to problem size): ", '%' );
  scanf( "%d %d", &m_input, &n_input );
  fprintf( stdout, "%c %d %d\n", '%', m_input, n_input );


  fprintf( stdout, "\n" );


  if     ( m_input >  0 ) {
    sprintf( m_dim_desc, "m = %d", m_input );
    sprintf( m_dim_tag,  "m%dc", m_input);
  }
  else if( m_input <  -1 ) {
    sprintf( m_dim_desc, "m = p/%d", -m_input );
    sprintf( m_dim_tag,  "m%dp", -m_input );
  }
  else if( m_input == -1 ) {
    sprintf( m_dim_desc, "m = p" );
    sprintf( m_dim_tag,  "m%dp", 1 );
  }
  if     ( n_input >  0 ) {
    sprintf( n_dim_desc, "n = %d", n_input );
    sprintf( n_dim_tag,  "n%dc", n_input);
  }
  else if( n_input <  -1 ) {
    sprintf( n_dim_desc, "n = p/%d", -n_input );
    sprintf( n_dim_tag,  "n%dp", -n_input );
  }
  else if( n_input == -1 ) {
    sprintf( n_dim_desc, "n = p" );
    sprintf( n_dim_tag,  "n%dp", 1 );
  }

  for ( p = p_first, i = 1; p <= p_last; p += p_inc, i += 1 )
  {

    m = m_input;
    n = n_input;

    if( m < 0 ) m = p / f2c_abs(m_input);
    if( n < 0 ) n = p / f2c_abs(n_input);

    min_m_n = min( m, n );

    //datatype = FLA_FLOAT;
    //datatype = FLA_DOUBLE;
    //datatype = FLA_COMPLEX;
    datatype = FLA_DOUBLE_COMPLEX;

    FLA_Obj_create( datatype, m,       n, 0, 0, &A );
    FLA_Obj_create( datatype, m,       n, 0, 0, &A_orig );
    FLA_Obj_create( datatype, m,       m, 0, 0, &U );
    FLA_Obj_create( datatype, n,       n, 0, 0, &V );
    FLA_Obj_create( datatype, n,       n, 0, 0, &Vh );

    dt_real = FLA_Obj_datatype_proj_to_real( A );
    dt_comp = FLA_Obj_datatype_proj_to_complex( A );

    FLA_Obj_create( dt_real,  min_m_n,   1, 0, 0, &s );
    FLA_Obj_create( dt_real,  min_m_n,   1, 0, 0, &d );
    FLA_Obj_create( dt_real,  min_m_n-1, 1, 0, 0, &e );

    FLA_Obj_create( dt_comp,  min_m_n,   k_accum, 0, 0, &G );
    FLA_Obj_create( dt_comp,  min_m_n,   k_accum, 0, 0, &H );

    FLA_Obj_create( dt_real,  min_m_n, min_m_n, 0, 0, &RG );
    FLA_Obj_create( dt_real,  min_m_n, min_m_n, 0, 0, &RH );
    FLA_Obj_create( datatype, min_m_n, min_m_n, 0, 0, &W );

    FLA_Obj_create( datatype, 32, min_m_n, 0, 0, &TU );
    FLA_Obj_create( datatype, 32, min_m_n, 0, 0, &TV );

    FLA_Obj_create( datatype, min_m_n,   1, 0, 0, &rU );
    FLA_Obj_create( datatype, min_m_n,   1, 0, 0, &rV );

    FLA_Obj_create( dt_real,  1,  1, 0, 0, &alpha );

    *FLA_DOUBLE_PTR( alpha ) = 1.0 / sqrt( (double) min_m_n );

    FLA_Random_unitary_matrix( U );
    FLA_Random_unitary_matrix( V );

    //FLA_Fill_with_uniform_dist( FLA_ONE, s );
    //FLA_Fill_with_inverse_dist( FLA_ONE, s );
    FLA_Fill_with_geometric_dist( alpha,   s );

    {
      FLA_Obj UL, UR;
      FLA_Obj VL, VR;

      FLA_Part_1x2( U,   &UL, &UR,   min_m_n, FLA_LEFT );
      FLA_Part_1x2( V,   &VL, &VR,   min_m_n, FLA_LEFT );

      FLA_Apply_diag_matrix( FLA_RIGHT, FLA_NO_CONJUGATE, s, UL );
      FLA_Gemm( FLA_NO_TRANSPOSE, FLA_CONJ_TRANSPOSE,
                FLA_ONE, UL, VL, FLA_ZERO, A );
    }

    FLA_Copy( A, A_orig );

    // Clear the contents of s, U, and V just to be safe.

    FLA_Set( FLA_ZERO, s );
    FLA_Set( FLA_ZERO, U );
    FLA_Set( FLA_ZERO, V );

    // Reduce to bidiagonal form, realify, form U and V, and apply
    // realifying scalars to U and V.

    FLA_Bidiag_UT( A, TU, TV );
    FLA_Bidiag_UT_realify( A, rU, rV );
    FLA_Bidiag_UT_extract_diagonals( A, d, e );
    FLA_Bidiag_UT_form_U( A, TU, U );
    FLA_Bidiag_UT_form_V( A, TV, V );

    {
      FLA_Obj UL, UR;
      FLA_Obj VL, VR;

      FLA_Part_1x2( U,   &UL, &UR,   min_m_n, FLA_LEFT );
      FLA_Part_1x2( V,   &VL, &VR,   min_m_n, FLA_LEFT );

      FLA_Apply_diag_matrix( FLA_RIGHT, FLA_CONJUGATE,    rU, UL );
      FLA_Apply_diag_matrix( FLA_RIGHT, FLA_NO_CONJUGATE, rV, VL );
    }

    FLA_Copyt( FLA_CONJ_TRANSPOSE, V, Vh );

    time_Bsvd_v( 0, FLA_ALG_REFERENCE, n_repeats, m, n, n_iter_max, k_accum, b_alg,
                 A_orig, d, e, s, G, H, RG, RH, W, U, Vh, &dtime, &diff1, &diff2, &gflops );

    fprintf( stdout, "data_REFq( %d, 1:3 ) = [ %d %6.3lf %9.2e %6.2le %6.2le ]; \n", i, p, gflops, dtime, diff1, diff2 );
    fflush( stdout );


    for ( variant = 1; variant <= n_variants; variant++ ){
      
      fprintf( stdout, "data_var%d( %d, 1:3 ) = [ %d ", variant, i, p );
      fflush( stdout );

      time_Bsvd_v( variant, FLA_ALG_UNBLOCKED, n_repeats, m, n, n_iter_max, k_accum, b_alg,
                   A_orig, d, e, s, G, H, RG, RH, W, U, V, &dtime, &diff1, &diff2, &gflops );

      fprintf( stdout, "%6.3lf %9.2e %6.2le %6.2le ", gflops, dtime, diff1, diff2 );
      fflush( stdout );

      fprintf( stdout, "];\n" );
      fflush( stdout );
    }

    fprintf( stdout, "\n" );

    FLA_Obj_free( &A );
    FLA_Obj_free( &A_orig );
    FLA_Obj_free( &s );
    FLA_Obj_free( &d );
    FLA_Obj_free( &e );
    FLA_Obj_free( &G );
    FLA_Obj_free( &H );
    FLA_Obj_free( &RG );
    FLA_Obj_free( &RH );
    FLA_Obj_free( &W );
    FLA_Obj_free( &U );
    FLA_Obj_free( &V );
    FLA_Obj_free( &Vh );
    FLA_Obj_free( &TU );
    FLA_Obj_free( &TV );
    FLA_Obj_free( &rU );
    FLA_Obj_free( &rV );
    FLA_Obj_free( &alpha );
  }

/*
  fprintf( stdout, "figure;\n" );

  fprintf( stdout, "plot( data_REF( :,1 ), data_REF( :, 2 ), '-' ); \n" );

  fprintf( stdout, "hold on;\n" );

  for ( i = 1; i <= n_variants; i++ ) {
    fprintf( stdout, "plot( data_var%d( :,1 ), data_var%d( :, 2 ), '%c:%c' ); \n",
            i, i, colors[ i-1 ], ticks[ i-1 ] );
    fprintf( stdout, "plot( data_var%d( :,1 ), data_var%d( :, 4 ), '%c-.%c' ); \n",
            i, i, colors[ i-1 ], ticks[ i-1 ] );
  }

  fprintf( stdout, "legend( ... \n" );
  fprintf( stdout, "'Reference', ... \n" );

  for ( i = 1; i < n_variants; i++ )
    fprintf( stdout, "'unb\\_var%d', 'blk\\_var%d', ... \n", i, i );
  fprintf( stdout, "'unb\\_var%d', 'blk\\_var%d' ); \n", i, i );

  fprintf( stdout, "xlabel( 'problem size p' );\n" );
  fprintf( stdout, "ylabel( 'GFLOPS/sec.' );\n" );
  fprintf( stdout, "axis( [ 0 %d 0 %.2f ] ); \n", p_last, max_gflops );
  fprintf( stdout, "title( 'FLAME Bsvd_v performance (%s, %s)' );\n", 
           m_dim_desc, n_dim_desc );
  fprintf( stdout, "print -depsc tridiag_%s_%s.eps\n", m_dim_tag, n_dim_tag );
  fprintf( stdout, "hold off;\n");
  fflush( stdout );
*/

  FLA_Finalize( );

  return 0;
}

void fill_singularvalues( FLA_Obj l )
{
  FLA_Obj lT,              l0,
          lB,              lambda1,
                           l2;
  FLA_Obj alpha;
  FLA_Obj beta;

  FLA_Obj_create( FLA_Obj_datatype( l ), 1, 1, 0, 0, &alpha );
  FLA_Obj_create( FLA_Obj_datatype( l ), 1, 1, 0, 0, &beta );
  FLA_Copy( FLA_ONE, alpha );
  FLA_Copy( FLA_ONE, beta );

  FLA_Part_2x1( l,    &lT, 
                      &lB,            0, FLA_TOP );

  while ( FLA_Obj_length( lT ) < FLA_Obj_length( l ) ){

    FLA_Repart_2x1_to_3x1( lT,                &l0, 
                        /* ** */            /* ******* */
                                              &lambda1, 
                           lB,                &l2,        1, FLA_BOTTOM );

    /*------------------------------------------------------------*/

    FLA_Copy( alpha, beta );
    FLA_Invert( FLA_NO_CONJUGATE, beta );

    FLA_Copy( beta, lambda1 );

    //FLA_Negate( beta );
    //FLA_Copy( FLA_TWO, lambda1 );
    //FLA_Mult_add( FLA_ONE, beta, lambda1 );

//printf( "lambda = %23.19e\n", *FLA_DOUBLE_PTR( lambda1 ) );

    FLA_Mult_add( FLA_ONE, FLA_ONE, alpha );

    /*------------------------------------------------------------*/

    FLA_Cont_with_3x1_to_2x1( &lT,                l0, 
                                                  lambda1, 
                            /* ** */           /* ******* */
                              &lB,                l2,     FLA_TOP );

  }

  FLA_Obj_free( &alpha );
  FLA_Obj_free( &beta );
}
