#
# cmake file for Embedded Learning Library subprojects
#

cmake_minimum_required(VERSION 3.8 FATAL_ERROR)

# Error on non-existent dependency in add_dependencies.
cmake_policy(SET CMP0046 NEW)

# Include modules in the CMake directory.
set(ELL_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
list(APPEND CMAKE_MODULE_PATH "${ELL_ROOT}/CMake")
include(CompilerCache)

project(ELL CXX ASM)
if(MSVC)
  enable_language(ASM_MASM)
endif()

file(STRINGS "VERSION" ELL_VERSION)
message(STATUS "ELL version ${ELL_VERSION}")

# Define custom configuration types for Visual Studio
if(CMAKE_CONFIGURATION_TYPES)
  # Copy important configuration info from Release config to new Documentation config
  list(APPEND CMAKE_CONFIGURATION_TYPES Documentation)
  set(CMAKE_CXX_FLAGS_DOCUMENTATION ${CMAKE_CXX_FLAGS_RELEASE})
  set(CMAKE_C_FLAGS_DOCUMENTATION ${CMAKE_C_FLAGS_RELEASE})
  set(CMAKE_EXE_LINKER_FLAGS_DOCUMENTATION ${CMAKE_EXE_LINKER_FLAGS_RELEASE})
  set(CMAKE_EXE_LINKER_FLAGS_DOCUMENTATION ${CMAKE_EXE_LINKER_FLAGS_RELEASE})
  set(CMAKE_SHARED_LINKER_FLAGS_DOCUMENTATION ${CMAKE_SHARED_LINKER_FLAGS_RELEASE})
  set(CMAKE_MODULE_LINKER_FLAGS_DOCUMENTATION ${CMAKE_MODULE_LINKER_FLAGS_RELEASE})
endif()

# Try to create a compilation database, which is useful to have when working
# with clang tooling
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Options
# To set an option:
# cmake -DMyOption=ON|OFF buildDirectory
# (so if we're running cmake from a 'build' directory inside the main directory, do this:
#      cmake -DBUILD_DOC=OFF ..  )
option(BUILD_DOC "Build Doxygen documentation" ON)
option(STRICT_MODE "Build with 'warnings as errors'" OFF)
option(PROFILING "Enable performance profiling tests" OFF)
option(DISABLE_PYTHON "Explicitly disable building python modules" OFF)
option(CNTK "Enable CNTK importer and related unit tests (requires CNTK python module)" OFF)
option(ONNX "Enable ONNX importer and related unit tests (requires PyTorch and ONNX python modules)" OFF)

set(FLAKE8_CONFIG "${ELL_ROOT}/.flake8")
set(TEST_MODELS_REPO "https://github.com/Microsoft/ell-test-models" CACHE STRING "URL to the git repo containing test models" )
message(STATUS "Configuring tests to use TEST_MODELS_REPO at: ${TEST_MODELS_REPO}")

if(NOT ELL_EXTERNAL_DIR)
  set(ELL_EXTERNAL_DIR "${ELL_ROOT}/external" CACHE STRING "Directory to install external dependencies" )
endif(NOT ELL_EXTERNAL_DIR)

set(RPI_PASSWORD "$ENV{RPI_PASSWORD}")
set(RPI_CLUSTER "$ENV{RPI_CLUSTER}")
set(RPI_KEY "$ENV{RPI_APIKEY}")

if(DISABLE_PYTHON)
  message(STATUS "Python has been explicitly disabled")
  set(PYTHON_ENABLED OFF)
else()
  foreach(PYTHON_VERSION 3.7 3.6)
    find_package(PythonInterp ${PYTHON_VERSION} QUIET)
    find_package(PythonLibs ${PYTHON_VERSION} QUIET)

    message(STATUS "Looking for python ${PYTHON_VERSION}: PYTHONINTERP_FOUND=${PYTHONINTERP_FOUND}, PYTHONLIBS_FOUND=${PYTHONLIBS_FOUND}")
    if(${PYTHONINTERP_FOUND} AND ${PYTHONLIBS_FOUND})
      message(STATUS "PYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}")
      message(STATUS "PYTHON_VERSION_STRING=${PYTHON_VERSION_STRING}")
      message(STATUS "PYTHON_VERSION_MAJOR=${PYTHON_VERSION_MAJOR}")

      message(STATUS "PYTHON_LIBRARIES=${PYTHON_LIBRARIES}")
      message(STATUS "PYTHON_INCLUDE_PATH=${PYTHON_INCLUDE_PATH}")
      message(STATUS "PYTHON_INCLUDE_DIRS=${PYTHON_INCLUDE_DIRS}")
      message(STATUS "PYTHON_DEBUG_LIBRARIES=${PYTHON_DEBUG_LIBRARIES}")
      message(STATUS "PYTHONLIBS_VERSION_STRING=${PYTHONLIBS_VERSION_STRING}")

      break()
    endif()
  endforeach()

  if(${PYTHONLIBS_FOUND})
    set(PYTHON_ENABLED ON)
  else()
    set(PYTHON_ENABLED OFF)
    message(STATUS "### Python 3.6 or later not found, so python features will be disabled")
  endif()
endif()

include(Flake8)

# Turn on ability to create folders to organize projects
set_property(GLOBAL PROPERTY USE_FOLDERS ON)

# Set C++ version
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Apply -fPIC where applicable to the platform
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# Find the platform-specific way of working with threads
set(THREADS_PREFER_PTHREAD_FLAG ON)
set(CMAKE_THREAD_PREFER_PTHREAD ON)
find_package(Threads REQUIRED)

if(STRICT_MODE)
  message("-- Turning on strict mode with warnings as errors.")
endif()

# Turn on ctest tests
enable_testing()

# Set up global variables to help find NuGet projects
set(PACKAGE_ROOT ${ELL_EXTERNAL_DIR})

include(OpenBLASSetup)
include(LLVMSetup)
include(SWIGSetup)
include(CopySharedLibraries)
include(AddPrecompiledHeader)

if(MSVC)
  # Set Visual Studio-specific options
  add_definitions(-DUNICODE)
  add_compile_options(/utf-8)
  add_compile_options(/MP)
  add_compile_options(/bigobj)
  add_compile_options(/W4)
  add_compile_options(/permissive-)
  if(STRICT_MODE)
    add_compile_options(/WX)
  endif()
  # the debug linker needs to match how LLVM was built (either /MD or /MDd)
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${LLVM_MD_OPTION}")
  set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${LLVM_MD_OPTION}")
else()
  # Set Clang/GCC-specific options
  add_compile_options(-Wall)
  if(STRICT_MODE)
    add_compile_options(-Werror)
  endif()
  add_compile_options(-Wsign-compare)
  add_compile_options(-Wno-missing-braces)
  add_compile_options(-Wmissing-field-initializers)
  add_compile_options(-fvisibility-inlines-hidden)
  add_compile_options(-Wno-unknown-pragmas)
  add_compile_options(-Wno-comment)
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb3 -O0")
  set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb3 -O0")
  set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -ggdb3")
  set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -ggdb3")
  if(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang)
    add_compile_options(-Wno-backslash-newline-escape)
    add_compile_options(-Wno-self-assign)
  else() # GCC
    add_compile_options(-Wno-ignored-attributes)
  endif()
endif()

# Print helpful message if LLVM not found on Windows
if(NOT LLVM_FOUND AND WIN32)
  message(WARNING "LLVM not found. Run the following command from the main project directory:\n    nuget.exe restore external/packages.config -PackagesDirectory external")
endif()

# Include cmake projects for libraries and executables
add_subdirectory(libraries)
add_subdirectory(tools)
include(CommonInterfaces)
add_subdirectory(interfaces)
# Add examples (has dependencies on SWIG-generated interfaces)
add_subdirectory(examples)

# Add user directories to ELL build if requested
if(EXISTS "${ELL_ROOT}/user")
  # Add root user directory if it has a CMakeLists.txt file and INCLUDE_IN_ELL_BUILD.txt file
  if(EXISTS"${ELL_ROOT}/user/CMakeLists.txt" AND EXISTS "${ELL_ROOT}/user/INCLUDE_IN_ELL_BUILD.txt")
    message(STATUS "Adding user directory to ELL build")
    add_subdirectory(user)
  endif()

  # Now add all child directories that have CMakeLists.txt files and INCLUDE_IN_ELL_BUILD.txt file
  file(GLOB children RELATIVE "${ELL_ROOT}/user" "${ELL_ROOT}/user/*")
  foreach(child ${children})
    if(IS_DIRECTORY "${ELL_ROOT}/user/${child}" AND EXISTS "${ELL_ROOT}/user/${child}/CMakeLists.txt" AND EXISTS "${ELL_ROOT}/user/${child}/INCLUDE_IN_ELL_BUILD.txt")
      message(STATUS "Adding user directory ${child} to ELL build")
      add_subdirectory("user/${child}")
    endif()
  endforeach()
endif()

message(STATUS "Writing: ${CMAKE_BINARY_DIR}/config.json")
set(JSON "{ \"test_models_repo\": \"${TEST_MODELS_REPO}\" }")
file(WRITE "${CMAKE_BINARY_DIR}/config.json" ${JSON})
configure_file("${CMAKE_BINARY_DIR}/config.json" "config.json" COPYONLY)

# Add project for solution-level documentation
set (DOC README.md
         StyleGuide.md)
add_custom_target(documentation ALL DEPENDS ${DOC} SOURCES ${DOC})
set_property(TARGET documentation PROPERTY FOLDER "documentation")

# Generate doxygen documentation
if(BUILD_DOC)
  find_package(Doxygen 1.8 QUIET)

  # search external NuGet package directory also
  if(NOT DOXYGEN_FOUND)
    set(DOXYGEN_PACKAGE_NAME Doxygen)
    set(DOXYGEN_PACKAGE_VERSION 1.8.13)
    set(DOXYGEN_PACKAGE_DIR ${PACKAGE_ROOT}/${DOXYGEN_PACKAGE_NAME}.${DOXYGEN_PACKAGE_VERSION})
    find_program(DOXYGEN_EXECUTABLE doxygen
      HINTS "${DOXYGEN_PACKAGE_DIR}/tools")
      if(DOXYGEN_EXECUTABLE)
        set(DOXYGEN_FOUND TRUE)
      endif()
  endif()

  if(DOXYGEN_FOUND)
    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
    set (DOXYFILE_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile")

    add_custom_target(doc
      COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_LOCATION}
      WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
      COMMENT "Building Doxygen documentation" VERBATIM
      SOURCES Doxyfile)
    set_property(TARGET doc PROPERTY FOLDER "documentation")
  else()
    message(WARNING "Doxygen processor not found")
  endif()
endif()
