include_guard()

# Change these defaults to point to your infrastructure if desired
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
set(CMKR_TAG "archive_264e4ace" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)

# Set these from the command line to customize for development/debugging purposes
set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable")
set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation")

# Disable cmkr if generation is disabled
if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION)
    message(STATUS "[cmkr] Skipping automatic cmkr generation")
    macro(cmkr)
    endmacro()
    return()
endif()

# Disable cmkr if no cmake.toml file is found
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml")
    message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml")
    macro(cmkr)
    endmacro()
    return()
endif()

# Convert a Windows native path to CMake path
if(CMKR_EXECUTABLE MATCHES "\\\\")
    string(REPLACE "\\" "/" CMKR_EXECUTABLE_CMAKE "${CMKR_EXECUTABLE}")
    set(CMKR_EXECUTABLE "${CMKR_EXECUTABLE_CMAKE}" CACHE FILEPATH "" FORCE)
    unset(CMKR_EXECUTABLE_CMAKE)
endif()

# Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher)
function(cmkr_exec)
    execute_process(COMMAND ${ARGV} RESULT_VARIABLE CMKR_EXEC_RESULT)
    if(NOT CMKR_EXEC_RESULT EQUAL 0)
        message(FATAL_ERROR "cmkr_exec(${ARGV}) failed (exit code ${CMKR_EXEC_RESULT})")
    endif()
endfunction()

# Windows-specific hack (CMAKE_EXECUTABLE_PREFIX is not set at the moment)
if(WIN32)
    set(CMKR_EXECUTABLE_NAME "cmkr.exe")
else()
    set(CMKR_EXECUTABLE_NAME "cmkr")
endif()

# Use cached cmkr if found
set(CMKR_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_${CMKR_TAG}")
set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}")

if(NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE AND CMKR_EXECUTABLE MATCHES "^${CMAKE_CURRENT_BINARY_DIR}/_cmkr")
    message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'")
    unset(CMKR_EXECUTABLE CACHE)
endif()

if(CMKR_EXECUTABLE AND EXISTS "${CMKR_EXECUTABLE}")
    message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'")
elseif(CMKR_EXECUTABLE AND NOT CMKR_EXECUTABLE STREQUAL CMKR_CACHED_EXECUTABLE)
    message(FATAL_ERROR "[cmkr] '${CMKR_EXECUTABLE}' not found")
else()
    set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE)
    message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'")
    
    message(STATUS "[cmkr] Fetching cmkr...")
    if(EXISTS "${CMKR_DIRECTORY}")
        cmkr_exec("${CMAKE_COMMAND}" -E rm -rf "${CMKR_DIRECTORY}")
    endif()
    find_package(Git QUIET REQUIRED)
    cmkr_exec("${GIT_EXECUTABLE}"
        clone
        --config advice.detachedHead=false
        --branch ${CMKR_TAG}
        --depth 1
        ${CMKR_REPO}
        "${CMKR_DIRECTORY}"
    )
    message(STATUS "[cmkr] Building cmkr...")
    cmkr_exec("${CMAKE_COMMAND}"
        --no-warn-unused-cli
        "${CMKR_DIRECTORY}"
        "-B${CMKR_DIRECTORY}/build"
        "-DCMAKE_BUILD_TYPE=Release"
        "-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}"
        "-DCMKR_GENERATE_DOCUMENTATION=OFF"
    )
    cmkr_exec("${CMAKE_COMMAND}"
        --build "${CMKR_DIRECTORY}/build"
        --config Release
        --parallel
    )
    cmkr_exec("${CMAKE_COMMAND}"
        --install "${CMKR_DIRECTORY}/build"
        --config Release
        --prefix "${CMKR_DIRECTORY}"
        --component cmkr
    )
    if(NOT EXISTS ${CMKR_EXECUTABLE})
        message(FATAL_ERROR "[cmkr] Failed to bootstrap '${CMKR_EXECUTABLE}'")
    endif()
    cmkr_exec("${CMKR_EXECUTABLE}" version)
    message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}")
endif()
execute_process(COMMAND "${CMKR_EXECUTABLE}" version
    RESULT_VARIABLE CMKR_EXEC_RESULT
)
if(NOT CMKR_EXEC_RESULT EQUAL 0)
    message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding")
endif()

# This is the macro that contains black magic
macro(cmkr)
    # When this macro is called from the generated file, fake some internal CMake variables
    get_source_file_property(CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" CMKR_CURRENT_LIST_FILE)
    if(CMKR_CURRENT_LIST_FILE)
        set(CMAKE_CURRENT_LIST_FILE "${CMKR_CURRENT_LIST_FILE}")
        get_filename_component(CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
    endif()

    # File-based include guard (include_guard is not documented to work)
    get_source_file_property(CMKR_INCLUDE_GUARD "${CMAKE_CURRENT_LIST_FILE}" CMKR_INCLUDE_GUARD)
    if(NOT CMKR_INCLUDE_GUARD)
        set_source_files_properties("${CMAKE_CURRENT_LIST_FILE}" PROPERTIES CMKR_INCLUDE_GUARD TRUE)

        file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_PRE)

        # Generate CMakeLists.txt
        cmkr_exec("${CMKR_EXECUTABLE}" gen
            WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
        )

        file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST)

        # Delete the temporary file if it was left for some reason
        set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt")
        if(EXISTS "${CMKR_TEMP_FILE}")
            file(REMOVE "${CMKR_TEMP_FILE}")
        endif()

        if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST)
            # Copy the now-generated CMakeLists.txt to CMakerLists.txt
            # This is done because you cannot include() a file you are currently in
            configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY)
            
            # Add the macro required for the hack at the start of the cmkr macro
            set_source_files_properties("${CMKR_TEMP_FILE}" PROPERTIES
                CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}"
            )
            
            # 'Execute' the newly-generated CMakeLists.txt
            include("${CMKR_TEMP_FILE}")
            
            # Delete the generated file
            file(REMOVE "${CMKR_TEMP_FILE}")
            
            # Do not execute the rest of the original CMakeLists.txt
            return()
        endif()
        # Resume executing the unmodified CMakeLists.txt
    endif()
endmacro()
