# bbowda - Black Box Optimization With Data Analysis
# Copyright (C) 2024 DAGOPT Optimization Technologies GmbH (www.dagopt.com)
#                    written by Kevin Kofler <kofler@dagopt.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. A copy of the GNU General Public
# License version 3 can be found in the file gpl-3.0.txt.
#
# Linking bbowda statically or dynamically (directly or indirectly) with
# other modules is making a combined work based on bbowda. Thus, the terms
# and conditions of the GNU General Public License cover the whole
# combination.
#
# In addition, as a special exception, the copyright holder of bbowda gives
# you permission to combine the bbowda program:
# * with free software programs or libraries that are released under the
#   GNU Library or Lesser General Public License (LGPL), either version 2
#   of the License, or (at your option) any later version,
# * with free software programs or libraries that are released under the
#   IBM Common Public License (CPL), either version 1.0 of the License, or
#   (at your option) any later version,
# * with free software programs or libraries that are released under the
#   eclipse.org Eclipse Public License (EPL), either version 1.0 of the
#   License, or (at your option) any later version,
# * with free software programs or libraries that are released under the
#   CeCILL-C Free Software License Agreement, either version 1 of the License,
#   or (at your option) any later version,
# * with code included in the standard release of MUMPS under the old MUMPS
#   Conditions of Use as reproduced in licenses.txt (or modified versions
#   of such code, with unchanged license; variants of the license where only
#   the list of contributors and/or the list of suggested citations changed
#   shall be considered the same license) and
# * if you qualify for a free of charge license of DONLP2, with code
#   included in the standard release of DONLP2 under the DONLP2 Conditions
#   of Use as reproduced in licenses.txt (or modified versions of such code,
#   with unchanged license).
# (For avoidance of doubt, this implies that it is permitted, e.g., to combine
# the bbowda program with current versions of Ipopt released under the EPL
# version 2.0, because 2.0 is >= 1.0. Its dependency MUMPS is released under
# the CeCILL-C version 1, which is also listed above.)
#
# You may copy and distribute such a system following the terms of the GNU
# GPL for bbowda and the licenses of the other code concerned, provided that
# you include the source code of that other code when and as the GNU GPL
# requires distribution of source code.
#
# Note that people who make modified versions of bbowda are not obligated
# to grant this special exception for their modified versions; it is their
# choice whether to do so. The GNU General Public License gives permission
# to release a modified version without this exception; this exception also
# makes it possible to release a modified version which carries forward
# this exception.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

cmake_minimum_required(VERSION 3.12)

project(bbowda LANGUAGES C)

include(CheckCSourceCompiles)
include(CheckLibraryExists)
include(GenerateExportHeader)
include(GNUInstallDirs)

option(BBOWDA_SHARED "Build libbbowda as a shared library." ON)
option(WITH_DOXYGEN "Enable Doxygen documentation." ON)
option(WITH_DOXYGEN_PDF "Enable Doxygen PDF documentation." OFF)
option(WITH_CXX "Enable the C++ binding (bbowdapp)." ON)

set(NLP_SOLVER "NLopt" CACHE STRING
    "Select a gradient-based NLP solver for the surrogate and density problems,\
 options are: NLopt Ipopt donlp2 donlp3.")
set_property(CACHE NLP_SOLVER PROPERTY STRINGS "NLopt" "Ipopt" "donlp2"
                                               "donlp3")

set(CMAKE_C_FLAGS "-fomit-frame-pointer -O3 -ftree-vectorize")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wwrite-strings")
set(CMAKE_C_VISIBILITY_PRESET hidden)

find_library(LPSOLVE_LIBRARY NAMES lpsolve55_pic lpsolve55 REQUIRED)
find_path(LPSOLVE_INCLUDE_DIR lp_lib.h REQUIRED PATH_SUFFIXES lpsolve)
mark_as_advanced(LPSOLVE_LIBRARY LPSOLVE_INCLUDE_DIR)
check_library_exists("${LPSOLVE_LIBRARY}" "colamd" "" LPSOLVE_HAS_COLAMD)
mark_as_advanced(LPSOLVE_HAS_COLAMD)
if(LPSOLVE_HAS_COLAMD)
  set(LPSOLVE_LIBRARIES "${LPSOLVE_LIBRARY}")
else()
  find_library(COLAMD_LIBRARY colamd REQUIRED)
  mark_as_advanced(COLAMD_LIBRARY)
  set(LPSOLVE_LIBRARIES "${LPSOLVE_LIBRARY};${COLAMD_LIBRARY}")
endif()
message(STATUS "Found lp_solve: ${LPSOLVE_LIBRARIES}, ${LPSOLVE_INCLUDE_DIR}")

include_directories("${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}"
                    "${LPSOLVE_INCLUDE_DIR}")

set(SOURCES bbowda.c eval.c gmmem.c eqconst.c covar.c xmalloc.c)
set_source_files_properties(eqconst.c COMPILE_FLAGS "-Wno-unused-function")

message(STATUS "Using NLP solver ${NLP_SOLVER}")
if(NLP_SOLVER STREQUAL "NLopt")
  find_package(NLopt REQUIRED)
  add_definitions("-DUSE_NLOPT")
  list(APPEND SOURCES nlp_nlopt/nlp_nlopt.c)
  set_source_files_properties(nlp_nlopt/nlp_nlopt.c PROPERTIES COMPILE_FLAGS
                              "-Wno-unused-parameter")
  set(NLP_SOLVER_LIBRARIES ${NLOPT_LIBRARIES})
elseif(NLP_SOLVER STREQUAL "Ipopt")
  find_package(PkgConfig REQUIRED)
  pkg_check_modules(IPOPT REQUIRED IMPORTED_TARGET ipopt)
  set(IPOPT_LIBRARIES PkgConfig::IPOPT)
  set(NLP_SOLVER_LIBRARIES ${IPOPT_LIBRARIES})
  set(CMAKE_REQUIRED_FLAGS
      "-Werror=incompatible-pointer-types -Wno-unused-parameter")
  set(CMAKE_REQUIRED_LIBRARIES PkgConfig::IPOPT)
  check_c_source_compiles("
#include <IpStdCInterface.h>
void f(Number *p) {}
int main(void) {double d; f(&d); return 0;}
  " IPOPT_Number_IS_double)
  set(CMAKE_REQUIRED_LIBRARIES)
  set(CMAKE_REQUIRED_FLAGS)
  mark_as_advanced(IPOPT_Number_IS_double)
  if(NOT IPOPT_Number_IS_double)
    message(FATAL_ERROR "Unsupported version of Ipopt. Ipopt must use double "
            "precision (Number = double).")
  endif()
  add_definitions("-DUSE_IPOPT")
  list(APPEND SOURCES nlp_ipopt/nlp_ipopt.c)
  set_source_files_properties(nlp_ipopt/nlp_ipopt.c PROPERTIES COMPILE_FLAGS
                              "-Wno-unused-parameter")
elseif(NLP_SOLVER STREQUAL "donlp2")
  message(WARNING "The free use of donlp2 and parts of it is restricted for "
    "research purposes. Commercial uses require permission and licensing from "
    "Prof.Peter Spellucci. By selecting the donlp2 solver, you agree to its "
    "proprietary license. Otherwise, please select a different NLP solver.")
  message(WARNING "The donlp2 solver is NOT reentrant. Use another solver "
    "(either donlp3 or an entirely different solver) if you require BBOWDA to "
    "be reentrant.")
  set(DONLP2_FILES donlp2.c newx.c o8comm.h o8cons.h o8fint.h o8fuco.h o8gene.h
                   o8para.h user_eval.c)
  foreach(DONLP2_FILE IN LISTS DONLP2_FILES)
    if(NOT EXISTS
       "${CMAKE_CURRENT_SOURCE_DIR}/nlp_donlp2/donlp2_intv_dyn/${DONLP2_FILE}")
      message(FATAL_ERROR "${DONLP2_FILE} not found. The donlp2 backend "
      "requires the following files from the donlp2_intv_dyn.tar.gz "
      "distribution to be extracted to the nlp_donlp2/donlp2_intv_dyn "
      "directory: ${DONLP2_FILES}")
    endif()
  endforeach()
  set(CMAKE_REQUIRED_FLAGS
      "-Werror=incompatible-pointer-types -Wno-unused-parameter")
  set(CMAKE_REQUIRED_INCLUDES
      "${CMAKE_CURRENT_SOURCE_DIR}/nlp_donlp2/donlp2_intv_dyn")
  check_c_source_compiles("
#include <o8para.h>
void f(DOUBLE *p) {}
int main(void) {double d; f(&d); return 0;}
  " DONLP2_DOUBLE_IS_double)
  set(CMAKE_REQUIRED_INCLUDES)
  set(CMAKE_REQUIRED_FLAGS)
  mark_as_advanced(DONLP2_DOUBLE_IS_double)
  if(NOT DONLP2_DOUBLE_IS_double)
    message(FATAL_ERROR "Unsupported version of donlp2. Donlp2 must use double "
            "precision (DOUBLE = double).")
  endif()
  list(APPEND SOURCES nlp_donlp2/nlp_donlp2.c
                      nlp_donlp2/donlp2_intv_dyn/user_eval.c
                      nlp_donlp2/donlp2_intv_dyn/newx.c
                      nlp_donlp2/donlp2_intv_dyn/donlp2.c)
  set(DONLP2_INCLUDE_DIR
      "${CMAKE_CURRENT_SOURCE_DIR}/nlp_donlp2/donlp2_intv_dyn")
  set_source_files_properties(nlp_donlp2/nlp_donlp2.c PROPERTIES
                              COMPILE_FLAGS "-Wno-unused"
                              INCLUDE_DIRECTORIES "${DONLP2_INCLUDE_DIR}")
  set_source_files_properties(nlp_donlp2/donlp2_intv_dyn/user_eval.c
                              PROPERTIES COMPILE_FLAGS "-Wno-unused")
  set_source_files_properties(nlp_donlp2/donlp2_intv_dyn/newx.c PROPERTIES
                              COMPILE_FLAGS "-Wno-unused")
  set_source_files_properties(nlp_donlp2/donlp2_intv_dyn/donlp2.c PROPERTIES
                              COMPILE_FLAGS "-Wno-unused -Wno-write-strings")
elseif(NLP_SOLVER STREQUAL "donlp3")
  message(WARNING "The free use of donlp3 and parts of it is restricted for "
    "research purposes. Commercial uses require permission and licensing from "
    "Prof.Peter Spellucci. By selecting the donlp3 solver, you agree to its "
    "proprietary license. Otherwise, please select a different NLP solver.")
  add_subdirectory(nlp_donlp3/donlp3_c_binding)
  set(CMAKE_REQUIRED_FLAGS
      "-Werror=incompatible-pointer-types -Wno-unused-parameter")
  set(CMAKE_REQUIRED_INCLUDES
      "${CMAKE_CURRENT_SOURCE_DIR}/nlp_donlp3/donlp3_c_binding"
      "${DONLP3_INCLUDE_DIR}")
  check_c_source_compiles("
#include <donlp3_c_binding.h>
void f(DOUBLE *p) {}
int main(void) {double d; f(&d); return 0;}
  " DONLP3_DOUBLE_IS_double)
  set(CMAKE_REQUIRED_INCLUDES)
  set(CMAKE_REQUIRED_FLAGS)
  mark_as_advanced(DONLP3_DOUBLE_IS_double)
  if(NOT DONLP3_DOUBLE_IS_double)
    message(FATAL_ERROR "Unsupported version of donlp3. Donlp3 must use double "
            "precision (DOUBLE = double).")
  endif()
  add_definitions("-DUSE_DONLP3")
  list(APPEND SOURCES nlp_donlp3/nlp_donlp3.c)
  set_source_files_properties(nlp_donlp3/nlp_donlp3.c PROPERTIES COMPILE_FLAGS
                              "-Wno-unused-parameter -Wno-unused-variable")
  set(NLP_SOLVER_LIBRARIES donlp3_c_binding)
else()
  message(FATAL_ERROR "Unsupported NLP_SOLVER ${NLP_SOLVER}")
endif()

if(BBOWDA_SHARED)
  set(BBOWDA_LIB_TYPE SHARED)
else()
  set(BBOWDA_LIB_TYPE STATIC)
endif()
message(STATUS "Building a ${BBOWDA_LIB_TYPE} libbbowda")
add_library(bbowda ${BBOWDA_LIB_TYPE} ${SOURCES})
generate_export_header(bbowda)
# force PIC so that the static library can be linked into shared libraries
set_target_properties(bbowda PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_libraries(bbowda ${NLP_SOLVER_LIBRARIES} ${LPSOLVE_LIBRARIES}
                             ${PROBLEM_LIBRARIES} m)

install(TARGETS bbowda
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

set_source_files_properties(example.c COMPILE_FLAGS "-Wno-unused-parameter")
add_executable(bbowda_example example.c)
target_link_libraries(bbowda_example bbowda)

install(TARGETS bbowda_example RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

if(WITH_CXX)
  message(STATUS "Building C++ binding (bbowdapp)")
  add_subdirectory(bbowdapp)
else()
  message(STATUS "Not building C++ binding (bbowdapp)")
endif()

if(WITH_DOXYGEN)
  message(STATUS "Building Doxygen documentation")
  find_package(Doxygen REQUIRED dot)
  # also document the C function bbowda, not only methods in classes
  set(DOXYGEN_EXTRACT_ALL YES)
  if (WITH_DOXYGEN_PDF)
    message(STATUS "Building Doxygen PDF documentation")
    find_package(LATEX REQUIRED COMPONENTS PDFLATEX)
    find_program(MAKE_EXECUTABLE NAMES gmake make smake REQUIRED)
    mark_as_advanced(MAKE_EXECUTABLE)
    # generate PDF documentation through LaTeX
    set(DOXYGEN_GENERATE_LATEX YES)
  else()
    message(STATUS "Not building Doxygen PDF documentation")
  endif()
  doxygen_add_docs(doc bbowda.h bbowdapp/bbowdapp.hh ALL)
  if (WITH_DOXYGEN_PDF)
    # fix the above target to actually invoke the generated Makefile
    add_custom_command(TARGET doc POST_BUILD COMMAND "${MAKE_EXECUTABLE}"
                       WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/latex"
                       COMMENT "Building Doxygen LaTeX PDF documentation...")
  endif()
else()
  message(STATUS "Not building Doxygen documentation")
endif()

# special "release" target for the maintainer
if (UNIX)
  add_custom_target(release
                    COMMAND "./release.sh"
                    COMMENT "Preparing release..."
                    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
endif (UNIX)
