#-----------------------------------------------------------------------------
#
#  CMake Config
#
#  Libosmium
#
#-----------------------------------------------------------------------------

cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")


#-----------------------------------------------------------------------------
#
#  Project version
#
#-----------------------------------------------------------------------------

set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo;MinSizeRel;Dev;Coverage"
    CACHE STRING
    "List of available configuration types"
    FORCE)

project(libosmium)

set(LIBOSMIUM_VERSION_MAJOR 2)
set(LIBOSMIUM_VERSION_MINOR 11)
set(LIBOSMIUM_VERSION_PATCH 1)

set(LIBOSMIUM_VERSION
    "${LIBOSMIUM_VERSION_MAJOR}.${LIBOSMIUM_VERSION_MINOR}.${LIBOSMIUM_VERSION_PATCH}")

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)


#-----------------------------------------------------------------------------
#
#  Build options
#
#  (Change with -DOPTION=VALUE on cmake command line.)
#
#-----------------------------------------------------------------------------

if(CMAKE_BUILD_TYPE STREQUAL "Dev")
    set(dev_build ON)
else()
    set(dev_build OFF)
endif()

option(BUILD_EXAMPLES   "compile example programs" ON)
option(BUILD_TESTING    "compile unit tests, please run them with ctest" ON)

option(BUILD_HEADERS    "compile every header file on its own" ${dev_build})
option(BUILD_BENCHMARKS "compile benchmark programs" ${dev_build})
option(BUILD_DATA_TESTS "compile data tests, please run them with ctest" ${dev_build})

option(INSTALL_GDALCPP   "also install gdalcpp headers" OFF)
option(INSTALL_PROTOZERO "also install protozero headers" OFF)
option(INSTALL_UTFCPP    "also install utfcpp headers" OFF)

option(WITH_PROFILING    "add flags needed for profiling" OFF)


#-----------------------------------------------------------------------------
#
#  CCache support
#
#-----------------------------------------------------------------------------

option(BUILD_WITH_CCACHE "build using ccache" OFF)

if(BUILD_WITH_CCACHE)
    find_program(CCACHE_PROGRAM ccache)
    if(CCACHE_PROGRAM)
        set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "CCACHE_CPP2=1 ${CCACHE_PROGRAM}")

        # workaround for some clang versions
        if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
            add_definitions(-Qunused-arguments)
        endif()
    endif()
endif()


#-----------------------------------------------------------------------------
#
#  Coverage support
#
#-----------------------------------------------------------------------------

include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-fkeep-inline-functions" HAS_KEEP_INLINE_FUNCTIONS)
if(HAS_KEEP_INLINE_FUNCTIONS)
    set(extra_coverage_flags_ "-fkeep-inline-functions")
endif()

set(CMAKE_CXX_FLAGS_COVERAGE
    "-g -O0 -fno-inline-functions -fno-inline --coverage ${extra_coverage_flags_}"
    CACHE STRING "Flags used by the compiler during coverage builds.")

set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
    "--coverage"
    CACHE STRING "Flags used by the linker during coverage builds.")

if(CMAKE_BUILD_TYPE STREQUAL "Coverage")
    if(BUILD_EXAMPLES OR BUILD_HEADERS OR BUILD_BENCHMARKS OR BUILD_DATA_TESTS)
        message(WARNING "Coverage builds don't work for anything but the unit tests")
    endif()

    if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
        string(REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "llvm-cov-\\1.\\2"
               gcov_ ${CMAKE_CXX_COMPILER_VERSION})
    else()
        set(gcov_ "gcov")
    endif()

    find_program(GCOV ${gcov_} DOC "Coverage tool")
    find_program(GCOVR "gcovr" DOC "Coverage report tool")

    set(coverage_report_dir "${CMAKE_BINARY_DIR}/coverage")
    file(MAKE_DIRECTORY ${coverage_report_dir})
    add_custom_target(coverage
        ${GCOVR}
        ${CMAKE_BINARY_DIR}
        --root=${CMAKE_SOURCE_DIR}
        --html --html-details
        #--verbose
        #--keep
        '--filter=.*include/osmium.*'
        --sort-percentage
        --gcov-executable=${GCOV}
        --output=${coverage_report_dir}/index.html)
endif()


#-----------------------------------------------------------------------------
#
#  Find external dependencies
#
#-----------------------------------------------------------------------------

find_package(Boost 1.38)
mark_as_advanced(CLEAR BOOST_ROOT)

if(Boost_FOUND)
    include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
else()
    set(BOOST_ROOT "NOT FOUND: please choose" CACHE PATH "")
    message(FATAL_ERROR "PLEASE, specify the directory where the Boost library is installed in BOOST_ROOT")
endif()

# set OSMIUM_INCLUDE_DIR so FindOsmium will not set anything different
set(OSMIUM_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include")

include_directories(${OSMIUM_INCLUDE_DIR})

find_package(Osmium COMPONENTS io gdal geos proj sparsehash)

# The find_package put the directory where it found the libosmium includes
# into OSMIUM_INCLUDE_DIRS. We remove it again, because we want to make
# sure to use our own include directory already set up above.
list(FIND OSMIUM_INCLUDE_DIRS "${OSMIUM_INCLUDE_DIR}" _own_index)
list(REMOVE_AT OSMIUM_INCLUDE_DIRS ${_own_index})
set(_own_index)

include_directories(SYSTEM ${OSMIUM_INCLUDE_DIRS})

if(MSVC)
    find_path(GETOPT_INCLUDE_DIR getopt.h)
    find_library(GETOPT_LIBRARY NAMES wingetopt)
    if(GETOPT_INCLUDE_DIR AND GETOPT_LIBRARY)
        include_directories(SYSTEM ${GETOPT_INCLUDE_DIR})
        list(APPEND OSMIUM_LIBRARIES ${GETOPT_LIBRARY})
    else()
        set(GETOPT_MISSING 1)
    endif()
endif()


#-----------------------------------------------------------------------------
#
#  Decide which C++ version to use (Minimum/default: C++11).
#
#-----------------------------------------------------------------------------
if(NOT MSVC)
    if(NOT USE_CPP_VERSION)
        set(USE_CPP_VERSION c++11)
    endif()
    message(STATUS "Use C++ version: ${USE_CPP_VERSION}")
    # following only available from cmake 2.8.12:
    #   add_compile_options(-std=${USE_CPP_VERSION})
    # so using this instead:
    add_definitions(-std=${USE_CPP_VERSION})
endif()


#-----------------------------------------------------------------------------
#
#  Compiler and Linker flags
#
#-----------------------------------------------------------------------------
if(MSVC)
    set(USUAL_COMPILE_OPTIONS "/Ox")
    set(USUAL_LINK_OPTIONS "/debug")
else()
    set(USUAL_COMPILE_OPTIONS "-O3 -g")
    set(USUAL_LINK_OPTIONS "")
endif()

if(WIN32)
    add_definitions(-DWIN32 -D_WIN32 -DMSWIN32 -DBGDWIN32
                    -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0600)
endif()

set(CMAKE_CXX_FLAGS_DEV "${USUAL_COMPILE_OPTIONS}"
    CACHE STRING "Flags used by the compiler during developer builds."
    FORCE)

set(CMAKE_EXE_LINKER_FLAGS_DEV "${USUAL_LINK_OPTIONS}"
    CACHE STRING "Flags used by the linker during developer builds."
    FORCE)
mark_as_advanced(
    CMAKE_CXX_FLAGS_DEV
    CMAKE_EXE_LINKER_FLAGS_DEV
)

set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${USUAL_COMPILE_OPTIONS}"
    CACHE STRING "Flags used by the compiler during RELWITHDEBINFO builds."
    FORCE)

if(WITH_PROFILING)
    add_definitions(-fno-omit-frame-pointer)
endif()


#-----------------------------------------------------------------------------
#
#  Build Type
#
#-----------------------------------------------------------------------------

# In 'Dev' mode: compile with very strict warnings and turn them into errors.
if(CMAKE_BUILD_TYPE STREQUAL "Dev")
    if(NOT MSVC)
        add_definitions(-Werror)
    endif()
    add_definitions(${OSMIUM_WARNING_OPTIONS})
#    add_definitions(${OSMIUM_WARNING_OPTIONS} ${OSMIUM_DRACONIC_CLANG_OPTIONS} -Wno-documentation -Wno-format-nonliteral -Wno-deprecated -Wno-covered-switch-default -Wno-shadow)
endif()

# Force RelWithDebInfo build type if none was given
if(CMAKE_BUILD_TYPE)
    set(build_type ${CMAKE_BUILD_TYPE})
else()
    set(build_type "RelWithDebInfo")
endif()

set(CMAKE_BUILD_TYPE ${build_type}
    CACHE STRING
    "Choose the type of build, options are: ${CMAKE_CONFIGURATION_TYPES}."
    FORCE)


#-----------------------------------------------------------------------------
#
#  Unit and data tests
#
#-----------------------------------------------------------------------------
enable_testing()

if(BUILD_TESTING OR BUILD_DATA_TESTS)
    find_program(MEMORYCHECK_COMMAND valgrind)

    set(MEMORYCHECK_COMMAND_OPTIONS
        "--trace-children=yes --leak-check=full --show-reachable=yes --error-exitcode=1")

    set(MEMORYCHECK_SUPPRESSIONS_FILE "${PROJECT_SOURCE_DIR}/test/valgrind.supp")
endif()

if(BUILD_TESTING)
    add_subdirectory(test)
endif()

if(BUILD_DATA_TESTS)
    add_subdirectory(test/data-tests)
endif()

if(BUILD_EXAMPLES)
    add_subdirectory(test/examples)
endif()


#-----------------------------------------------------------------------------
#
#  Optional "cppcheck" target that checks C++ code
#
#-----------------------------------------------------------------------------
message(STATUS "Looking for cppcheck")
find_program(CPPCHECK cppcheck)

if(CPPCHECK)
    message(STATUS "Looking for cppcheck - found")
    set(CPPCHECK_OPTIONS
        --enable=warning,style,performance,portability,information,missingInclude --force -Uassert)

    # cpp doesn't find system includes for some reason, suppress that report
    set(CPPCHECK_OPTIONS ${CPPCHECK_OPTIONS} --suppress=missingIncludeSystem)

    file(GLOB_RECURSE ALL_INCLUDES   include/osmium/*.hpp)
    file(GLOB         ALL_EXAMPLES   examples/*.cpp)
    file(GLOB         ALL_BENCHMARKS benchmarks/*.cpp)
    file(GLOB         ALL_UNIT_TESTS test/t/*/test_*.cpp)
    file(GLOB         ALL_DATA_TESTS test/data-tests/*.cpp)

    if(Osmium_DEBUG)
        message(STATUS "Checking includes      : ${ALL_INCLUDES}")
        message(STATUS "Checking example code  : ${ALL_EXAMPLES}")
        message(STATUS "Checking benchmarks    : ${ALL_BENCHMARKS}")
        message(STATUS "Checking unit test code: ${ALL_UNIT_TESTS}")
        message(STATUS "Checking data test code: ${ALL_DATA_TESTS}")
    endif()

    set(CPPCHECK_FILES
        ${ALL_INCLUDES}
        ${ALL_EXAMPLES}
        ${ALL_BENCHMARKS}
        ${ALL_UNIT_TESTS}
        ${ALL_DATA_TESTS})

    add_custom_target(cppcheck
        ${CPPCHECK}
        --std=c++11 ${CPPCHECK_OPTIONS}
        -I ${CMAKE_SOURCE_DIR}/include
        ${CPPCHECK_FILES}
    )
else()
    message(STATUS "Looking for cppcheck - not found")
    message(STATUS "  Build target 'cppcheck' will not be available.")
endif()


#-----------------------------------------------------------------------------
#
#  Examples, benchmarks and documentation
#
#-----------------------------------------------------------------------------

if(BUILD_EXAMPLES)
    add_subdirectory(examples)
endif()

if(BUILD_BENCHMARKS)
    add_subdirectory(benchmarks)
endif()

add_subdirectory(doc)


#-----------------------------------------------------------------------------
#
#  Headers
#
#  This will try to compile include files on their own to detect missing
#  include directives and other dependency-related problems. Note that if this
#  work, it is not enough to be sure it will compile in production code.
#  But if it reports an error we know we are missing something.
#
#-----------------------------------------------------------------------------
if(BUILD_HEADERS)
    file(GLOB_RECURSE
         ALL_HPPS
         RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/include"
         include/osmium/*.hpp)

    file(MAKE_DIRECTORY header_check)

    foreach(hpp ${ALL_HPPS})
        string(REPLACE ".hpp" "" tmp ${hpp})
        string(REPLACE "/" "__" libname ${tmp})

        # Create a dummy .cpp file that includes the header file we want to
        # check.
        set(DUMMYCPP ${CMAKE_BINARY_DIR}/header_check/${libname}.cpp)
        file(WRITE ${DUMMYCPP} "#include <${hpp}>\n")

        # There is no way in CMake to just compile but not link a C++ file,
        # so we pretend to build a library here.
        add_library(${libname} STATIC ${DUMMYCPP} include/${hpp})

        #### this is better but only supported from cmake 3.0:
        ###add_library(${libname} OBJECT ${DUMMYCPP} include/${hpp})

    endforeach()
endif()


#-----------------------------------------------------------------------------
#
#  Optional "clang-tidy" target
#
#-----------------------------------------------------------------------------
message(STATUS "Looking for clang-tidy")
find_program(CLANG_TIDY NAMES clang-tidy clang-tidy-3.9 clang-tidy-3.8 clang-tidy-3.7 clang-tidy-3.6 clang-tidy-3.5)

if(CLANG_TIDY)
    message(STATUS "Looking for clang-tidy - found")

    if(BUILD_EXAMPLES)
        file(GLOB CT_ALL_EXAMPLES examples/*.cpp)
    endif()

    if(BUILD_TESTING)
        file(GLOB CT_ALL_UNIT_TESTS test/t/*/test_*.cpp)
    endif()

    if(BUILD_HEADERS)
        file(GLOB_RECURSE CT_ALL_INCLUDES ${CMAKE_BINARY_DIR}/header_check/osmium__*.cpp)
    endif()

    if(BUILD_BENCHMARKS)
        file(GLOB CT_ALL_BENCHMARKS benchmarks/*.cpp)
    endif()

    if(BUILD_DATA_TESTS)
        file(GLOB CT_ALL_DATA_TESTS test/data-tests/*.cpp)
    endif()

    if(Osmium_DEBUG)
        message(STATUS "Checking example code  : ${CT_ALL_EXAMPLES}")
        message(STATUS "Checking unit test code: ${CT_ALL_UNIT_TESTS}")
        message(STATUS "Checking includes      : ${CT_ALL_INCLUDES}")
        message(STATUS "Checking benchmarks    : ${CT_ALL_BENCHMARKS}")
        message(STATUS "Checking data test code: ${CT_ALL_DATA_TESTS}")
    endif()

    set(CT_CHECK_FILES
        ${CT_ALL_EXAMPLES}
        ${CT_ALL_UNIT_TESTS}
        ${CT_ALL_INCLUDES}
        ${CT_ALL_BENCHMARKS}
        ${CT_ALL_DATA_TESTS})

    # For a list of check options, see:
    # http://clang.llvm.org/extra/clang-tidy/checks/list.html

    list(APPEND CT_CHECKS "cert-*"
                         "-cert-err60-cpp") # even the std lib doesn't do this

    # disabled, because it is slow
#    list(APPEND CT_CHECKS "clang-analyzer-*")

    list(APPEND CT_CHECKS "google-*"
                         "-google-explicit-constructor"
                         "-google-readability-casting"
                         "-google-readability-function")

    list(APPEND CT_CHECKS "llvm-*"
                         "-llvm-include-order")

    list(APPEND CT_CHECKS "misc-*"
                         "-misc-argument-comment")

    list(APPEND CT_CHECKS "modernize-*")

    list(APPEND CT_CHECKS "readability-*"
                         "-readability-identifier-naming"
                         "-readability-named-parameter")

    string(REPLACE ";" "," ALL_CHECKS "${CT_CHECKS}")

    add_custom_target(clang-tidy
        ${CLANG_TIDY}
        -p ${CMAKE_BINARY_DIR}
        -header-filter='include/osmium/.*'
        -checks="${ALL_CHECKS}"
        ${CT_CHECK_FILES}
    )
else()
    message(STATUS "Looking for clang-tidy - not found")
    message(STATUS "  Build target 'clang-tidy' will not be available.")
endif()

#-----------------------------------------------------------------------------
#
#  Installation
#
#  External libraries are only installed if the options are set in case they
#  are installed from somewhere else.
#
#-----------------------------------------------------------------------------
install(DIRECTORY include/osmium DESTINATION include)

if(INSTALL_GDALCPP)
    install(FILES include/gdalcpp.hpp DESTINATION include)
endif()

if(INSTALL_PROTOZERO)
    install(DIRECTORY include/protozero DESTINATION include)
endif()

if(INSTALL_UTFCPP)
    install(FILES include/utf8.h DESTINATION include)
    install(DIRECTORY include/utf8 DESTINATION include)
endif()


#-----------------------------------------------------------------------------
#
#  Packaging
#
#-----------------------------------------------------------------------------

set(CPACK_PACKAGE_VERSION_MAJOR ${LIBOSMIUM_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${LIBOSMIUM_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${LIBOSMIUM_VERSION_PATCH})

if(WIN32)
    set(CPACK_GENERATOR ZIP)
else()
    set(CPACK_GENERATOR TGZ)
endif()

include(CPack)


#-----------------------------------------------------------------------------
#
#  Print warnings at the end
#
#-----------------------------------------------------------------------------
if(BUILD_DATA_TESTS AND OSM_TESTDATA STREQUAL "OSM_TESTDATA-NOTFOUND")
    message("\n========================== WARNING ==========================")
    message("osm-testdata directory not found, data tests were disabled!\n")
    message("You can get it from https://github.com/osmcode/osm-testdata")
    message("Clone it into the same directory libosmium is in")
    message("or set the OSM_TESTDATA cmake variable to its path.")
    message("=============================================================\n")
endif()

#-----------------------------------------------------------------------------
