cmake_minimum_required(VERSION 3.21)

project(townengine LANGUAGES C)

# dependencies
find_package(SDL2 REQUIRED GLOBAL)
find_package(SDL2_image REQUIRED GLOBAL)
find_package(SDL2_ttf REQUIRED GLOBAL)

if (NOT CMAKE_BUILD_TYPE)
        set(CMAKE_BUILD_TYPE Debug)
endif ()

set(TWN_TARGET townengine CACHE INTERNAL "")
set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")

option(TWN_FEATURE_DYNLIB_GAME  "Enable dynamic library loading support" ON)
option(TWN_ARCHIVE_DATA         "Enable archival of assets"              OFF)

# add -fPIC globally so that it's linked well
add_compile_options($<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:-fPIC>)

set(PHYSFS_BUILD_SHARED FALSE)
set(PHYSFS_DISABLE_INSTALL TRUE)
set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall")
set(PHYSFS_ARCHIVE_GRP OFF)
set(PHYSFS_ARCHIVE_WAD OFF)
set(PHYSFS_ARCHIVE_HOG OFF)
set(PHYSFS_ARCHIVE_MVL OFF)
set(PHYSFS_ARCHIVE_QPAK OFF)
set(PHYSFS_ARCHIVE_SLB OFF)
set(PHYSFS_ARCHIVE_ISO9660 OFF)
set(PHYSFS_ARCHIVE_VDF OFF)
add_subdirectory(third-party/physfs)
add_subdirectory(third-party/libxm)


if(UNIX)
        set(SYSTEM_SOURCE_FILES townengine/system/linux/elf.c)
else()
        set(SYSTEM_SOURCE_FILES)
endif()

set(TWN_SOURCE_FILES
        third-party/physfs/extras/physfsrwops.c
        third-party/stb/stb_vorbis.c
        third-party/glad/src/glad.c

        townengine/twn_loop.c
        townengine/twn_main.c
        townengine/config.h
        townengine/context/context.c townengine/context.h
        townengine/audio/audio.c townengine/audio.h
        townengine/util.c townengine/util.h
        townengine/rendering.c townengine/rendering.h
        townengine/input/input.c townengine/input.h
        townengine/camera.c townengine/camera.h
        townengine/textures/textures.c
        townengine/twn_game_object.c

        $<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:townengine/twn_main.c>

        ${SYSTEM_SOURCE_FILES})

list(TRANSFORM TWN_SOURCE_FILES PREPEND ${TWN_ROOT_DIR}/)

# base engine code, reused for games and tools
if (TWN_FEATURE_DYNLIB_GAME)
        add_library(${TWN_TARGET} SHARED ${TWN_SOURCE_FILES})
else ()
        add_library(${TWN_TARGET} STATIC ${TWN_SOURCE_FILES})
endif ()

source_group(TREE ${TWN_ROOT_DIR} FILES ${TWN_SOURCE_FILES})

set_target_properties(${TWN_TARGET} PROPERTIES
                                           C_STANDARD 11
                                           C_STANDARD_REQUIRED ON
                                           C_EXTENSIONS ON) # extensions are required by stb_ds.h

target_precompile_headers(${TWN_TARGET} PRIVATE
        third-party/glad/include/glad/glad.h
        third-party/stb/stb_ds.h)

# distribution definitions
set(ORGANIZATION_NAME "wanp" CACHE STRING
                      "App developer/publisher's identifier")
set(APP_NAME "townengine" CACHE STRING
             "App identifier")
set(PACKAGE_EXTENSION "btw" CACHE STRING
                      "File extension used to look for data archives")

function(give_options target)
        if(MSVC)
                # close enough i say
                target_compile_options(${target} PRIVATE
                                                 /W4
                                                 $<$<CONFIG:Release>:/GL>)

                target_link_options(${target} PRIVATE
                                              $<$<CONFIG:Release>:/LTCG>)

        else()
                set(WARNING_FLAGS_CLANG
                        -Weverything
                        -Wno-padded
                        -Wno-declaration-after-statement
                        -Wno-unsafe-buffer-usage
                        -Wno-unused-command-line-argument)

                set(WARNING_FLAGS
                        -Wall
                        -Wextra
                        -Wpedantic
                        -Wshadow
                        -Wdouble-promotion
                        -Wconversion -Wno-sign-conversion
                        -Werror=vla
                        $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-Wcast-align=strict>
                        $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:${WARNING_FLAGS_CLANG}>)

                set(BUILD_FLAGS
                        # these SHOULDN'T break anything...
                        -fno-math-errno
                        -ffp-contract=fast
                        -fno-signed-zeros
                        -fno-trapping-math
                        -freciprocal-math)

                set(BUILD_FLAGS_RELEASE
                        -O3
                        -flto
                        -mavx -mavx2
                        -Wl,--gc-sections
                        -fdata-sections
                        -ffunction-sections
                        -funroll-loops
                        -fomit-frame-pointer
                        $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-fallow-store-data-races>)

                set(BUILD_FLAGS_DEBUG
                        -O0
                        -g3
                        -gdwarf
                        -fno-omit-frame-pointer
                        -fstack-protector-all
                        -fsanitize=undefined
                        -fsanitize=address)

                target_compile_options(${target} PRIVATE
                                                 ${WARNING_FLAGS}
                                                 ${BUILD_FLAGS}
                                                 $<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
                                                 $<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>)

                target_link_options(${target} PRIVATE
                                              ${BUILD_FLAGS}
                                              $<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
                                              $<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>)
                                                    
        endif()

        target_compile_definitions(${target} PRIVATE
                                             ORGANIZATION_NAME="${ORGANIZATION_NAME}"
                                             APP_NAME="${APP_NAME}"
                                             PACKAGE_EXTENSION="${PACKAGE_EXTENSION}"
                                             $<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>)
endfunction()


function(include_deps target)
        # header-only libraries should be marked as "system includes"
        # to suppress compiler warnings in their code (it's not my problem after all)
        set(THIRD_PARTY_INCLUDES
                third-party/physfs/src
                third-party/physfs/extras
                third-party/libxm/include
                third-party/glad/include
                third-party/stb
                third-party/x-watcher)

        list(TRANSFORM THIRD_PARTY_INCLUDES PREPEND ${TWN_ROOT_DIR}/)
        target_include_directories(${target} SYSTEM PRIVATE ${THIRD_PARTY_INCLUDES})

        # allow access to headers from any point in source tree
        target_include_directories(${target} PRIVATE ${TWN_ROOT_DIR})
endfunction()


function(link_deps target)
        target_link_libraries(${target} PUBLIC
                                        SDL2::SDL2
                                        SDL2_image::SDL2_image
                                        SDL2_ttf::SDL2_ttf
                                        physfs-static
                                        xms)
endfunction()


function(use_townengine target sources output_directory data_dir)
        if (TWN_FEATURE_DYNLIB_GAME)
                # game shared library, for reloading
                add_library(${target}_game SHARED ${sources})
                give_options(${target}_game)
                include_deps(${target}_game)
                target_link_libraries(${target}_game PUBLIC SDL2::SDL2 ${TWN_TARGET})
                set_target_properties(${target}_game PROPERTIES
                                                     OUTPUT_NAME game
                                                     LIBRARY_OUTPUT_DIRECTORY ${output_directory}
                                                     RUNTIME_OUTPUT_DIRECTORY ${output_directory})

                # launcher binary, loads game and engine shared library
                add_executable(${target}_app ${TWN_ROOT_DIR}/townengine/twn_main.c)

                # todo: copy instead?
                # put libtownengine.so alongside the binary
                set_target_properties(${TWN_TARGET} PROPERTIES
                                                    LIBRARY_OUTPUT_DIRECTORY ${output_directory})
        else ()
                add_executable(${target}_app ${sources})
        endif ()

        set_target_properties(${target}_app PROPERTIES
                                            OUTPUT_NAME launcher
                                            LIBRARY_OUTPUT_DIRECTORY ${output_directory})

        # system libraries
        find_library(MATH_LIBRARY m)
        if (MATH_LIBRARY)
                target_link_libraries(${target}_app PUBLIC ${MATH_LIBRARY})
        endif ()

        give_options(${target}_app)
        include_deps(${target}_app)
        link_deps(${target}_app)
        target_link_libraries(${target}_app PUBLIC ${TWN_TARGET})
        set_target_properties(${target}_app PROPERTIES
                                            RUNTIME_OUTPUT_DIRECTORY ${output_directory})

        # copy dlls for baby windows
        add_custom_command(TARGET ${target}_app POST_BUILD
                           COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:${target}_app>
                                                                         $<TARGET_FILE_DIR:${target}_app>
                           COMMAND_EXPAND_LISTS)

        set(TWN_BOOTSTRAP_EXEC_ARGS
                "$<IF:$<BOOL:${TWN_ARCHIVE_DATA}>,--data-dir ./data.${PACKAGE_EXTENSION},--data-dir ${data_dir}>")

        string(JOIN "\n" TWN_BOOTSTRAP_SH
                         "#!/bin/env sh"
                         "cd \"$(dirname \"$0\")\""
                         "LD_LIBRARY_PATH=./ ./launcher ${TWN_BOOTSTRAP_EXEC_ARGS}"
                         "")

        FILE(GENERATE OUTPUT ${output_directory}/${target}
             CONTENT "${TWN_BOOTSTRAP_SH}"
             FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)

        if (WIN32)
                string(JOIN "\n" TWN_BOOTSTRAP_BAT
                                 "pushd \"%~dp0\""
                                 "launcher ${TWN_BOOTSTRAP_EXEC_ARGS}"
                                 "")

                FILE(GENERATE OUTPUT ${output_directory}/${target}.bat
                     CONTENT "${TWN_BOOTSTRAP_BAT}"
                     FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
        endif ()

        if (TWN_ARCHIVE_DATA)
                # zip up assets
                add_custom_command(TARGET ${target}_app POST_BUILD
                                   COMMAND cd ${data_dir} && ${CMAKE_COMMAND} -E tar "cf" ${output_directory}/data.${PACKAGE_EXTENSION} --format=zip ./
                                   WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
        endif ()
endfunction()

give_options(${TWN_TARGET})
include_deps(${TWN_TARGET})
link_deps(${TWN_TARGET})

# build the testgame if this cmake list is built directly
if (${CMAKE_PROJECT_NAME} MATCHES townengine)
        add_subdirectory(apps/testgame)
endif ()

# move compie_commands.json into root directory so that it plays nicer with editors
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_custom_command(TARGET ${TWN_TARGET} POST_BUILD
                   COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json
                                                                 ${TWN_ROOT_DIR})
