if ((NOT DEFINED PICO_RISCV_TOOLCHAIN_PATH) OR (NOT DEFINED PICO_ARM_TOOLCHAIN_PATH))
    # Must define PICO_RISCV_TOOLCHAIN_PATH and PICO_ARM_TOOLCHAIN_PATH
    # to ensure both compilers are present
    message(
        "Skipping universal examples as PICO_RISCV_TOOLCHAIN_PATH and "
        "PICO_ARM_TOOLCHAIN_PATH are not defined"
        )
    return()
endif()

include(ExternalProject)

# The way this universal build works is it builds separate binaries for each platform,
# then links them into a single block loop. This universal binary will then run on any
# platform
#  - On RP2040 the bootrom will just execute the RP2040 binary at the start of flash
#  - On RP2350 the bootrom will search the block loop for the appropriate IMAGE_DEF for
#    Arm/RISC-V, translate it's address to the start of flash using the rolling window
#    delta, and execute it on the appropriate CPU
#
# The build will output a TARGET.bin file which can be written using picotool, and a
# TARGET.uf2 file which can be dragged and dropped onto the device in BOOTSEL mode
function (add_universal_target TARGET SOURCE)
    set(oneValueArgs SOURCE_TARGET PADDING PACKADDR)
    set(multiValueArgs PLATFORMS)
    cmake_parse_arguments(PARSE_ARGV 2 PARSED "" "${oneValueArgs}" "${multiValueArgs}")

    set(SOURCE_TARGET ${TARGET})
    if (PARSED_SOURCE_TARGET)
        set(SOURCE_TARGET ${PARSED_SOURCE_TARGET})
    endif()
    set(PADDING 0x1000)
    if (PARSED_PADDING)
        set(PADDING ${PARSED_PADDING})
    endif()
    set(PACKADDR 0x10000000)
    if (PARSED_PACKADDR)
        set(PACKADDR ${PARSED_PACKADDR})
    endif()
    set(PLATFORMS "rp2040;rp2350-arm-s;rp2350-riscv")
    if (PARSED_PLATFORMS)
        set(PLATFORMS ${PARSED_PLATFORMS})
    endif()
    # rp2040 must come first, as that has checksum requirements at the start of the binary
    list(FIND PLATFORMS "rp2040" idx)
    if (idx GREATER 0)
        message(FATAL_ERROR "rp2040 must come first in PLATFORMS for universal binaries")
    endif()

    add_custom_target(${TARGET} ALL)

    set(DEPS "")
    set(BINS "")
    # Build binaries for each of the platforms
    foreach(platform IN LISTS PLATFORMS)
        ExternalProject_Add(${TARGET}_${platform}
            PREFIX pioasm
            SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/wrapper
            BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}/${platform}/wrapper
            CMAKE_ARGS
                "--no-warn-unused-cli"
                "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}"
                "-DPICO_SDK_PATH:FILEPATH=${PICO_SDK_PATH}"
                "-DPICO_EXAMPLES_PATH:FILEPATH=${PICO_EXAMPLES_PATH}"
                "-DPICO_PLATFORM=${platform}"
                "-DPICO_ARM_TOOLCHAIN_PATH=${PICO_ARM_TOOLCHAIN_PATH}"
                "-DPICO_RISCV_TOOLCHAIN_PATH=${PICO_RISCV_TOOLCHAIN_PATH}"
                "-DPICO_BOARD_RP2040=${PICO_BOARD_RP2040}"
                "-DPICO_BOARD_RP2350=${PICO_BOARD_RP2350}"
                "-DUNIVERSAL_PROJECT_DIR:FILEPATH=${SOURCE}"
                "-DUNIVERSAL_BINARY_DIR:FILEPATH=${CMAKE_CURRENT_BINARY_DIR}/${TARGET}/${platform}"
                "-DSOURCE_TARGET=${SOURCE_TARGET}"
                "-Dpicotool_DIR=${picotool_INSTALL_DIR}/picotool"
                "-Dpioasm_DIR=${PIOASM_INSTALL_DIR}/pioasm"
            BUILD_ALWAYS 1 # force dependency checking
            INSTALL_COMMAND ""
            )

        set(ORIGINAL_BIN ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}/${platform}/${SOURCE_TARGET}.bin)

        list(APPEND DEPS ${TARGET}_${platform})
        list(APPEND BINS ${ORIGINAL_BIN})
    endforeach()

    set(BINDIR ${CMAKE_CURRENT_BINARY_DIR}/${TARGET})
    set(COMBINED ${BINDIR}/${TARGET}.bin)

    pico_init_picotool()
    if (NOT picotool_FOUND)
        message(FATAL_ERROR "Cannot link universal binary without picotool")
    endif()

    # Link the binaries for different platforms into a single block loop, with
    # appropriate rolling window deltas. This creates a universal binary file,
    # which will run on any of the platforms when loaded using picotool.
    add_custom_target(${TARGET}_combined
            COMMAND picotool link ${COMBINED} ${BINS} --pad ${PADDING}
            DEPENDS ${DEPS}
            )

    # Create UF2s targeting the absolute and rp2040 family IDs, then combine these
    # into a single universal UF2. This is required as there isn't a single family
    # ID which is accepted by both RP2040 and RP2350. Instead, the 2 UF2 files are
    # concatenated together and the device ignores the part not targeting it.
    add_custom_target(${TARGET}_rp2350_uf2
            COMMAND picotool uf2 convert ${COMBINED} ${BINDIR}/rp2350.uf2 --family absolute --offset ${PACKADDR}
            DEPENDS ${TARGET}_combined
            )
    add_custom_target(${TARGET}_rp2040_uf2
            COMMAND picotool uf2 convert ${COMBINED} ${BINDIR}/rp2040.uf2 --family rp2040 --offset ${PACKADDR}
            DEPENDS ${TARGET}_combined
            )
    add_custom_target(${TARGET}_uf2
            COMMAND ${CMAKE_COMMAND} -E cat ${BINDIR}/rp2040.uf2 ${BINDIR}/rp2350.uf2 > ${BINDIR}/${TARGET}.uf2
            DEPENDS ${TARGET}_rp2350_uf2 ${TARGET}_rp2040_uf2
            )

    add_dependencies(${TARGET} ${TARGET}_combined ${TARGET}_uf2)
endfunction()

# hello_universal binary
add_universal_target(hello_universal
    ${CMAKE_CURRENT_LIST_DIR}/hello_universal
    )

# blink binary
add_universal_target(blink_universal
    ${CMAKE_CURRENT_LIST_DIR}/../blink
    SOURCE_TARGET blink
    )

# nuke binary - is no_flash, so needs to be sent to SRAM on RP2040
add_universal_target(nuke_universal
    ${CMAKE_CURRENT_LIST_DIR}/../flash/nuke
    SOURCE_TARGET flash_nuke
    PACKADDR 0x20000000
    )
