cmake_minimum_required(VERSION 3.24)

project(window_renderer_test VERSION 0.1.0 LANGUAGES C CXX)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
    set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE)
    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel)
endif()

option(WR_FETCH_MISSING_DEPENDENCIES
    "Download and build dependencies (SDL3, Vulkan, Slang) that are not installed" ON)
option(WR_COMPILE_SHADERS
    "Compile the Slang shader during the build (requires slangc)" ON)

include(FetchContent)

# ---------------------------------------------------------------------------
# SDL3
# ---------------------------------------------------------------------------
find_package(SDL3 3.2 CONFIG QUIET)

if(NOT SDL3_FOUND)
    if(NOT WR_FETCH_MISSING_DEPENDENCIES)
        message(FATAL_ERROR
            "SDL3 was not found. Install SDL3, or configure with "
            "-DWR_FETCH_MISSING_DEPENDENCIES=ON.")
    endif()

    message(STATUS "SDL3 not found; fetching and building it from source")

    # Build a static SDL3 so the result does not depend on a system SDL3
    # runtime being present when the application is run.
    set(SDL_SHARED OFF CACHE BOOL "Build the SDL3 shared library")
    set(SDL_STATIC ON CACHE BOOL "Build the SDL3 static library")
    set(SDL_TEST_LIBRARY OFF CACHE BOOL "Build the SDL3 test library")
    set(SDL_TESTS OFF CACHE BOOL "Build the SDL3 tests")
    set(SDL_EXAMPLES OFF CACHE BOOL "Build the SDL3 examples")
    set(SDL_INSTALL OFF CACHE BOOL "Enable SDL3 install rules")

    FetchContent_Declare(SDL3
        GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
        GIT_TAG        release-3.4.18
        GIT_SHALLOW    TRUE
    )
    FetchContent_MakeAvailable(SDL3)
endif()

# ---------------------------------------------------------------------------
# Vulkan headers
# ---------------------------------------------------------------------------
find_package(Vulkan QUIET)

if(Vulkan_FOUND AND Vulkan_INCLUDE_DIRS)
    set(WR_VULKAN_INCLUDE_DIRS ${Vulkan_INCLUDE_DIRS})
else()
    if(NOT WR_FETCH_MISSING_DEPENDENCIES)
        message(FATAL_ERROR
            "Vulkan headers were not found. Install the Vulkan SDK, or configure "
            "with -DWR_FETCH_MISSING_DEPENDENCIES=ON.")
    endif()

    message(STATUS "Vulkan headers not found; fetching Vulkan-Headers")

    FetchContent_Declare(vulkan_headers
        GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Headers.git
        GIT_TAG        vulkan-sdk-1.4.350.0
        GIT_SHALLOW    TRUE
    )
    FetchContent_MakeAvailable(vulkan_headers)

    set(WR_VULKAN_INCLUDE_DIRS ${vulkan_headers_SOURCE_DIR}/include)
endif()

# ---------------------------------------------------------------------------
# volk (Vulkan meta-loader)
#
# volk is not guaranteed to be shipped with Vulkan headers. The project
# includes <volk/volk.h>, so a fetched volk checkout is placed in a generated
# include directory with the expected layout.
# ---------------------------------------------------------------------------
find_path(WR_VOLK_INCLUDE_DIR
    NAMES volk/volk.h
    HINTS ${WR_VULKAN_INCLUDE_DIRS}
)
find_file(WR_VOLK_SOURCE_FILE
    NAMES volk.c
    HINTS ${WR_VULKAN_INCLUDE_DIRS}
    PATH_SUFFIXES volk
)

if(NOT WR_VOLK_INCLUDE_DIR OR NOT WR_VOLK_SOURCE_FILE)
    if(NOT WR_FETCH_MISSING_DEPENDENCIES)
        message(FATAL_ERROR
            "volk was not found in the Vulkan headers. Install volk, or configure "
            "with -DWR_FETCH_MISSING_DEPENDENCIES=ON.")
    endif()

    message(STATUS "volk not found; fetching it")

    FetchContent_Declare(volk
        GIT_REPOSITORY https://github.com/zeux/volk.git
        GIT_TAG        vulkan-sdk-1.4.350.0
        GIT_SHALLOW    TRUE
        SOURCE_SUBDIR  _disable_add_subdirectory
    )
    FetchContent_MakeAvailable(volk)

    set(WR_VOLK_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated/include)
    file(MAKE_DIRECTORY ${WR_VOLK_INCLUDE_DIR}/volk)
    configure_file(
        ${volk_SOURCE_DIR}/volk.h
        ${WR_VOLK_INCLUDE_DIR}/volk/volk.h
        COPYONLY
    )
    set(WR_VOLK_SOURCE_FILE ${volk_SOURCE_DIR}/volk.c)
endif()

# ---------------------------------------------------------------------------
# Vulkan Memory Allocator (VMA)
#
# VMA is not part of the Vulkan headers either. The project includes
# <vma/vk_mem_alloc.h>, so a fetched copy is placed in the same generated
# include directory.
# ---------------------------------------------------------------------------
find_path(WR_VMA_INCLUDE_DIR
    NAMES vma/vk_mem_alloc.h
    HINTS ${WR_VULKAN_INCLUDE_DIRS}
)

if(NOT WR_VMA_INCLUDE_DIR)
    if(NOT WR_FETCH_MISSING_DEPENDENCIES)
        message(FATAL_ERROR
            "Vulkan Memory Allocator (VMA) was not found. Install VMA, or configure "
            "with -DWR_FETCH_MISSING_DEPENDENCIES=ON.")
    endif()

    message(STATUS "Vulkan Memory Allocator not found; fetching it")

    FetchContent_Declare(vma
        GIT_REPOSITORY https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
        GIT_TAG        v3.5.0
        GIT_SHALLOW    TRUE
        SOURCE_SUBDIR  _disable_add_subdirectory
    )
    FetchContent_MakeAvailable(vma)

    set(WR_VMA_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated/include)
    file(MAKE_DIRECTORY ${WR_VMA_INCLUDE_DIR}/vma)
    configure_file(
        ${vma_SOURCE_DIR}/include/vk_mem_alloc.h
        ${WR_VMA_INCLUDE_DIR}/vma/vk_mem_alloc.h
        COPYONLY
    )
endif()

# ---------------------------------------------------------------------------
# Application
# ---------------------------------------------------------------------------
add_executable(main
    src/main.c
    src/wapp/wapp.c
    src/config/config_src.c
    src/window/window_src.c
    src/gpu/gpu_c_src.c
    src/gpu/gpu_cc_src.cpp
    ${WR_VOLK_SOURCE_FILE}
)

target_include_directories(main PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/src
    ${WR_VULKAN_INCLUDE_DIRS}
    ${WR_VOLK_INCLUDE_DIR}
    ${WR_VMA_INCLUDE_DIR}
)

target_compile_definitions(main PRIVATE
    WP_DEBUG_ASSERT
    CGLM_DEFINE_PRINTS
    USE_SDL_WINDOW_BACKEND
    USE_VULKAN_RENDER_BACKEND
    VK_NO_PROTOTYPES
)

if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    target_compile_options(main PRIVATE
        $<$<COMPILE_LANGUAGE:CXX>:-Wno-nullability-completeness>
    )
endif()

target_link_libraries(main PRIVATE
    SDL3::SDL3
    ${CMAKE_DL_LIBS}
)

if(UNIX AND NOT APPLE)
    target_link_libraries(main PRIVATE m)
endif()

# ---------------------------------------------------------------------------
# Shaders
#
# The application loads "shaders/shader.spv" relative to its working directory,
# so the SPIR-V output is placed in <build-dir>/shaders.
# ---------------------------------------------------------------------------
if(WR_COMPILE_SHADERS)
    find_program(SLANGC_EXECUTABLE NAMES slangc)

    if(NOT SLANGC_EXECUTABLE AND WR_FETCH_MISSING_DEPENDENCIES)
        # Download the prebuilt Slang release instead of requiring the user to
        # install the compiler. Asset names and hashes come from the pinned
        # GitHub release below.
        set(WR_SLANG_VERSION "2026.20")

        string(TOLOWER "${CMAKE_HOST_SYSTEM_PROCESSOR}" WR_SLANG_ARCH)
        if(WR_SLANG_ARCH MATCHES "^(x86_64|amd64|x64)$")
            set(WR_SLANG_ARCH "x86_64")
        elseif(WR_SLANG_ARCH MATCHES "^(aarch64|arm64|arm64e)$")
            set(WR_SLANG_ARCH "aarch64")
        else()
            set(WR_SLANG_ARCH "")
        endif()

        if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
            set(WR_SLANG_OS "linux")
        elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
            set(WR_SLANG_OS "macos")
        elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
            set(WR_SLANG_OS "windows")
        else()
            set(WR_SLANG_OS "")
        endif()

        if(WR_SLANG_OS AND WR_SLANG_ARCH)
            set(WR_SLANG_PLATFORM "${WR_SLANG_OS}-${WR_SLANG_ARCH}")
        else()
            set(WR_SLANG_PLATFORM "")
        endif()

        if(WR_SLANG_PLATFORM STREQUAL "linux-x86_64")
            set(WR_SLANG_ASSET
                "slang-${WR_SLANG_VERSION}-linux-x86_64-glibc-2.28.tar.gz")
            set(WR_SLANG_SHA256
                "84ebd777968caceea26b21bfd014341078be6047be6c5a963a69f984f896cebd")
        elseif(WR_SLANG_PLATFORM STREQUAL "linux-aarch64")
            set(WR_SLANG_ASSET
                "slang-${WR_SLANG_VERSION}-linux-aarch64-glibc-2.28.tar.gz")
            set(WR_SLANG_SHA256
                "9959d741713162efdbd863ff6d32400432b6e103eae3a9ee0afbdba6a00c6a9e")
        elseif(WR_SLANG_PLATFORM STREQUAL "macos-x86_64")
            set(WR_SLANG_ASSET
                "slang-${WR_SLANG_VERSION}-macos-x86_64.tar.gz")
            set(WR_SLANG_SHA256
                "b4f01f615dc625f60c593d7d0a4bbd38e05dfa2a34bfd3973017c309d866feaa")
        elseif(WR_SLANG_PLATFORM STREQUAL "macos-aarch64")
            set(WR_SLANG_ASSET
                "slang-${WR_SLANG_VERSION}-macos-aarch64.tar.gz")
            set(WR_SLANG_SHA256
                "07fa8833fdbb343c3674c84fdd885910f495f3daaaa0c732b32980151e8b58c4")
        elseif(WR_SLANG_PLATFORM STREQUAL "windows-x86_64")
            set(WR_SLANG_ASSET
                "slang-${WR_SLANG_VERSION}-windows-x86_64.zip")
            set(WR_SLANG_SHA256
                "e6dd32bbddf1720ebb4e679d55e9635ce792865a5bd84f3d2cd9af28647c0174")
        elseif(WR_SLANG_PLATFORM STREQUAL "windows-aarch64")
            set(WR_SLANG_ASSET
                "slang-${WR_SLANG_VERSION}-windows-aarch64.zip")
            set(WR_SLANG_SHA256
                "4f18a7e76a59969bc9bf6ea596bd9e496a3c689af173990ae028b18f03e1ac1a")
        else()
            set(WR_SLANG_ASSET "")
        endif()

        if(NOT WR_SLANG_ASSET)
            message(FATAL_ERROR
                "slangc was not found, and no prebuilt Slang archive is available "
                "for ${CMAKE_HOST_SYSTEM_NAME}/${CMAKE_HOST_SYSTEM_PROCESSOR}. "
                "Install the Slang compiler, or configure with "
                "-DWR_COMPILE_SHADERS=OFF.")
        endif()

        set(WR_SLANG_DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/slang")
        set(WR_SLANG_ARCHIVE
            "${WR_SLANG_DOWNLOAD_DIR}/${WR_SLANG_ASSET}")
        set(WR_SLANG_URL
            "https://github.com/shader-slang/slang/releases/download/v${WR_SLANG_VERSION}/${WR_SLANG_ASSET}")

        if(EXISTS "${WR_SLANG_ARCHIVE}")
            file(SHA256 "${WR_SLANG_ARCHIVE}" WR_SLANG_ACTUAL_SHA256)
            if(NOT WR_SLANG_ACTUAL_SHA256 STREQUAL WR_SLANG_SHA256)
                message(STATUS "Removing corrupt Slang archive")
                file(REMOVE "${WR_SLANG_ARCHIVE}")
            endif()
        endif()

        if(NOT EXISTS "${WR_SLANG_ARCHIVE}")
            message(STATUS "Downloading Slang ${WR_SLANG_VERSION} for ${WR_SLANG_PLATFORM}")
            file(DOWNLOAD
                "${WR_SLANG_URL}"
                "${WR_SLANG_ARCHIVE}"
                EXPECTED_HASH SHA256=${WR_SLANG_SHA256}
                STATUS WR_SLANG_DOWNLOAD_STATUS
                TLS_VERIFY ON
            )
            list(GET WR_SLANG_DOWNLOAD_STATUS 0 WR_SLANG_DOWNLOAD_CODE)
            list(GET WR_SLANG_DOWNLOAD_STATUS 1 WR_SLANG_DOWNLOAD_MESSAGE)
            if(NOT WR_SLANG_DOWNLOAD_CODE EQUAL 0)
                file(REMOVE "${WR_SLANG_ARCHIVE}")
                message(FATAL_ERROR
                    "Failed to download Slang from ${WR_SLANG_URL}: "
                    "${WR_SLANG_DOWNLOAD_MESSAGE}")
            endif()
        endif()

        if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
            set(WR_SLANG_EXECUTABLE
                "${WR_SLANG_DOWNLOAD_DIR}/bin/slangc.exe")
        else()
            set(WR_SLANG_EXECUTABLE
                "${WR_SLANG_DOWNLOAD_DIR}/bin/slangc")
        endif()

        if(NOT EXISTS "${WR_SLANG_EXECUTABLE}")
            message(STATUS "Extracting Slang ${WR_SLANG_VERSION}")
            file(ARCHIVE_EXTRACT
                INPUT "${WR_SLANG_ARCHIVE}"
                DESTINATION "${WR_SLANG_DOWNLOAD_DIR}"
            )
        endif()

        if(NOT EXISTS "${WR_SLANG_EXECUTABLE}")
            message(FATAL_ERROR
                "The Slang archive did not contain bin/slangc")
        endif()

        set(SLANGC_EXECUTABLE "${WR_SLANG_EXECUTABLE}")
        message(STATUS "Using downloaded Slang compiler: ${SLANGC_EXECUTABLE}")
    endif()

    if(NOT SLANGC_EXECUTABLE)
        message(FATAL_ERROR
            "slangc was not found. Install the Slang compiler, configure with "
            "-DWR_FETCH_MISSING_DEPENDENCIES=ON to download it, or configure with "
            "-DWR_COMPILE_SHADERS=OFF.")
    endif()

    set(WR_SHADER_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/src/shaders/shader.slang)
    set(WR_SHADER_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/shaders)
    set(WR_SHADER_OUTPUT ${WR_SHADER_OUTPUT_DIR}/shader.spv)

    add_custom_command(
        OUTPUT ${WR_SHADER_OUTPUT}
        COMMAND ${CMAKE_COMMAND} -E make_directory ${WR_SHADER_OUTPUT_DIR}
        COMMAND ${SLANGC_EXECUTABLE}
                ${WR_SHADER_SOURCE}
                -entry vertMain
                -entry fragMain
                -o ${WR_SHADER_OUTPUT}
        DEPENDS ${WR_SHADER_SOURCE}
        COMMENT "Compiling shader.slang"
        VERBATIM
    )

    add_custom_target(shaders ALL DEPENDS ${WR_SHADER_OUTPUT})
    add_dependencies(main shaders)
endif()

# ---------------------------------------------------------------------------
# Assets
#
# The application loads "assets/collections.png" relative to its working
# directory, so the assets directory is copied into <build-dir>/assets.
# ---------------------------------------------------------------------------
set(WR_ASSETS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/assets)
set(WR_ASSETS_DEST_DIR ${CMAKE_CURRENT_BINARY_DIR}/assets)

if(EXISTS ${WR_ASSETS_SOURCE_DIR})
    add_custom_target(copy_assets ALL
        COMMAND ${CMAKE_COMMAND} -E copy_directory
                ${WR_ASSETS_SOURCE_DIR}
                ${WR_ASSETS_DEST_DIR}
        COMMENT "Copying assets"
        VERBATIM
    )
    add_dependencies(main copy_assets)
endif()

add_custom_target(run
    COMMAND $<TARGET_FILE:main>
    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
    DEPENDS main
    USES_TERMINAL
)

if(TARGET shaders)
    add_dependencies(run shaders)
endif()
