# This is the main engine for SIESTA tests.
# We should probably leave only the "simple" tests as the default
# without their output verification. This can be done by choosing
# the appropriate tests when making the cmake targets (maybe via labels?)

# TODO
# Instead of doing everything in CMake, first running, then checking.
# It would be better to control the 2nd run in a wrapper executable.
# This executable would run siesta, then check.
# That would also remove the problem of labels for tests.

file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/install_tests")
set(_TEST_PSEUDO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Pseudos")
set(_TEST_GLOB_DIR "${CMAKE_CURRENT_BINARY_DIR}")

# Perhaps this should be listed in the top-level CMakeLists.txt.
# We still rely on the old version (for now)
if(DEFINED VERIFY_TESTS)
  message(DEPRECATION "VERIFY_TESTS is superseeded by SIESTA_TESTS_VERIFY")
else()
  set(VERIFY_TESTS TRUE)
endif()

# Currently not a CACHE variable, it probably should be
set(SIESTA_TESTS_VERIFY "${VERIFY_TESTS}" PARENT_SCOPE)

file(
  COPY
    test-cfg.yml
    out_digest_yaml.awk
    yaml_compare.py
  DESTINATION
    "${CMAKE_CURRENT_BINARY_DIR}"
  )


list(APPEND CMAKE_MESSAGE_CONTEXT test)
message(STATUS "Default pseudo directory for tests: ${_TEST_PSEUDO_DIR}")

# Checking the output files requires Python3
# The below siesta_parseout function may break
# if Python 3 cannot be located!
find_package(Python3
  3.5.0 # subprocess limitation
  COMPONENTS Interpreter
  )


function(siesta_parseout)
  # Parse output files.
  set(oneValueArgs NAME OUTDIR OUTFILE PARENT ISSERIAL)
  set(multiValueArgs EXTRA_FILES LABELS)
  cmake_parse_arguments(_spout "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  if(DEFINED _spout_UNPARSED_ARGUMENTS)
    if(NOT DEFINED _spout_NAME)
      # get name from the first argument
      list(POP_BACK _spout_UNPARSED_ARGUMENTS _spout_NAME)
    endif()
    list(LENGTH _spout_UNPARSED_ARGUMENTS _spout_len)
    if(_spout_len GREATER 0)
      message(FATAL_ERROR "Unparsed arguments in ${CMAKE_CURRENT_FUNCTION} test=${_spout_NAME}"
        "Arguments are:\n" "  ${_spout_UNPARSED_ARGUMENTS}")
    endif()
  endif()

  set(_spout_REFDIR "${CMAKE_CURRENT_SOURCE_DIR}/Reference")
  set(name "${_spout_PARENT}[verify]")

  # Note that the verification test produces an exit
  # code that is catched by cmake, so if the SIESTA_TESTS_VERIFY
  # env-var is not set, it will not run!
  add_test(
    NAME ${name}
    COMMAND ${Python3_EXECUTABLE} "${_TEST_GLOB_DIR}/yaml_compare.py"
          -c "${_TEST_GLOB_DIR}/test-cfg.yml"
          -p "${_TEST_GLOB_DIR}/out_digest_yaml.awk"
          -t "${_spout_OUTDIR}/${_spout_OUTFILE}.out"
          -r "${_spout_REFDIR}/${_spout_NAME}.out"
          -o "${_spout_OUTDIR}/${_spout_NAME}.yml"
  )

  set_tests_properties(${name}
    PROPERTIES
      ENVIRONMENT "SCRIPTS_DIR='${_TEST_GLOB_DIR}'"
      LABELS "${_spout_LABELS}"
      DEPENDS "${_spout_PARENT}"
      SKIP_RETURN_CODE 127)
  # Possible way to enforce dependency: FIXTURES_REQUIRED "${_spout_OUTFILE}-dep"

endfunction()


#  Function to set up tests
#
# It accepts a few arguments
# NAME : directory of test (optional, an unnamed argument will be accepted as NAME)
# PSEUDO_DIR : files to be copied in as pseudos
# EXEC: the target from which the executable would be retrieved
# MPI_NPROC : number of MPI processors
# OMP_NPROC : number of OpenMP processors
# EXTRA_FILES : extra files to be copied in to the run directory
# SERIAL : to skip MPI (regardless of other arguments)
# This function uses these external variables:
#  SIESTA_WITH_MPI
#  SIESTA_WITH_OPENMP
#  SIESTA_TESTS_MPI_NUMPROCS
### SUBTEST
function(siesta_subtest)
  set(options SERIAL)
  set(oneValueArgs NAME MPI_NPROC PSEUDO_DIR NAME_ALTER EXEC)
  set(multiValueArgs EXTRA_FILES LABELS EXTRA_ARGS)
  cmake_parse_arguments(_stest "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  # Check for bad arguments
  # A single unparsed-argument will be the equivalent of NAME
  # in case it has not been passed.
  if(DEFINED _stest_UNPARSED_ARGUMENTS)
    if(NOT DEFINED _stest_NAME)
      # get name from the first argument
      list(POP_BACK _stest_UNPARSED_ARGUMENTS _stest_NAME)
    endif()
    list(LENGTH _stest_UNPARSED_ARGUMENTS _stest_len)
    if(_stest_len GREATER 0)
      message(FATAL_ERROR "Unparsed arguments in ${CMAKE_CURRENT_FUNCTION} test=${_stest_NAME}"
        "Arguments are:\n"
        "  ${_stest_UNPARSED_ARGUMENTS}")
    endif()
  endif()

  # Keyword only options
  # In this case serial may be used to overwrite MPI and OpenMP tests
  if(NOT DEFINED _stest_SERIAL)
    set(_stest_SERIAL FALSE)
  endif()

  # Name of test *must* be defined
  if(NOT DEFINED _stest_NAME)
    message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} missing test directory argument: NAME")
  endif()

  set(msg_info "${_stest_NAME}")
  list(APPEND CMAKE_MESSAGE_INDENT "  ")

  # Check for number of ranks. It will be reduced to {SIESTA_TESTS_MPI_NUMPROCS} if too high.
  if(DEFINED _stest_MPI_NPROC)
    if(SIESTA_WITH_MPI)
      if(_stest_MPI_NPROC GREATER SIESTA_TESTS_MPI_NUMPROCS)
        message(WARNING "Reducing number of MPI processors in test ${_stest_NAME} to ${SIESTA_TESTS_NUMPROCS}<${_stest_MPI_NPROC}")
        set(_stest_MPI_NPROC ${SIESTA_TESTS_MPI_NUMPROCS})
      endif()
    endif()
    set(msg_info "${msg_info} (MPI=${_stest_MPI_NPROC})")
  else()
    # set default value
    set(_stest_MPI_NPROC ${SIESTA_TESTS_MPI_NUMPROCS})
  endif()

  # Check for OpenMP
  # It will understand this variable as the maximum value
  if(DEFINED ENV{OMP_NUM_THREADS})
    # set the OMP_NPROC to this value if undefined
    if(DEFINED _stest_OMP_NPROC)
      if(_stest_OMP_NPROC GREATER ENV{OMP_NUM_THREADS})
        message(WARNING "Reducing number of OpenMP ranks in test ${_stest_NAME} to $ENV{OMP_NUM_THREADS}<${_stest_OMP_NPROC}")
        set(_stest_OMP_NPROC $ENV{OMP_NUM_THREADS})
      endif()
    else()
      set(_stest_OMP_NPROC $ENV{OMP_NUM_THREADS})
    endif()
    set(msg_info "${msg_info} (OMP=${_stest_OMP_NPROC})")
  else()
    # set default value of 1
    set(_stest_OMP_NPROC 1)
  endif()

  message(VERBOSE "New test ${msg_info}")

  if(NOT DEFINED _stest_PSEUDO_DIR)
    set(_stest_PSEUDO_DIR "${_TEST_PSEUDO_DIR}")
  else()
    message(DEBUG "test(${_stest_NAME}) will be using pseudos from: ${_stest_PSEUDO_DIR}")
  endif()

  # Build up the variables
  # First set the test name
  if(NOT DEFINED _stest_NAME_ALTER)
    set(_stest_NAME_ALTER "${_stest_NAME}")
  endif()
  set(_stest_test "${_stest_NAME_ALTER}")
  # This just does not work, using set_tests_properties does not expose the
  # env variable. Whether this is due to the command being wrapped in `sh`
  # isn't fully clear to me. I basically have to force the env in the
  # execution statement. :(
  set(_stest_env "SIESTA_PS_PATH='${_stest_PSEUDO_DIR}'") # env-vars to add
  set(_stest_pre "")
  set(_stest_post "")
  if( SIESTA_WITH_MPI AND (NOT _stest_SERIAL) AND _stest_MPI_NPROC GREATER 1 )
    set(_stest_pre
      "${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${_stest_MPI_NPROC} ${MPIEXEC_PREFLAGS}"
      )
    set(_stest_post
      "${MPIEXEC_POSTFLAGS}"
      )
    set(_stest_test "${_stest_test}_mpi${_stest_MPI_NPROC}")
  endif()

  if( SIESTA_WITH_OPENMP AND (NOT _stest_SERIAL) )
    list(APPEND _stest_env "OMP_NUM_THREADS=${_stest_OMP_NPROC}")
    set(_stest_test "${_stest_test}_omp${_stest_OMP_NPROC}")
  endif()

  # Create the working directory
  # Since we might have multiple tests for the same directory,
  # we will need to parse the options before doing anything
  set(_stest_tdir "${CMAKE_CURRENT_BINARY_DIR}/${_stest_test}")
  set(_stest_wdir "${CMAKE_CURRENT_BINARY_DIR}/${_stest_test}/work")
  get_filename_component(_stest_parent_dir "${CMAKE_CURRENT_BINARY_DIR}" NAME)

  # Now we can prepare the test directory:
  file(MAKE_DIRECTORY "${_stest_tdir}" "${_stest_wdir}")
  file(GLOB fdf_files "*.fdf")
  file(COPY
    ${fdf_files}
    DESTINATION "${CMAKE_CURRENT_BINARY_DIR}"
    )

  # Copy in extra files
  if(DEFINED _stest_EXTRA_FILES)
    message(DEBUG "test(${_stest_NAME}) copying extra files: ${_stest_EXTRA_FILES}")
    foreach(_extra IN LISTS _stest_EXTRA_FILES)
      file(COPY "${_extra}" DESTINATION "${_stest_wdir}")
    endforeach()
  endif()

  # Dirty fix for transiesta test.
  if(NOT DEFINED _stest_EXEC)
    set(_stest_EXEC "${PROJECT_NAME}.siesta")
  endif()
  get_target_property(_stest_EXEC_name ${_stest_EXEC} OUTPUT_NAME)
  if( "${_stest_EXEC_name}" STREQUAL "" )
    set(_stest_EXEC_name "${_stest_EXEC}")
  endif()
  set(_stest_FULL_NAME "${_stest_EXEC_name}-${_stest_parent_dir}-${_stest_test}")

  # Join arguments of EXTRA_ARGS into a string
  list(JOIN _stest_EXTRA_ARGS " " EXTRA_ARGS_STR)
  add_test(
    NAME "${_stest_FULL_NAME}"
    COMMAND sh -c "SIESTA_PS_PATH='${_stest_PSEUDO_DIR}' ${_stest_pre} \
        '$<TARGET_FILE:${_stest_EXEC}>' -out ${_stest_test}.out ${EXTRA_ARGS_STR} \
        ../../${_stest_NAME}.fdf ${_stest_post}"
    WORKING_DIRECTORY "${_stest_wdir}"  )

  # Add environment variables to the test

  set_tests_properties("${_stest_FULL_NAME}"
    PROPERTIES
      ENVIRONMENT "${_stest_env}"
      LABELS "${_stest_LABELS}"
      SKIP_RETURN_CODE 127)
 
  # Possible way to enforce dependency: FIXTURES_SETUP "${_stest_test}-dep"
  list(POP_BACK CMAKE_MESSAGE_INDENT)

  # Add output verification
  siesta_parseout(${_stest_NAME_ALTER}
    ISSERIAL ${_stest_SERIAL}
    PARENT "${_stest_FULL_NAME}"
    OUTFILE "${_stest_test}"
    OUTDIR "${_stest_wdir}"
    LABELS "${_stest_LABELS}"
    )

endfunction()



add_subdirectory(00.BasisSets)
add_subdirectory(01.PseudoPotentials)
add_subdirectory(02.SpinPolarization)
add_subdirectory(03.SpinOrbit)
add_subdirectory(04.SCFMixing)
add_subdirectory(05.Bands)
add_subdirectory(06.DensityOfStates)
add_subdirectory(07.ForceConstants)
add_subdirectory(08.GeometryOptimization)
add_subdirectory(09.MolecularDynamics)
add_subdirectory(10.Functionals)
add_subdirectory(11.ElectronicProperties)
add_subdirectory(12.Solvers)
add_subdirectory(13.MiscOptions)
add_subdirectory(14.FileIO)
add_subdirectory(15.TDDFT)
add_subdirectory(16.TranSiesta)

add_subdirectory(17.Wannier90)
add_subdirectory(Dependency_Tests)

# Pop back the test context
list(POP_BACK CMAKE_MESSAGE_CONTEXT)
