#------------------------------------------------------------------------------
# Project Phoenix
#
# Copyright (c) 2017-2018 RWTH Aachen University, Germany,
# Virtual Reality & Immersive Visualization Group.
#------------------------------------------------------------------------------
#                                 License
#
# Licensed under the 3-Clause BSD License (the "License");
# you may not use this file except in compliance with the License.
# See the file LICENSE for the full text.
# You may obtain a copy of the License at
#
#     https://opensource.org/licenses/BSD-3-Clause
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#------------------------------------------------------------------------------

include(CTest)
enable_testing()

include(WarningLevels)

conan_or_find_package(catch REQUIRED)
conan_or_find_package(trompeloeil REQUIRED)

define_property(TARGET
  PROPERTY testing__removes_libraries
  BRIEF_DOCS "Libraries to be removed for mocking"
  FULL_DOCS "Libraries to be removed for mocking")
define_property(TARGET
  PROPERTY testing__adds_libraries
  BRIEF_DOCS "Libraries to be added for mocking"
  FULL_DOCS "Libraries to be added for mocking")

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY TESTING__AVAILABLE_TESTS "")

set(IS_BUILD_SERVER
  FALSE CACHE BOOL
  "Is this the build server? So we, e.g., simulate user input for tests requiring it."
  )


function(CREATE_TEST_MAIN)
  testing__add_library_(NAME test_main ${ARGV})
endfunction()


function(CREATE_MOCK_MAIN)
  testing__add_library_(NAME mock_main ${ARGV})
endfunction()


function(TESTING__ADD_LIBRARY_)
  set(options)
  set(oneValueArgs NAME)
  set(multiValueArgs SOURCES HEADERS INCLUDE_DIRECTORIES LIBRARIES )
  cmake_parse_arguments(TESTING__ADD_LIBRARY_
    "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  add_library(${TESTING__ADD_LIBRARY__NAME}
    ${TESTING__ADD_LIBRARY__SOURCES}
    ${TESTING__ADD_LIBRARY__HEADERS}
    )
  target_include_directories(${TESTING__ADD_LIBRARY__NAME}
    PUBLIC ${TESTING__ADD_LIBRARY__INCLUDE_DIRECTORIES}
    )
  target_link_libraries(${TESTING__ADD_LIBRARY__NAME}
    ${TESTING__ADD_LIBRARY__LIBRARIES}
    )
  set_property(TARGET ${TESTING__ADD_LIBRARY__NAME} PROPERTY POSITION_INDEPENDENT_CODE ON)
endfunction()


function(ADD_TESTS)
  set(options )
  set(oneValueArgs NAME)
  set(multiValueArgs
    SOURCES HEADERS INCLUDE_DIRECTORIES LIBRARIES PATH_TO_ADD)
  cmake_parse_arguments(ADD_TESTS_
    "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  foreach(TEST_SOURCE_FILE ${ADD_TESTS__SOURCES})
    add_single_test(
      NAME ${TEST_NAME}
      SOURCE ${TEST_SOURCE_FILE}
      INCLUDE_DIRECTORIES ${ADD_TESTS__INCLUDE_DIRECTORIES}
      LIBRARIES ${ADD_TESTS__LIBRARIES}
      PATH_TO_ADD ${ADD_TESTS__PATH_TO_ADD}
      )
  endforeach()
endfunction()


function(ADD_SINGLE_TEST)
  set(options )
  set(oneValueArgs)
  set(multiValueArgs SOURCE INCLUDE_DIRECTORIES LIBRARIES PATH_TO_ADD)
  cmake_parse_arguments(ADD_SINGLE_TEST_
    "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  get_filename_component(
    TEST_NAME
    ${ADD_SINGLE_TEST__SOURCE} NAME_WE)

  add_executable(${TEST_NAME}
    ${ADD_SINGLE_TEST__SOURCE}
    )
  target_include_directories(${TEST_NAME}
    PRIVATE ${ADD_SINGLE_TEST__INCLUDE_DIRECTORIES}
    )
  target_link_libraries(${TEST_NAME}
    ${ADD_SINGLE_TEST__LIBRARIES}
    ${CONAN_OR_CMAKE_catch}
    test_main mock_main
    )
  set_warning_levels_RWTH(${TEST_NAME})
  if(IS_BUILD_SERVER)
    target_compile_definitions(${TEST_NAME} PUBLIC -DIS_BUILD_SERVER)
  endif()
  set_property(TARGET ${TEST_NAME}
    PROPERTY FOLDER "Tests")
  source_group("Source Files"
    FILES ${ADD_SINGLE_TEST__SOURCE})

  if(ADD_SINGLE_TEST__PATH_TO_ADD AND WIN32 AND MSVC)
    CONFIGURE_MSVC_USERFILE_(${TEST_NAME}
      ${ADD_SINGLE_TEST__PATH_TO_ADD})
  endif()
  
  add_test(NAME ${TEST_NAME}
    COMMAND ${TEST_NAME}
    )
  testing__set_timeout_(${TEST_NAME})

  set_property(GLOBAL APPEND PROPERTY
    TESTING__AVAILABLE_TESTS "${TEST_NAME}")  
endfunction()


function(CONFIGURE_MSVC_USERFILE_ TARGET_NAME PATH_TO_ADD)
  file(TO_NATIVE_PATH "${PATH_TO_ADD}/Release" _DLL_PATH_RELEASE)
  file(TO_NATIVE_PATH "${PATH_TO_ADD}/Debug" _DLL_PATH_DEBUG)
  set(SOURCE_USER_FILE
    "${CMAKE_SOURCE_DIR}/cmake/VisualStudio2013.vcxproj.user.in")
  set(DESTINATION_USER_FILE
    "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}.vcxproj.user")
  configure_file(${SOURCE_USER_FILE} ${DESTINATION_USER_FILE} @ONLY)
endfunction()


function(TESTING__SET_TIMEOUT_ TARGET)
  if(NOT ${TARGET} MATCHES "integration")
    set_tests_properties(${TARGET} PROPERTIES TIMEOUT 10.0)
  else()
    set_tests_properties(${TARGET} PROPERTIES TIMEOUT 120.0)
  endif()
endfunction()


function(ADD_MOCK)
  set(options )
  set(oneValueArgs NAME)
  set(multiValueArgs
    SOURCES
    HEADERS
    INCLUDE_DIRECTORIES
    INCLUDE_DIRECTORIES_OF
    COMPILE_OPTIONS
    REMOVES_LIBRARIES
    ADDS_LIBRARIES
  )
  cmake_parse_arguments(ADD_MOCK_
    "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  string(TOUPPER ${ADD_MOCK__NAME} ADD_MOCK__NAME_UPPER)
  string(TOLOWER ${ADD_MOCK__NAME} ADD_MOCK__NAME_LOWER)
  
  add_library(${ADD_MOCK__NAME} SHARED
    ${ADD_MOCK__SOURCES}
    ${ADD_MOCK__HEADERS}
    )
  target_link_libraries(${ADD_MOCK__NAME}
    ${CONAN_OR_CMAKE_trompeloeil}
    mock_main test_main
    )
  foreach(CURRENT_TARGET ${ADD_MOCK__INCLUDE_DIRECTORIES_OF})
    get_target_property(CURRENT_INCLUCE_DIRECTORIES
      ${CURRENT_TARGET} INTERFACE_INCLUDE_DIRECTORIES)
    if (CURRENT_INCLUCE_DIRECTORIES)
      target_include_directories(${ADD_MOCK__NAME}
	    PUBLIC ${CURRENT_INCLUCE_DIRECTORIES}
	  )
    endif()
  endforeach()
  target_include_directories(${ADD_MOCK__NAME}
    PUBLIC ${ADD_MOCK__INCLUDE_DIRECTORIES}
    )
  target_compile_definitions(${ADD_MOCK__NAME}
    PRIVATE ${ADD_MOCK__NAME_UPPER}_BUILD
    )
  target_compile_options(${ADD_MOCK__NAME}
    PUBLIC ${ADD_MOCK__COMPILE_OPTIONS}
    )

  set_property(TARGET ${ADD_MOCK__NAME} PROPERTY FOLDER "Tests/Mocks")

  set_target_properties(${ADD_MOCK__NAME}
    PROPERTIES testing__removes_libraries "${ADD_MOCK__REMOVES_LIBRARIES}")
  set_target_properties(${ADD_MOCK__NAME}
    PROPERTIES testing__adds_libraries "${ADD_MOCK__ADDS_LIBRARIES}")

  generate_export_header(${ADD_MOCK__NAME}
    EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/mocks/${ADD_MOCK__NAME_LOWER}_export.hpp
    )
  set_warning_levels_rwth(${ADD_MOCK__NAME})
endfunction()


function(AUTOREMOVE_MOCKED_TEST_SOURCE_FROM)
  set_property(GLOBAL PROPERTY TESTING__SOURCES__AUTOREMOVE_MOCKED ${ARGN})
endfunction()

function(GET_UNMOCKED_TEST_SOURCES test_list)
  get_property(TEST_SOURCES_LIST
    GLOBAL PROPERTY TESTING__SOURCES__AUTOREMOVE_MOCKED) 
  set(${test_list} ${TEST_SOURCES_LIST} PARENT_SCOPE)
endfunction()

function(add_mocked_test cpp_file)
  set(options )
  set(oneValueArgs )
  set(multiValueArgs LIBRARIES MOCKING_LIBRARIES_OF MOCKS)
  cmake_parse_arguments(ADD_MOCKED_TEST
    "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

 
  get_property(TEST_SOURCES_LIST
    GLOBAL PROPERTY TESTING__SOURCES__AUTOREMOVE_MOCKED) 
  list(REMOVE_ITEM TEST_SOURCES_LIST
    ${CMAKE_CURRENT_SOURCE_DIR}/src/${cpp_file}.cpp)
  set_property(GLOBAL PROPERTY TESTING__SOURCES__AUTOREMOVE_MOCKED ${TEST_SOURCES_LIST})
  
  set(test_sources src/${cpp_file}.cpp)
  add_single_test(
    SOURCE ${test_sources}
    )
  add_dependencies(${cpp_file} ${ADD_MOCKED_TEST_LIBRARIES})

  set(LINK_LIBRARIES )
  foreach(CURR_TARGET ${ADD_MOCKED_TEST_LIBRARIES})
    get_target_property(CURR_LINK_LIBRARIES ${CURR_TARGET} LINK_LIBRARIES)
    list(APPEND LINK_LIBRARIES ${CURR_LINK_LIBRARIES})

    target_link_libraries(${cpp_file} $<TARGET_FILE:${CURR_TARGET}>)
	
	get_target_property(CURR_INCLUDE_DIRECTORIES
      ${CURR_TARGET} INCLUDE_DIRECTORIES)
    target_include_directories(${cpp_file} PRIVATE ${CURR_INCLUDE_DIRECTORIES})
  endforeach()
  list(REMOVE_DUPLICATES LINK_LIBRARIES)
  #the libraries themselves must not be in LINK_LIBRARIES
  foreach(CURR_TARGET ${ADD_MOCKED_TEST_LIBRARIES})
    list(REMOVE_ITEM LINK_LIBRARIES ${CURR_TARGET})
  endforeach()

  foreach(MOCK ${ADD_MOCKED_TEST_MOCKS})
    get_target_property(LIBRARIES_TO_BE_REMOVED ${MOCK} testing__removes_libraries)
    foreach(LIBRARY_TO_BE_REMOVED ${LIBRARIES_TO_BE_REMOVED})
      list(REMOVE_ITEM LINK_LIBRARIES ${LIBRARY_TO_BE_REMOVED})
    endforeach()
    
    get_target_property(LIBRARIES_TO_BE_ADDED ${MOCK} testing__adds_libraries)
    foreach(LIBRARY_TO_BE_ADDED ${LIBRARIES_TO_BE_ADDED})
      list(APPEND LINK_LIBRARIES ${LIBRARY_TO_BE_ADDED})
    endforeach()

    target_link_libraries(${cpp_file} ${MOCK})
  endforeach()
  
  target_link_libraries(${cpp_file}
    ${CONAN_OR_CMAKE_trompeloeil}
    ${CONAN_OR_CMAKE_catch}
    ${LINK_LIBRARIES}
  )
endfunction()
#-------------------------------------------------------------------------------


function(CREATE_TEST_SUITES)
	testing__extract_unit_and_integration_test_targets_()

	ADD_TEST_SUITE(
	  NAME "Unit-Test-Suite"
	  TEST_REGEX "^test"
	  DEPEND_ON_TARGETS ${TESTING__UNIT_TESTS})

	ADD_TEST_SUITE(
	  NAME "Integration-Test-Suite"
	  TEST_REGEX "^integration"
	  DEPEND_ON_TARGETS ${TESTING__INTEGRATION_TESTS})
	
	ADD_TEST_SUITE(
	  NAME "Cpplint-Test-Suite"
	  TEST_REGEX "cpplint")

	ADD_TEST_SUITE(
	  NAME "Cppcheck-Test-Suite"
	  TEST_REGEX "cppcheck")
endfunction()


function(ADD_TEST_SUITE)
  set(options)
  set(oneValueArgs NAME TEST_REGEX)
  set(multiValueArgs DEPEND_ON_TARGETS)
  cmake_parse_arguments(ADD_TEST_SUITE
    "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  
  #we have to escape the " for visual studio)
  add_custom_target(${ADD_TEST_SUITE_NAME} COMMAND ctest --tests-regex \"${ADD_TEST_SUITE_TEST_REGEX}\" -C $<CONFIG>)
   # This comment just ends the escaped signs, for VS highlighting "
  if(ADD_TEST_SUITE_DEPEND_ON_TARGETS)
    add_dependencies(${ADD_TEST_SUITE_NAME} ${ADD_TEST_SUITE_DEPEND_ON_TARGETS})
  endif()
  set_property(TARGET ${ADD_TEST_SUITE_NAME} PROPERTY FOLDER "_Test-Suites_")
endfunction()


function(TESTING__EXTRACT_UNIT_AND_INTEGRATION_TEST_TARGETS_)
  set(TESTING__INTEGRATION_TESTS )
  set(TESTING__UNIT_TESTS )

  get_property(TESTING__AVAILABLE_TESTS GLOBAL PROPERTY TESTING__AVAILABLE_TESTS)
  foreach(CURRENT_TEST ${TESTING__AVAILABLE_TESTS})
    if(${CURRENT_TEST} MATCHES "integration_test")
      list(APPEND TESTING__INTEGRATION_TESTS ${CURRENT_TEST})
    else()
      list(APPEND TESTING__UNIT_TESTS ${CURRENT_TEST})
    endif()
  endforeach()
endfunction()