#ifndef _MATRIX_TEMPLATES_H_
#define _MATRIX_TEMPLATES_H_

/*--------------------------------------------------------------------------------*/
/* Includes */
/*--------------------------------------------------------------------------------*/
#include "test_templates.h"

/*--------------------------------------------------------------------------------*/
/* Group Specific Templates */
/*--------------------------------------------------------------------------------*/

/**
 *  Compare the outputs from the function under test and the reference
 *  function.
 */
#define MATRIX_COMPARE_INTERFACE(output_type, output_content_type)  \
    TEST_ASSERT_BUFFERS_EQUAL(                                      \
        ((output_type *) &matrix_output_ref)->pData,                \
        ((output_type *) &matrix_output_fut)->pData,                \
        ((output_type *) &matrix_output_fut)->numRows *             \
        ((output_type *) &matrix_output_ref)->numCols *             \
        sizeof(output_content_type))

/**
 * Comparison SNR thresholds for the data types used in matrix_tests.
 */
#define MATRIX_SNR_THRESHOLD 120

/**
 *  Compare the outputs from the function under test and the reference
 *  function using SNR.
 */
#define MATRIX_SNR_COMPARE_INTERFACE(output_type, output_content_type)  \
    do                                                                  \
    {                                                                   \
        TEST_CONVERT_AND_ASSERT_SNR(                                    \
            (float32_t *)matrix_output_f32_ref,                         \
            ((output_type *) &matrix_output_ref)->pData,                \
            (float32_t *)matrix_output_f32_fut,                         \
            ((output_type *) &matrix_output_ref)->pData,                \
            ((output_type *) &matrix_output_fut)->numRows *             \
            ((output_type *) &matrix_output_ref)->numCols,              \
            output_content_type,                                        \
            MATRIX_SNR_THRESHOLD                                        \
            );                                                          \
    } while (0)

/**
 *  Compare the outputs from the function under test and the reference
 *  function using SNR. This is special for float64_t
 */
#define MATRIX_DBL_SNR_COMPARE_INTERFACE(output_type)                   \
    do                                                                  \
    {                                                                   \
        TEST_ASSERT_DBL_SNR(                                            \
            (float64_t *)matrix_output_f32_ref,                         \
            (float64_t *)matrix_output_f32_fut,                         \
            ((output_type *) &matrix_output_fut)->numRows *             \
            ((output_type *) &matrix_output_ref)->numCols,              \
            MATRIX_SNR_THRESHOLD                                        \
            );                                                          \
    } while (0)

/*--------------------------------------------------------------------------------*/
/* Input Interfaces */
/*--------------------------------------------------------------------------------*/
/*
 *  General:
 *  Input interfaces provide inputs to functions inside test templates.  They
 *  ONLY provide the inputs.  The output variables should be hard coded.
 *
 *  The input interfaces must have the following format:
 *
 *  ARM_xxx_INPUT_INTERFACE() or
 *  REF_xxx_INPUT_INTERFACE()
 *
 *  The xxx must be lowercase, and is intended to be the indentifying substring
 *  in the function's name.  Acceptable values are 'sub' or 'add' from the
 *  functions arm_add_q31.
 */

#define ARM_mat_add_INPUT_INTERFACE(input_a_ptr, input_b_ptr)    \
    PAREN(input_a_ptr, input_b_ptr, (void *) &matrix_output_fut)

#define REF_mat_add_INPUT_INTERFACE(input_a_ptr, input_b_ptr)    \
    PAREN(input_a_ptr, input_b_ptr, (void *) &matrix_output_ref)

#define ARM_mat_cmplx_mult_INPUT_INTERFACE(input_a_ptr, input_b_ptr)    \
    PAREN(input_a_ptr, input_b_ptr, (void *) &matrix_output_fut)

#define REF_mat_cmplx_mult_INPUT_INTERFACE(input_a_ptr, input_b_ptr)    \
    PAREN(input_a_ptr, input_b_ptr, (void *) &matrix_output_ref)

#define ARM_mat_inverse_INPUT_INTERFACE(input_ptr)  \
    PAREN(input_ptr, (void *) &matrix_output_fut)

#define REF_mat_inverse_INPUT_INTERFACE(input_ptr)  \
    PAREN(input_ptr, (void *) &matrix_output_ref)

#define ARM_mat_mult_INPUT_INTERFACE(input_a_ptr, input_b_ptr)      \
    PAREN(input_a_ptr, input_b_ptr, (void *) &matrix_output_fut)

#define REF_mat_mult_INPUT_INTERFACE(input_a_ptr, input_b_ptr)      \
    PAREN(input_a_ptr, input_b_ptr, (void *) &matrix_output_ref)

#define ARM_mat_mult_fast_INPUT_INTERFACE(input_a_ptr, input_b_ptr) \
    PAREN(input_a_ptr, input_b_ptr, (void *) &matrix_output_fut)

#define REF_mat_mult_fast_INPUT_INTERFACE(input_a_ptr, input_b_ptr) \
    PAREN(input_a_ptr, input_b_ptr, (void *) &matrix_output_ref)

#define ARM_mat_sub_INPUT_INTERFACE(input_a_ptr, input_b_ptr)    \
    PAREN(input_a_ptr, input_b_ptr, (void *) &matrix_output_fut)

#define REF_mat_sub_INPUT_INTERFACE(input_a_ptr, input_b_ptr)    \
    PAREN(input_a_ptr, input_b_ptr, (void *) &matrix_output_ref)

#define ARM_mat_trans_INPUT_INTERFACE(input_ptr)    \
    PAREN(input_ptr, (void *) &matrix_output_fut)

#define REF_mat_trans_INPUT_INTERFACE(input_ptr)    \
    PAREN(input_ptr, (void *) &matrix_output_ref)

/*--------------------------------------------------------------------------------*/
/* Dimension Validation Interfaces */
/*--------------------------------------------------------------------------------*/

#define MATRIX_TEST_VALID_ADDITIVE_DIMENSIONS(input_type,   \
                                              matrix_a_ptr, \
                                              matrix_b_ptr) \
    ((((input_type) (matrix_a_ptr))->numRows ==             \
      ((input_type) (matrix_b_ptr))->numRows) &&            \
     (((input_type) (matrix_a_ptr))->numCols ==             \
      ((input_type) (matrix_b_ptr))->numCols))

#define MATRIX_TEST_VALID_MULTIPLICATIVE_DIMENSIONS(input_type,     \
                                                    matrix_a_ptr,   \
                                                    matrix_b_ptr)   \
    (((input_type) (matrix_a_ptr))->numCols ==                      \
     ((input_type) (matrix_b_ptr))->numRows)

#define MATRIX_TEST_VALID_SQUARE_DIMENSIONS(input_type, \
                                            matrix_ptr) \
    (((input_type)(matrix_ptr))->numRows ==             \
     ((input_type)(matrix_ptr))->numCols)

#define MATRIX_TEST_VALID_DIMENSIONS_ALWAYS(input_type, \
                                            matrix_ptr) \
    (1 == 1)                                            \

/*--------------------------------------------------------------------------------*/
/* Output Configuration Interfaces */
/*--------------------------------------------------------------------------------*/
/* The matrix tests assume the output matrix is always the correct size.  These
 * interfaces size the properly size the output matrices according to the input
 * matrices and the operation at hand.*/

#define MATRIX_TEST_CONFIG_ADDITIVE_OUTPUT(input_type,      \
                                           matrix_a_ptr,    \
                                           matrix_b_ptr)    \
    do                                                      \
    {                                                       \
        ((input_type) &matrix_output_fut)->numRows =        \
            ((input_type)(matrix_a_ptr))->numRows;          \
        ((input_type) &matrix_output_fut)->numCols =        \
            ((input_type)(matrix_a_ptr))->numCols;          \
        ((input_type) &matrix_output_ref)->numRows =        \
            ((input_type)(matrix_a_ptr))->numRows;          \
        ((input_type) &matrix_output_ref)->numCols =        \
            ((input_type)(matrix_a_ptr))->numCols;          \
    } while (0)

#define MATRIX_TEST_CONFIG_MULTIPLICATIVE_OUTPUT(input_type,    \
                                                 matrix_a_ptr,  \
                                                 matrix_b_ptr)  \
    do                                                          \
    {                                                           \
        ((input_type) &matrix_output_fut)->numRows =            \
            ((input_type)(matrix_a_ptr))->numRows;              \
        ((input_type) &matrix_output_fut)->numCols =            \
            ((input_type)(matrix_b_ptr))->numCols;              \
        ((input_type) &matrix_output_ref)->numRows =            \
            ((input_type)(matrix_a_ptr))->numRows;              \
        ((input_type) &matrix_output_ref)->numCols =            \
            ((input_type)(matrix_b_ptr))->numCols;              \
    } while (0)

#define MATRIX_TEST_CONFIG_SAMESIZE_OUTPUT(input_type,  \
                                           matrix_ptr)  \
    do                                                  \
    {                                                   \
        ((input_type) &matrix_output_fut)->numRows =    \
            ((input_type)(matrix_ptr))->numRows;        \
        ((input_type) &matrix_output_fut)->numCols =    \
            ((input_type)(matrix_ptr))->numCols;        \
        ((input_type) &matrix_output_ref)->numRows =    \
            ((input_type)(matrix_ptr))->numRows;        \
        ((input_type) &matrix_output_ref)->numCols =    \
            ((input_type)(matrix_ptr))->numCols;        \
    } while (0)

#define MATRIX_TEST_CONFIG_TRANSPOSE_OUTPUT(input_type,     \
                                            matrix_ptr)     \
        do                                                  \
        {                                                   \
            ((input_type) &matrix_output_fut)->numRows =    \
                ((input_type)(matrix_ptr))->numCols;        \
            ((input_type) &matrix_output_fut)->numCols =    \
                ((input_type)(matrix_ptr))->numRows;        \
            ((input_type) &matrix_output_ref)->numRows =    \
                ((input_type)(matrix_ptr))->numCols;        \
            ((input_type) &matrix_output_ref)->numCols =    \
                ((input_type)(matrix_ptr))->numRows;        \
        } while (0)

/*--------------------------------------------------------------------------------*/
/* TEST Templates */
/*--------------------------------------------------------------------------------*/

#define MATRIX_TEST_TEMPLATE_ELT1(arr_desc_inputs,                      \
                                  input_type,                           \
                                  output_type, output_content_type,     \
                                  fut, fut_arg_interface,               \
                                  ref, ref_arg_interface,               \
                                  output_config_interface,              \
                                  dim_validation_interface,             \
                                  compare_interface)                    \
    do                                                                  \
    {                                                                   \
        TEMPLATE_DO_ARR_DESC(                                           \
            input_idx, input_type, input, arr_desc_inputs               \
            ,                                                           \
            JTEST_DUMP_STRF("Matrix Dimensions: %dx%d\n",               \
                         (int)input->numRows,                           \
                         (int)input->numCols);                          \
                                                                        \
            if (dim_validation_interface(input_type,                     \
                                        input)) {                       \
                output_config_interface(input_type,                     \
                                        input);                         \
                TEST_CALL_FUT_AND_REF(                                  \
                    fut, fut_arg_interface(input),                      \
                    ref, ref_arg_interface(input));                     \
                compare_interface(output_type,                          \
                                  output_content_type);                 \
            } else {                                                    \
                arm_status matrix_test_retval;                          \
                TEST_CALL_FUT(                                          \
                    matrix_test_retval = fut,                           \
                    fut_arg_interface(input));                          \
                                                                        \
                /* If dimensions are known bad, the fut should */       \
                /* detect it. */                                        \
                if ( matrix_test_retval != ARM_MATH_SIZE_MISMATCH) {     \
                    return JTEST_TEST_FAILED;                           \
                }                                                       \
            });                                                         \
        return JTEST_TEST_PASSED;                                       \
    } while (0)


#define MATRIX_TEST_TEMPLATE_ELT2(arr_desc_inputs_a,                    \
                                  arr_desc_inputs_b,                    \
                                  input_type,                           \
                                  output_type, output_content_type,     \
                                  fut, fut_arg_interface,               \
                                  ref, ref_arg_interface,               \
                                  output_config_interface,              \
                                  dim_validation_interface,             \
                                  compare_interface)                    \
    do                                                                  \
    {                                                                   \
        TEMPLATE_DO_ARR_DESC(                                           \
            input_a_idx, input_type, input_a, arr_desc_inputs_a         \
            ,                                                           \
            input_type input_b = ARR_DESC_ELT(                          \
                input_type, input_a_idx,                                \
                &(arr_desc_inputs_b));                                  \
                                                                        \
            JTEST_DUMP_STRF("Matrix Dimensions: A %dx%d  B %dx%d\n",    \
                     (int)input_a->numRows,                             \
                     (int)input_a->numCols,                             \
                     (int)input_b->numRows,                             \
                     (int)input_b->numCols);                            \
                                                                        \
            if (dim_validation_interface(input_type,                     \
                                        input_a,                        \
                                        input_b)) {                     \
                                                                        \
                output_config_interface(input_type,                     \
                                        input_a,                        \
                                        input_b);                       \
                                                                        \
                TEST_CALL_FUT_AND_REF(                                  \
                    fut, fut_arg_interface(input_a, input_b),           \
                    ref, ref_arg_interface(input_a, input_b));          \
                                                                        \
                compare_interface(output_type, output_content_type);    \
                                                                        \
            } else {                                                    \
                arm_status matrix_test_retval;                          \
                TEST_CALL_FUT(                                          \
                    matrix_test_retval = fut, fut_arg_interface(input_a, input_b)); \
                                                                        \
                /* If dimensions are known bad, the fut should */       \
                /* detect it. */                                        \
                if ( matrix_test_retval != ARM_MATH_SIZE_MISMATCH) {     \
                    return JTEST_TEST_FAILED;                           \
                }                                                       \
            });                                                         \
        return JTEST_TEST_PASSED;                                       \
    } while (0)

/**
 *  Specialization of #MATRIX_TEST_TEMPLATE_ELT2() for matrix tests.
 *
 *  @note This macro relies on the existance of ARM_xxx_INPUT_INTERFACE and
 *  REF_xxx_INPUT_INTERFACEs.
 */
#define MATRIX_DEFINE_TEST_TEMPLATE_ELT2(fn_name, suffix,           \
                                         output_config_interface,   \
                                         dim_validation_interface,  \
                                         comparison_interface)      \
        JTEST_DEFINE_TEST(arm_##fn_name##_##suffix##_test,          \
                          arm_##fn_name##_##suffix)                 \
        {                                                           \
            MATRIX_TEST_TEMPLATE_ELT2(                              \
                matrix_##suffix##_a_inputs,                         \
                matrix_##suffix##_b_inputs,                         \
                arm_matrix_instance_##suffix * ,                    \
                arm_matrix_instance_##suffix,                       \
                TYPE_FROM_ABBREV(suffix),                           \
                arm_##fn_name##_##suffix,                           \
                ARM_##fn_name##_INPUT_INTERFACE,                    \
                ref_##fn_name##_##suffix,                           \
                REF_##fn_name##_INPUT_INTERFACE,                    \
                output_config_interface,                            \
                dim_validation_interface,                           \
                comparison_interface);                              \
        }                                                           \

/**
 *  Specialization of #MATRIX_TEST_TEMPLATE_ELT1() for matrix tests.
 *
 *  @note This macro relies on the existance of ARM_xxx_INPUT_INTERFACE and
 *  REF_xxx_INPUT_INTERFACEs.
 */
#define MATRIX_DEFINE_TEST_TEMPLATE_ELT1(fn_name, suffix,           \
                                         output_config_interface,   \
                                         dim_validation_interface)  \
        JTEST_DEFINE_TEST(arm_##fn_name##_##suffix##_test,          \
                          arm_##fn_name##_##suffix)                 \
        {                                                           \
            MATRIX_TEST_TEMPLATE_ELT1(                              \
                matrix_##suffix##_a_inputs,                         \
                arm_matrix_instance_##suffix * ,                    \
                arm_matrix_instance_##suffix,                       \
                TYPE_FROM_ABBREV(suffix),                           \
                arm_##fn_name##_##suffix,                           \
                ARM_##fn_name##_INPUT_INTERFACE,                    \
                ref_##fn_name##_##suffix,                           \
                REF_##fn_name##_INPUT_INTERFACE,                    \
                output_config_interface,                            \
                dim_validation_interface,                           \
                MATRIX_COMPARE_INTERFACE);                          \
        }                                                           \


#endif /* _MATRIX_TEMPLATES_H_ */
