cmake_minimum_required(VERSION 3.24)
project(DocView VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
include(CTest)
if(MSVC)
    add_compile_options(/utf-8 /permissive- /Zc:__cplusplus)
endif()
if(WIN32 AND (NOT MSVC OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8))
    message(FATAL_ERROR "DocView on Windows requires a 64-bit MSVC-compatible toolchain and matching Qt WebEngine.")
endif()

find_package(Qt6 6.11.2 EXACT REQUIRED COMPONENTS Core Gui Network Qml Quick QuickControls2 WebEngineQuick Xml LinguistTools)
if(WIN32)
    find_package(libzip 1.11 CONFIG REQUIRED)
    find_package(tomlplusplus 3.4 CONFIG REQUIRED)
    set(DOCVIEW_ZIP_TARGET libzip::zip)
    set(DOCVIEW_TOML_TARGET tomlplusplus::tomlplusplus)
else()
    find_package(PkgConfig REQUIRED)
    pkg_check_modules(LIBZIP REQUIRED IMPORTED_TARGET libzip>=1.11)
    pkg_check_modules(TOML REQUIRED IMPORTED_TARGET tomlplusplus>=3.4)
    pkg_check_modules(SECCOMP REQUIRED IMPORTED_TARGET libseccomp>=2.5)
    pkg_check_modules(FONTCONFIG REQUIRED IMPORTED_TARGET fontconfig>=2.13)
    set(DOCVIEW_ZIP_TARGET PkgConfig::LIBZIP)
    set(DOCVIEW_TOML_TARGET PkgConfig::TOML)
endif()
include(cmake/Pdfium.cmake)
find_package(qpdf 12.4.1 EXACT CONFIG REQUIRED)

add_library(docview_common
    src/common/ipc.cpp src/common/contracts.cpp src/common/sandbox.cpp
    src/common/worker_process.cpp src/common/session_storage.cpp src/common/single_instance.cpp
    src/common/windows_sandbox.cpp src/common/windows_pipe.cpp src/common/windows_runtime_staging.cpp
    src/common/worker_runtime.cpp
    src/common/font_contract.cpp src/common/worker_font_client.cpp
    src/web/resource_policy.cpp src/web/renderer_watchdog.cpp)
target_include_directories(docview_common PUBLIC src)
target_link_libraries(docview_common PUBLIC Qt6::Core Qt6::Network)
if(WIN32)
    target_compile_definitions(docview_common PUBLIC _WIN32_WINNT=0x0A00 NOMINMAX WIN32_LEAN_AND_MEAN)
    target_link_libraries(docview_common PRIVATE userenv advapi32 kernel32 shell32 ole32 psapi)
else()
    target_link_libraries(docview_common PRIVATE PkgConfig::SECCOMP)
endif()

add_library(docview_core src/core/config.cpp src/core/input.cpp src/core/state.cpp src/core/types.cpp
    src/common/memory_pressure.cpp)
target_include_directories(docview_core PUBLIC src)
target_link_libraries(docview_core PUBLIC Qt6::Core ${DOCVIEW_TOML_TARGET})
if(WIN32)
    target_compile_definitions(docview_core PRIVATE _WIN32_WINNT=0x0A00 NOMINMAX WIN32_LEAN_AND_MEAN)
    target_link_libraries(docview_core PRIVATE shell32 ole32)
endif()
add_library(docview_pdf src/pdf/pdf_document.cpp src/pdf/link_border_reader.cpp src/pdf/pdf_metadata_context.cpp src/pdf/structure_reader.cpp)
target_sources(docview_pdf PRIVATE src/pdf/system_font_adapter.cpp)
target_include_directories(docview_pdf PUBLIC src)
target_link_libraries(docview_pdf PUBLIC Qt6::Core PDFium::PDFium docview_common)
target_link_libraries(docview_pdf PRIVATE qpdf::libqpdf)
add_library(docview_font_broker src/broker/font_broker.cpp src/broker/font_backend.cpp)
target_include_directories(docview_font_broker PUBLIC src)
target_link_libraries(docview_font_broker PUBLIC docview_common Qt6::Core)
if(WIN32)
    target_sources(docview_font_broker PRIVATE src/broker/font_backend_win.cpp)
    target_link_libraries(docview_font_broker PRIVATE gdi32)
else()
    target_sources(docview_font_broker PRIVATE src/broker/font_backend_linux.cpp)
    target_link_libraries(docview_font_broker PRIVATE PkgConfig::FONTCONFIG)
endif()
add_executable(docview-pdf-worker src/pdf/main.cpp)
target_link_libraries(docview-pdf-worker PRIVATE docview_pdf docview_common)
add_library(docview_archive src/archive/archive.cpp)
target_include_directories(docview_archive PUBLIC src)
target_link_libraries(docview_archive PUBLIC Qt6::Core Qt6::Xml ${DOCVIEW_ZIP_TARGET})
add_executable(docview-archive-worker src/archive/main.cpp)
target_link_libraries(docview-archive-worker PRIVATE docview_archive docview_common)

set(DOCVIEW_SHELL_SOURCES src/app/controller.cpp src/app/pdf_canvas.cpp src/web/web_profile.cpp resources.qrc)
qt_add_executable(docview src/app/main.cpp src/app/benchmark.cpp src/app/translations.cpp ${DOCVIEW_SHELL_SOURCES})
target_link_libraries(docview PRIVATE docview_core docview_common docview_font_broker Qt6::Gui Qt6::Quick Qt6::Qml Qt6::QuickControls2 Qt6::WebEngineQuick)
add_dependencies(docview docview-pdf-worker docview-archive-worker)
qt_add_translations(TARGETS docview
    SOURCE_TARGETS docview docview_core docview_common docview_pdf docview_archive
                   docview_font_broker docview-pdf-worker docview-archive-worker
    SOURCES qml/Main.qml qml/WebPane.qml
    TS_FILES resources/i18n/docview_ja.ts
    LUPDATE_OPTIONS -locations relative -no-obsolete
    LUPDATE_TARGET docview_lupdate
    LRELEASE_TARGET docview_lrelease
    RESOURCE_PREFIX "/i18n")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    # Register candidate revalidation before any install rule copies a binary.
    include(cmake/PdfiumSupplementalNotices.cmake)
endif()
if(UNIX)
    set_target_properties(docview-pdf-worker PROPERTIES INSTALL_RPATH "$ORIGIN/../lib")
    install(FILES "${DOCVIEW_PDFIUM_LIBRARY}" DESTINATION lib)
endif()
install(TARGETS docview docview-pdf-worker docview-archive-worker RUNTIME DESTINATION bin)
install(FILES resources/config.example.toml DESTINATION share/doc/docview)
install(DIRECTORY resources/licenses/qpdf DESTINATION share/doc/docview/licenses)
install(FILES "${DOCVIEW_PDFIUM_ROOT}/LICENSE" DESTINATION share/doc/docview/pdfium)
install(DIRECTORY "${DOCVIEW_PDFIUM_ROOT}/licenses/" DESTINATION share/doc/docview/pdfium/licenses)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    install(FILES docs/LINUX-DEVELOPMENT-PACKAGE.md DESTINATION share/doc/docview)
    install(FILES resources/licenses/README.md DESTINATION share/doc/docview RENAME THIRD-PARTY-SCOPE.md)
    include(cmake/LinuxDevelopmentPackage.cmake)
endif()
if(WIN32)
    include(cmake/WindowsDeployment.cmake)
    foreach(worker docview-pdf-worker docview-archive-worker)
        docview_configure_windows_worker(${worker})
        docview_install_windows_worker(${worker})
    endforeach()
    docview_install_windows_gui(docview)
endif()

if(BUILD_TESTING)
    find_package(Qt6 REQUIRED COMPONENTS Test QuickTest)
    find_package(Python3 REQUIRED COMPONENTS Interpreter)
    qt_add_executable(test_translations tests/test_translations.cpp src/app/translations.cpp)
    target_link_libraries(test_translations PRIVATE docview_core Qt6::Test Qt6::Quick Qt6::Qml Qt6::QuickControls2)
    qt_add_resources(test_translations test_translations_catalog
        PREFIX "/i18n" BASE "${CMAKE_CURRENT_BINARY_DIR}"
        FILES "${CMAKE_CURRENT_BINARY_DIR}/docview_ja.qm")
    add_dependencies(test_translations docview_lrelease)
    add_test(NAME translations COMMAND test_translations)
    set_tests_properties(translations PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;QT_QUICK_BACKEND=software;LANG=C.UTF-8")
    add_test(NAME runtime_manifest COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_runtime_manifest.py" -v)
    add_test(NAME benchmark_runner COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_benchmark_runner.py" -v)
    if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
        add_test(NAME linux_package COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_linux_package.py" -v)
        add_test(NAME dependency_provenance COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_dependency_provenance_qt.py" -v)
        add_test(NAME ubuntu_runtime_inventory COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_ubuntu_validation_runtime.py" -v)
        add_test(NAME pdfium_candidate_staging COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_stage_pdfium_candidate.py" -v)
        add_test(NAME pdfium_candidate_integration COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_pdfium_candidate_integration.py" -v)
    endif()
    foreach(name core archive pdf security instance renderer_watchdog worker_process runtime_staging font_contract font_broker system_font_adapter memory_pressure)
        add_executable(test_${name} tests/test_${name}.cpp)
        target_link_libraries(test_${name} PRIVATE docview_common Qt6::Test)
        if(TARGET docview_${name})
            target_link_libraries(test_${name} PRIVATE docview_${name})
        endif()
        add_test(NAME ${name} COMMAND test_${name})
    endforeach()
    target_link_libraries(test_memory_pressure PRIVATE docview_core)
    target_link_libraries(test_pdf PRIVATE docview_font_broker qpdf::libqpdf)
    target_link_libraries(test_system_font_adapter PRIVATE docview_pdf)
    add_executable(pdf-worker-evidence tests/render_pdf_worker.cpp)
    target_link_libraries(pdf-worker-evidence PRIVATE docview_common docview_font_broker Qt6::Gui)
    add_dependencies(pdf-worker-evidence docview-pdf-worker)
    if(WIN32)
        foreach(name windows_pipe windows_resources windows_worker)
            add_executable(test_${name} tests/test_${name}.cpp)
            target_link_libraries(test_${name} PRIVATE docview_common Qt6::Test)
            add_test(NAME ${name} COMMAND test_${name})
        endforeach()
        add_executable(docview-windows-worker-probe tests/windows_worker_probe.cpp)
        target_link_libraries(docview-windows-worker-probe PRIVATE docview_common ws2_32 userenv advapi32 ole32)
        docview_configure_windows_worker(docview-windows-worker-probe)
        add_dependencies(test_windows_worker docview-windows-worker-probe)
        set_tests_properties(windows_worker PROPERTIES TIMEOUT 120)
    endif()
    add_dependencies(test_pdf docview-pdf-worker)
    add_dependencies(test_archive docview-archive-worker)
    if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
        # Exercises the actual 10/30/120-second timers without shortening the
        # production budgets. Run explicitly, or opt in to the long CTest case.
        add_executable(test_worker_deadlines tests/test_worker_deadlines.cpp)
        target_link_libraries(test_worker_deadlines PRIVATE docview_common Qt6::Test)
        option(DOCVIEW_ENABLE_LONG_TESTS "Register real-time worker watchdog tests" OFF)
        if(DOCVIEW_ENABLE_LONG_TESTS)
            add_test(NAME worker_deadlines COMMAND test_worker_deadlines)
            set_tests_properties(worker_deadlines PROPERTIES TIMEOUT 150 LABELS "long")
        endif()
    endif()
    add_executable(test_pdf_canvas tests/test_pdf_canvas.cpp src/app/pdf_canvas.cpp)
    target_link_libraries(test_pdf_canvas PRIVATE docview_pdf docview_common docview_font_broker Qt6::Test Qt6::Quick Qt6::Gui)
    add_dependencies(test_pdf_canvas docview-pdf-worker)
    add_test(NAME pdf_canvas COMMAND test_pdf_canvas)
    set_tests_properties(pdf_canvas PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
    add_test(NAME pdf_canvas_hidpi COMMAND test_pdf_canvas annotationFlagsHitTargetsMatchRenderedPixels sceneGraphPresentsPdfPixelsAndReplacesOldPage)
    set_tests_properties(pdf_canvas_hidpi PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;QT_SCALE_FACTOR=2")
    qt_add_executable(test_app tests/test_app.cpp ${DOCVIEW_SHELL_SOURCES})
    target_link_libraries(test_app PRIVATE docview_core docview_common docview_font_broker Qt6::Gui Qt6::Quick Qt6::Qml Qt6::QuickControls2 Qt6::WebEngineQuick Qt6::Test ${DOCVIEW_ZIP_TARGET})
    target_compile_definitions(test_app PRIVATE DOCVIEW_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
    add_dependencies(test_app docview-pdf-worker docview-archive-worker)
    add_test(NAME app COMMAND test_app)
    qt_add_executable(test_web_qml tests/test_web_qml.cpp src/web/web_profile.cpp resources.qrc)
    target_link_libraries(test_web_qml PRIVATE docview_common Qt6::Gui Qt6::Quick Qt6::Qml Qt6::WebEngineQuick Qt6::QuickTest Qt6::Test)
    add_test(NAME web COMMAND test_web_qml)
    set_tests_properties(app web PROPERTIES TIMEOUT 90)
endif()
