include_guard(DIRECTORY)

#[[
    Add rules for creating Google Protobuf source files.

    qm_create_protobuf(<OUT>
        INPUT <files...>
        [OUTPUT_DIR <dir>]
        [TARGET <target>]
        [INCLUDE_DIRECTORIES <dirs...>]
        [OPTIONS <options...>]
        [DEPENDS <deps...>]
        [CREATE_ONCE]
    )

    INPUT: source files
    OUTPUT_DIR: output directory

    TARGET: add a custom target to run the generating command

    INCLUDE_DIRECTORIES: extra include directories
    OPTIONS: extra options passed to protobuf compiler
    DEPENDS: dependencies

    CREATE_ONCE: create proto code files at configure phase if not exist

    OUT: output source file paths
#]]
function(qm_create_protobuf _out)
    set(options CREATE_ONCE)
    set(oneValueArgs OUTPUT_DIR TARGET)
    set(multiValueArgs INPUT INCLUDE_DIRECTORIES OPTIONS DEPENDS)
    cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    # Find `protoc`
    if(NOT PROTOC_EXECUTABLE)
        if(NOT TARGET protobuf::protoc)
            message(FATAL_ERROR "qm_create_protobuf: protobuf compiler not found. Add find_package(Protobuf) to CMake to enable.")
        endif()

        get_target_property(PROTOC_EXECUTABLE protobuf::protoc LOCATION)

        if(NOT PROTOC_EXECUTABLE)
            message(FATAL_ERROR "qm_create_protobuf: failed to get the location of `protoc`, you could set PROTOC_EXECUTABLE manually.")
        endif()

        # Cache value
        set(PROTOC_EXECUTABLE ${PROTOC_EXECUTABLE} PARENT_SCOPE)
    endif()

    if(NOT FUNC_INPUT)
        message(FATAL_ERROR "qm_create_protobuf: INPUT not specified.")
    endif()

    if(FUNC_OUTPUT_DIR)
        get_filename_component(_out_dir ${FUNC_OUTPUT_DIR} ABSOLUTE)
        file(MAKE_DIRECTORY ${_out_dir})
    else()
        set(_out_dir ${CMAKE_CURRENT_BINARY_DIR})
    endif()

    # Collect include paths and out files
    set(_include_options)
    set(_out_files)
    set(_input_names)

    foreach(_item IN LISTS FUNC_INCLUDE_DIRECTORIES)
        get_filename_component(_abs_path ${_item} ABSOLUTE)
        list(APPEND _include_options -I${_abs_path})
    endforeach()

    foreach(_item IN LISTS FUNC_INPUT)
        get_filename_component(_item ${_item} ABSOLUTE)
        get_filename_component(_abs_path ${_item} DIRECTORY)
        list(APPEND _include_options -I${_abs_path})
    endforeach()

    list(REMOVE_DUPLICATES _include_options)

    set(_create_once_warning)
    set(_create_once_warning_printed off)

    # Prepare for create once
    if(FUNC_CREATE_ONCE)
        # Check if options contain generator expressions
        foreach(_opt IN LISTS _include_options LISTS FUNC_OPTIONS)
            string(GENEX_STRIP "${_opt}" _no_genex)

            if(NOT _no_genex STREQUAL _opt)
                set(_create_once_warning "options contain generator expressions, skip generating source file now")
                break()
            endif()
        endforeach()
    endif()

    foreach(_item IN LISTS FUNC_INPUT)
        get_filename_component(_basename ${_item} NAME_WLE)
        list(APPEND _out_files ${_out_dir}/${_basename}.pb.h ${_out_dir}/${_basename}.pb.cc)

        get_filename_component(_name ${_item} NAME)
        list(APPEND _input_names ${_name})

        if(FUNC_CREATE_ONCE AND(NOT EXISTS ${_out_dir}/${_basename}.pb.h OR NOT EXISTS ${_out_dir}/${_basename}.pb.cc))
            if(_create_once_warning)
                if(NOT _create_once_warning_printed)
                    message(WARNING "qm_create_protobuf: ${_create_once_warning}")
                    set(_create_once_warning_printed on)
                endif()
            else()
                get_filename_component(_abs_file ${_item} ABSOLUTE)

                if(NOT EXISTS ${_abs_file})
                    message(WARNING "qm_create_protobuf: input file \"${_name}\" is not available, skip generating source file now")
                else()
                    message(STATUS "Protoc: Generating ${_basename}.pb.h, ${_basename}.pb.cc")
                    execute_process(COMMAND
                        ${PROTOC_EXECUTABLE} --cpp_out=${_out_dir} ${_include_options} ${FUNC_OPTIONS} ${_name}
                    )
                endif()
            endif()
        endif()

        add_custom_command(
            OUTPUT ${_out_dir}/${_basename}.pb.h ${_out_dir}/${_basename}.pb.cc
            COMMAND ${PROTOC_EXECUTABLE} --cpp_out=${_out_dir} ${_include_options} ${FUNC_OPTIONS} ${_name}
            DEPENDS ${_item} ${FUNC_DEPENDS}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
            VERBATIM
        )
    endforeach()

    if(FUNC_TARGET)
        if(NOT TARGET ${FUNC_TARGET})
            add_custom_target(${FUNC_TARGET})
        endif()

        add_dependencies(${FUNC_TARGET} ${_out_files})
    endif()

    set(${_out} ${_out_files} PARENT_SCOPE)
endfunction()
