#
# Load cmake modules
#

get_property(__idf_environment_set GLOBAL PROPERTY __IDF_ENVIRONMENT_SET)

if(NOT __idf_environment_set)

    # Set IDF_PATH, as nothing else will work without this.
    set(IDF_PATH "$ENV{IDF_PATH}")
    if(NOT IDF_PATH)
        # Documentation says you should set IDF_PATH in your environment, but we
        # can infer it relative to tools/cmake directory if it's not set.
        get_filename_component(IDF_PATH "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
    endif()
    file(TO_CMAKE_PATH "${IDF_PATH}" IDF_PATH)
    set(ENV{IDF_PATH} ${IDF_PATH})

    set(CMAKE_MODULE_PATH
        "${IDF_PATH}/tools/cmake"
        "${IDF_PATH}/tools/cmake/third_party"
        ${CMAKE_MODULE_PATH})
    include(utilities)
    include(components)
    include(kconfig)
    include(targets)
    include(git_submodules)
    include(GetGitRevisionDescription)
    include(crosstool_version_check)
    include(ldgen)

    set_default(PYTHON "python")

    if(NOT PYTHON_DEPS_CHECKED AND NOT BOOTLOADER_BUILD)
        message(STATUS "Checking Python dependencies...")
        execute_process(COMMAND "${PYTHON}" "${IDF_PATH}/tools/check_python_dependencies.py"
            RESULT_VARIABLE result)
        if(NOT result EQUAL 0)
            message(FATAL_ERROR "Some Python dependencies must be installed. Check above message for details.")
        endif()
    endif()

    idf_set_target()

    set_property(GLOBAL APPEND PROPERTY __IDF_COMPONENTS_PREFIX "idf_component")
    set_property(GLOBAL PROPERTY __IDF_ENVIRONMENT_SET 1)
endif()

macro(idf_set_variables)
    set_default(IDF_BUILD_ARTIFACTS OFF)

    if(IDF_BUILD_ARTIFACTS)
        if(NOT IDF_BUILD_ARTIFACTS_DIR OR NOT IDF_PROJECT_EXECUTABLE)
            message(FATAL_ERROR "IDF_BUILD_ARTIFACTS and IDF_PROJECT_EXECUTABLE needs to be specified \
                    if IDF_BUILD_ARTIFACTS is ON.")
        endif()
    endif()

    set_default(IDF_COMPONENT_DIRS "${IDF_EXTRA_COMPONENT_DIRS} ${IDF_PATH}/components")
    set_default(IDF_COMPONENTS "")
    set_default(IDF_COMPONENT_REQUIRES_COMMON "cxx ${IDF_TARGET} newlib freertos heap log soc \
                                                esp_rom esp_common xtensa")

    set(IDF_PROJECT_PATH "${CMAKE_SOURCE_DIR}")

    set(ESP_PLATFORM 1 CACHE BOOL INTERNAL)

    spaces2list(IDF_COMPONENT_DIRS)
    spaces2list(IDF_COMPONENTS)
    spaces2list(IDF_COMPONENT_REQUIRES_COMMON)
endmacro()

# Add all the IDF global compiler & preprocessor options
# (applied to all components). Some are config-dependent
#
# If you only want to set options for a particular component,
# don't call or edit this function. TODO DESCRIBE WHAT TO DO INSTEAD
#
function(idf_set_global_compile_options)
    # Temporary trick to support both gcc5 and gcc8 builds
    if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL 5.2.0)
        set(GCC_NOT_5_2_0 0 CACHE STRING "GCC is 5.2.0 version")
    else()
        set(GCC_NOT_5_2_0 1 CACHE STRING "GCC is not 5.2.0 version")
    endif()
    list(APPEND compile_definitions "GCC_NOT_5_2_0=${GCC_NOT_5_2_0}")

    list(APPEND compile_definitions "ESP_PLATFORM" "HAVE_CONFIG_H")

    list(APPEND compile_options "${CMAKE_C_FLAGS}")
    list(APPEND c_compile_options "${CMAKE_C_FLAGS}")
    list(APPEND cxx_compile_options "${CMAKE_CXX_FLAGS}")

    if(CONFIG_OPTIMIZATION_LEVEL_RELEASE)
        list(APPEND compile_options "-Os")
    else()
        list(APPEND compile_options "-Og")
    endif()

    list(APPEND c_compile_options "-std=gnu99")
    list(APPEND cxx_compile_options "-std=gnu++11" "-fno-rtti")

    if(CONFIG_CXX_EXCEPTIONS)
        list(APPEND cxx_compile_options "-fexceptions")
    else()
        list(APPEND cxx_compile_options "-fno-exceptions")
    endif()

    # Default compiler configuration
    list(APPEND compile_options "-ffunction-sections"
                                "-fdata-sections"
                                "-fstrict-volatile-bitfields"
                                "-nostdlib")

    list(APPEND compile_options "-Wall"
                                "-Werror=all"
                                "-Wno-error=unused-function"
                                "-Wno-error=unused-but-set-variable"
                                "-Wno-error=unused-variable"
                                "-Wno-error=deprecated-declarations"
                                "-Wextra"
                                "-Wno-unused-parameter"
                                "-Wno-sign-compare")

    list(APPEND c_compile_options "-Wno-old-style-declaration")

    if(CONFIG_DISABLE_GCC8_WARNINGS)
        list(APPEND compile_options
            "-Wno-parentheses"
            "-Wno-sizeof-pointer-memaccess"
            "-Wno-clobbered"
        )

        # doesn't use GCC_NOT_5_2_0 because idf_set_global_variables was not called before
        if(NOT CMAKE_C_COMPILER_VERSION VERSION_EQUAL 5.2.0)
            list(APPEND compile_options
                "-Wno-format-overflow"
                "-Wno-stringop-truncation"
                "-Wno-misleading-indentation"
                "-Wno-cast-function-type"
                "-Wno-implicit-fallthrough"
                "-Wno-unused-const-variable"
                "-Wno-switch-unreachable"
                "-Wno-format-truncation"
                "-Wno-memset-elt-size"
                "-Wno-int-in-bool-context"
            )
        endif()
    endif()

    # Stack protection
    if(NOT BOOTLOADER_BUILD)
        if(CONFIG_STACK_CHECK_NORM)
            list(APPEND compile_options "-fstack-protector")
        elseif(CONFIG_STACK_CHECK_STRONG)
            list(APPEND compile_options "-fstack-protector-strong")
        elseif(CONFIG_STACK_CHECK_ALL)
            list(APPEND compile_options "-fstack-protector-all")
        endif()
    endif()

    if(CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED)
        list(APPEND compile_definitions "NDEBUG")
    endif()

    # Always generate debug symbols (even in Release mode, these don't
    # go into the final binary so have no impact on size)
    list(APPEND compile_options "-ggdb")

    # Use EXTRA_CFLAGS, EXTRA_CXXFLAGS and EXTRA_CPPFLAGS to add more priority options to the compiler
    # EXTRA_CPPFLAGS is used for both C and C++
    # Unlike environments' CFLAGS/CXXFLAGS/CPPFLAGS which work for both host and target build,
    # these works only for target build
    set(EXTRA_CFLAGS "$ENV{EXTRA_CFLAGS}")
    set(EXTRA_CXXFLAGS "$ENV{EXTRA_CXXFLAGS}")
    set(EXTRA_CPPFLAGS "$ENV{EXTRA_CPPFLAGS}")
    spaces2list(EXTRA_CFLAGS)
    spaces2list(EXTRA_CXXFLAGS)
    spaces2list(EXTRA_CPPFLAGS)
    list(APPEND c_compile_options ${EXTRA_CFLAGS})
    list(APPEND cxx_compile_options ${EXTRA_CXXFLAGS})
    list(APPEND compile_options ${EXTRA_CPPFLAGS})

    set_default(IDF_COMPILE_DEFINITIONS "${compile_definitions}")
    set_default(IDF_COMPILE_OPTIONS "${compile_options}")
    set_default(IDF_C_COMPILE_OPTIONS "${c_compile_options}")
    set_default(IDF_CXX_COMPILE_OPTIONS "${cxx_compile_options}")
    set_default(IDF_INCLUDE_DIRECTORIES "${CONFIG_DIR}")

    set(IDF_COMPILE_DEFINITIONS ${IDF_COMPILE_DEFINITIONS} PARENT_SCOPE)
    set(IDF_COMPILE_OPTIONS ${IDF_COMPILE_OPTIONS} PARENT_SCOPE)
    set(IDF_C_COMPILE_OPTIONS ${IDF_C_COMPILE_OPTIONS} PARENT_SCOPE)
    set(IDF_CXX_COMPILE_OPTIONS ${IDF_CXX_COMPILE_OPTIONS} PARENT_SCOPE)
    set(IDF_INCLUDE_DIRECTORIES ${CONFIG_DIR} PARENT_SCOPE)
endfunction()

# Verify the IDF environment is configured correctly (environment, toolchain, etc)
function(idf_verify_environment)
    if(NOT CMAKE_PROJECT_NAME)
        message(FATAL_ERROR "Internal error, IDF project.cmake should have set this variable already")
    endif()

    # Check toolchain is configured properly in cmake
    if(NOT ( ${CMAKE_SYSTEM_NAME} STREQUAL "Generic" AND ${CMAKE_C_COMPILER} MATCHES xtensa))
        message(FATAL_ERROR "Internal error, toolchain has not been set correctly by project "
            "(or an invalid CMakeCache.txt file has been generated somehow)")
    endif()

    #
    # Warn if the toolchain version doesn't match
    #
    # TODO: make these platform-specific for diff toolchains
    get_expected_ctng_version(expected_toolchain expected_gcc)
    gcc_version_check("${expected_gcc}")
    crosstool_version_check("${expected_toolchain}")
endfunction()

# idf_get_git_revision
#
# Set global IDF_VER to the git revision of ESP-IDF.
#
# Running git_describe() here automatically triggers rebuilds
# if the ESP-IDF git version changes
function(idf_get_git_revision)
    git_describe(IDF_VER_GIT "${IDF_PATH}")
    if(EXISTS "${IDF_PATH}/version.txt")
        file(STRINGS "${IDF_PATH}/version.txt" IDF_VER_T)
        set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${IDF_PATH}/version.txt")
    else()
        set(IDF_VER_T ${IDF_VER_GIT})
    endif()
    # cut IDF_VER to required 32 characters.
    string(SUBSTRING "${IDF_VER_T}" 0 31 IDF_VER)
    message(STATUS "IDF_VER: ${IDF_VER}")
    add_definitions(-DIDF_VER=\"${IDF_VER}\")
    git_submodule_check("${IDF_PATH}")
    set(IDF_VER ${IDF_VER} PARENT_SCOPE)
endfunction()

# app_get_revision
#
# Set global PROJECT_VER
#
# If PROJECT_VER variable set in project CMakeLists.txt file, its value will be used.
# Else, if the _project_path/version.txt exists, its contents will be used as PROJECT_VER.
# Else, if the project is located inside a Git repository, the output of git describe will be used.
# Otherwise, PROJECT_VER will be "1".
function(app_get_revision _project_path)
    if(NOT DEFINED PROJECT_VER)
        if(EXISTS "${_project_path}/version.txt")
            file(STRINGS "${_project_path}/version.txt" PROJECT_VER)
            set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_project_path}/version.txt")
        else()
            git_describe(PROJECT_VER_GIT "${_project_path}")
            if(PROJECT_VER_GIT)
                set(PROJECT_VER ${PROJECT_VER_GIT})
            else()
                message(STATUS "Project is not inside a git repository, \
                        will not use 'git describe' to determine PROJECT_VER.")
                set(PROJECT_VER "1")
            endif()
        endif()
    endif()
    message(STATUS "Project version: ${PROJECT_VER}")
    set(PROJECT_VER ${PROJECT_VER} PARENT_SCOPE)
endfunction()

# idf_link_components
#
# Link library components to the target
function(idf_link_components target components)
    foreach(component ${components})
        component_get_target(component_target ${component})

        # Add each component library's link-time dependencies (which are otherwise ignored) to the executable
        # LINK_DEPENDS in order to trigger a re-link when needed (on Ninja/Makefile generators at least).
        # (maybe this should probably be something CMake does, but it doesn't do it...)
        if(TARGET ${component_target})
            get_target_property(type ${component_target} TYPE)
            get_target_property(imported ${component_target} IMPORTED)
            if(NOT imported)
                if(${type} STREQUAL STATIC_LIBRARY OR ${type} STREQUAL EXECUTABLE)
                    get_target_property(link_depends "${component_target}" LINK_DEPENDS)
                    if(link_depends)
                        set_property(TARGET ${target} APPEND PROPERTY LINK_DEPENDS "${link_depends}")
                    endif()
                endif()
            endif()

            if(${type} MATCHES .+_LIBRARY)
                list(APPEND libraries ${component_target})
            endif()
        endif()
    endforeach()

    if(libraries)
        # gc-sections is necessary for linking some IDF binary libraries
        # (and without it, IDF apps are much larger than they should be)
        target_link_libraries(${target} "-Wl,--gc-sections")
        target_link_libraries(${target} "-Wl,--start-group")
        target_link_libraries(${target} ${libraries})
        message(STATUS "Component libraries: ${IDF_COMPONENT_LIBRARIES}")
    endif()
endfunction()

# idf_import_components
#
# Adds ESP-IDF as a subdirectory to the current project and imports the components
function(idf_import_components var idf_path build_path)
    add_subdirectory(${idf_path} ${build_path})
    set(${var} ${BUILD_COMPONENTS} PARENT_SCOPE)
endfunction()
