/*

    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_UNB_OPT_FUSED 3
#define FLA_ALG_BLOCKED       4
#define FLA_ALG_BLOCKED_FUSED 5

FLA_Error REF_Bidiag_UT_u( FLA_Obj A, FLA_Obj tu, FLA_Obj tv );

void time_Bidiag_UT_u(
               int variant, int type, int n_repeats, int m, int n, int nb_alg,
               FLA_Obj A, FLA_Obj TU, FLA_Obj TV, FLA_Obj TTU, FLA_Obj TTV, FLA_Obj tu, FLA_Obj tv,
               double *dtime, double *diff, double *gflops );


void time_Bidiag_UT_u(
               int variant, int type, int n_repeats, int m, int n, int nb_alg,
               FLA_Obj A, FLA_Obj TU, FLA_Obj TV, FLA_Obj TTU, FLA_Obj TTV, FLA_Obj tu, FLA_Obj tv,
               double *dtime, double *diff, double *gflops )
{
  int irep;

  double
    dtime_old = 1.0e9;

  FLA_Obj
    A_save, norm;

  if (
       ( variant == 1 && type == FLA_ALG_UNB_OPT_FUSED ) ||
       ( variant == 5 && type == FLA_ALG_UNB_OPT_FUSED ) ||
       ( variant == 1 && type == FLA_ALG_BLOCKED_FUSED ) ||
       ( variant == 5 && type == FLA_ALG_BLOCKED_FUSED ) ||
       FALSE
     )
  {
    *gflops = 0.0;
    *diff   = 0.0;
    return;
  }

  if (
       //( variant == 0 ) ||

       //( variant == 1 && type == FLA_ALG_UNBLOCKED ) ||
       //( variant == 2 && type == FLA_ALG_UNBLOCKED ) ||
       //( variant == 3 && type == FLA_ALG_UNBLOCKED ) ||
       //( variant == 4 && type == FLA_ALG_UNBLOCKED ) ||
       //( variant == 5 && type == FLA_ALG_UNBLOCKED ) ||

       //( variant == 1 && type == FLA_ALG_UNB_OPT ) ||
       //( variant == 2 && type == FLA_ALG_UNB_OPT ) ||
       //( variant == 3 && type == FLA_ALG_UNB_OPT ) ||
       //( variant == 4 && type == FLA_ALG_UNB_OPT ) ||
       //( variant == 5 && type == FLA_ALG_UNB_OPT ) ||

       //( variant == 2 && type == FLA_ALG_UNB_OPT_FUSED ) ||
       //( variant == 3 && type == FLA_ALG_UNB_OPT_FUSED ) ||
       //( variant == 4 && type == FLA_ALG_UNB_OPT_FUSED ) ||

       //( variant == 1 && type == FLA_ALG_BLOCKED ) ||
       //( variant == 2 && type == FLA_ALG_BLOCKED ) ||
       //( variant == 3 && type == FLA_ALG_BLOCKED ) ||
       //( variant == 4 && type == FLA_ALG_BLOCKED ) ||
       //( variant == 5 && type == FLA_ALG_BLOCKED ) ||

       //( variant == 2 && type == FLA_ALG_BLOCKED_FUSED ) ||
       //( variant == 3 && type == FLA_ALG_BLOCKED_FUSED ) ||
       //( variant == 4 && type == FLA_ALG_BLOCKED_FUSED ) ||
       FALSE
  )
  {
    *gflops = 0.0;
    *diff   = 0.0;
    return;
  }

  FLA_Obj_create_conf_to( FLA_NO_TRANSPOSE, A, &A_save );

  FLA_Obj_create( FLA_Obj_datatype_proj_to_real( A ), 1, 1, 0, 0, &norm );

  FLA_Copy_external( A, A_save );

  for ( irep = 0 ; irep < n_repeats; irep++ ){

    FLA_Copy_external( A_save, A );

    *dtime = FLA_Clock();

    switch( variant ){

    case 0:
      REF_Bidiag_UT_u( A, tu, tv );
      break;

    // Time variant 1
    case 1:
    {
      switch( type ){
      case FLA_ALG_UNBLOCKED:
        FLA_Bidiag_UT_u_unb_var1( A, TU, TV );
        break;
      case FLA_ALG_UNB_OPT:
        FLA_Bidiag_UT_u_opt_var1( A, TU, TV );
        break;
      case FLA_ALG_UNB_OPT_FUSED:
        break;
      case FLA_ALG_BLOCKED:
        FLA_Bidiag_UT_u_blk_var1( A, TTU, TTV );
        break;
      case FLA_ALG_BLOCKED_FUSED:
        break;
      }
      break;
    }

    // Time variant 2
    case 2:
    {
      switch( type ){
      case FLA_ALG_UNBLOCKED:
        FLA_Bidiag_UT_u_unb_var2( A, TU, TV );
        break;
      case FLA_ALG_UNB_OPT:
        FLA_Bidiag_UT_u_opt_var2( A, TU, TV );
        break;
      case FLA_ALG_UNB_OPT_FUSED:
        FLA_Bidiag_UT_u_ofu_var2( A, TU, TV );
        break;
      case FLA_ALG_BLOCKED:
        FLA_Bidiag_UT_u_blk_var2( A, TTU, TTV );
        break;
      case FLA_ALG_BLOCKED_FUSED:
        FLA_Bidiag_UT_u_blf_var2( A, TTU, TTV );
        break;
      }
      break;
    }

    // Time variant 3
    case 3:
    {
      switch( type ){
      case FLA_ALG_UNBLOCKED:
        FLA_Bidiag_UT_u_unb_var3( A, TU, TV );
        break;
      case FLA_ALG_UNB_OPT:
        FLA_Bidiag_UT_u_opt_var3( A, TU, TV );
        break;
      case FLA_ALG_UNB_OPT_FUSED:
        FLA_Bidiag_UT_u_ofu_var3( A, TU, TV );
        break;
      case FLA_ALG_BLOCKED:
        FLA_Bidiag_UT_u_blk_var3( A, TTU, TTV );
        break;
      case FLA_ALG_BLOCKED_FUSED:
        FLA_Bidiag_UT_u_blf_var3( A, TTU, TTV );
        break;
      }
      break;
    }

    // Time variant 4
    case 4:
    {
      switch( type ){
      case FLA_ALG_UNBLOCKED:
        FLA_Bidiag_UT_u_unb_var4( A, TU, TV );
        break;
      case FLA_ALG_UNB_OPT:
        FLA_Bidiag_UT_u_opt_var4( A, TU, TV );
        break;
      case FLA_ALG_UNB_OPT_FUSED:
        FLA_Bidiag_UT_u_ofu_var4( A, TU, TV );
        break;
      case FLA_ALG_BLOCKED:
        FLA_Bidiag_UT_u_blk_var4( A, TTU, TTV );
        break;
      case FLA_ALG_BLOCKED_FUSED:
        FLA_Bidiag_UT_u_blf_var4( A, TTU, TTV );
        break;
      }
      break;
    }

    // Time variant 5
    case 5:
    {
      switch( type ){
      case FLA_ALG_UNBLOCKED:
        FLA_Bidiag_UT_u_unb_var5( A, TU, TV );
        break;
      case FLA_ALG_UNB_OPT:
        FLA_Bidiag_UT_u_opt_var5( A, TU, TV );
        break;
      case FLA_ALG_UNB_OPT_FUSED:
        break;
      case FLA_ALG_BLOCKED:
        FLA_Bidiag_UT_u_blk_var5( A, TTU, TTV );
        break;
      case FLA_ALG_BLOCKED_FUSED:
        break;
      }
      break;
    }

    }

    *dtime = FLA_Clock() - *dtime;
    dtime_old = min( *dtime, dtime_old );

  }

  if ( variant > 0 )
  //if ( FALSE )
  {

//FLA_Obj_show( "A_save", A_save, "%10.3e + %10.3e ", "" );

/*
if ( ( variant >= 3 && type == FLA_ALG_UNBLOCKED && m < 10 ) )
{
if ( FLA_Obj_is_complex( A ) )
{
FLA_Obj_show( "A", A, "%10.3e + %10.3e ", "" );
FLA_Obj_show( "TU", TU, "%10.3e + %10.3e ", "" );
FLA_Obj_show( "TV", TV, "%10.3e + %10.3e ", "" );
}
else
{
FLA_Obj_show( "A", A, "%10.3e", "" );
FLA_Obj_show( "TU", TU, "%10.3e", "" );
FLA_Obj_show( "TV", TV, "%10.3e", "" );
}
}
*/
    FLA_Obj AL, AR;
    FLA_Obj QU;
    FLA_Obj QV, QVL, QVR;
    FLA_Obj E, EL, ER;
    FLA_Obj F;
    FLA_Obj WU, WWU, WV, WWV;
    dim_t   m_A, n_A, m_TU, m_TTU;

//FLA_Obj_show( "A_save", A_save, "%10.3e", "" );

    m_A = FLA_Obj_length( A );
    n_A = FLA_Obj_width( A );
    m_TU = FLA_Obj_length( TU );
    m_TTU = FLA_Obj_length( TTU );

//FLA_Obj_show( "A bidiag", A, "%10.3e", "" );
//FLA_Copy( A_save, A );
//FLA_QR_UT( A, TU );
//FLA_Obj_show( "A QR", A, "%10.3e", "" );

    FLA_Obj_create( FLA_Obj_datatype( A ), m_A,  m_A, 0, 0, &QU );
    FLA_Obj_create( FLA_Obj_datatype( A ), n_A,  n_A, 0, 0, &QV );

    FLA_Obj_create( FLA_Obj_datatype( A ), m_TU,  m_A, 0, 0, &WU );
    FLA_Obj_create( FLA_Obj_datatype( A ), m_TTU, m_A, 0, 0, &WWU );

    FLA_Obj_create( FLA_Obj_datatype( A ), m_TU,  n_A, 0, 0, &WV );
    FLA_Obj_create( FLA_Obj_datatype( A ), m_TTU, n_A, 0, 0, &WWV );

    FLA_Set_to_identity( QU );
    FLA_Set_to_identity( QV );

    FLA_Part_1x2( QV,   &QVL, &QVR,   1, FLA_LEFT );
    FLA_Part_1x2( A,    &AL,  &AR,    1, FLA_LEFT );

    if ( type == FLA_ALG_BLOCKED || type == FLA_ALG_BLOCKED_FUSED )
    {
      FLA_Apply_Q_UT( FLA_LEFT, FLA_CONJ_TRANSPOSE, FLA_FORWARD, FLA_COLUMNWISE, A, TTU, WWU, QU );
      FLA_Apply_Q_UT( FLA_RIGHT, FLA_NO_TRANSPOSE, FLA_FORWARD, FLA_ROWWISE, AR, TTV, WWV, QVR );
    }
    else
    {
      FLA_Apply_Q_UT( FLA_LEFT, FLA_CONJ_TRANSPOSE, FLA_FORWARD, FLA_COLUMNWISE, A, TU, WU, QU );
      FLA_Apply_Q_UT( FLA_RIGHT, FLA_NO_TRANSPOSE, FLA_FORWARD, FLA_ROWWISE, AR, TV, WV, QVR );
    }
/*
    FLA_Obj eye;

    FLA_Obj_create_conf_to( FLA_NO_TRANSPOSE, A, &eye );     
    FLA_Set_to_identity( eye );

    //FLA_Gemm( FLA_NO_TRANSPOSE, FLA_CONJ_TRANSPOSE,
    //          FLA_ONE, QV, QV, FLA_MINUS_ONE, eye );
    FLA_Gemm( FLA_NO_TRANSPOSE, FLA_CONJ_TRANSPOSE,
              FLA_ONE, QU, QU, FLA_MINUS_ONE, eye );

FLA_Obj_show( "eye", eye, "%10.3e", "" );
    FLA_Norm_frob( eye, norm );
    FLA_Obj_extract_real_scalar( norm, diff );
    FLA_Obj_free( &eye );
*/

    FLA_Obj_create_conf_to( FLA_NO_TRANSPOSE, A, &E );     
    FLA_Obj_create_conf_to( FLA_NO_TRANSPOSE, A, &F );


    FLA_Gemm( FLA_NO_TRANSPOSE, FLA_NO_TRANSPOSE,
              FLA_ONE, A_save, QV, FLA_ZERO, E );
    FLA_Gemm( FLA_NO_TRANSPOSE, FLA_NO_TRANSPOSE,
              FLA_ONE, QU, E, FLA_ZERO, F );

//FLA_Obj_show( "A_save", A_save, "%10.3e", "" );

    FLA_Copy( A, E );
    FLA_Triangularize( FLA_UPPER_TRIANGULAR, FLA_NONUNIT_DIAG, E );
    FLA_Part_1x2( E,    &EL, &ER,      1, FLA_LEFT );
    FLA_Triangularize( FLA_LOWER_TRIANGULAR, FLA_NONUNIT_DIAG, ER );

//FLA_Obj_show( "B", E, "%10.3e", "" );
//FLA_Obj_show( "Q'AV", F, "%10.3e", "" );
//FLA_Obj_show( "B", E, "%10.3e + %10.3e ", "" );
//FLA_Obj_show( "Q'AV", F, "%10.3e + %10.3e ", "" );

    *diff = FLA_Max_elemwise_diff( E, F );
    FLA_Obj_free( &E );
    FLA_Obj_free( &F );

    FLA_Obj_free( &QU );
    FLA_Obj_free( &QV );
    FLA_Obj_free( &WU );
    FLA_Obj_free( &WWU );
    FLA_Obj_free( &WV );
    FLA_Obj_free( &WWV );
  }
  else
  {
    *diff = 0.0;
  }

  *gflops = 4.0 * n * n * ( m - n / 3.0 ) /
            dtime_old / 1e9;

  if ( FLA_Obj_is_complex( A ) )
    *gflops *= 4.0;

  *dtime = dtime_old;

  FLA_Copy_external( A_save, A );

  FLA_Obj_free( &A_save );
  FLA_Obj_free( &norm );
}

