cmake_minimum_required(VERSION 3.15)

# Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24:
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
    cmake_policy(SET CMP0135 NEW)
endif ()

project(odr LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
option(ODR_TEST "enable tests" OFF)
option(ODR_CLI "enable command line interface" ON)
option(ODR_WITH_HTTP_SERVER "Build the HTTP server (requires cpp-httplib)" ON)
option(ODR_CLANG_TIDY "Run clang-tidy static analysis" OFF)
option(WITH_PDF2HTMLEX "Deprecated: `ODR_WITH_PDF2HTMLEX` instead! Build with pdf2htmlEX" ON)
option(WITH_WVWARE "Deprecated: `ODR_WITH_WVWARE` instead! Build with wvWare" ON)
option(WITH_LIBMAGIC "Deprecated: `ODR_WITH_WVWARE` instead! Build with libmagic" ON)
option(ODR_WITH_PDF2HTMLEX "Build with pdf2htmlEX" "${WITH_PDF2HTMLEX}")
option(ODR_WITH_WVWARE "Build with wvWare" "${WITH_WVWARE}")
option(ODR_WITH_LIBMAGIC "Build with libmagic" "${WITH_LIBMAGIC}")
option(ODR_BUNDLE_ASSETS "Bundle assets during build and install" OFF)
option(ODR_PYTHON "Build Python bindings" OFF)
option(ODR_JNI "Build JNI bindings" OFF)

include(GNUInstallDirs)

# TODO defining global compiler flags seems to be bad practice with conan
# TODO consider using conan profiles
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR
        CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    # using clang or gcc

    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
    # debugging
    #set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG")
    # benchmarking
    #set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-omit-frame-pointer")
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    # using Visual Studio C++

    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
endif ()

# MSVC reads sources in the system codepage by default; force UTF-8 so non-ASCII
# literals (e.g. the PDFDocEncoding table) compile (C2015) and encode correctly.
# GCC/Clang already default to UTF-8.
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

find_package(pugixml REQUIRED)
find_package(miniz REQUIRED)
find_package(cryptopp REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(vincentlaucsb-csv-parser REQUIRED)
find_package(uchardet REQUIRED)
find_package(utf8cpp REQUIRED)
find_package(argon2 REQUIRED)

include(FetchContent)
FetchContent_Declare(odr.js
        URL "https://github.com/opendocument-app/OpenDocument.js/releases/download/v2.0.0/build.zip"
        URL_HASH SHA256=5ccbe1c5bb46d65bc0dccc6c6df6548a571eef5536fcb95f9dcb80add26800b6
)
FetchContent_MakeAvailable(odr.js)

set(PRE_CONFIGURE_FILE "src/odr/internal/git_info.cpp.in")
set(POST_CONFIGURE_FILE "${CMAKE_CURRENT_BINARY_DIR}/src/odr/internal/git_info.cpp")
if (EXISTS "${PROJECT_SOURCE_DIR}/.git")
    include("cmake/git_watcher.cmake")
else ()
    if (NOT DEFINED GIT_HEAD_SHA1)
        set(GIT_HEAD_SHA1 "unknown")
    endif ()
    if (NOT DEFINED GIT_IS_DIRTY)
        set(GIT_IS_DIRTY "false")
    endif ()
    configure_file("${PRE_CONFIGURE_FILE}" "${POST_CONFIGURE_FILE}" @ONLY)
endif ()

set(ODR_SOURCE_FILES
        "src/odr/archive.cpp"
        "src/odr/document.cpp"
        "src/odr/document_element.cpp"
        "src/odr/document_path.cpp"
        "src/odr/exceptions.cpp"
        "src/odr/file.cpp"
        "src/odr/filesystem.cpp"
        "src/odr/global_params.cpp"
        "src/odr/html.cpp"
        "src/odr/logger.cpp"
        "src/odr/odr.cpp"
        "src/odr/quantity.cpp"
        "src/odr/style.cpp"
        "src/odr/table_dimension.cpp"
        "src/odr/table_position.cpp"

        "${CMAKE_CURRENT_BINARY_DIR}/src/odr/internal/git_info.cpp"
        "src/odr/internal/magic.cpp"
        "src/odr/internal/open_strategy.cpp"
        "${CMAKE_CURRENT_BINARY_DIR}/src/odr/internal/project_info.cpp"

        "src/odr/internal/cfb/cfb_archive.cpp"
        "src/odr/internal/cfb/cfb_file.cpp"
        "src/odr/internal/cfb/cfb_impl.cpp"
        "src/odr/internal/cfb/cfb_util.cpp"

        "src/odr/internal/common/document.cpp"
        "src/odr/internal/common/file.cpp"
        "src/odr/internal/common/filesystem.cpp"
        "src/odr/internal/common/image_file.cpp"
        "src/odr/internal/common/path.cpp"
        "src/odr/internal/common/random.cpp"
        "src/odr/internal/common/style.cpp"
        "src/odr/internal/common/table_cursor.cpp"
        "src/odr/table_position.cpp"
        "src/odr/internal/common/table_range.cpp"
        "src/odr/internal/common/temporary_file.cpp"

        "src/odr/internal/crypto/crypto_util.cpp"

        "src/odr/internal/csv/csv_file.cpp"
        "src/odr/internal/csv/csv_util.cpp"

        "src/odr/internal/html/common.cpp"
        "src/odr/internal/html/document.cpp"
        "src/odr/internal/html/document_style.cpp"
        "src/odr/internal/html/document_element.cpp"
        "src/odr/internal/html/filesystem.cpp"
        "src/odr/internal/html/font_file.cpp"
        "src/odr/internal/html/html_service.cpp"
        "src/odr/internal/html/html_writer.cpp"
        "src/odr/internal/html/image_file.cpp"
        "src/odr/internal/html/pdf_file.cpp"
        "src/odr/internal/html/pdf2htmlex_wrapper.hpp"
        "src/odr/internal/html/text_file.cpp"

        "src/odr/internal/json/json_file.cpp"
        "src/odr/internal/json/json_util.cpp"

        "src/odr/internal/odf/odf_crypto.cpp"
        "src/odr/internal/odf/odf_document.cpp"
        "src/odr/internal/odf/odf_element_registry.cpp"
        "src/odr/internal/odf/odf_file.cpp"
        "src/odr/internal/odf/odf_manifest.cpp"
        "src/odr/internal/odf/odf_meta.cpp"
        "src/odr/internal/odf/odf_parser.cpp"
        "src/odr/internal/odf/odf_style.cpp"

        "src/odr/internal/oldms/text/doc_document.cpp"
        "src/odr/internal/oldms/text/doc_element_registry.cpp"
        "src/odr/internal/oldms/text/doc_helper.cpp"
        "src/odr/internal/oldms/text/doc_io.cpp"
        "src/odr/internal/oldms/text/doc_parser.cpp"
        "src/odr/internal/oldms/text/doc_style.cpp"
        "src/odr/internal/oldms/spreadsheet/xls_document.cpp"
        "src/odr/internal/oldms/spreadsheet/xls_element_registry.cpp"
        "src/odr/internal/oldms/spreadsheet/xls_io.cpp"
        "src/odr/internal/oldms/spreadsheet/xls_parser.cpp"
        "src/odr/internal/oldms/spreadsheet/xls_style.cpp"
        "src/odr/internal/oldms/presentation/ppt_document.cpp"
        "src/odr/internal/oldms/presentation/ppt_element_registry.cpp"
        "src/odr/internal/oldms/presentation/ppt_io.cpp"
        "src/odr/internal/oldms/presentation/ppt_parser.cpp"
        "src/odr/internal/oldms/presentation/ppt_style.cpp"
        "src/odr/internal/oldms/oldms_file.cpp"

        "src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp"
        "src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.cpp"
        "src/odr/internal/ooxml/presentation/ooxml_presentation_parser.cpp"
        "src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp"
        "src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.cpp"
        "src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.cpp"
        "src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_style.cpp"
        "src/odr/internal/ooxml/text/ooxml_text_document.cpp"
        "src/odr/internal/ooxml/text/ooxml_text_element_registry.cpp"
        "src/odr/internal/ooxml/text/ooxml_text_parser.cpp"
        "src/odr/internal/ooxml/text/ooxml_text_style.cpp"
        "src/odr/internal/ooxml/ooxml_crypto.cpp"
        "src/odr/internal/ooxml/ooxml_file.cpp"
        "src/odr/internal/ooxml/ooxml_meta.cpp"
        "src/odr/internal/ooxml/ooxml_util.cpp"

        "src/odr/internal/pdf/pdf_afm.cpp"
        "src/odr/internal/pdf/pdf_afm_data.cpp"
        "src/odr/internal/pdf/pdf_cid.cpp"
        "src/odr/internal/pdf/pdf_cid_data.cpp"
        "src/odr/internal/pdf/pdf_cmap.cpp"
        "src/odr/internal/pdf/pdf_cmap_parser.cpp"
        "src/odr/internal/pdf/pdf_color.cpp"
        "src/odr/internal/pdf/pdf_document.cpp"
        "src/odr/internal/pdf/pdf_document_parser.cpp"
        "src/odr/internal/pdf/pdf_encoding.cpp"
        "src/odr/internal/pdf/pdf_encoding_data.cpp"
        "src/odr/internal/pdf/pdf_encryption.cpp"
        "src/odr/internal/pdf/pdf_file.cpp"
        "src/odr/internal/pdf/pdf_file_object.cpp"
        "src/odr/internal/pdf/pdf_file_parser.cpp"
        "src/odr/internal/pdf/pdf_filter.cpp"
        "src/odr/internal/pdf/pdf_function.cpp"
        "src/odr/internal/pdf/pdf_image.cpp"
        "src/odr/internal/pdf/pdf_graphics_operator_parser.cpp"
        "src/odr/internal/pdf/pdf_graphics_state.cpp"
        "src/odr/internal/pdf/pdf_object.cpp"
        "src/odr/internal/pdf/pdf_object_parser.cpp"
        "src/odr/internal/pdf/pdf_page_extractor.cpp"
        "src/odr/internal/pdf/pdf_shading.cpp"

        "src/odr/internal/font/cff_builder.cpp"
        "src/odr/internal/font/cff_font.cpp"
        "src/odr/internal/font/cff_standard_strings.cpp"
        "src/odr/internal/font/cff_transform.cpp"
        "src/odr/internal/font/type1_charstring.cpp"
        "src/odr/internal/font/type1_crypt.cpp"
        "src/odr/internal/font/type1_font.cpp"
        "src/odr/internal/font/type1_transform.cpp"
        "src/odr/internal/font/sfnt_font.cpp"
        "src/odr/internal/font/sfnt_transform.cpp"
        "src/odr/internal/font/font_file.cpp"

        "src/odr/internal/svm/svm_file.cpp"
        "src/odr/internal/svm/svm_format.cpp"
        "src/odr/internal/svm/svm_to_svg.cpp"

        "src/odr/internal/text/text_file.cpp"
        "src/odr/internal/text/text_util.cpp"

        "src/odr/internal/util/byte_util.cpp"
        "src/odr/internal/util/byte_stream_util.cpp"
        "src/odr/internal/util/byte_string.cpp"
        "src/odr/internal/util/document_util.cpp"
        "src/odr/internal/util/file_util.cpp"
        "src/odr/internal/util/hash_util.cpp"
        "src/odr/internal/util/odr_meta_util.cpp"
        "src/odr/internal/util/stream_util.cpp"
        "src/odr/internal/util/string_util.cpp"
        "src/odr/internal/util/xml_util.cpp"

        "src/odr/internal/zip/zip_archive.cpp"
        "src/odr/internal/zip/zip_exceptions.cpp"
        "src/odr/internal/zip/zip_file.cpp"
        "src/odr/internal/zip/zip_util.cpp"
)

add_library(odr ${ODR_SOURCE_FILES})
set_target_properties(odr PROPERTIES OUTPUT_NAME odr)
target_include_directories(odr
        PUBLIC
        src
        ${CMAKE_CURRENT_BINARY_DIR}/src
)
target_link_libraries(odr
        PRIVATE
        pugixml::pugixml
        miniz::miniz
        cryptopp::cryptopp
        nlohmann_json::nlohmann_json
        vincentlaucsb-csv-parser::vincentlaucsb-csv-parser
        uchardet::uchardet
        utf8::cpp
        argon2::argon2
)

set(ODR_BUILD_ODR_DATA_PATH "${CMAKE_CURRENT_BINARY_DIR}/data")
file(COPY "${odr.js_SOURCE_DIR}/" DESTINATION "${ODR_BUILD_ODR_DATA_PATH}")
set(ODR_INSTALL_ODR_DATA_PATH "${CMAKE_INSTALL_DATADIR}")

# Validate an injected data-file path before it is used to bundle assets.
#
# These paths (FONTCONFIG_DATA_PATH, POPPLER_DATA_PATH, ...) are consumed but
# never discovered by CMake — they must be injected from outside via `-D...=`;
# our conanfile.py bridges them from the dependency runenv. A consumer that
# builds odrcore without that bridge left the variable empty, and
# `file(COPY "${empty}/" ...)` degraded to `file(COPY "/" ...)`, i.e. an attempt
# to recursively copy the whole root filesystem (see issue #599). This guard
# fails fast with an actionable message instead of copying "/".
#
#   path  value of the externally-injected path variable (may be empty)
#   name  name of that variable, for the diagnostic
function(odr_require_data_path path name)
    if (NOT path)
        message(FATAL_ERROR
                "ODR_BUNDLE_ASSETS is ON but ${name} is not set. Provide it "
                "explicitly (e.g. -D${name}=<path>) or build through the conan "
                "recipe, or disable bundling with -DODR_BUNDLE_ASSETS=OFF.")
    endif ()
    if (NOT EXISTS "${path}")
        message(FATAL_ERROR "${name} points to a path that does not exist: '${path}'.")
    endif ()
endfunction()

if (ODR_WITH_HTTP_SERVER)
    find_package(httplib REQUIRED)
    target_sources(odr
            PRIVATE
            "src/odr/http_server.cpp"
    )
    target_link_libraries(odr
            PRIVATE
            httplib::httplib
    )
    target_compile_definitions(odr
            PRIVATE
            ODR_WITH_HTTP_SERVER
    )
endif ()
if (ODR_WITH_PDF2HTMLEX)
    find_package(pdf2htmlEX REQUIRED)
    find_package(poppler REQUIRED)
    target_sources(odr
            PRIVATE
            "src/odr/internal/html/pdf2htmlex_wrapper.cpp"
            "src/odr/internal/pdf_poppler/poppler_pdf_file.cpp"
    )
    target_link_libraries(odr
            PRIVATE
            pdf2htmlex::pdf2htmlex
            poppler::poppler
    )
    target_compile_definitions(odr
            PRIVATE
            ODR_WITH_PDF2HTMLEX
    )

    if (ODR_BUNDLE_ASSETS)
        odr_require_data_path("${FONTCONFIG_DATA_PATH}" FONTCONFIG_DATA_PATH)
        odr_require_data_path("${POPPLER_DATA_PATH}" POPPLER_DATA_PATH)
        odr_require_data_path("${PDF2HTMLEX_DATA_PATH}" PDF2HTMLEX_DATA_PATH)

        set(ODR_BUILD_FONTCONFIG_DATA_PATH "${ODR_BUILD_ODR_DATA_PATH}/fontconfig")
        set(ODR_BUILD_POPPLER_DATA_PATH "${ODR_BUILD_ODR_DATA_PATH}/poppler")
        set(ODR_BUILD_PDF2HTMLEX_DATA_PATH "${ODR_BUILD_ODR_DATA_PATH}/pdf2htmlex")

        file(COPY "${FONTCONFIG_DATA_PATH}/" DESTINATION "${ODR_BUILD_FONTCONFIG_DATA_PATH}")
        file(COPY "${POPPLER_DATA_PATH}/" DESTINATION "${ODR_BUILD_POPPLER_DATA_PATH}")
        file(COPY "${PDF2HTMLEX_DATA_PATH}/" DESTINATION "${ODR_BUILD_PDF2HTMLEX_DATA_PATH}")

        set(ODR_INSTALL_FONTCONFIG_DATA_PATH "${ODR_INSTALL_ODR_DATA_PATH}/fontconfig")
        set(ODR_INSTALL_POPPLER_DATA_PATH "${ODR_INSTALL_ODR_DATA_PATH}/poppler")
        set(ODR_INSTALL_PDF2HTMLEX_DATA_PATH "${ODR_INSTALL_ODR_DATA_PATH}/pdf2htmlex")
    else ()
        set(ODR_BUILD_FONTCONFIG_DATA_PATH "${FONTCONFIG_DATA_PATH}")
        set(ODR_BUILD_POPPLER_DATA_PATH "${POPPLER_DATA_PATH}")
        set(ODR_BUILD_PDF2HTMLEX_DATA_PATH "${PDF2HTMLEX_DATA_PATH}")

        set(ODR_INSTALL_FONTCONFIG_DATA_PATH "${FONTCONFIG_DATA_PATH}")
        set(ODR_INSTALL_POPPLER_DATA_PATH "${POPPLER_DATA_PATH}")
        set(ODR_INSTALL_PDF2HTMLEX_DATA_PATH "${PDF2HTMLEX_DATA_PATH}")
    endif ()
endif ()
if (ODR_WITH_WVWARE)
    find_package(wvware REQUIRED)
    target_sources(odr
            PRIVATE
            "src/odr/internal/html/wvware_wrapper.cpp"
            "src/odr/internal/oldms_wvware/wvware_oldms_file.cpp"
    )
    target_link_libraries(odr
            PRIVATE
            wvware::wvware
    )
    target_compile_definitions(odr
            PRIVATE
            ODR_WITH_WVWARE
    )
endif ()
if (ODR_WITH_LIBMAGIC)
    find_package(libmagic REQUIRED)
    target_sources(odr
            PRIVATE
            "src/odr/internal/libmagic/libmagic.cpp"
    )
    target_link_libraries(odr
            PRIVATE
            libmagic::libmagic
    )
    target_compile_definitions(odr
            PRIVATE
            ODR_WITH_LIBMAGIC
    )

    if (ODR_BUNDLE_ASSETS)
        odr_require_data_path("${LIBMAGIC_DATABASE_PATH}" LIBMAGIC_DATABASE_PATH)

        set(ODR_BUILD_LIBMAGIC_DATABASE_PATH "${ODR_BUILD_ODR_DATA_PATH}/magic.mgc")

        file(COPY_FILE "${LIBMAGIC_DATABASE_PATH}" "${ODR_BUILD_LIBMAGIC_DATABASE_PATH}")

        set(ODR_INSTALL_LIBMAGIC_DATABASE_PATH "${ODR_INSTALL_ODR_DATA_PATH}/magic.mgc")
    else ()
        set(ODR_BUILD_LIBMAGIC_DATABASE_PATH "${LIBMAGIC_DATABASE_PATH}")

        set(ODR_INSTALL_LIBMAGIC_DATABASE_PATH "${LIBMAGIC_DATABASE_PATH}")
    endif ()
endif ()

configure_file("src/odr/internal/project_info.cpp.in" "src/odr/internal/project_info.cpp")

if (EXISTS "${PROJECT_SOURCE_DIR}/.git")
    add_dependencies(odr check_git)
endif ()

if (ODR_CLI)
    add_subdirectory("cli")
endif ()

if (ODR_PYTHON)
    add_subdirectory("python")
endif ()

if (ODR_JNI)
    add_subdirectory("jni")
endif ()

if (ODR_TEST)
    add_subdirectory("test")
endif ()

if (ODR_CLANG_TIDY)
    add_subdirectory("static_analysis/clang-tidy")
endif ()

if (NOT ODR_WITH_HTTP_SERVER)
    # Don't ship the public header for an API that wasn't built.
    set(ODR_HEADER_EXCLUDE PATTERN "http_server.hpp" EXCLUDE)
endif ()
install(
        DIRECTORY src/ "${CMAKE_CURRENT_BINARY_DIR}/src/"
        DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
        FILES_MATCHING PATTERN "*.hpp"
        ${ODR_HEADER_EXCLUDE}
)
set(ODR_INSTALL_TARGETS odr)
if (ODR_CLI)
    list(APPEND ODR_INSTALL_TARGETS meta translate back_translate)
endif ()
install(
        TARGETS ${ODR_INSTALL_TARGETS}
        RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
        BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}"
        LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
)
install(
        DIRECTORY "${ODR_BUILD_ODR_DATA_PATH}/"
        DESTINATION "${ODR_INSTALL_ODR_DATA_PATH}"
)
