#  :copyright: (c) 2017 Alex Huszagh.
#  :license: FreeBSD, see LICENSE.txt for more details.

# Description
# ===========
#
#   Use:
#       Move to a custom directory, ideally out of source, and
#       type `cmake $LXW_SOURCE $FLAGS`, where `LXW_SOURCE` is the
#       path to the libxlsxwriter project, and `FLAGS` are custom
#       flags to pass to the compiler.
#
#   Example:
#       For example, in the project directory, to build libxlsxwriter
#       and the unittests in release mode, type:
#           mkdir build && cd build
#           cmake .. -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release
#           cmake --build . --config Release
#           ctest -C Release -V
#           cmake --build . --config Release --target install
#
#       If using a Makefile generator, you may use the simpler
#           mkdir build && cd build
#           cmake .. -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release
#           make
#           make test
#           make install
#
#   Flags:
#       ZLIB_ROOT
#           The ZLIB root directory can be specified either through
#           an environment variable (`export ZLIB_ROOT=/usr/include`)
#           or using a flag with CMake (`-DZLIB_ROOT:STRING=/usr/include`).
#           This sets the preferred search path for the ZLIB installation.
#
#       BUILD_TESTS
#           Build unittests (default off). To build the unittests,
#           pass `-DBUILD_TESTS=ON` during configuration.
#
#       BUILD_EXAMPLES
#           Build example files (default off). To build the examples,
#           pass `-DBUILD_EXAMPLES=ON` during configuration.
#
#       BUILD_FUZZERS
#           Build fuzz harnesses (default off). To build the harnesses,
#           pass `-DBUILD_FUZZERS=ON` during configuration.
#
#       USE_STANDARD_TMPFILE
#           Use the standard tmpfile() function (default off). To enable
#           the standard tmpfile, pass `-DUSE_STANDARD_TMPFILE=ON`
#           during configuration. This may produce bugs while cross-
#           compiling or using MinGW/MSYS.
#
#       USE_DTOA_LIBRARY
#           Use the third party emyg_dtoa() library (default off). The
#           emyg_dtoa() library is used to avoid sprintf double issues with
#           different locale settings. To enable this library, pass
#           `-DUSE_DTOA_LIBRARY=ON` during configuration.
#
#       USE_NO_MD5
#           Compile without third party MD5 support. This will turn off the
#           functionality of avoiding duplicate image files in the output xlsx
#           file. To enable this option pass `-DUSE_NO_MD5=ON` during
#           configuration.
#
#       USE_OPENSSL_MD5 Compile with OpenSSL MD5 support. This will link
#           against libcrypto for MD5 support rather than using the local MD5
#           support. MD5 support is required to avoid duplicate image files in
#           the output xlsx file. To enable this option pass
#           `-DUSE_OPENSSL_MD5=ON` during configuration.
#
#       USE_STATIC_MSVC_RUNTIME
#           Use the static msvc runtime library when compiling with msvc (default off)
#           To enable, pass `-DUSE_STATIC_MSVC_RUNTIME` during configuration.
#
#   Toolchains:
#       On multiarch Linux systems, which can build and run multiple
#       binary targets on the same system, we include an `i686-toolchain`
#       file to enable building i686 (x86 32-bit) targets on x86_64 systems.
#       To use the i686 toolchain, pass the `-DCMAKE_TOOLCHAIN_FILE` option
#       during CMake configuration. For example, from the build directory,
#       you would use:
#           cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/i686-toolchain.cmake
#
#   CMake Options:
#       CMake sets debug and release builds with the `CMAKE_BUILD_TYPE`
#       option, which can be set as a flag during configuration.
#       To build in release mode, pass `-DCMAKE_BUILD_TYPE=Release`
#       during configuration.
#
#       CMake sets the creation of static and shared libraries with the
#       `BUILD_SHARED_LIBS` option, which can be set as a flag during
#       configuration. To build a static library, pass
#       `-DBUILD_SHARED_LIBS=OFF` during configuration.
#
#   Generators:
#       CMake also supports custom build generators, such as MakeFiles,
#       Ninja, Visual Studio, and XCode. For example, to generate
#       a Visual Studio solution, configure with:
#           cmake .. -G "Visual Studio 14 2015 Win64"
#
#       For more information on using generators, see:
#           https://cmake.org/cmake/help/v3.0/manual/cmake-generators.7.html
#

set(CMAKE_LEGACY_CYGWIN_WIN32 1)
if(MSVC)
    cmake_minimum_required(VERSION 3.4)
else()
    cmake_minimum_required(VERSION 3.1)
endif()

SET(XLSX_PROJECT_NAME "xlsxwriter" CACHE STRING "Optional project and binary name")
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
project(${XLSX_PROJECT_NAME} C)
enable_testing()

# POLICY
# ------

# The use of the word ZLIB_ROOT should still work prior to "3.12.0",
# just it's been generalized for all packages now. Just set the policy
# to new, so we use it, and it will be used prior to 3.12 anyway.
if(${CMAKE_VERSION} VERSION_GREATER "3.12" OR ${CMAKE_VERSION} VERSION_EQUAL "3.12")
    cmake_policy(SET CMP0074 NEW)
endif()

# OPTIONS
# -------
SET(ZLIB_ROOT "" CACHE STRING "Optional root for the ZLIB installation")

option(BUILD_TESTS "Build libxlsxwriter tests" OFF)
option(BUILD_EXAMPLES "Build libxlsxwriter examples" OFF)
option(BUILD_FUZZERS "Build harness(es) for fuzzing" OFF)
option(USE_SYSTEM_MINIZIP "Use system minizip installation" OFF)
option(USE_STANDARD_TMPFILE "Use the C standard library's tmpfile()" OFF)
option(USE_NO_MD5 "Build libxlsxwriter without third party MD5 lib" OFF)
option(USE_OPENSSL_MD5 "Build libxlsxwriter with the OpenSSL MD5 lib" OFF)
option(USE_MEM_FILE "Use fmemopen()/open_memstream() in place of temporary files" OFF)
option(IOAPI_NO_64 "Disable 64-bit filesystem support" OFF)
option(USE_DTOA_LIBRARY "Use the locale independent third party Milo Yip DTOA library" OFF)

if(MSVC)
    option(USE_STATIC_MSVC_RUNTIME "Use the static runtime library" OFF)
endif()

if(DEFINED ENV{${ZLIB_ROOT}})
    set(ZLIB_ROOT $ENV{ZLIB_ROOT})
endif()

if(IOAPI_NO_64)
    list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS IOAPI_NO_64=1)
endif()

# CONFIGURATIONS
# --------------
if(USE_SYSTEM_MINIZIP)
    list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS USE_SYSTEM_MINIZIP)
endif()

if(USE_STANDARD_TMPFILE)
    list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS USE_STANDARD_TMPFILE)
endif()

if(NOT USE_OPENSSL_MD5 AND USE_NO_MD5)
    list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS USE_NO_MD5)
endif()

if(USE_OPENSSL_MD5)
    list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS USE_OPENSSL_MD5)
    if(NOT MSVC)
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations")
    endif()
endif()

if(USE_MEM_FILE OR USE_FMEMOPEN)
    list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS USE_FMEMOPEN)
endif()

if(USE_DTOA_LIBRARY)
    list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS USE_DTOA_LIBRARY)
endif()

if(NOT BUILD_SHARED_LIBS)
    if(UNIX)
        set(CMAKE_POSITION_INDEPENDENT_CODE ON)
    elseif(MINGW OR MSYS)
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static -static-libgcc -Wno-char-subscripts -Wno-long-long")
        list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS USE_FILE32API)
    elseif(MSVC)
        set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Fd\"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pdb\"")
        set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Ox /Zi /Fd\"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pdb\"")
        set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} /Zi /Fd\"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pdb\"")
        set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} /Fd\"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pdb\"")
    endif()
endif()

if(MSVC AND USE_STATIC_MSVC_RUNTIME)
    foreach(flag_var CMAKE_C_FLAGS
                     CMAKE_C_FLAGS_DEBUG
                     CMAKE_C_FLAGS_RELEASE
                     CMAKE_C_FLAGS_MINSIZEREL
                     CMAKE_C_FLAGS_RELWITHDEBINFO)
        if(${flag_var} MATCHES "/MD")
            string(REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
        endif()
    endforeach()
endif()

# Configure pkg-config
find_package(PkgConfig)
file(READ "include/xlsxwriter.h" ver)

string(REGEX MATCH "LXW_VERSION \"([^\"]+)\"" _ ${ver})
set(VERSION ${CMAKE_MATCH_1})
string(REGEX MATCH "LXW_SOVERSION \"([^\"]+)\"" _ ${ver})
set(SOVERSION ${CMAKE_MATCH_1})
set(PREFIX ${CMAKE_INSTALL_PREFIX})

configure_file(dev/release/pkg-config.txt xlsxwriter.pc @ONLY)

# INCLUDES
# --------
enable_language(CXX)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)

# ZLIB
if(PKG_CONFIG_FOUND)
    pkg_check_modules(ZLIB zlib)
    list(APPEND LXW_PRIVATE_INCLUDE_DIRS ${ZLIB_INCLUDE_DIRS})
else(NOT ZLIB_FOUND)
    find_package(ZLIB "1.0" REQUIRED)
    list(APPEND LXW_PRIVATE_INCLUDE_DIRS ${ZLIB_INCLUDE_DIRS})
    message("zlib version: " ${ZLIB_VERSION})
endif()

# MINIZIP
if(USE_SYSTEM_MINIZIP)
    if(PKG_CONFIG_FOUND)
        pkg_check_modules(MINIZIP minizip)
        list(APPEND LXW_PRIVATE_INCLUDE_DIRS ${MINIZIP_INCLUDE_DIRS}/..)
    else(NOT MINIZIP_FOUND)
        find_package(MINIZIP "1.0" REQUIRED)
        list(APPEND LXW_PRIVATE_INCLUDE_DIRS ${MINIZIP_INCLUDE_DIRS})
    endif()
endif()

# LIBRARY
# -------
list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS NOCRYPT NOUNCRYPT)

# Ensure CRT Secure warnings are disabled
if(MSVC)
    list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS _CRT_SECURE_NO_WARNINGS)
endif()

# Ensure "TESTING" macro is defined if building tests
if(BUILD_TESTS)
    list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS TESTING)
endif()

# Define "LXW_BIG_ENDIAN" macro on big-endian architectures
include(TestBigEndian)
TEST_BIG_ENDIAN(LXW_TARGET_BIG_ENDIAN)
if(LXW_TARGET_BIG_ENDIAN)
    list(APPEND LXW_PRIVATE_COMPILE_DEFINITIONS LXW_BIG_ENDIAN)
endif()

file(GLOB LXW_SOURCES src/*.c)
file(GLOB_RECURSE LXW_HEADERS RELATIVE include *.h)

if(NOT USE_SYSTEM_MINIZIP)
    list(APPEND LXW_SOURCES third_party/minizip/ioapi.c third_party/minizip/zip.c)
    if(MSVC)
        list(APPEND LXW_SOURCES third_party/minizip/iowin32.c)
    endif()
endif()

if (NOT USE_STANDARD_TMPFILE)
    list(APPEND LXW_SOURCES third_party/tmpfileplus/tmpfileplus.c)
endif()

if(NOT USE_OPENSSL_MD5 AND NOT USE_NO_MD5)
    list(APPEND LXW_SOURCES third_party/md5/md5.c)
endif()

if(USE_OPENSSL_MD5)
    if(PKG_CONFIG_FOUND)
        pkg_check_modules(LIBCRYPTO libcrypto)
        include_directories(${LIBCRYPTO_INCLUDE_DIRS})
    else(NOT LIBCRYPTO_FOUND)
        find_package(OpenSSL REQUIRED)
        include_directories(${OPENSSL_INCLUDE_DIR})
        message(STATUS "OpenSSL version: ${OPENSSL_VERSION}")
    endif()
endif()

if (USE_DTOA_LIBRARY)
    list(APPEND LXW_SOURCES third_party/dtoa/emyg_dtoa.c)
endif()

set(LXW_PROJECT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(LXW_LIB_DIR "${LXW_PROJECT_DIR}/lib")
add_library(${PROJECT_NAME} "")

set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${SOVERSION})
target_sources(${PROJECT_NAME}
    PRIVATE ${LXW_SOURCES}
    PUBLIC ${LXW_HEADERS}
)
if(ZLIB_LDFLAGS)
    target_link_libraries(${PROJECT_NAME} LINK_PUBLIC ${ZLIB_LDFLAGS})
else()
    target_link_libraries(${PROJECT_NAME} LINK_PUBLIC ${ZLIB_LIBRARIES})
endif()
if(MINIZIP_LDFLAGS)
    target_link_libraries(${PROJECT_NAME} LINK_PUBLIC ${MINIZIP_LDFLAGS})
else()
    target_link_libraries(${PROJECT_NAME} LINK_PUBLIC ${MINIZIP_LIBRARIES})
endif()
if(LIBCRYPTO_LDFLAGS)
    target_link_libraries(${PROJECT_NAME} LINK_PUBLIC ${LIBCRYPTO_LDFLAGS})
else()
    target_link_libraries(${PROJECT_NAME} LINK_PUBLIC ${LIB_CRYPTO} ${OPENSSL_CRYPTO_LIBRARY})
endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE ${LXW_PRIVATE_COMPILE_DEFINITIONS})

# /utf-8 needs VS2015 Update 2 or above.
# In CMake 3.7 and above, we can use (MSVC_VERSION GREATER_EQUAL 1900) here.
if(MSVC AND NOT (MSVC_VERSION LESS 1900))
    target_compile_options(${PROJECT_NAME} PRIVATE /utf-8)
endif()

if (WINDOWSSTORE)
  target_compile_definitions(${PROJECT_NAME} PRIVATE -DIOWIN32_USING_WINRT_API)
endif()

target_include_directories(${PROJECT_NAME}
    PRIVATE ${LXW_PRIVATE_INCLUDE_DIRS}
    PUBLIC include include/xlsxwriter
)

# TESTS
# -----

# Create test and runner.
#
# Args:
#   sources       Name of variable holding source files
#   target        Test name
#

macro(CreateTest sources target)
    set(output_name xlsxwriter_${target})
    set(dependencies ${output_name})

    add_executable(${output_name} ${${sources}})
    target_link_libraries(${output_name} ${PROJECT_NAME})
    target_compile_definitions(${output_name} PRIVATE TESTING COLOR_OK)
    add_test(NAME ${output_name}
        COMMAND ${output_name}
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    )
endmacro(CreateTest)

file(GLOB LXW_UTILITY_SOURCES test/unit/utility/test*.c)
file(GLOB LXW_XMLWRITER_SOURCES test/unit/xmlwriter/test*.c)
file(GLOB LXW_WORKSHEET_SOURCES test/unit/worksheet/test*.c)
file(GLOB LXW_SST_SOURCES test/unit/sst/test*.c)
file(GLOB LXW_WORKBOOK_SOURCES test/unit/workbook/test*.c)
file(GLOB LXW_APP_SOURCES test/unit/app/test*.c)
file(GLOB LXW_CONTENTTYPES_SOURCES test/unit/content_types/test*.c)
file(GLOB LXW_CORE_SOURCES test/unit/core/test*.c)
file(GLOB LXW_RELATIONSHIPS_SOURCES test/unit/relationships/test*.c)
file(GLOB LXW_FORMAT_SOURCES test/unit/format/test*.c)
file(GLOB LXW_STYLES_SOURCES test/unit/styles/test*.c)
file(GLOB LXW_DRAWING_SOURCES test/unit/drawing/test*.c)
file(GLOB LXW_CHART_SOURCES test/unit/chart/test*.c)
file(GLOB LXW_CUSTOM_SOURCES test/unit/custom/test*.c)
file(GLOB LXW_FUNCTIONAL_SOURCES test/functional/src/*.c)

if(NOT MSVC)
# Skip unit tests on Windows since ctest.h doesn't support it.
set(LXW_UNIT_SOURCES
    test/unit/test_all.c
    ${LXW_UTILITY_SOURCES}
    ${LXW_XMLWRITER_SOURCES}
    ${LXW_WORKSHEET_SOURCES}
    ${LXW_SST_SOURCES}
    ${LXW_WORKBOOK_SOURCES}
    ${LXW_APP_SOURCES}
    ${LXW_CONTENTTYPES_SOURCES}
    ${LXW_CORE_SOURCES}
    ${LXW_RELATIONSHIPS_SOURCES}
    ${LXW_FORMAT_SOURCES}
    ${LXW_STYLES_SOURCES}
    ${LXW_DRAWING_SOURCES}
    ${LXW_CHART_SOURCES}
    ${LXW_CUSTOM_SOURCES}
)
else()
set(LXW_UNIT_SOURCES
    test/cpp/test_compilation.cpp
)
endif()

if(BUILD_TESTS)
    # unit tests
    CreateTest(LXW_UNIT_SOURCES unit)

    # functional tests
    find_package(Python COMPONENTS Interpreter REQUIRED)
    find_program(Pytest_EXECUTABLE NAMES pytest)

    if (NOT Pytest_EXECUTABLE)
        message("Please install the Python pytest library to run functional tests:")
        message("    pip install pytest\n")
    endif()

    foreach(source ${LXW_FUNCTIONAL_SOURCES})
       get_filename_component(basename ${source} NAME_WE)
       add_executable(${basename} ${source})
       target_link_libraries(${basename} xlsxwriter)
       set_target_properties(${basename} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "test/functional/src")
    endforeach(source)

    add_custom_command(TARGET xlsxwriter_unit POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/test/functional test/functional
    )

    if(USE_NO_MD5)
        add_test(NAME functional
            COMMAND pytest -v test/functional -m "not skipif"
            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
        )
    else()
        add_test(NAME functional
            COMMAND pytest -v test/functional
            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
        )
    endif()

endif()

# EXAMPLES
# --------
file(GLOB LXW_EXAMPLE_SOURCES examples/*.c)

if(BUILD_EXAMPLES)
    foreach(source ${LXW_EXAMPLE_SOURCES})
        get_filename_component(basename ${source} NAME_WE)
        add_executable(${basename} ${source})
        target_link_libraries(${basename} ${PROJECT_NAME})
        set_target_properties(${basename} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "examples")
    endforeach(source)
endif()

# FUZZING
# -------
if (BUILD_FUZZERS AND DEFINED ENV{LIB_FUZZING_ENGINE})
    add_subdirectory(dev/fuzzing)
endif()

# INSTALL
# -------
include(GNUInstallDirs)

install(TARGETS ${PROJECT_NAME}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(FILES include/xlsxwriter.h DESTINATION include)
install(DIRECTORY include/xlsxwriter
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
    FILES_MATCHING PATTERN "*.h"
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/xlsxwriter.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
