﻿cmake_minimum_required(VERSION 3.20)
cmake_policy(VERSION 3.20)
cmake_policy(SET CMP0135 NEW)
cmake_policy(SET CMP0074 NEW)
cmake_policy(SET CMP0169 OLD)

project(Scaffolder
  DESCRIPTION "Scaffolder is a tool for generating scaffolds for 3D printing."
  LANGUAGES C CXX
)

if(DEFINED VERSION)
  message(STATUS "Scaffolder version: ${VERSION}")
else()
  set(VERSION "dev")
  message(STATUS "Scaffolder version: ${VERSION} (development build)")
endif()

# -----------------------------------------------------------------------------
# Project layout and options
# -----------------------------------------------------------------------------
set(SCAFFOLDER_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
set(SCAFFOLDER_INCLUDE_DIR "${SCAFFOLDER_ROOT}/include")
set(SCAFFOLDER_SRC_DIR "${SCAFFOLDER_ROOT}/src")
set(SCAFFOLDER_CMAKE_DIR "${SCAFFOLDER_ROOT}/cmake")

list(APPEND CMAKE_MODULE_PATH "${SCAFFOLDER_CMAKE_DIR}")

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# -----------------------------------------------------------------------------
# External dependency
# -----------------------------------------------------------------------------
set(SCAFFOLDER_EXTERNAL_URL
  "https://github.com/nodtem66/Scaffolder/releases/download/v1.5.3/external-154.tar.gz"
  CACHE STRING "Download URL for external dependencies archive")
set(SCAFFOLDER_EXTERNAL_ARCHIVE
  "${SCAFFOLDER_ROOT}/scripts/external.tar.gz"
  CACHE STRING "Local path to save the downloaded external archive")
set(SCAFFOLDER_EXTERNAL_HASH
  "da26b5473307a8f0d416e59cff3f98cfa1ac291d512d85a35d9eede0a8d57969"
  CACHE STRING "SHA-256 hash of the external dependencies archive")

option(LIBIGL_WITH_OPENGL        "Use OpenGL"             OFF)
option(LIBIGL_WITH_OPENGL_GLFW   "Use GLFW"               OFF)
option(LIBIGL_BUILD_TESTS        "Build libigl unit test" OFF)
option(LIBIGL_BUILD_TUTORIALS    "Build libigl tutorial"  OFF)
option(LIBIGL_USE_STATIC_LIBRARY "Use LibIGL as static library" ON)
option(IGL_STATIC_RUNTIME "Use LibIGL as static library" ON)
option(BUILD_SCAFFOLDER "Build binary" ON)
option(BUILD_PYSCAFFOLDER "Build python library" OFF)
option(BUILD_WITH_OPENMP "USE OpenMP" ON)

add_compile_definitions(OPENCTM_STATIC)

# -----------------------------------------------------------------------------
# Compiler configuration
# -----------------------------------------------------------------------------
if(MSVC)
  # Enable parallel compilation for Visual Studio.
  add_compile_options(/MP /bigobj /D_CRT_SECURE_NO_WARNINGS /Zc:__cplusplus)
  add_compile_definitions(NOMINMAX _USE_MATH_DEFINES WIN32_LEAN_AND_MEAN)
  add_compile_options(
    # Type conversion warnings. These can be fixed with some effort and possibly more verbose code.
    /wd4267 # conversion from 'size_t' to 'type', possible loss of data
    /wd4244 # conversion from 'type1' to 'type2', possible loss of data
    /wd4018 # signed/unsigned mismatch
    /wd4305 # truncation from 'double' to 'float'
    # This one is from template instantiations generated by autoexplicit.sh:
    /wd4667 # no function template defined that matches forced instantiation ()
    # This one is easy to fix, just need to switch to safe version of C functions
    /wd4996 # this function or variable may be unsafe
    # This one is when using bools in adjacency matrices
    /wd4804 #'+=': unsafe use of type 'bool' in operation
  )
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  add_compile_options(-pedantic)
endif()

message(STATUS "CMAKE SYSTEM NAME: ${CMAKE_SYSTEM_NAME}")

if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
  message(STATUS "[OSX Detected]")
  add_compile_definitions(_DARWIN_C_SOURCE _GNU_SOURCE _XOPEN_SOURCE=600)
endif()

if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  message(STATUS "[CLANG Detected]")
endif()

message(STATUS "PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}")

set(REQUIRED_EXTERNALS
  "${SCAFFOLDER_ROOT}/external/libigl/CMakeLists.txt"
  "${SCAFFOLDER_ROOT}/external/sol2/CMakeLists.txt"
  "${SCAFFOLDER_ROOT}/external/pybind11/CMakeLists.txt"
  "${SCAFFOLDER_ROOT}/external/vcglib/wrap/"
  "${SCAFFOLDER_ROOT}/external/OpenCTM-1.0.3/lib/openctm.h"
)
set(MISSING_EXTERNALS)
foreach(required_path IN LISTS REQUIRED_EXTERNALS)
  if(NOT EXISTS "${required_path}")
    list(APPEND MISSING_EXTERNALS "${required_path}")
  endif()
endforeach()

if(MISSING_EXTERNALS)
  include(download_external_files)
  download_external_files(
    "${SCAFFOLDER_EXTERNAL_URL}"
    "${SCAFFOLDER_EXTERNAL_ARCHIVE}"
    "${SCAFFOLDER_EXTERNAL_HASH}"
  )
  file(ARCHIVE_EXTRACT INPUT "${SCAFFOLDER_ROOT}/scripts/external.tar.gz" DESTINATION ${SCAFFOLDER_ROOT})
endif()

# -----------------------------------------------------------------------------
# OpenMP configuration
# -----------------------------------------------------------------------------
if(BUILD_WITH_OPENMP)
  find_package(OpenMP REQUIRED)
  message(STATUS "OpenMP: ${OpenMP_CXX_FLAGS}")
endif()

find_package(Threads REQUIRED)
find_package(VCG REQUIRED)
find_package(LIBIGL REQUIRED)
find_package(Sol2 REQUIRED)
find_package(OpenCTM REQUIRED)

# -----------------------------------------------------------------------------
# Common target setup
# -----------------------------------------------------------------------------
set(SCAFFOLDER_COMMON_INCLUDE_DIRS
  "${SCAFFOLDER_INCLUDE_DIR}"
  "${VCG_INCLUDE_DIR}"
  "${EIGEN3_INCLUDE_DIR}"
  "${SOL2_INCLUDE_DIR}"
  "${LUA_INCLUDE_DIR}"
  "${OpenCTM_INCLUDE_DIR}"
  "${LIBIGL_INCLUDE_DIR}"
  "${OpenCTM_INCLUDE_DIR}/liblzma/"
)

set(SCAFFOLDER_COMMON_LINK_LIBS
  igl::core
  Threads::Threads
  Eigen3::Eigen
  ${LUA_LIBRARIES}
)

if(TARGET OpenMP::OpenMP_CXX)
  list(APPEND SCAFFOLDER_COMMON_LINK_LIBS OpenMP::OpenMP_CXX)
endif()

function(scaffolder_configure_target target_name)
  target_include_directories(${target_name} PRIVATE ${SCAFFOLDER_COMMON_INCLUDE_DIRS})
  target_link_libraries(${target_name} PRIVATE ${SCAFFOLDER_COMMON_LINK_LIBS})
  if (TARGET OpenMP::OpenMP_CXX)
    target_compile_definitions(${target_name} PRIVATE USE_PARALLEL=1)
  else()
    target_compile_definitions(${target_name} PRIVATE USE_PARALLEL=0)
  endif()
  if(DEFINED VERSION)
    target_compile_definitions(${target_name} PRIVATE SCAFFOLDER_VERSION="${VERSION}")
  endif()
endfunction()

if(BUILD_SCAFFOLDER)
  file(GLOB MAIN_SOURCES
    "${SCAFFOLDER_ROOT}/include/*/*.c"
    "${SCAFFOLDER_ROOT}/include/*/*.cpp"
    "${SCAFFOLDER_SRC_DIR}/utils.cpp"
    "${SCAFFOLDER_SRC_DIR}/Scaffolder.cpp"
    "${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp"
    "${OpenCTM_INCLUDE_DIR}/*.c"
    "${OpenCTM_INCLUDE_DIR}/liblzma/*.c"
  )
  include(update_deps_file)
  update_deps_file("main_sources" "${MAIN_SOURCES}")

  add_executable(${PROJECT_NAME} ${MAIN_SOURCES} "${SCAFFOLDER_SRC_DIR}/Main.cpp")
  scaffolder_configure_target(${PROJECT_NAME})

  add_executable("${PROJECT_NAME}.SliceTest"
    "${SCAFFOLDER_SRC_DIR}/SliceTest.cpp"
    "${SCAFFOLDER_SRC_DIR}/utils.cpp"
    "${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp"
  )
  scaffolder_configure_target("${PROJECT_NAME}.SliceTest")

  include(GNUInstallDirs)
  install(TARGETS "${PROJECT_NAME}" DESTINATION bin COMPONENT scaffolder)
  install(TARGETS "${PROJECT_NAME}.SliceTest" DESTINATION bin COMPONENT scaffolder)
endif()

if(BUILD_PYSCAFFOLDER)
  message(STATUS "[Build PyScaffolder]")
  find_package(Python COMPONENTS Interpreter Development)
  find_package(PyBind11 REQUIRED)

  file(GLOB PYTHON_SOURCES
    "${SCAFFOLDER_ROOT}/src/python/*.cpp"
    "${SCAFFOLDER_ROOT}/src/Scaffolder.cpp"
    "${SCAFFOLDER_ROOT}/src/utils.cpp"
  )
  include(update_deps_file)
  update_deps_file("python_sources" "${PYTHON_SOURCES}")

  pybind11_add_module("Py${PROJECT_NAME}" ${PYTHON_SOURCES})
  target_include_directories("Py${PROJECT_NAME}" PRIVATE
    "${SCAFFOLDER_SRC_DIR}/python/"
    "${PyBind11_INCLUDE_DIR}"
  )
  scaffolder_configure_target("Py${PROJECT_NAME}")
  include(GNUInstallDirs)
  install(TARGETS "Py${PROJECT_NAME}"
    COMPONENT python
    DESTINATION "."
  )
endif()
