cmake_minimum_required(VERSION 3.24)
project(yave)

function(build_luajit LUAJIT_TARGET)
    set(LUA_DIR ${CMAKE_SOURCE_DIR}/external/LuaJIT)
    set(LUA_SOURCE_DIR ${LUA_DIR}/src)

    if(WIN32)
        set(LUA_SHARED_OBJECT ${LUA_SOURCE_DIR}/lua51.dll)
    else()
        set(LUA_SHARED_OBJECT ${LUA_SOURCE_DIR}/lua51.so)
    endif()

    if(MSVC)
        set(LUA_LIB_FILE ${LUA_SOURCE_DIR}/lua51.lib)
        add_custom_command(OUTPUT ${LUA_LIB_FILE} COMMAND cmd /c ${LUA_SOURCE_DIR}/msvcbuild.bat WORKING_DIRECTORY ${LUA_SOURCE_DIR})
    else()
        set(LUA_LIB_FILE ${LUA_SHARED_OBJECT})
        set(MAKE_CMD make)
        if(MINGW)
            set(MAKE_CMD mingw32-make)
        endif()
        add_custom_command(OUTPUT ${LUA_LIB_FILE} COMMAND ${MAKE_CMD} WORKING_DIRECTORY ${LUA_DIR})
    endif()

    add_custom_target(copy-luajit DEPENDS ${LUA_LIB_FILE})
    add_custom_command(TARGET copy-luajit POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${LUA_SHARED_OBJECT} ${CMAKE_CURRENT_BINARY_DIR})

    add_library(${LUAJIT_TARGET} STATIC IMPORTED GLOBAL)
    add_dependencies(${LUAJIT_TARGET} copy-luajit)
    set_target_properties(${LUAJIT_TARGET} PROPERTIES IMPORTED_LOCATION ${LUA_LIB_FILE})
endfunction(build_luajit)

function(enable_unity_build UB_SUFFIX SOURCE_VARIABLE_NAME)
    message("Using unity build for target ${UB_SUFFIX}")
    set(FILES ${${SOURCE_VARIABLE_NAME}})

    # Exclude all translation units from compilation
    set_source_files_properties(${FILES} PROPERTIES HEADER_FILE_ONLY true)

    # Generate a unique filename for the unity build translation unit
    set(UNIT_BUILD_FILE ${CMAKE_CURRENT_BINARY_DIR}/blob_${UB_SUFFIX}.cpp)
    file(WRITE ${UNIT_BUILD_FILE} "// Unity Build generated by CMake\n")
    file(WRITE ${UNIT_BUILD_FILE} "#define YAVE_UNITY_BUILD\n")

    # Add include statement for each translation unit
    foreach(SOURCE_FILE ${FILES})
        get_filename_component(FILE_EXT "${SOURCE_FILE}" EXT)
        if(NOT "${FILE_EXT}" MATCHES "\.h(pp)?")
            file(APPEND ${UNIT_BUILD_FILE} "#include <${SOURCE_FILE}>\n")
        endif()
    endforeach(SOURCE_FILE)

    # Complement list of translation units with the name of ub
    set(${SOURCE_VARIABLE_NAME} ${${SOURCE_VARIABLE_NAME}} ${UNIT_BUILD_FILE} PARENT_SCOPE)
endfunction(enable_unity_build)


function(optimize_shader SHADER_TARGET SHADER_BIN)
    # add_custom_command(TARGET ${SHADER_TARGET} POST_BUILD COMMAND echo optimizing ${SHADER_BIN})
    add_custom_command(TARGET ${SHADER_TARGET} POST_BUILD COMMAND spirv-opt ${SHADER_BIN} -O --preserve-bindings -o ${SHADER_BIN})
endfunction(optimize_shader)


function(add_slang_shader SHADER_TARGET SHADER_FILE SHADER_DEFINES)
    get_filename_component(SHADER_NAME ${SHADER_FILE} NAME_WE)
    get_property(SLANGC GLOBAL PROPERTY SLANG_EXECUTABLE)
    foreach(DEF ${SHADER_DEFINES})
        set(COMPILE_ARGS "${COMPILE_ARGS} -D${DEF}=1")
        set(NAME_SUFFIX "${NAME_SUFFIX}_${DEF}")
    endforeach()
    string(STRIP "${COMPILE_ARGS}" COMPILE_ARGS)
    set(SHADER_BIN "${SHADER_NAME}${NAME_SUFFIX}.spv")
    add_custom_command(
        TARGET ${SHADER_TARGET}
        COMMAND ${SLANGC}
            ${COMPILE_ARGS}
            -target spirv
            -matrix-layout-column-major
            -fvk-use-entrypoint-name
            -preserve-params
            -O0
            -o ${SHADER_BIN}
            "${CMAKE_SOURCE_DIR}/shaders/${SHADER_FILE}"
    )
    optimize_shader(${SHADER_TARGET}_optim ${SHADER_BIN})
endfunction(add_slang_shader)


function(setup_slang)
    include(ExternalProject)
    # https://stackoverflow.com/questions/16384831/externalproject-add-doesnt-install-the-release-configuration-of-the-project
    set(SLANG_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/slangc")
    ExternalProject_Add(slang
        SOURCE_DIR "${CMAKE_SOURCE_DIR}/external/slang"
        CMAKE_ARGS "-DCMAKE_BUILD_TYPE=Release;-DCMAKE_INSTALL_PREFIX=${SLANG_INSTALL_DIR}"
        EXCLUDE_FROM_ALL true
    )
    set_property(GLOBAL PROPERTY SLANG_EXECUTABLE "${SLANG_INSTALL_DIR}/bin/slangc")
endfunction(setup_slang)


option(YAVE_BUILD_YAVE "Build yave" ON)
option(YAVE_BUILD_EDITOR "Build editor" ON)
option(YAVE_TRACY_PROFILING "Use Tracy profiling" ON)
option(YAVE_UNITY_BUILD "Force unity build" OFF)


set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 20)

# See note in y
# set(CMAKE_CXX_EXTENSIONS OFF)



find_package(Vulkan REQUIRED)

if(NOT Vulkan_FOUND)
    message(FATAL_ERROR "Vulkan SDK not found")
endif()
if(NOT Vulkan_glslc_FOUND)
    message(FATAL_ERROR "glslc not found in Vulkan SDK")
endif()


# add y subtree
add_subdirectory(y)


# setup includes
include_directories(${y_SOURCE_DIR})
include_directories(${yave_SOURCE_DIR})
include_directories(${Vulkan_INCLUDE_DIRS})
include_directories(external/sol/include)
include_directories(external/LuaJIT/src)


# Yave's core file
file(GLOB_RECURSE YAVE_FILES
    "yave/*.h"
    "yave/*.cpp"
)

set(TRACY_FILES
    "external/tracy/public/TracyClient.cpp"
)

set(SPIRV_REFLECT_FILES
    "external/spirv_reflect/spirv_reflect.c"
    "external/spirv_reflect/spirv_reflect.h"
)

# Editor files
file(GLOB_RECURSE EDITOR_FILES
    "editor/*.cpp"
    "editor/*.h"
)

file(GLOB_RECURSE EDITOR_EXTERNAL_FILES
    "external/imgui/*.cpp"
    "external/imgui/*.h"

    "external/tinygltf/*.hpp"
    "external/tinygltf/*.h"
)

# Shader files they are here so the IDE can find them
file(GLOB_RECURSE SHADER_FILES
    "shaders/*.frag"
    "shaders/*.vert"
    "shaders/*.geom"
    "shaders/*.comp"
    "shaders/*.glsl"
    "shaders/*.slang"
)


function(build_engine_shaders SHADER_TARGET)
    add_slang_shader(${SHADER_TARGET} "deferred_light.slang" "POINT")
    add_slang_shader(${SHADER_TARGET} "deferred_light.slang" "SPOT")

    add_slang_shader(${SHADER_TARGET} "deferred_locals.slang" "")
    add_slang_shader(${SHADER_TARGET} "deferred_locals.slang" "DEBUG")

    add_slang_shader(${SHADER_TARGET} "ssao.slang" "")
    add_slang_shader(${SHADER_TARGET} "ssao_upsample.slang" "")
    add_slang_shader(${SHADER_TARGET} "ssao_upsample.slang" "COMBINE_HIGH")

    add_slang_shader(${SHADER_TARGET} "atmosphere.slang" "")

    add_slang_shader(${SHADER_TARGET} "deferred_ambient.slang" "")

    add_slang_shader(${SHADER_TARGET} "blur.slang" "HORIZONTAL")
    add_slang_shader(${SHADER_TARGET} "blur.slang" "VERTICAL")

    add_slang_shader(${SHADER_TARGET} "bloom_upscale.slang" "")
    add_slang_shader(${SHADER_TARGET} "bloom_downscale.slang" "")

    add_slang_shader(${SHADER_TARGET} "passthrough.slang" "")
    add_slang_shader(${SHADER_TARGET} "downsample.slang" "")

    add_slang_shader(${SHADER_TARGET} "prev_camera.slang" "")
    add_slang_shader(${SHADER_TARGET} "depth_bounds.slang" "")
    add_slang_shader(${SHADER_TARGET} "histogram.slang" "")
    add_slang_shader(${SHADER_TARGET} "exposure_params.slang" "")
    add_slang_shader(${SHADER_TARGET} "exposure_debug.slang" "")
    add_slang_shader(${SHADER_TARGET} "update_transforms.slang" "")

    add_slang_shader(${SHADER_TARGET} "brdf_integrator.slang" "")
    add_slang_shader(${SHADER_TARGET} "linearize_depth.slang" "")

    add_slang_shader(${SHADER_TARGET} "ibl_convolution.slang" "EQUIREC")
    add_slang_shader(${SHADER_TARGET} "ibl_convolution.slang" "CUBE")

    add_slang_shader(${SHADER_TARGET} "id.slang" "")

    add_slang_shader(${SHADER_TARGET} "opaque.slang" "")
    add_slang_shader(${SHADER_TARGET} "opaque.slang" "ALPHA_TEST")
    add_slang_shader(${SHADER_TARGET} "opaque.slang" "SPECULAR")
    add_slang_shader(${SHADER_TARGET} "opaque.slang" "SPECULAR;ALPHA_TEST")

    add_slang_shader(${SHADER_TARGET} "wireframe.slang" "")
    add_slang_shader(${SHADER_TARGET} "copy.slang" "")
    add_slang_shader(${SHADER_TARGET} "screen.slang" "")
    add_slang_shader(${SHADER_TARGET} "tonemap.slang" "")
    add_slang_shader(${SHADER_TARGET} "temporal_mask.slang" "")
    add_slang_shader(${SHADER_TARGET} "taa_simple.slang" "")
    add_slang_shader(${SHADER_TARGET} "taa_resolve.slang" "")

    add_slang_shader(${SHADER_TARGET} "rtao.slang" "")
    add_slang_shader(${SHADER_TARGET} "filter_rtao.slang" "HORIZONTAL")
    add_slang_shader(${SHADER_TARGET} "filter_rtao.slang" "VERTICAL")

    add_slang_shader(${SHADER_TARGET} "basic_rt.slang" "")
endfunction(build_engine_shaders)

function(build_editor_shaders SHADER_TARGET)
    add_slang_shader(${SHADER_TARGET} "editor/depth_alpha.slang" "")
    add_slang_shader(${SHADER_TARGET} "editor/picking.slang" "")
    add_slang_shader(${SHADER_TARGET} "editor/imgui.slang" "")
    add_slang_shader(${SHADER_TARGET} "editor/selection.slang" "")
    add_slang_shader(${SHADER_TARGET} "editor/engine_view.slang" "")
    add_slang_shader(${SHADER_TARGET} "editor/imgui_billboard.slang" "")
endfunction(build_editor_shaders)



build_luajit(luajit)
setup_slang()

if(YAVE_UNITY_BUILD)
    enable_unity_build(yave YAVE_FILES)
endif()

if(YAVE_BUILD_YAVE)
    add_library(yave STATIC ${YAVE_FILES} ${SPIRV_REFLECT_FILES} ${SHADER_FILES} ${SHADER_LIBS})

    if(UNIX)
        target_link_libraries(yave xcb)
    endif()

    if(YAVE_TRACY_PROFILING)
        add_library(tracy STATIC ${TRACY_FILES})
        if(WIN32)
            target_link_libraries(tracy ws2_32 dbghelp advapi32 user32)
        endif()
        target_compile_options(tracy PUBLIC "-DTRACY_ENABLE")
        target_link_libraries(yave tracy)
    endif()

    target_link_libraries(yave y luajit)

    if(NOT MSVC)
        target_link_libraries(yave stdc++fs)
    endif()

    add_custom_target(shaders)
    add_custom_target(shaders_optim)
    build_engine_shaders(shaders)
    add_dependencies(shaders slang)
    add_dependencies(shaders_optim shaders)

    add_dependencies(yave shaders_optim)
endif()

if(YAVE_BUILD_EDITOR)
    add_executable(editor ${EDITOR_FILES} ${EDITOR_EXTERNAL_FILES})
    target_include_directories(editor PRIVATE external/imgui)

    build_editor_shaders(shaders)
    add_dependencies(editor shaders)

    target_link_libraries(editor yave)
endif()

