/**
 *
 * @file run_list.c
 *
 * @copyright 2019-2021 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 ***
 *
 * @brief Chameleon auxiliary routines for testing structures
 *
 * @version 1.1.0
 * @author Lucas Barros de Assis
 * @author Mathieu Faverge
 * @author Philippe Swartvagher
 * @date 2020-03-03
 *
 */
#include "testings.h"

/**
 ********************************************************************************
 *
 * @brief Searches for a specific value by its name.
 *
 *******************************************************************************
 *
 * @param[in] arglist
 *          The list of arguments.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @retval The argument structure of the argument, NULL if not found.
 *
 *******************************************************************************
 */
const run_arg_t *
run_arg_get_byname( const run_arg_list_t *arglist, const char *name )
{
    const run_arg_t *arg = arglist->head;

    while( arg != NULL ) {
        if ( strcasecmp( name, arg->param->name ) == 0 ) {
            return arg;
        }
        arg = arg->next;
    }

    return arg;
}

/**
 ********************************************************************************
 *
 * @brief Searches for a specific value by its name.
 *
 *******************************************************************************
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 *
 *******************************************************************************
 */
val_t
run_arg_get( run_arg_list_t *arglist, const char *name, val_t defval )
{
    run_arg_t *arg = arglist->head;

    while( arg != NULL ) {
        if ( strcasecmp( name, arg->param->name ) == 0 ) {
            return arg->value;
        }
        arg = arg->next;
    }

    assert( arg  == NULL );
    arg = calloc( 1, sizeof(run_arg_t) );

    arg->param = parameters_getbyname( name );
    if ( arg->param == NULL ) {
        fprintf( stderr, "Argument %s is not registered\n", name );
        exit(1);
    }
    arg->value = defval;

    if( arglist->head == NULL ) {
        assert( arglist->tail == NULL );
        arglist->head = arg;
        arglist->tail = arg;
    }
    else {
        assert( arglist->tail != NULL );
        assert( arglist->tail->next == NULL );
        arglist->tail->next = arg;
        arglist->tail = arg;
    }

    return defval;
}

/**
 ********************************************************************************
 *
 * @brief Adds a single generic argument value by name
 *
 *******************************************************************************
 *
 * @param[inout] arglist
 *          The list of arguments to update.
 *          On exit, the argument _name_ of value _value_ is added to the list.
 *
 * @param[in] name
 *          The name of the argument to add in the list.
 *
 * @param[in] value
 *          The value of the argument to add.
 *
 * @retval 0 for success, -1 if it fails to find a paramater named _name_.
 *
 *******************************************************************************
 */
int
run_arg_add( run_arg_list_t *arglist, const char *name, val_t value )
{
    run_arg_t *arg;

    assert( arglist );

    arg = calloc( 1, sizeof(run_arg_t) );
    arg->param = parameters_getbyname( name );
    if ( arg->param == NULL ) {
        fprintf( stderr, "Argument %s does not exist\n", name );
        free( arg );
        return -1;
    }
    arg->value = value;

    if( arglist->head == NULL ) {
        assert( arglist->tail == NULL );
        arglist->head = arg;
        arglist->tail = arg;
    }
    else {
        assert( arglist->tail != NULL );
        assert( arglist->tail->next == NULL );
        arglist->tail->next = arg;
        arglist->tail = arg;
    }

    return 0;
}

/**
 * @brief Adds a single int argument value by name
 *
 * @param[inout] arglist
 *          The list of arguments to update.
 *          On exit, the argument _name_ of value _value_ is added to the list.
 *
 * @param[in] name
 *          The name of the argument to add in the list.
 *
 * @param[in] value
 *          The value of the argument to add.
 *
 * @retval 0 for success, -1 if it fails to find a paramater named _name_.
 */
int
run_arg_add_int( run_arg_list_t *arglist, const char *name, int value )
{
    val_t v;
    v.ival = value;
    return run_arg_add( arglist, name, v );
}

/**
 * @brief Adds a single double argument value by name
 *
 * @param[inout] arglist
 *          The list of arguments to update.
 *          On exit, the argument _name_ of value _value_ is added to the list.
 *
 * @param[in] name
 *          The name of the argument to add in the list.
 *
 * @param[in] value
 *          The value of the argument to add.
 *
 * @retval 0 for success, -1 if it fails to find a paramater named _name_.
 */
int
run_arg_add_double( run_arg_list_t *arglist, const char *name, double value )
{
    val_t v;
    v.dval = value;
    return run_arg_add( arglist, name, v );
}

/**
 * @brief Searches for a specific int value by its name.
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 */
int
run_arg_get_int( run_arg_list_t *arglist, const char *name, int defval )
{
    val_t val, rval;
    val.ival = defval;
    rval = run_arg_get( arglist, name, val );
    return rval.ival;
}

/**
 * @brief Searches for a specific float value by its name.
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 */
float
run_arg_get_float( run_arg_list_t *arglist, const char *name, float defval )
{
    val_t val, rval;
    val.sval = defval;
    rval = run_arg_get( arglist, name, val );
    return rval.sval;
}

/**
 * @brief Searches for a specific double value by its name.
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 */
double
run_arg_get_double( run_arg_list_t *arglist, const char *name, double defval )
{
    val_t val, rval;
    val.dval = defval;
    rval = run_arg_get( arglist, name, val );
    return rval.dval;
}

/**
 * @brief Searches for a specific single complex value by its name.
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 */
CHAMELEON_Complex32_t
run_arg_get_complex32( run_arg_list_t *arglist, const char *name, CHAMELEON_Complex32_t defval )
{
    val_t val, rval;
    val.cval = defval;
    rval = run_arg_get( arglist, name, val );
    return rval.cval;
}

/**
 * @brief Searches for a double complex value by its name.
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 */
CHAMELEON_Complex64_t
run_arg_get_complex64( run_arg_list_t *arglist, const char *name, CHAMELEON_Complex64_t defval )
{
    val_t val, rval;
    val.zval = defval;
    rval = run_arg_get( arglist, name, val );
    return rval.zval;
}

/**
 * @brief Searches for a cham_trans_t value by its name.
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 */
cham_trans_t
run_arg_get_trans( run_arg_list_t *arglist, const char *name, cham_trans_t defval )
{
    val_t val, rval;
    val.trans = defval;
    rval = run_arg_get( arglist, name, val );
    return rval.trans;
}

/**
 * @brief Searches for a cham_uplo_t value by its name.
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 */
cham_uplo_t
run_arg_get_uplo( run_arg_list_t *arglist, const char *name, cham_uplo_t defval )
{
    val_t val, rval;
    val.uplo = defval;
    rval = run_arg_get( arglist, name, val );
    return rval.uplo;
}

/**
 * @brief Searches for a cham_diag_t value by its name.
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 */
cham_diag_t
run_arg_get_diag( run_arg_list_t *arglist, const char *name, cham_diag_t defval )
{
    val_t val, rval;
    val.diag = defval;
    rval = run_arg_get( arglist, name, val );
    return rval.diag;
}

/**
 * @brief Searches for a cham_side_t value by its name.
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 */
cham_side_t
run_arg_get_side( run_arg_list_t *arglist, const char *name, cham_side_t defval )
{
    val_t val, rval;
    val.side = defval;
    rval = run_arg_get( arglist, name, val );
    return rval.side;
}

/**
 * @brief Searches for a cham_normtype_t value by its name.
 *
 * @param[inout] arglist
 *          The list of arguments.
 *          On exit, if the argument was not in the list, the default value is
 *          stored in it.
 *
 * @param[in] name
 *          The name of the argument to look for.
 *
 * @param[in] defval
 *          The default value if no argument is found with this name. This value
 *          is added to the list if not found.
 *
 * @retval The value of the argument _name_.
 */
cham_normtype_t
run_arg_get_ntype( run_arg_list_t *arglist, const char *name, cham_normtype_t defval )
{
    val_t val, rval;
    val.ntype = defval;
    rval = run_arg_get( arglist, name, val );
    return rval.ntype;
}

/**
 ********************************************************************************
 *
 * @brief Frees all of memory allocated for an argument list.
 *
 *******************************************************************************
 *
 * @param[inout] args
 *          The list of arguments to free.
 *
 *******************************************************************************
 */
void run_arg_list_destroy( run_arg_list_t *arglist )
{
    run_arg_t *arg1, *arg2;

    arg1 = arglist->head;
    while( arg1 != NULL ) {
        arg2 = arg1->next;
        free( arg1 );
        arg1 = arg2;
    }

    arglist->head = NULL;
    arglist->tail = NULL;
}

/**
 ********************************************************************************
 *
 * @brief Copy a run_arg list
 *
 *******************************************************************************
 *
 * @param[in] arglist
 *          The list of running arguments to copy.
 *
 * @param[out] tailptr
 *          If tailptr is not NULL, on exit it containes the pointer to the tail
 *          of the list.
 *
 * @return The pointer to the head of the copy
 *
 *******************************************************************************
 */
run_arg_list_t
run_arg_list_copy( const run_arg_list_t *list )
{
    run_arg_list_t   copy;
    const run_arg_t *arg = list->head;
    run_arg_t *copy_curr = NULL;

    copy.head = NULL;
    copy.tail = NULL;

    /* Compute the size */
    while( arg != NULL ) {
        copy_curr = malloc( sizeof( run_arg_t ) );
        memcpy( copy_curr, arg, sizeof( run_arg_t ) );
        if ( copy.head == NULL ) {
            copy.head = copy_curr;
            copy.tail = copy_curr;
        }
        else {
            copy.tail->next = copy_curr;
            copy.tail = copy_curr;
        }
        arg = arg->next;
    }

    return copy;
}

/**
 ********************************************************************************
 *
 * @brief Add a single run argument list to the list of runs to perform
 *
 *******************************************************************************
 *
 * @param[inout] runlist
 *          The list of all the runs to perform
 *
 * @param[in] arglist
 *          The list of running arguments to copy into a new run added to the
 *          list of run.
 *
 *******************************************************************************
 */
void
run_list_add_one( run_list_t *runlist,
                  run_arg_t  *arglist )
{
    run_arg_list_t  list = { .head = arglist, .tail = NULL };
    run_list_elt_t *run;

    run = malloc( sizeof( run_list_elt_t ) );
    run->next = NULL;
    run->args = run_arg_list_copy( &list );

    if ( runlist->head == NULL ) {
        assert( runlist->tail == NULL );
        runlist->head = run;
        runlist->tail = run;
    }
    else {
        assert( runlist->tail->next == NULL );
        runlist->tail->next = run;
        runlist->tail = run;
    }
}

/**
 ********************************************************************************
 *
 * @brief Recursive function to generate the list of runs from the cartesian
 * product of the parameter values
 *
 *******************************************************************************
 *
 * @param[in] test_params
 *          The list of parameters that are considered in the test
 *
 * @param[inout] runlist
 *          The list of all the runs generated by the cartesian product.
 *
 * @param[in] arglist
 *          The current list of running arguments.
 *
 *******************************************************************************
 */
void
run_list_generate_rec( const char **test_params,
                       run_list_t  *runlist,
                       run_arg_t   *arglist )
{
    parameter_t *param = NULL;
    int          is_invalid = 1;
    run_arg_t    runarg;
    vallist_t   *vallist;

    /* End of the recursion */
    if ( *test_params == NULL ) {
        /* Add the current run_arg list to the tests */
        run_list_add_one( runlist, arglist );
        return;
    }

    /* Let's get the parameter */
    while( is_invalid && (*test_params != NULL) )
    {
        param = parameters_getbyname( *test_params );
        test_params++;

        is_invalid = ( param == NULL ) ||
            !(param->flags & PARAM_INPUT) ||
            ( param->vallist == NULL );
    }

    if ( is_invalid ) {
        /* Let's recurse one last time to register the test */
        run_list_generate_rec( test_params, runlist, arglist );
        return;
    }

    /* Let's iterate on all values */
    vallist = param->vallist;
    runarg.param = param;
    runarg.next = arglist;
    while ( vallist != NULL ) {
        runarg.value = vallist->value;
        run_list_generate_rec( test_params, runlist, &runarg );
        vallist = vallist->next;
    }

    return;
}

/**
 ********************************************************************************
 *
 * @brief Generate the list of runs from the cartesian product of the parameter
 * values.
 *
 *******************************************************************************
 *
 * @param[in] test_params
 *          The list of parameters that are considered in the test
 *
 * @return The list of all the runs generated by the cartesian product.
 *
 *******************************************************************************
 */
run_list_t *
run_list_generate( const char **params )
{
    run_list_t *runlist = calloc( 1, sizeof(run_list_t) );
    run_list_generate_rec( params, runlist, NULL );
    return runlist;
}

/**
 ********************************************************************************
 *
 * @brief Frees the run list
 *
 *******************************************************************************
 *
 * @param[inout] run
 *          The list of run to free.
 *
 *******************************************************************************
 */
void
run_list_destroy( run_list_elt_t *run )
{
    run_arg_list_destroy( &(run->args) );
    free( run );
}

/**
 * @brief The common input parameters to all tests
 */
const char *common_input[]  = { "threads", "gpus", "P", "Q", NULL };

/**
 * @brief The common output parameters to all tests
 */
const char *common_output[] = { "time", "gflops", NULL };

/**
 ********************************************************************************
 *
 * @brief Print into a string the header associated to a list of parameters
 *
 *******************************************************************************
 *
 * @param[in] list
 *          The list of parameters that will be printed.
 *
 * @param[in] human
 *          Boolean to to switch between human readable and csv outputs.
 *
 * @param[in] str
 *          Pointer to the string that will store the printing
 *
 * @return The pointer to the end of the string
 *
 *******************************************************************************
 */
char *
run_print_header_partial( const char **list, int human, char *str )
{
    parameter_t *param;
    const char **pname = list;
    int rc;

    while( *pname != NULL ) {
        if ( human ) {
            param = parameters_getbyname( *pname );
            assert( param != NULL );
            switch ( param->valtype ) {
            case TestTrans:
            case TestUplo:
            case TestDiag:
            case TestSide:
            case TestNormtype:
            case TestString:
                rc = sprintf( str, " %-*s", param->psize, *pname );
                break;
            default:
                rc = sprintf( str, " %*s", param->psize, *pname );
            }
        }
        else {
            rc = sprintf( str, ";%s", *pname );
        }
        assert( rc > 0 );
        str += rc;
        pname++;
    }
    return str;
}

/**
 ********************************************************************************
 *
 * @brief Print the header associated to a test
 *
 *******************************************************************************
 *
 * @param[in] test
 *          The test that is used.
 *
 * @param[in] check
 *          Tells if check parameters should be printed or not.
 *
 * @param[in] human
 *          Boolean to to switch between human readable and csv outputs.
 *
 *******************************************************************************
 */
void
run_print_header( const testing_t *test,
                  int check, int human )
{
    int rc, rank = CHAMELEON_Comm_rank();
    char str[2048];
    char *str_ptr = str;

    if ( rank ) {
        return;
    }

    if ( human ) {
        rc = sprintf( str_ptr, "%3s %-12s",
                      "Id", "Function" );
    }
    else {
        rc = sprintf( str_ptr, "%s;%s",
                      "Id", "Function" );
    }
    str_ptr += rc;

    /* Common input */
    str_ptr = run_print_header_partial( common_input, human, str_ptr );

    /* Specific input */
    str_ptr = run_print_header_partial( test->params, human, str_ptr );

    /* Common output */
    str_ptr = run_print_header_partial( common_output, human, str_ptr );

    /* Specific output */
    str_ptr = run_print_header_partial( test->output, human, str_ptr );

    /* Specific check output */
    if ( check ) {
        run_print_header_partial( test->outchk, human, str_ptr );
    }
    fprintf( stdout, "%s\n", str );
    return;
}

/**
 ********************************************************************************
 *
 * @brief Print into a string the data associated to a list of parameters
 *
 *******************************************************************************
 *
 * @param[in] list
 *          The list of parameters that will be printed.
 *
 * @param[in] arglist
 *          The argument list in which to find the values of the parameters.
 *
 * @param[in] human
 *          Boolean to to switch between human readable and csv outputs.
 *
 * @param[in] str
 *          Pointer to the string that will store the printing
 *
 * @return The pointer to the end of the string
 *
 *******************************************************************************
 */
char *
run_print_line_partial( const char **list, const run_arg_list_t *arglist,
                        int human, char *str )
{
    parameter_t     *param;
    const char     **pname = list;
    const run_arg_t *arg;
    val_t value;

    while( *pname != NULL ) {
        arg = run_arg_get_byname( arglist, *pname );
        if ( arg == NULL ) {
            /* Should be a common parameter */
            param = parameters_getbyname( *pname );
            assert( param != NULL );

            value = param->value;
        }
        else {
            param = arg->param;
            assert( param != NULL );
            value = arg->value;
        }

        str = param->sprint( value, human, param->psize, str );

        pname++;
    }
    return str;
}

/**
 ********************************************************************************
 *
 * @brief Print the data associated to one run of a test
 *
 *******************************************************************************
 *
 * @param[in] test
 *          The test that is used.
 *
 * @param[in] arglist
 *          The argument list in which to find the values of the parameters.
 *
 * @param[in] check
 *          Tells if check parameters should be printed or not.
 *
 * @param[in] human
 *          Boolean to to switch between human readable and csv outputs.
 *
 * @param[in] id
 *          The id of the run
 *
 *******************************************************************************
 */
void
run_print_line( const testing_t *test, const run_arg_list_t *arglist,
                int check, int human, int id )
{
    int rc, rank = CHAMELEON_Comm_rank();
    char str[2048];
    char *str_ptr = str;

    if ( rank ) {
        return;
    }

    if ( human ) {
        rc = sprintf( str_ptr, "%3d %-12s",
                      id, test->name );
    }
    else {
        rc = sprintf( str_ptr, "%d;%s",
                      id, test->name );
    }
    str_ptr += rc;

    /* Common input */
    str_ptr = run_print_line_partial( common_input, arglist, human, str_ptr );

    /* Specific input */
    str_ptr = run_print_line_partial( test->params, arglist, human, str_ptr );

    /* Common output */
    str_ptr = run_print_line_partial( common_output, arglist, human, str_ptr );

    /* Specific output */
    str_ptr = run_print_line_partial( test->output, arglist, human, str_ptr );

    /* Specific check output */
    if ( check ) {
        run_print_line_partial( test->outchk, arglist, human, str_ptr );
    }

    fprintf( stdout, "%s\n", str );
    fflush(stdout);
    return;
}
