################################################################################
#
#        Creates a 'format' target that runs clang-format
#
################################################################################

# - Include guard
if(NOT __CLANGFORMAT_INCLUDED)
    set(__CLANGFORMAT_INCLUDED 1)
else()
    return()
endif()

#------------------------------------------------------------------------------#

set(FORMAT_LOGDIR ${PROJECT_BINARY_DIR}/format CACHE PATH
    "Output folder of files that are formatted")

#------------------------------------------------------------------------------#

find_program(CLANG_FORMAT_COMMAND
    NAMES
        clang-format-8.0
        clang-format-7.0
        clang-format-6.0
        clang-format)

#------------------------------------------------------------------------------#

function(exclude_from_format)
    cmake_parse_arguments(ARG
        "" "" "HEADERS;SOURCES;FILES" ${ARGN})

    function(_add_labels _path)
        foreach(_FILE ${ARGN})
            if(NOT IS_ABSOLUTE ${_FILE})
                set(_FILE ${CMAKE_CURRENT_LIST_DIR}${_path}/${_FILE})
            endif()
            set(_LABELS "EXCLUDE_FORMAT")
            get_source_file_property(_EXISTING_LABELS ${_FILE} LABELS)
            if(_EXISTING_LABELS)
                list(APPEND _LABELS ${_EXISTING_LABELS})
            endif()
            set_source_files_properties(${_FILE} PROPERTIES
                LABELS "${_LABELS}")
            get_source_file_property(_EXISTING_LABELS ${_FILE} LABELS)
        endforeach()
    endfunction()

    _add_labels("/include" ${ARG_HEADERS})
    _add_labels("/src" ${ARG_SOURCES})
    _add_labels("" ${ARG_FILES})

endfunction()

#------------------------------------------------------------------------------#

function(create_format_directory)
    if(NOT EXISTS ${FORMAT_LOGDIR})
        execute_process(COMMAND ${CMAKE_COMMAND}
            -E make_directory ${FORMAT_LOGDIR})
    endif()
endfunction()

#------------------------------------------------------------------------------#

FUNCTION(go100x_format_target)
    if(CLANG_FORMAT_COMMAND)
        cmake_parse_arguments(FMTTARGET
            "" "NAME" "SOURCES" ${ARGN})
        string(REGEX REPLACE "-format$" "" TARGET_NAME "${FMTTARGET_NAME}")
        # ensure not empty list
        if(NOT "${FMTTARGET_SOURCES}" STREQUAL "")
            # create output directory
            create_format_directory()
            set(SOURCES ) # list of sources
            set(FILELOG ) # string of sources (for log)
            foreach(_SOURCE ${FMTTARGET_SOURCES})
                # files in binary directory are almost always generated files
                string(FIND "${_SOURCE}" "${PROJECT_BINARY_DIR}" IN_BINARY_DIR)
                # ignore generated files
                if(IN_BINARY_DIR GREATER -1)
                    continue()
                endif()
                # check if exists (valid full path)
                if(EXISTS "${_SOURCE}")
                    # do nothing -- absolute path was specified
                elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${_SOURCE}")
                    # a relative path was specified
                    set(_SOURCE ${CMAKE_CURRENT_LIST_DIR}/${_SOURCE})
                else()
                    # neither relative nor absolute
                    message(STATUS "[format] file not found: '${_SOURCE}'")
                    continue()
                endif()
                #string(REPLACE "${PROJECT_SOURCE_DIR}/" "" _SOURCE ${_SOURCE})
                get_source_file_property(SOURCE_LABELS ${_SOURCE} LABELS)
                list(FIND SOURCE_LABELS "EXCLUDE_FORMAT" EXCLUDE_FORMAT)
                if(EXCLUDE_FORMAT GREATER -1)
                    continue()
                endif()
                # append to log
                set(FILELOG "${FILELOG}${_SOURCE}\n")
                # add to list to be processed
                list(APPEND SOURCES ${_SOURCE})
            endforeach()
            # write format file
            set(FMTLOG_OUT ${FORMAT_LOGDIR}/${TARGET_NAME}.txt)
            file(WRITE ${FMTLOG_OUT} "${FILELOG}")
            file(RELATIVE_PATH FMTLOG_RELATIVE_OUT ${PROJECT_BINARY_DIR} ${FMTLOG_OUT})
            # get the number of source files
            list(LENGTH SOURCES NUM_SOURCES)
            # if there are too many source files (arguments), command will fail
            # so process no more than 500 at a time
            if(NUM_SOURCES GREATER 500)
                # function to generate indices
                function(generate_indices VAR NUM)
                    set(INDICES )
                    set(N 0)
                    while(N LESS NUM)
                        list(APPEND INDICES ${N})
                        math(EXPR N "${N}+1")
                    endwhile()
                    set(${VAR} ${INDICES} PARENT_SCOPE)
                endfunction()
                # generate 500 indices
                generate_indices(INDICES 500)
                set(FORMAT_COMMANDS )
                # while there are still sources in list
                while(NUM_SOURCES GREATER 0)
                    # if below 500, re-generate indices
                    if(NUM_SOURCES LESS 500)
                        generate_indices(INDICES ${NUM_SOURCES})
                    endif()
                    # get the subset of first 500
                    list(GET SOURCES ${INDICES} SOURCES_SUBSET)
                    # add the target for block of 500
                    list(APPEND FORMAT_COMMANDS COMMAND ${CLANG_FORMAT_COMMAND} -i ${SOURCES_SUBSET})
                    # remove 500 we just processed
                    list(REMOVE_AT SOURCES ${INDICES})
                    # update the number of sources remaining
                    list(LENGTH SOURCES NUM_SOURCES)
                endwhile()
                # add the custom command
                list(LENGTH FORMAT_COMMANDS NUM_COMMANDS)
                if(NUM_COMMANDS GREATER 0)
                    add_custom_target(${FMTTARGET_NAME}
                        ${FORMAT_COMMANDS}
                        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                        COMMENT
                        "Running '${CLANG_FORMAT_COMMAND}' on source files in '${TARGET_NAME}' target (see ${FMTLOG_RELATIVE_OUT})...")
                endif()
            else()
                list(LENGTH SOURCES NUM_SOURCES)
                if(NUM_SOURCES GREATER 0)
                    # if less than 500 source files, add all in one target
                    add_custom_target(${FMTTARGET_NAME}
                        COMMAND ${CLANG_FORMAT_COMMAND} -i ${SOURCES}
                        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                        COMMENT
                        "Running '${CLANG_FORMAT_COMMAND}' on source files in '${TARGET_NAME}' target (see ${FMTLOG_RELATIVE_OUT})...")
                endif()
            endif()
        endif()
    endif()
ENDFUNCTION()

#------------------------------------------------------------------------------#
