#
# CMakeLists.txt
#
# Copyright (C) 2022 by Posit Software, PBC
#
# Unless you have received this program directly from Posit Software pursuant
# to the terms of a commercial license agreement with Posit Software, then
# this program is licensed to you under the terms of version 3 of the
# GNU Affero General Public License. This program is distributed WITHOUT
# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
#
#

project (CORE)

add_subdirectory(tex/synctex)

# include files
file(GLOB_RECURSE CORE_HEADER_FILES "*.h*")

# source files
set(CORE_SOURCE_FILES
   Backtrace.cpp
   Base64.cpp
   BoostErrors.cpp
   BrowserUtils.cpp
   collection/MruList.cpp
   ConfigProfile.cpp
   ConfigUtils.cpp
   DateTime.cpp
   ExponentialBackoff.cpp
   Exec.cpp
   FileInfo.cpp
   FileSerializer.cpp
   FileUtils.cpp
   GitGraph.cpp
   HtmlUtils.cpp
   Jwt.cpp
   Log.cpp
   LogOptions.cpp
   OneShotCommand.cpp
   PerformanceTimer.cpp
   ProgramOptions.cpp
   RegexUtils.cpp
   RecursionGuard.cpp
   Settings.cpp
   SocketRpc.cpp
   StringUtils.cpp
   SqlIdentifier.cpp
   ColorUtils.cpp
   Thread.cpp
   Timer.cpp
   Trace.cpp
   WaitUtils.cpp
   YamlUtil.cpp
   ZlibUtil.cpp
   file_lock/FileLock.cpp
   file_lock/AdvisoryFileLock.cpp
   file_lock/LinkBasedFileLock.cpp
   gwt/GwtFileHandler.cpp
   gwt/GwtLogHandler.cpp
   gwt/GwtSymbolMaps.cpp
   libclang/CodeCompleteResults.cpp
   libclang/Cursor.cpp
   libclang/Diagnostic.cpp
   libclang/LibClang.cpp
   libclang/SourceIndex.cpp
   libclang/SourceLocation.cpp
   libclang/SourceRange.cpp
   libclang/Token.cpp
   libclang/TranslationUnit.cpp
   libclang/UnsavedFiles.cpp
   libclang/Utils.cpp
   json/JsonRpc.cpp
   http/Cookie.cpp
   http/Header.cpp
   http/Message.cpp
   http/MultipartRelated.cpp
   http/ChunkParser.cpp
   http/ChunkProxy.cpp
   http/CSRFToken.cpp
   http/FormProxy.cpp
   http/ProxyUtils.cpp
   http/NoProxyRules.cpp
   http/Request.cpp
   http/RequestParser.cpp
   http/Response.cpp
   http/SocketProxy.cpp
   http/Ssl.cpp
   http/SslContextCache.cpp
   http/URL.cpp
   http/UriHandler.cpp
   http/Util.cpp
   markdown/Markdown.cpp
   markdown/MathJax.cpp
   markdown/sundown/autolink.c
   markdown/sundown/buffer.c
   markdown/sundown/houdini_href_e.c
   markdown/sundown/houdini_html_e.c
   markdown/sundown/html.c
   markdown/sundown/html_smartypants.c
   markdown/sundown/markdown.c
   markdown/sundown/stack.c
   r_util/RActiveSessions.cpp
   r_util/RActiveSessionStorage.cpp
   r_util/RActiveSessionsStorage.cpp
   r_util/RPackageInfo.cpp
   r_util/RProjectFile.cpp
   r_util/RSessionContext.cpp
   r_util/RTokenizer.cpp
   r_util/RSourceIndex.cpp
   r_util/RUserData.cpp
   spelling/HunspellCustomDictionaries.cpp
   spelling/HunspellDictionaryManager.cpp
   spelling/HunspellSpellingEngine.cpp
   system/Architecture.cpp
   system/ChildProcessSubprocPoll.cpp
   system/Crypto.cpp
   system/Environment.cpp
   system/Locale.cpp
   system/Process.cpp
   system/ShellUtils.cpp
   system/System.cpp
   system/Xdg.cpp
   system/file_monitor/FileMonitor.cpp
   system/encryption/Encryption.cpp
   terminal/PrivateCommand.cpp
   tex/TexLogParser.cpp
   tex/TexMagicComment.cpp
   tex/TexSynctex.cpp
   text/AnsiCodeParser.cpp
   text/DcfParser.cpp
   text/TextCursor.cpp
   text/TemplateFilter.cpp
   text/TermBufferParser.cpp
)

if (RSTUDIO_HAS_SOCI)
   list(APPEND CORE_SOURCE_FILES Database.cpp)
   list(APPEND CORE_SOURCE_FILES QueryBuilder.cpp)
   list(APPEND CORE_SOURCE_FILES SqlPreprocessor.cpp)
endif()

# UNIX specific
if (UNIX)

   include (CheckCXXSourceCompiles)
   CHECK_CXX_SOURCE_COMPILES (
   "# include <dirent.h>
   int func (const char *d, dirent ***list, void *sort)
   {
      int n = scandir(d, list, 0, (int(*)(const dirent **, const dirent **))sort);
      return n;
   }

   int main (int, char **)
   {
      return 0;
   }
   "
   HAVE_SCANDIR_POSIX)

   # platform introspection
   check_symbol_exists(SA_NOCLDWAIT "signal.h" HAVE_SA_NOCLDWAIT)
   check_symbol_exists(SO_PEERCRED "sys/socket.h" HAVE_SO_PEERCRED)
   check_function_exists(inotify_init1 HAVE_INOTIFY_INIT1)
   check_function_exists(getpeereid HAVE_GETPEEREID)
   check_function_exists(setresuid HAVE_SETRESUID)
   if(EXISTS "/proc/self")
      set(HAVE_PROCSELF TRUE)
   endif()

   # missing on non-glibc platforms like macOS, musl-based Linux distros, and
   # the BSDs
   check_function_exists(group_member HAVE_GROUP_MEMBER)

   # missing on musl-based Linux distros and some BSDs
   CHECK_CXX_SOURCE_COMPILES (
   "#include <execinfo.h>
   int main (int, char **)
   {
      return 0;
   }
   "
   HAVE_EXECINFO)

   # find packages and libraries
   find_library(PTHREAD_LIBRARIES pthread)
   if(NOT APPLE)
      find_library(UTIL_LIBRARIES util)
      find_library(UUID_LIBRARIES uuid)
      find_library(RT_LIBRARIES rt)
   endif()
   find_package(ZLIB REQUIRED QUIET)

   # include directories and libraries
   set(CORE_SYSTEM_LIBRARIES
      ${PTHREAD_LIBRARIES}
      ${UTIL_LIBRARIES}
      ${UUID_LIBRARIES}
      ${RT_LIBRARIES}
      ${ZLIB_LIBRARIES})

   # add Apple frameworks we depend on
   if(APPLE)
      list(APPEND CORE_SYSTEM_LIBRARIES ${CORE_FOUNDATION_LIBRARY})
      list(APPEND CORE_SYSTEM_LIBRARIES ${CORE_SERVICES_LIBRARY})
      list(APPEND CORE_SYSTEM_LIBRARIES ${SECURITY_LIBRARY})
   endif()

   # add OpenSSL
   list(APPEND CORE_SYSTEM_LIBRARIES ${OPENSSL_LIBRARIES})
   list(APPEND CORE_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR})

   # add PAM
   if(RSTUDIO_SERVER AND RSTUDIO_USE_PAM)
      find_package(PAM REQUIRED)
      list(APPEND CORE_SYSTEM_LIBRARIES ${PAM_LIBRARIES})
      list(APPEND CORE_INCLUDE_DIRS ${PAM_INCLUDE_DIRS})
   endif()

   # source files
   set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES}
      ${DIRECTORY_MONITOR_CPP}
      PosixStringUtils.cpp
      r_util/REnvironmentPosix.cpp
      r_util/RSessionLaunchProfile.cpp
      r_util/RVersionsPosix.cpp
      system/Container.cpp
      system/PosixChildProcessTracker.cpp
      system/PosixEnvironment.cpp
      system/PosixFileScanner.cpp
      system/PosixInterrupts.cpp
      system/PosixLibraryLoader.cpp
      system/PosixNfs.cpp
      system/PosixParentProcessMonitor.cpp
      system/PosixOutputCapture.cpp
      system/PosixSched.cpp
      system/PosixShellUtils.cpp
      system/PosixSystem.cpp
      system/PosixUser.cpp
      system/PosixGroup.cpp
      system/PosixChildProcess.cpp
      system/PosixProcess.cpp
      system/User.cpp
   )

   if(APPLE)
      set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES}
         http/Keychain.mm
         system/file_monitor/MacFileMonitor.cpp
         system/recycle_bin/MacRecycleBin.cpp
         system/MacResources.cpp
      )
   else()
      set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES}
         system/file_monitor/LinuxFileMonitor.cpp
         system/recycle_bin/LinuxRecycleBin.cpp
         system/LinuxResources.cpp
      )
   endif()

# Win32 specific
else()

   # system libraries
   set(CORE_SYSTEM_LIBRARIES
      WS2_32
      MsWSock
      RpcRT4
      ShLwApi
      AdvAPI32
      Crypt32
      ${OPENSSL_LIBRARIES}
   )

   # source files
   set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES}
      http/NamedPipeProtocol.cpp
      system/Win32FileScanner.cpp
      system/RegistryKey.cpp
      system/Win32Environment.cpp
      system/Win32Interrupts.cpp
      system/Win32LibraryLoader.cpp
      system/Win32ParentProcessMonitor.cpp
      system/Win32ConPty.cpp
      system/Win32OutputCapture.cpp
      system/Win32RuntimeLibrary.cpp
      system/Win32ShellUtils.cpp
      system/Win32System.cpp
      system/Win32ChildProcess.cpp
      system/Win32Resources.cpp
      system/file_monitor/Win32FileMonitor.cpp
      system/recycle_bin/Win32RecycleBin.cpp
      r_util/RToolsInfo.cpp
   )

endif()

configure_file(
   ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
   ${CMAKE_CURRENT_BINARY_DIR}/config.h)

# define include dirs
list(APPEND CORE_INCLUDE_DIRS include)

# search for core addins
if(RSTUDIO_ADDINS_PATH)
   set(CORE_ADDIN_PATH ${RSTUDIO_ADDINS_PATH}/core)
   if(EXISTS ${CORE_ADDIN_PATH})
      file(GLOB_RECURSE ADDIN_HEADER_FILES "${CORE_ADDIN_PATH}/*.h*")
      list(APPEND CORE_HEADER_FILES ${ADDIN_HEADER_FILES})
      file(GLOB_RECURSE ADDIN_SOURCE_FILES "${CORE_ADDIN_PATH}/*.c*")
      list(APPEND CORE_SOURCE_FILES ${ADDIN_SOURCE_FILES})
      list(APPEND CORE_INCLUDE_DIRS ${CORE_ADDIN_PATH}/include)
   endif()
endif()

set(SHARED_CORE_INCLUDE_DIRS
   ${SHARED_CORE_SOURCE_DIR}/include)

include_directories(
   ${CORE_INCLUDE_DIRS}
   ${SHARED_CORE_INCLUDE_DIRS}
   ${EXT_SOURCE_DIR}
   ${CMAKE_CURRENT_BINARY_DIR}
   ${TESTS_INCLUDE_DIR}
)

# define shared library
add_library(rstudio-core STATIC ${CORE_SOURCE_FILES} ${CORE_HEADER_FILES})
define_source_file_names(rstudio-core)

# link dependencies
target_link_libraries(rstudio-core
   rstudio-shared-core
   rstudio-dtl
   rstudio-hunspell
   rstudio-utfcpp
   rstudio-websocketpp
   ${Boost_LIBRARIES}
   ${SOCI_LIBRARIES}
   ${CORE_SYSTEM_LIBRARIES}
   fmt::fmt
   gsl::gsl-lite-v1
   tl::expected
   yaml-cpp::yaml-cpp
)

if(WIN32)
   target_link_libraries(rstudio-core rstudio-zlib)
endif()

# define executable (for running unit tests)
if (RSTUDIO_UNIT_TESTS_ENABLED)

   # Find all test files (including both standard tests and gtest files)
   file(GLOB_RECURSE CORE_TEST_FILES "*Tests.cpp")

   if (NOT RSTUDIO_HAS_SOCI)
      list(REMOVE_ITEM CORE_TEST_FILES "${CMAKE_CURRENT_SOURCE_DIR}/DatabaseTests.cpp")
   endif()

   # Use gtest from vendor directory
   set(GTEST_SOURCE_DIR ${CMAKE_SOURCE_DIR}/src/cpp/tests/cpp/tests/vendor/gtest)
   include_directories(${GTEST_SOURCE_DIR}/include)

   # Add Google Test if not already added
   if(NOT TARGET gtest)
      add_subdirectory(${GTEST_SOURCE_DIR} gtest-build EXCLUDE_FROM_ALL)
   endif()

   add_executable(rstudio-core-tests
      TestMain.cpp
      ${CORE_TEST_FILES}
      ${CORE_HEADER_FILES}
   )

   target_link_libraries(rstudio-core-tests
      rstudio-shared-core
      rstudio-core
      rstudio-core-synctex
      rstudio-dtl
      rstudio-hunspell
      rstudio-utfcpp
      ${Boost_LIBRARIES}
      ${SOCI_LIBRARIES}
      ${CORE_SYSTEM_LIBRARIES}
      gtest
   )

   if(NOT WIN32)
      target_link_libraries(rstudio-core-tests pthread)
   endif()

   if(WIN32)
      target_link_libraries(rstudio-core-tests rstudio-zlib)
   endif()

   # normalize before baking these in, so the compiled-in strings don't carry a
   # literal "core/.." segment into FilePath comparisons and error messages
   get_filename_component(RSTUDIO_DB_SCHEMA_DIR_ABS
      "${CMAKE_CURRENT_SOURCE_DIR}/../server/db" ABSOLUTE)
   get_filename_component(RSTUDIO_CPP_SOURCE_DIR_ABS
      "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE)

   target_compile_definitions(rstudio-core-tests PRIVATE
      RSTUDIO_DB_SCHEMA_DIR="${RSTUDIO_DB_SCHEMA_DIR_ABS}"
      RSTUDIO_CPP_SOURCE_DIR="${RSTUDIO_CPP_SOURCE_DIR_ABS}"
   )

   enable_testing()
   include(GoogleTest)
   gtest_discover_tests(rstudio-core-tests)

endif()
