cmake_minimum_required(VERSION 3.5.0 FATAL_ERROR)
if (POLICY CMP0077)
    cmake_policy(SET CMP0077 NEW)
endif ()
if (POLICY CMP0069)
    cmake_policy(SET CMP0069 NEW)
endif ()
project(cppgir VERSION 0.1.0)

include(GNUInstallDirs)
include(CTest)
enable_testing()

set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 11)

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
    add_compile_options (-Wall -Wextra $<$<COMPILE_LANGUAGE:CXX>:-Wnon-virtual-dtor>)
endif()

# clang debug stdc++
if (${CMAKE_C_COMPILER_ID} STREQUAL "Clang")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fstandalone-debug")
endif()

## OPTIONS ##
option(BUILD_DOC "build documentation" ON)
option(EXAMPLES_EXPECTED "use expected<> in examples" OFF)

## CONTENT ##

find_package(Boost 1.58 REQUIRED COMPONENTS program_options)

find_path(FORMAT_INCLUDE_DIRS fmt/format.h)
find_library(FORMAT_LIBRARIES fmt)
if (${FORMAT_INCLUDE_DIRS} STREQUAL "FORMAT_INCLUDE_DIRS-NOTFOUND" OR
    ${FORMAT_LIBRARIES} STREQUAL "FORMAT_LIBRARIES-NOTFOUND")
    message (FATAL_ERROR "fmtlib.net format library not found")
endif ()

# required ignore file
set(GI_IGNORE_FILE_DIR data)
set(GI_IGNORE_FILE cppgir.ignore)
if (UNIX)
    set(GI_IGNORE_FILE_PLATFORM cppgir_unix.ignore)
else ()
    set(GI_IGNORE_FILE_PLATFORM cppgir_win.ignore)
endif ()
set(GI_IGNORE_FILE_INSTALL_DIR ${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME})

# add fixed fallback search places
set(GI_DEFAULT_GIRPATH "/usr/${CMAKE_INSTALL_DATADIR}:/usr/local/${CMAKE_INSTALL_DATADIR}")

add_executable(cppgir tools/cppgir.cpp
    tools/genbase.cpp tools/genbase.hpp
    tools/genns.cpp tools/genns.hpp
    tools/genutils.cpp tools/genutils.hpp
    tools/function.cpp tools/function.hpp
    tools/repository.cpp tools/repository.hpp
    tools/common.hpp)
target_compile_definitions(cppgir PRIVATE
    -DDEFAULT_GIRPATH=${GI_DEFAULT_GIRPATH})
target_compile_definitions(cppgir PRIVATE
    -DDEFAULT_IGNORE_FILE=${GI_IGNORE_FILE_INSTALL_DIR}/${GI_IGNORE_FILE}:${GI_IGNORE_FILE_INSTALL_DIR}/${GI_IGNORE_FILE_PLATFORM})
target_link_libraries(cppgir Boost::program_options stdc++fs ${FORMAT_LIBRARIES})
set_property(TARGET cppgir PROPERTY CXX_STANDARD 14)

add_library(gi INTERFACE)
target_include_directories(gi INTERFACE
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/gi>"
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/override>"
    "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>"
    "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/gi>"
    "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/override>"
)

set(EXPECTED_LITE_INCLUDE "expected-lite/include")
if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/${EXPECTED_LITE_INCLUDE}/nonstd/expected.hpp)
    target_include_directories(gi INTERFACE
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${EXPECTED_LITE_INCLUDE}>"
    )
else ()
    message (FATAL_ERROR "missing submodule expected-lite")
endif ()

include(FindPkgConfig)
pkg_check_modules(GOBJECT gobject-2.0)
pkg_check_modules(GIO gio-2.0 gio-unix-2.0)
pkg_check_modules(GST gstreamer-1.0)
pkg_check_modules(GTK gtk+-3.0)

## TEST ##

if (BUILD_TESTING AND GOBJECT_FOUND)
    add_executable(gi-test test/main.cpp
        test/test_object.c test/test_object.h test/test_boxed.c test/test_boxed.h)
    target_include_directories(gi-test PRIVATE "gi" "override")
    target_link_libraries(gi-test gi ${GOBJECT_LDFLAGS})
    target_compile_options(gi-test PRIVATE ${GOBJECT_CFLAGS})

    add_test(NAME gi-test COMMAND gi-test)
endif ()

## EXAMPLES ##

# generated wrappers' dir
set (GENERATED_DIR_DEFAULT "/tmp/gi")
set (GENERATED_ARGS "")
if (EXAMPLES_EXPECTED)
    set (GENERATED_DIR_DEFAULT "/tmp/gi_expected")
    set (GENERATED_ARGS "--expected")
endif ()
if (NOT GENERATED_DIR)
    set (GENERATED_DIR ${GENERATED_DIR_DEFAULT})
endif ()

set(EXAMPLE_TARGETS "")
set(EXAMPLE_NS "")

if (GOBJECT_FOUND)
    add_executable(example-gobject EXCLUDE_FROM_ALL examples/gobject.cpp)
    target_compile_options(example-gobject PRIVATE ${GOBJECT_CFLAGS})
    target_link_libraries(example-gobject PRIVATE ${GOBJECT_LDFLAGS})
    set_property(TARGET example-gobject PROPERTY CXX_STANDARD 14)

    message(STATUS "adding GObject example")
    set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gobject)
    set(EXAMPLE_NS ${EXAMPLE_NS} GObject-2.0)
endif ()

if (GIO_FOUND)
    add_executable(example-gio EXCLUDE_FROM_ALL examples/gio.cpp)
    target_compile_options(example-gio PRIVATE ${GIO_CFLAGS} ${GOBJECT_CFLAGS} ${GIO_CFLAGS})
    target_link_libraries(example-gio PRIVATE ${GIO_LDFLAGS} ${GOBJECT_LDFLAGS} ${GIO_LDFLAGS})

    add_executable(example-gio-dbus-client EXCLUDE_FROM_ALL examples/gio-dbus-client.cpp)
    target_compile_options(example-gio-dbus-client PRIVATE ${GIO_CFLAGS} ${GOBJECT_CFLAGS})
    target_link_libraries(example-gio-dbus-client PRIVATE ${GIO_LDFLAGS} ${GOBJECT_LDFLAGS})

    find_package(Boost 1.65 COMPONENTS fiber)
    if (Boost_FOUND)
        # no import target; multiple calls do not override first call targets
        add_executable(example-gio-async EXCLUDE_FROM_ALL examples/gio-async.cpp)
        target_include_directories(example-gio-async PRIVATE ${Boost_INCLUDE_DIRS})
        target_compile_options(example-gio-async PRIVATE ${GIO_CFLAGS} ${GOBJECT_CFLAGS})
        target_link_libraries(example-gio-async PRIVATE ${GIO_LDFLAGS} ${GOBJECT_LDFLAGS} ${Boost_LIBRARIES})
        set(GIO_ASYNC_EXAMPLE_TARGET example-gio-async)
    else ()
        set(GIO_ASYNC_EXAMPLE_TARGET "")
        message(STATUS "disabling Gio async example")
    endif ()

    message(STATUS "adding Gio examples")
    set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio example-gio-dbus-client ${GIO_ASYNC_EXAMPLE_TARGET})
    set(EXAMPLE_NS ${EXAMPLE_NS} Gio-2.0)
endif ()

if (GST_FOUND)
    add_executable(example-gst EXCLUDE_FROM_ALL examples/gst.cpp)
    # add generated files
    foreach (GENSRC IN ITEMS ${GENERATED_DIR}/glib/glib.cpp
                ${GENERATED_DIR}/gst/gst.cpp ${GENERATED_DIR}/gobject/gobject.cpp)
        target_sources(example-gst PRIVATE ${GENSRC})
        set_property(SOURCE ${GENSRC} PROPERTY GENERATED true)
    endforeach ()
    target_link_libraries(example-gst PRIVATE ${GST_LDFLAGS})
    target_compile_options(example-gst PRIVATE ${GST_CFLAGS})
    set_property(TARGET example-gst PROPERTY CXX_STANDARD 14)

    message(STATUS "adding Gst example")
    set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gst)
    set(EXAMPLE_NS ${EXAMPLE_NS} Gst-1.0)
endif ()

if (GTK_FOUND)
    add_executable(example-gtk EXCLUDE_FROM_ALL examples/gtk.cpp examples/gtk-obj.cpp)
    target_compile_options(example-gtk PRIVATE ${GOBJECT_CFLAGS} ${GTK_CFLAGS})
    target_link_libraries(example-gtk PRIVATE ${GOBJECT_LDFLAGS} ${GTK_LIBRARIES})

    message(STATUS "adding Gtk example")
    set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gtk)
    set(EXAMPLE_NS ${EXAMPLE_NS} Gtk-3.0)
endif ()

# optional Qt example
find_package(Qt5Core 5.9)
if (Qt5Core_FOUND AND GIO_FOUND)
    set(CMAKE_INCLUDE_CURRENT_DIR ON)
    add_executable(example-gio-qt-async EXCLUDE_FROM_ALL examples/gio-qt-async.cpp)
    target_compile_options(example-gio-qt-async PRIVATE ${GIO_CFLAGS} ${GOBJECT_CFLAGS})
    target_link_libraries(example-gio-qt-async PRIVATE ${GIO_LDFLAGS} ${GOBJECT_LDFLAGS} Qt5::Core)
    set_target_properties(example-gio-qt-async PROPERTIES AUTOMOC ON)

    message(STATUS "adding Qt GIO async example")
    set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio-qt-async)
endif ()

add_custom_command(OUTPUT ${GENERATED_DIR}
    COMMENT "Generating wrapper code for: ${EXAMPLE_NS}"
    DEPENDS cppgir
    COMMAND cppgir --class --ignore ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE}:${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE_PLATFORM}
        ${GENERATED_ARGS} --output ${GENERATED_DIR} ${EXAMPLE_NS}
    COMMAND cmake -E touch_nocreate ${GENERATED_DIR}
    WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})

message(STATUS "example programs: ${EXAMPLE_TARGETS}")
add_custom_target(examples)
if (EXAMPLE_TARGETS)
    add_dependencies(examples ${EXAMPLE_TARGETS})
endif ()

add_custom_target(wrappers DEPENDS ${GENERATED_DIR})
foreach (example ${EXAMPLE_TARGETS})
    target_link_libraries(${example} PRIVATE gi)
    target_include_directories(${example} PRIVATE ${GENERATED_DIR})
    add_dependencies(${example} wrappers)
endforeach ()


## INSTALL ##

# manpage processor
find_program(RONN ronn DOC "ronn markdown man page processor")
if (${RONN} STREQUAL "RONN-NOTFOUND")
    message(STATUS "ronn manpage processor not found; not building manpage")
elseif (BUILD_DOC)
    message(STATUS "building manpage")
    add_custom_command(OUTPUT cppgir.1
        COMMAND ${RONN} --roff --pipe ${CMAKE_CURRENT_LIST_DIR}/docs/cppgir.md > cppgir.1
        DEPENDS docs/cppgir.md
        WORKING_DIRECTORY .)
    add_custom_target(manpages ALL DEPENDS cppgir.1)
endif()

# generator
install(TARGETS cppgir
    DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})

# headers
install(DIRECTORY gi override
    DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/${PROJECT_NAME})
install(DIRECTORY ${EXPECTED_LITE_INCLUDE}/nonstd
    DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/${PROJECT_NAME}/gi)

# doc
install(FILES README.md docs/cppgir.md
    DESTINATION ${CMAKE_INSTALL_FULL_DOCDIR})
install(DIRECTORY examples
    DESTINATION ${CMAKE_INSTALL_FULL_DOCDIR})
if (TARGET manpages)
    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cppgir.1
        DESTINATION ${CMAKE_INSTALL_FULL_MANDIR}/man1)
endif()

# pkgconfig
set(PKG_CONFIG "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc")
set(PKG_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
# configure pkg config file
configure_file("cmake/cppgir.pc.in" "${PKG_CONFIG}" @ONLY)
install(FILES "${PKG_CONFIG}"
    DESTINATION "${PKG_CONFIG_INSTALL_DIR}")

# ignore file
install(FILES ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE} ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE_PLATFORM}
    DESTINATION ${GI_IGNORE_FILE_INSTALL_DIR})

# cmake EXPORTS
set(CONFIG_PACKAGE_LOCATION lib/cmake/${PROJECT_NAME})
set(CONFIG_VERSION_NAME ${PROJECT_NAME}-config-version.cmake)
set(CONFIG_TARGETS_NAME ${PROJECT_NAME}-targets.cmake)
set(CONFIG_NAME ${PROJECT_NAME}-config.cmake)
set(TARGETS_EXPORT_NAME CppGirTargets)

install(TARGETS gi EXPORT "${TARGETS_EXPORT_NAME}")

include(CMakePackageConfigHelpers)
write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/cmake/${CONFIG_VERSION_NAME}"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion
)

install(FILES cmake/${CONFIG_NAME}
    "${CMAKE_CURRENT_BINARY_DIR}/cmake/${CONFIG_VERSION_NAME}"
    DESTINATION ${CONFIG_PACKAGE_LOCATION}
)

export(EXPORT ${TARGETS_EXPORT_NAME}
    FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/${CONFIG_TARGETS_NAME}"
    NAMESPACE CppGir::
)

install(EXPORT ${TARGETS_EXPORT_NAME}
  FILE ${CONFIG_TARGETS_NAME}
  NAMESPACE CppGir::
  DESTINATION ${CONFIG_PACKAGE_LOCATION}
)

# uninstall target;
# intermediate directories are not removed though
if(NOT TARGET uninstall)
  configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake-uninstall.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
    IMMEDIATE @ONLY)

  add_custom_target(uninstall
    COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
endif()
