#
# MeshTest
# -------
#
# ~~~
# MeshTest(
#   NAME <name of the the test>
#   PATH <working directory> # relative to SourceDir/Tests/Data
#   EXECUTABLE <executable target> # optional, defaults to ogs
#   EXECUTABLE_ARGS <arguments>
#   WRAPPER <time|memcheck|callgrind|mpirun> # optional
#   WRAPPER_ARGS <arguments> # optional
#   REQUIREMENTS # optional simple boolean expression which has to be true to
#                  enable the test, e.g.
#                  OGS_USE_PETSC AND (FOO OR BAR)
#   RUNTIME <in seconds> # optional for optimizing ctest duration
#                          values should be taken from envinf1 serial job
# )
# ~~~
# cmake-lint: disable=C0103,R0912,R0915
function(MeshTest)
    if(NOT OGS_BUILD_TESTING)
        return()
    endif()
    # parse arguments
    set(options NONE)
    set(oneValueArgs EXECUTABLE PATH NAME WRAPPER RUNTIME WORKING_DIRECTORY)
    set(multiValueArgs EXECUTABLE_ARGS DATA DIFF_DATA WRAPPER_ARGS REQUIREMENTS)
    cmake_parse_arguments(
        MeshTest "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}
    )

    set(MeshTest_SOURCE_PATH "${Data_SOURCE_DIR}/${MeshTest_PATH}")
    set(MeshTest_BINARY_PATH "${Data_BINARY_DIR}/${MeshTest_PATH}")
    file(MAKE_DIRECTORY ${MeshTest_BINARY_PATH})
    file(TO_NATIVE_PATH "${MeshTest_BINARY_PATH}" MeshTest_BINARY_PATH_NATIVE)
    set(MeshTest_STDOUT_FILE_PATH
        "${MeshTest_BINARY_PATH}/${MeshTest_NAME}_stdout.log"
    )

    # set defaults
    if(NOT DEFINED MeshTest_REQUIREMENTS)
        set(MeshTest_REQUIREMENTS TRUE)
    endif()
    set(timeout ${ogs.ctest.large_runtime})
    if(DEFINED MeshTest_RUNTIME)
        math(EXPR timeout "${MeshTest_RUNTIME} * 3")
    else()
        set(MeshTest_RUNTIME 1)
    endif()
    if(NOT DEFINED MeshTest_WORKING_DIRECTORY)
        set(MeshTest_WORKING_DIRECTORY ${MeshTest_BINARY_PATH})
    endif()

    # --- Implement wrappers ---
    # check requirements, disable if not met
    if(${MeshTest_REQUIREMENTS})
        # message(STATUS "Enabling test ${MeshTest_NAME}.")
    else()
        set(DISABLED_TESTS_LOG
            "${DISABLED_TESTS_LOG}\nRequirement ${MeshTest_REQUIREMENTS} not met! Disabling test ${MeshTest_NAME}."
            CACHE INTERNAL ""
        )
        return()
    endif()

    if(MeshTest_WRAPPER STREQUAL "time")
        if(TIME_TOOL_PATH)
            set(WRAPPER_COMMAND time)
        else()
            set(DISABLED_TESTS_LOG
                "${DISABLED_TESTS_LOG}\nDisabling time wrapper for ${MeshTest_NAME} as time exe was not found!"
                CACHE INTERNAL ""
            )
            set(MeshTest_WRAPPER_ARGS "")
        endif()
    elseif(MeshTest_WRAPPER STREQUAL "memcheck")
        if(VALGRIND_TOOL_PATH)
            set(WRAPPER_COMMAND
                "${VALGRIND_TOOL_PATH} --tool=memcheck --log-file=${MeshTest_SOURCE_PATH}/${MeshTest_NAME}_memcheck.log -v --leak-check=full --show-reachable=yes --track-origins=yes --malloc-fill=0xff --free-fill=0xff"
            )
            set(tester memcheck)
        else()
            set(DISABLED_TESTS_LOG
                "${DISABLED_TESTS_LOG}\nDisabling memcheck wrapper for ${MeshTest_NAME} as memcheck exe was not found!"
                CACHE INTERNAL ""
            )
            set(MeshTest_WRAPPER_ARGS "")
        endif()
    elseif(MeshTest_WRAPPER STREQUAL "callgrind")
        if(VALGRIND_TOOL_PATH)
            set(WRAPPER_COMMAND
                "${VALGRIND_TOOL_PATH} --tool=callgrind --branch-sim=yes --cache-sim=yes --dump-instr=yes --collect-jumps=yes"
            )
            unset(tester)
        else()
            set(DISABLED_TESTS_LOG
                "${DISABLED_TESTS_LOG}\nDisabling callgrind wrapper for ${MeshTest_NAME} as callgrind exe was not found!"
                CACHE INTERNAL ""
            )
            set(MeshTest_WRAPPER_ARGS "")
        endif()
    elseif(MeshTest_WRAPPER STREQUAL "mpirun")
        if(MPIRUN_TOOL_PATH)
            set(WRAPPER_COMMAND ${MPIRUN_TOOL_PATH})
        else()
            message(
                STATUS
                    "ERROR: mpirun was not found but is required for ${MeshTest_NAME}!"
            )
            return()
        endif()
    endif()

    # --- Implement testers ---
    if(NOT MeshTest_DIFF_DATA)
        message(
            FATAL_ERROR "MeshTest(): ${MeshTest_NAME} - no DIFF_DATA given!"
        )
    endif()

    string(LENGTH "${MeshTest_DIFF_DATA}" DIFF_DATA_LENGTH)
    if(${DIFF_DATA_LENGTH} GREATER 7500)
        message(
            FATAL_ERROR
                "${MeshTest_NAME}: DIFF_DATA to long! Consider using regex-syntax: TODO"
        )
    endif()

    set(SELECTED_DIFF_TOOL_PATH $<TARGET_FILE:vtkdiff>)

    list(LENGTH MeshTest_DIFF_DATA DiffDataLength)
    math(EXPR DiffDataLengthMod3 "${DiffDataLength} % 3")
    if(${DiffDataLengthMod3} EQUAL 0)
        math(EXPR DiffDataLastIndex "${DiffDataLength}-1")
        foreach(DiffDataIndex RANGE 0 ${DiffDataLastIndex} 4)
            list(GET MeshTest_DIFF_DATA "${DiffDataIndex}" REFERENCE_VTK_FILE)
            math(EXPR DiffDataAuxIndex "${DiffDataIndex}+1")
            list(GET MeshTest_DIFF_DATA "${DiffDataAuxIndex}" VTK_FILE)
            math(EXPR DiffDataAuxIndex "${DiffDataIndex}+2")
            list(GET MeshTest_DIFF_DATA "${DiffDataAuxIndex}" ABS_TOLERANCE)

            list(
                APPEND
                TESTER_COMMAND
                "${SELECTED_DIFF_TOOL_PATH} -m \
            ${MeshTest_SOURCE_PATH}/${REFERENCE_VTK_FILE} \
            ${MeshTest_BINARY_PATH}/${VTK_FILE} \
            --abs ${ABS_TOLERANCE}"
            )
        endforeach()
    else()
        message(FATAL_ERROR "The number of diff data arguments must be a
        multiple of three: expected.vtu output.vtu absolute_tolerance."
        )
    endif()

    # -----------
    if(TARGET ${MeshTest_EXECUTABLE})
        set(MeshTest_EXECUTABLE_PARSED $<TARGET_FILE:${MeshTest_EXECUTABLE}>)
    else()
        set(MeshTest_EXECUTABLE_PARSED ${MeshTest_EXECUTABLE})
    endif()

    set(FILES_TO_DELETE "")
    list(APPEND FILES_TO_DELETE "${MeshTest_STDOUT_FILE_PATH}")
    foreach(ITEM ${MeshTest_DIFF_DATA})
        if(ITEM MATCHES "^.*\.(vtu|vtk)$")
            list(APPEND FILES_TO_DELETE "${ITEM}")
        endif()
    endforeach()

    # Run the wrapper
    if(DEFINED MeshTest_WRAPPER)
        set(MeshTest_WRAPPER_STRING "-${MeshTest_WRAPPER}")
    endif()
    set(TEST_NAME
        "${MeshTest_EXECUTABLE}-${MeshTest_NAME}${MeshTest_WRAPPER_STRING}"
    )
    add_test(
        NAME ${TEST_NAME}
        COMMAND
            ${CMAKE_COMMAND} -DEXECUTABLE=${MeshTest_EXECUTABLE_PARSED}
            "-DEXECUTABLE_ARGS=${MeshTest_EXECUTABLE_ARGS}" # Quoted because
                                                            # passed as list see
                                                            # https://stackoverflow.com/a/33248574/80480
            -DBINARY_PATH=${MeshTest_BINARY_PATH}
            -DWRAPPER_COMMAND=${WRAPPER_COMMAND}
            "-DWRAPPER_ARGS=${MeshTest_WRAPPER_ARGS}"
            "-DFILES_TO_DELETE=${FILES_TO_DELETE}"
            -DSTDOUT_FILE_PATH=${MeshTest_STDOUT_FILE_PATH}
            -DWORKING_DIRECTORY=${MeshTest_WORKING_DIRECTORY}
            -DLOG_FILE=${PROJECT_BINARY_DIR}/logs/${TEST_NAME}.log
            -P
            ${PROJECT_SOURCE_DIR}/scripts/cmake/test/AddTestWrapper.cmake
    )
    current_dir_as_list(ProcessLib labels)
    if(${MeshTest_RUNTIME} LESS_EQUAL ${ogs.ctest.large_runtime})
        list(APPEND labels default)
    else()
        list(APPEND labels large)
    endif()
    set_tests_properties(${TEST_NAME}
        PROPERTIES
            COST ${MeshTest_RUNTIME}
            LABELS "meshtest;${labels}"
    )
    # Disabled for the moment, does not work with CI under load
    # if(NOT OGS_COVERAGE)
    #     set_tests_properties(${TEST_NAME} PROPERTIES TIMEOUT ${timeout})
    # endif()

    if(TARGET ${MeshTest_EXECUTABLE})
        add_dependencies(ctest ${MeshTest_EXECUTABLE})
        add_dependencies(ctest-large ${MeshTest_EXECUTABLE})
    endif()

    # Run the tester
    set(MeshTest_TESTER "vtkdiff")
    set(TESTER_NAME "${TEST_NAME}-${MeshTest_TESTER}")
    add_test(
        NAME ${TESTER_NAME}
        COMMAND
            ${CMAKE_COMMAND} -DSOURCE_PATH=${MeshTest_SOURCE_PATH}
            -DBINARY_PATH=${${MeshTest_BINARY_PATH}}
            -DSELECTED_DIFF_TOOL_PATH=${SELECTED_DIFF_TOOL_PATH}
            "-DTESTER_COMMAND=${TESTER_COMMAND}"
            -DVTKJS_CONVERTER=${VTKJS_CONVERTER}
            -DBINARY_PATH=${MeshTest_BINARY_PATH}
            -DVTKJS_OUTPUT_PATH=${PROJECT_SOURCE_DIR}/web/static/vis/${MeshTest_PATH}
            "-DVIS_FILES=${MeshTest_VIS}"
            -DLOG_FILE_BASE=${PROJECT_BINARY_DIR}/logs/${TESTER_NAME}
            -DGLOB_MODE=${GLOB_MODE}
            -P
            ${PROJECT_SOURCE_DIR}/scripts/cmake/test/AddTestTester.cmake
            --debug-output
        WORKING_DIRECTORY ${MeshTest_SOURCE_PATH}
    )
    set_tests_properties(${TESTER_NAME}
        PROPERTIES
            DEPENDS ${TEST_NAME}
            LABELS "tester;${labels}"
    )

endfunction()
