#  This file is part of t8code.
#  t8code is a C library to manage a collection (a forest) of multiple
#  connected adaptive space-trees of general element types in parallel.
#
#  Copyright (C) 2025 the developers
#
#  t8code is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  t8code is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with t8code; if not, write to the Free Software Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

# Function to add a test executable and register it with CTest.
# The test executable is built from the provided source files.
function( add_t8_test )
    set( options "" )
    set( oneValueArgs "NAME" )
    set( multiValueArgs "SOURCES" )
    cmake_parse_arguments( ADD_T8_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )

    # Get the path of the first file listed in the SOURCES list and use it to determine the build directory.
    list (LENGTH ADD_T8_TEST_SOURCES TEST_SOURCES_LENGTH)
    list(GET ADD_T8_TEST_SOURCES 0 TEST_SOURCE)
    
    get_filename_component(TEST_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/${TEST_SOURCE}" DIRECTORY)
    file(RELATIVE_PATH TEST_RELATIVE_DIR "${CMAKE_SOURCE_DIR}" "${TEST_SOURCE_DIR}")
    set(TEST_BUILD_DIR "${CMAKE_BINARY_DIR}/${TEST_RELATIVE_DIR}")

    add_executable( ${ADD_T8_TEST_NAME} ${ADD_T8_TEST_SOURCES} )

    # Link base libraries.
    target_include_directories( ${ADD_T8_TEST_NAME} PRIVATE ${CMAKE_SOURCE_DIR} )

    target_link_libraries( ${ADD_T8_TEST_NAME} PRIVATE GTest::gtest_main T8 pthread )

    # Check if test is a Fortran or mesh handle file.
    string( FIND ${ADD_T8_TEST_NAME} "fortran" is_fortran_file )
    string( FIND ${TEST_BUILD_DIR} "mesh_handle" is_mesh_handle_file )

    if ( (${is_fortran_file} GREATER_EQUAL 0) AND T8CODE_ENABLE_MPI ) 
        target_link_libraries( ${ADD_T8_TEST_NAME} PRIVATE MPI::MPI_Fortran )
    endif ()
    if ( (${is_mesh_handle_file} GREATER_EQUAL 0) )
        target_link_libraries( ${ADD_T8_TEST_NAME} PRIVATE T8_MESH_HANDLE )
    endif ()

    set_target_properties(${ADD_T8_TEST_NAME} PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY "${TEST_BUILD_DIR}"
        LIBRARY_OUTPUT_DIRECTORY "${TEST_BUILD_DIR}"
        ARCHIVE_OUTPUT_DIRECTORY "${TEST_BUILD_DIR}"
    )

    if( T8CODE_EXPORT_COMPILE_COMMANDS )
        set_target_properties( ${ADD_T8_TEST_NAME} PROPERTIES EXPORT_COMPILE_COMMANDS ON )
    endif( T8CODE_EXPORT_COMPILE_COMMANDS )

    # Split custom test command into a list by whitespace.
    # If MPI is enabled, and no custom test command is provided, use mpirun -np 4 as the default command.
    # If MPI is not enabled, use the custom test command as is.
    if ( T8CODE_ENABLE_MPI )
        if (${ADD_T8_TEST_NAME} MATCHES "_serial")
            separate_arguments(T8CODE_TEST_COMMAND_LIST NATIVE_COMMAND ${T8CODE_CUSTOM_SERIAL_TEST_COMMAND})
        elseif ( T8CODE_CUSTOM_PARALLEL_TEST_COMMAND STREQUAL "" )
            separate_arguments (T8CODE_TEST_COMMAND_LIST NATIVE_COMMAND "mpirun -np 4")
        else ( T8CODE_CUSTOM_PARALLEL_TEST_COMMAND STREQUAL "" )
            separate_arguments (T8CODE_TEST_COMMAND_LIST NATIVE_COMMAND ${T8CODE_CUSTOM_PARALLEL_TEST_COMMAND})
        endif ()
    else( T8CODE_ENABLE_MPI )
        separate_arguments (T8CODE_TEST_COMMAND_LIST NATIVE_COMMAND ${T8CODE_CUSTOM_SERIAL_TEST_COMMAND})
    endif ( T8CODE_ENABLE_MPI )
    add_test( NAME ${ADD_T8_TEST_NAME} COMMAND ${T8CODE_TEST_COMMAND_LIST} ${TEST_BUILD_DIR}/${ADD_T8_TEST_NAME} )
    if ( T8CODE_CODE_COVERAGE )
        # Extent time that a single test is allowed to run to collect coverage information (as this naturally takes more time).
        # The default timeout of 1500 seconds is actually exceeded for some tests.
        # Without the collection of coverage information, an extension of the default timeout is not needed.
        set_tests_properties(${ADD_T8_TEST_NAME} PROPERTIES TIMEOUT 3000)
    endif ( T8CODE_CODE_COVERAGE )
endfunction()

# Function to add a test executable for C++ tests.
# This function wraps the `add_t8_test` function to include common test sources.
# Common test sources are t8_gtest_main.cpp and t8_gtest_memory_macros.cxx
function ( add_t8_cpp_test)
    set( options "" )
    set( oneValueArgs "NAME" )
    set( multiValueArgs "SOURCES" )
    cmake_parse_arguments( ADD_T8_CPP_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )

    # add cpp sources to SOURCES list and call add_t8_test function
    list ( APPEND ADD_T8_CPP_TEST_SOURCES t8_gtest_main.cxx t8_gtest_memory_macros.cxx )
    add_t8_test( NAME ${ADD_T8_CPP_TEST_NAME} SOURCES ${ADD_T8_CPP_TEST_SOURCES} )
endfunction()

# Copy test files to build folder so that the t8_test programs can find them.
function( copy_test_file TEST_FILE_NAME )
    configure_file(${CMAKE_CURRENT_LIST_DIR}/testfiles/${TEST_FILE_NAME} ${CMAKE_CURRENT_BINARY_DIR}/test/testfiles/${TEST_FILE_NAME} COPYONLY)
endfunction()

add_t8_cpp_test( NAME t8_gtest_cmesh_bcast_parallel     SOURCES t8_cmesh/t8_gtest_bcast.cxx )
add_t8_cpp_test( NAME t8_gtest_eclass_serial            SOURCES t8_gtest_eclass.cxx )
add_t8_cpp_test( NAME t8_gtest_mat_serial               SOURCES t8_gtest_mat.cxx )
add_t8_cpp_test( NAME t8_gtest_refcount_serial          SOURCES t8_gtest_refcount.cxx )
add_t8_cpp_test( NAME t8_gtest_occ_linkage_serial       SOURCES t8_gtest_occ_linkage.cxx )
add_t8_cpp_test( NAME t8_gtest_version_serial           SOURCES t8_gtest_version.cxx )
add_t8_cpp_test( NAME t8_gtest_basics_serial            SOURCES t8_gtest_basics.cxx )
add_t8_cpp_test( NAME t8_gtest_netcdf_linkage_serial    SOURCES t8_gtest_netcdf_linkage.cxx )
add_t8_cpp_test( NAME t8_gtest_vtk_linkage_serial       SOURCES t8_gtest_vtk_linkage.cxx )

add_t8_cpp_test( NAME t8_gtest_type_serial                  SOURCES t8_types/t8_gtest_type.cxx )
add_t8_cpp_test( NAME t8_gtest_random_accessible_serial     SOURCES t8_types/t8_gtest_random_accessible.cxx )
add_t8_cpp_test( NAME t8_gtest_vec_serial                   SOURCES t8_types/t8_gtest_vec.cxx )

add_t8_cpp_test( NAME t8_gtest_hypercube_parallel                           SOURCES t8_cmesh/t8_gtest_hypercube.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_readmshfile_serial                     SOURCES t8_cmesh/t8_gtest_cmesh_readmshfile.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_copy_serial                            SOURCES t8_cmesh/t8_gtest_cmesh_copy.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_face_is_boundary_parallel              SOURCES t8_cmesh/t8_gtest_cmesh_face_is_boundary.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_partition_parallel                     SOURCES t8_cmesh/t8_gtest_cmesh_partition.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_set_partition_offsets_parallel         SOURCES t8_cmesh/t8_gtest_cmesh_set_partition_offsets.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_set_join_by_vertices_serial            SOURCES t8_cmesh/t8_gtest_cmesh_set_join_by_vertices.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_add_attributes_when_derive_parallel    SOURCES t8_cmesh/t8_gtest_cmesh_add_attributes_when_derive.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_vertex_conn_tree_to_vertex_parallel    SOURCES t8_cmesh/t8_gtest_cmesh_vertex_conn_tree_to_vertex.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_vertex_conn_vertex_to_tree_parallel    SOURCES t8_cmesh/t8_gtest_cmesh_vertex_conn_vertex_to_tree.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_vertex_conn_serial                     SOURCES t8_cmesh/t8_gtest_cmesh_vertex_conn.cxx )
add_t8_cpp_test( NAME t8_gtest_compute_first_element_serial                 SOURCES t8_cmesh/t8_gtest_compute_first_element.cxx )
add_t8_cpp_test( NAME t8_gtest_multiple_attributes_parallel                 SOURCES t8_cmesh/t8_gtest_multiple_attributes.cxx )
add_t8_cpp_test( NAME t8_gtest_attribute_gloidx_array_serial                SOURCES t8_cmesh/t8_gtest_attribute_gloidx_array.cxx )
add_t8_cpp_test( NAME t8_gtest_cmesh_bounding_box_serial                    SOURCES t8_cmesh/t8_gtest_cmesh_bounding_box.cxx )

add_t8_cpp_test( NAME t8_gtest_shmem_parallel  SOURCES t8_data/t8_gtest_shmem.cxx )
add_t8_cpp_test( NAME t8_gtest_data_pack_parallel       SOURCES t8_data/t8_gtest_data_handler.cxx t8_data/t8_data_handler_specs.cxx)

add_t8_cpp_test( NAME t8_gtest_element_volume_serial              SOURCES t8_forest/t8_gtest_element_volume.cxx )
add_t8_cpp_test( NAME t8_gtest_search_parallel                    SOURCES t8_forest/t8_gtest_search.cxx )
add_t8_cpp_test( NAME t8_gtest_half_neighbors_parallel            SOURCES t8_forest/t8_gtest_half_neighbors.cxx )
add_t8_cpp_test( NAME t8_gtest_find_owner_parallel                SOURCES t8_forest/t8_gtest_find_owner.cxx )
add_t8_cpp_test( NAME t8_gtest_user_data_parallel                 SOURCES t8_forest/t8_gtest_user_data.cxx )
add_t8_cpp_test( NAME t8_gtest_transform_serial                   SOURCES t8_forest/t8_gtest_transform.cxx )
add_t8_cpp_test( NAME t8_gtest_ghost_exchange_parallel            SOURCES t8_forest/t8_gtest_ghost_exchange.cxx )
add_t8_cpp_test( NAME t8_gtest_ghost_delete_parallel              SOURCES t8_forest/t8_gtest_ghost_delete.cxx )
add_t8_cpp_test( NAME t8_gtest_ghost_and_owner_parallel           SOURCES t8_forest/t8_gtest_ghost_and_owner.cxx )
add_t8_cpp_test( NAME t8_gtest_balance_parallel                   SOURCES t8_forest/t8_gtest_balance.cxx )
add_t8_cpp_test( NAME t8_gtest_forest_commit_parallel             SOURCES t8_forest/t8_gtest_forest_commit.cxx )
add_t8_cpp_test( NAME t8_gtest_forest_face_normal_serial          SOURCES t8_forest/t8_gtest_forest_face_normal.cxx )
add_t8_cpp_test( NAME t8_gtest_element_is_leaf_serial             SOURCES t8_forest/t8_gtest_element_is_leaf.cxx )
add_t8_cpp_test( NAME t8_gtest_partition_data_parallel            SOURCES t8_forest/t8_gtest_partition_data.cxx )
add_t8_cpp_test( NAME t8_gtest_set_partition_offset_parallel      SOURCES t8_forest/t8_gtest_set_partition_offset.cxx )
add_t8_cpp_test( NAME t8_gtest_partition_for_coarsening_parallel  SOURCES t8_forest/t8_gtest_partition_for_coarsening.cxx )
add_t8_cpp_test( NAME t8_gtest_weighted_partitioning_parallel     SOURCES t8_forest/t8_gtest_weighted_partitioning.cxx )

add_t8_cpp_test( NAME t8_gtest_permute_hole_serial          SOURCES t8_forest_incomplete/t8_gtest_permute_hole.cxx )
add_t8_cpp_test( NAME t8_gtest_recursive_serial             SOURCES t8_forest_incomplete/t8_gtest_recursive.cxx )
add_t8_cpp_test( NAME t8_gtest_iterate_replace_parallel     SOURCES t8_forest_incomplete/t8_gtest_iterate_replace.cxx )
add_t8_cpp_test( NAME t8_gtest_empty_local_tree_parallel    SOURCES t8_forest_incomplete/t8_gtest_empty_local_tree.cxx )
add_t8_cpp_test( NAME t8_gtest_empty_global_tree_parallel   SOURCES t8_forest_incomplete/t8_gtest_empty_global_tree.cxx )

if( CMAKE_BUILD_TYPE STREQUAL "Debug" )
    add_t8_cpp_test( NAME t8_gtest_geometry_negative_volume_serial      SOURCES t8_geometry/t8_gtest_geometry_negative_volume.cxx )
endif()

if( T8_ENABLE_OCC )
    add_t8_cpp_test( NAME t8_gtest_geometry_cad_serial                  SOURCES t8_geometry/t8_geometry_implementations/t8_gtest_geometry_cad.cxx )
endif()
add_t8_cpp_test( NAME t8_gtest_geometry_linear_serial                   SOURCES t8_geometry/t8_geometry_implementations/t8_gtest_geometry_linear.cxx )
add_t8_cpp_test( NAME t8_gtest_geometry_lagrange_serial                 SOURCES t8_geometry/t8_geometry_implementations/t8_gtest_geometry_lagrange.cxx )
add_t8_cpp_test( NAME t8_gtest_geometry_triangular_interpolation_serial SOURCES t8_geometry/t8_gtest_geometry_triangular_interpolation.cxx )
add_t8_cpp_test( NAME t8_gtest_geometry_handling_serial                 SOURCES t8_geometry/t8_gtest_geometry_handling.cxx )
add_t8_cpp_test( NAME t8_gtest_point_inside_serial                      SOURCES t8_geometry/t8_gtest_point_inside.cxx )

add_t8_cpp_test( NAME t8_gtest_vtk_writer_parallel          SOURCES t8_IO/t8_gtest_vtk_writer.cxx )
if( T8_WITH_VTK )
    add_t8_cpp_test( NAME t8_gtest_vtk_reader_parallel      SOURCES t8_IO/t8_gtest_vtk_reader.cxx )
endif()

add_t8_cpp_test( NAME t8_gtest_nca_serial                   SOURCES t8_schemes/t8_gtest_nca.cxx )
add_t8_cpp_test( NAME t8_gtest_pyra_connectivity_serial     SOURCES t8_schemes/t8_gtest_pyra_connectivity.cxx )
add_t8_cpp_test( NAME t8_gtest_face_neigh_serial            SOURCES t8_schemes/t8_gtest_face_neigh.cxx )
add_t8_cpp_test( NAME t8_gtest_get_linear_id_serial         SOURCES t8_schemes/t8_gtest_get_linear_id.cxx )
add_t8_cpp_test( NAME t8_gtest_ancestor_serial              SOURCES t8_schemes/t8_gtest_ancestor.cxx )
add_t8_cpp_test( NAME t8_gtest_ancestor_id_serial           SOURCES t8_schemes/t8_gtest_ancestor_id.cxx )
add_t8_cpp_test( NAME t8_gtest_element_count_leaves_serial  SOURCES t8_schemes/t8_gtest_element_count_leaves.cxx )
add_t8_cpp_test( NAME t8_gtest_element_ref_coords_serial    SOURCES t8_schemes/t8_gtest_element_ref_coords.cxx )
add_t8_cpp_test( NAME t8_gtest_descendant_serial            SOURCES t8_schemes/t8_gtest_descendant.cxx )
add_t8_cpp_test( NAME t8_gtest_find_parent_serial           SOURCES t8_schemes/t8_gtest_find_parent.cxx )
add_t8_cpp_test( NAME t8_gtest_equal_serial                 SOURCES t8_schemes/t8_gtest_equal.cxx )
add_t8_cpp_test( NAME t8_gtest_successor_serial             SOURCES t8_schemes/t8_gtest_successor.cxx )
add_t8_cpp_test( NAME t8_gtest_boundary_extrude_serial      SOURCES t8_schemes/t8_gtest_boundary_extrude.cxx )
add_t8_cpp_test( NAME t8_gtest_face_descendant_serial       SOURCES t8_schemes/t8_gtest_face_descendant.cxx )
add_t8_cpp_test( NAME t8_gtest_default_serial               SOURCES t8_schemes/t8_gtest_default.cxx )
add_t8_cpp_test( NAME t8_gtest_child_parent_face_serial     SOURCES t8_schemes/t8_gtest_child_parent_face.cxx )
add_t8_cpp_test( NAME t8_gtest_pack_unpack_serial           SOURCES t8_schemes/t8_gtest_pack_unpack.cxx )
add_t8_cpp_test( NAME t8_gtest_root_serial                  SOURCES t8_schemes/t8_gtest_root.cxx )
add_t8_cpp_test( NAME t8_gtest_scheme_consistency_serial    SOURCES t8_schemes/t8_gtest_scheme_consistency.cxx )
add_t8_cpp_test( NAME t8_gtest_input_equal_output_serial    SOURCES t8_schemes/t8_gtest_input_equal_output.cxx )
add_t8_cpp_test( NAME t8_gtest_face_corner_serial           SOURCES t8_schemes/t8_gtest_face_corner.cxx )
add_t8_cpp_test( NAME t8_gtest_set_linear_id_serial         SOURCES t8_schemes/t8_gtest_set_linear_id.cxx )
add_t8_cpp_test( NAME t8_gtest_elements_are_family_serial   SOURCES t8_schemes/t8_gtest_elements_are_family.cxx )
if( T8CODE_BUILD_FORTRAN_INTERFACE AND T8CODE_ENABLE_MPI )
  add_t8_test( NAME t8_test_fortran_mpi_interface_init_parallel    SOURCES api/t8_fortran_interface/t8_test_mpi_init.f90 )
endif()
 
add_t8_cpp_test( NAME t8_gtest_vector_split_serial          SOURCES t8_helper_functions/t8_gtest_vector_split.cxx )

if( T8CODE_BUILD_MESH_HANDLE )
  add_t8_cpp_test( NAME t8_gtest_mesh_handle_parallel             SOURCES mesh_handle/t8_gtest_mesh_handle.cxx )
  add_t8_cpp_test( NAME t8_gtest_compare_handle_to_forest_serial  SOURCES mesh_handle/t8_gtest_compare_handle_to_forest.cxx )
  add_t8_cpp_test( NAME t8_gtest_handle_ghost_parallel            SOURCES mesh_handle/t8_gtest_ghost.cxx )
  add_t8_cpp_test( NAME t8_gtest_custom_competence_serial         SOURCES mesh_handle/t8_gtest_custom_competence.cxx )
  add_t8_cpp_test( NAME t8_gtest_cache_competence_serial          SOURCES mesh_handle/t8_gtest_cache_competence.cxx )
  add_t8_cpp_test( NAME t8_gtest_handle_data_parallel             SOURCES mesh_handle/t8_gtest_handle_data.cxx )
endif()

copy_test_file( test_cube_unstructured_1.inp )
copy_test_file( test_cube_unstructured_2.inp )
copy_test_file( test_vtk_tri.vtu )
copy_test_file( test_vtk_cube.vtp )
copy_test_file( test_parallel_file.pvtu )
copy_test_file( test_parallel_file_0.vtu )
copy_test_file( test_parallel_file_1.vtu )
copy_test_file( test_polydata.pvtp )
copy_test_file( test_polydata_0.vtp )
copy_test_file( test_polydata_1.vtp )
copy_test_file( test_msh_file_vers4_ascii.msh )
copy_test_file( test_msh_file_vers4_bin.msh )
