cmake_minimum_required(VERSION 3.20)
project(n7_can_ota_firmware LANGUAGES C ASM)

if(NOT CMAKE_C_COMPILER_ID STREQUAL "GNU")
    message(FATAL_ERROR "GNU Arm Embedded GCC is required")
endif()

foreach(required OTA_PROJECT_ROOT OTA_SDK_ROOT OTA_SOURCE_ROOT OTA_GENERATED_SOURCE_ROOT OTA_OUTPUT_DIR)
    if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "")
        message(FATAL_ERROR "Missing -D${required}=...")
    endif()
    get_filename_component(${required} "${${required}}" ABSOLUTE)
endforeach()

file(MAKE_DIRECTORY "${OTA_OUTPUT_DIR}")

set(COMMON_FLAGS
    -mcpu=cortex-m0plus
    -mthumb
    -mlittle-endian
    -fsigned-char
    -ffunction-sections
    -fdata-sections
    -fno-common
    -fno-strict-aliasing
    -Og
    -g3
    -gdwarf-4
)

set(COMMON_DEFINITIONS
    tviibh8m
    CYT4BFBCJE
    CY_USE_PSVP=0
    CY_MCU_rev_d
)

set(SDK_INCLUDE_DIRECTORIES
    "${OTA_SDK_ROOT}/common/hdr"
    "${OTA_SDK_ROOT}/common/hdr/cmsis/include"
    "${OTA_SDK_ROOT}/common/src/drivers"
    "${OTA_SDK_ROOT}/common/src/mw"
    "${OTA_SDK_ROOT}/tviibh8m/hdr/rev_d"
    "${OTA_SDK_ROOT}/tviibh8m/hdr/rev_d/ip"
    "${OTA_SDK_ROOT}/tviibh8m/hdr/rev_d/mcureg"
    "${OTA_SDK_ROOT}/tviibh8m/src"
    "${OTA_SDK_ROOT}/tviibh8m/src/interrupts/rev_d"
    "${OTA_SDK_ROOT}/tviibh8m/src/drivers"
    "${OTA_SDK_ROOT}/tviibh8m/src/system/rev_d"
    "${OTA_SOURCE_ROOT}/src"
    "${OTA_GENERATED_SOURCE_ROOT}/src"
    "${OTA_PROJECT_ROOT}/firmware/application"
    "${OTA_PROJECT_ROOT}/firmware/bootloader/src"
    "${OTA_PROJECT_ROOT}/firmware/common"
)

file(GLOB_RECURSE SDK_DRIVER_SOURCES CONFIGURE_DEPENDS
    "${OTA_SDK_ROOT}/common/src/drivers/*.c"
    "${OTA_SDK_ROOT}/common/src/mw/*.c"
    "${OTA_SDK_ROOT}/tviibh8m/src/drivers/*.c"
    "${OTA_SDK_ROOT}/tviibh8m/src/mw/*.c"
)
list(FILTER SDK_DRIVER_SOURCES EXCLUDE REGEX "/cxpi/")
list(FILTER SDK_DRIVER_SOURCES EXCLUDE REGEX "/smif/ver3_1/")
list(FILTER SDK_DRIVER_SOURCES EXCLUDE REGEX "/smif/ver4/")

list(APPEND SDK_DRIVER_SOURCES
    "${OTA_SDK_ROOT}/common/src/startup/startup.c"
    "${OTA_SDK_ROOT}/common/src/startup/gcc/startup_cm0plus.S"
    "${OTA_SDK_ROOT}/common/src/drivers/syslib/gcc/cy_syslib_gcc.S"
    "${OTA_SDK_ROOT}/tviibh8m/src/interrupts/rev_d/cy_interrupt_map.c"
    "${OTA_SDK_ROOT}/tviibh8m/src/system/rev_d/system_tviibh8m_cm0plus.c"
    "${OTA_PROJECT_ROOT}/firmware/bootloader/src/ota_can.c"
)

add_library(t2g_platform OBJECT ${SDK_DRIVER_SOURCES})
target_include_directories(t2g_platform PRIVATE ${SDK_INCLUDE_DIRECTORIES})
target_compile_definitions(t2g_platform PRIVATE ${COMMON_DEFINITIONS})
target_compile_options(t2g_platform PRIVATE
    ${COMMON_FLAGS}
    "$<$<COMPILE_LANGUAGE:C>:-include${OTA_PROJECT_ROOT}/firmware/gcc/gcc_compat.h>"
)

add_library(ota_application_support OBJECT
    "${OTA_PROJECT_ROOT}/firmware/application/ota_enter_bootloader.c"
    "${OTA_PROJECT_ROOT}/firmware/application/ota_seat_state.c"
)
target_include_directories(ota_application_support PRIVATE ${SDK_INCLUDE_DIRECTORIES})
target_compile_definitions(ota_application_support PRIVATE ${COMMON_DEFINITIONS})
target_compile_options(ota_application_support PRIVATE
    ${COMMON_FLAGS}
    -include${OTA_PROJECT_ROOT}/firmware/gcc/gcc_compat.h
)

set(LINKER_TEMPLATE "${OTA_PROJECT_ROOT}/firmware/gcc/ota_linker.ld")
set(APP_LINKER "${CMAKE_CURRENT_BINARY_DIR}/ota_application.ld")
set(BOOT_LINKER "${CMAKE_CURRENT_BINARY_DIR}/ota_bootloader.ld")
execute_process(
    COMMAND ${CMAKE_C_COMPILER} -E -P -x c
        -D_LINK_flash_=1 -D_CORE_cm0plus_=1 -DCY_MCU_rev_d=1 -DCY_USE_PSVP=0
        -DOTA_APPLICATION_IMAGE=1 "${LINKER_TEMPLATE}" -o "${APP_LINKER}"
    RESULT_VARIABLE APP_LINKER_RESULT
)
if(NOT APP_LINKER_RESULT EQUAL 0)
    message(FATAL_ERROR "Application linker preprocessing failed: ${APP_LINKER_RESULT}")
endif()
execute_process(
    COMMAND ${CMAKE_C_COMPILER} -E -P -x c
        -D_LINK_flash_=1 -D_CORE_cm0plus_=1 -DCY_MCU_rev_d=1 -DCY_USE_PSVP=0
        -DOTA_BOOTLOADER_IMAGE=1 "${LINKER_TEMPLATE}" -o "${BOOT_LINKER}"
    RESULT_VARIABLE BOOT_LINKER_RESULT
)
if(NOT BOOT_LINKER_RESULT EQUAL 0)
    message(FATAL_ERROR "Bootloader linker preprocessing failed: ${BOOT_LINKER_RESULT}")
endif()

set(CRYPTO_LIBRARY
    "${OTA_SDK_ROOT}/tviibh8m/src/drivers/crypto/libs/libcrypto_server_cm0plus_gcc.a")

function(configure_image_target target output_name linker_script)
    set_target_properties(${target} PROPERTIES
        OUTPUT_NAME "${output_name}"
        SUFFIX ".elf"
        RUNTIME_OUTPUT_DIRECTORY "${OTA_OUTPUT_DIR}"
        LINK_DEPENDS "${linker_script}"
    )
    target_include_directories(${target} PRIVATE ${SDK_INCLUDE_DIRECTORIES})
    target_compile_definitions(${target} PRIVATE ${COMMON_DEFINITIONS})
    target_compile_options(${target} PRIVATE
        ${COMMON_FLAGS}
        -include${OTA_PROJECT_ROOT}/firmware/gcc/gcc_compat.h
    )
    target_link_options(${target} PRIVATE
        -mcpu=cortex-m0plus
        -mthumb
        -mlittle-endian
        -nostartfiles
        --specs=nano.specs
        --specs=nosys.specs
        "-T${linker_script}"
        -Wl,--gc-sections
        -Wl,--cref
        "-Wl,-Map,${OTA_OUTPUT_DIR}/${output_name}.map"
    )
    target_link_libraries(${target} PRIVATE "${CRYPTO_LIBRARY}")
    add_custom_command(TARGET ${target} POST_BUILD
        COMMAND ${CMAKE_OBJCOPY} --strip-debug
            "$<TARGET_FILE:${target}>" "${OTA_OUTPUT_DIR}/${output_name}.flash.elf"
        COMMAND ${CMAKE_OBJCOPY} -O ihex
            "$<TARGET_FILE:${target}>" "${OTA_OUTPUT_DIR}/${output_name}.hex"
        COMMAND ${CMAKE_OBJCOPY} -O binary
            "$<TARGET_FILE:${target}>" "${OTA_OUTPUT_DIR}/${output_name}.raw.bin"
        COMMAND ${CMAKE_SIZE} "$<TARGET_FILE:${target}>"
        VERBATIM
    )
endfunction()

set(PROFILES
    "motor0-forward|0|1"
    "motor0-reverse|0|2"
    "motor1-forward|1|1"
    "motor1-reverse|1|2"
    "motor2-forward|2|1"
    "motor2-reverse|2|2"
    "motor3-forward|3|1"
    "motor3-reverse|3|2"
)

foreach(profile IN LISTS PROFILES)
    string(REPLACE "|" ";" profile_fields "${profile}")
    list(GET profile_fields 0 profile_name)
    list(GET profile_fields 1 profile_mode)
    list(GET profile_fields 2 profile_direction)
    string(REPLACE "-" "_" target_suffix "${profile_name}")
    set(target "app_${target_suffix}")
    add_executable(${target}
        "${OTA_GENERATED_SOURCE_ROOT}/src/main_cm0plus.c"
        $<TARGET_OBJECTS:t2g_platform>
        $<TARGET_OBJECTS:ota_application_support>
    )
    target_compile_definitions(${target} PRIVATE
        MOTOR_AUTOMATED_TEST_SCENARIO=5
        MOTOR_AUTOMATED_TEST_MODE=${profile_mode}
        MOTOR_AUTOMATED_TEST_TARGET_X100=0
        MOTOR_AUTOMATED_TEST_DURATION_MS=0
        MOTOR_AUTOMATED_TEST_DIRECTION=${profile_direction}
        MOTOR_AUTOMATED_TEST_BUILD_KEY=0x8718A55A
        MOTOR_OTA_INTERACTIVE_TEST=1
    )
    configure_image_target(${target} "${profile_name}" "${APP_LINKER}")
endforeach()

add_executable(app_self_compose
    "${OTA_GENERATED_SOURCE_ROOT}/src/main_cm0plus.c"
    $<TARGET_OBJECTS:t2g_platform>
    $<TARGET_OBJECTS:ota_application_support>
)
target_compile_definitions(app_self_compose PRIVATE
    MOTOR_AUTOMATED_TEST_SCENARIO=0
    MOTOR_AUTOMATED_TEST_MODE=0
    MOTOR_AUTOMATED_TEST_TARGET_X100=0
    MOTOR_AUTOMATED_TEST_DURATION_MS=0
    MOTOR_AUTOMATED_TEST_DIRECTION=0
    MOTOR_AUTOMATED_TEST_BUILD_KEY=0x8718A55A
    MOTOR_OTA_INTERACTIVE_TEST=0
    MOTOR_OTA_SELF_COMPOSE=1
)
configure_image_target(app_self_compose "self-compose" "${APP_LINKER}")

add_executable(app_seat_calibration
    "${OTA_GENERATED_SOURCE_ROOT}/src/main_cm0plus.c"
    $<TARGET_OBJECTS:t2g_platform>
    $<TARGET_OBJECTS:ota_application_support>
)
target_compile_definitions(app_seat_calibration PRIVATE
    MOTOR_AUTOMATED_TEST_SCENARIO=0
    MOTOR_AUTOMATED_TEST_MODE=0
    MOTOR_AUTOMATED_TEST_TARGET_X100=0
    MOTOR_AUTOMATED_TEST_DURATION_MS=0
    MOTOR_AUTOMATED_TEST_DIRECTION=0
    MOTOR_AUTOMATED_TEST_BUILD_KEY=0x8718A55A
    MOTOR_OTA_INTERACTIVE_TEST=0
    MOTOR_OTA_SELF_COMPOSE=0
    MOTOR_OTA_CALIBRATION=1
)
configure_image_target(app_seat_calibration "seat-calibration" "${APP_LINKER}")

add_executable(ota_bootloader
    "${OTA_PROJECT_ROOT}/firmware/bootloader/src/main_cm0plus.c"
    "${OTA_PROJECT_ROOT}/firmware/bootloader/src/ota_flash.c"
    $<TARGET_OBJECTS:t2g_platform>
)
configure_image_target(ota_bootloader "ota-bootloader" "${BOOT_LINKER}")
