cmake_minimum_required(VERSION 3.17)

set(SORT_TYPE_NAME Lexical)

# This function post process the region for easier use.
#
# Tasks:
# - Symbol translation using a steering file is configured.
function(process_region)
  cmake_parse_arguments(REGION "" "OBJECT" "" ${ARGN})

  process_region_common(${ARGN})

  get_property(empty GLOBAL PROPERTY ${REGION_OBJECT}_EMPTY)
  if(NOT empty)
    # For scatter files we move any system symbols into first non-empty load section.
    get_parent(OBJECT ${REGION_OBJECT} PARENT parent TYPE SYSTEM)
    get_property(symbols GLOBAL PROPERTY ${parent}_SYMBOLS)
    set_property(GLOBAL APPEND PROPERTY ${REGION_OBJECT}_SYMBOLS ${symbols})
    set_property(GLOBAL PROPERTY ${parent}_SYMBOLS)
  endif()

  get_property(sections GLOBAL PROPERTY ${REGION_OBJECT}_SECTION_LIST_ORDERED)
  foreach(section ${sections})

    get_property(name_clean GLOBAL PROPERTY ${section}_NAME_CLEAN)
    get_property(noinput    GLOBAL PROPERTY ${section}_NOINPUT)
    get_property(type       GLOBAL PROPERTY ${section}_TYPE)

    get_property(indicies GLOBAL PROPERTY ${section}_SETTINGS_INDICIES)
    list(LENGTH indicies length)
    foreach(idx ${indicies})
      set(steering_postfixes Base Limit)
      get_property(symbols GLOBAL PROPERTY ${section}_SETTING_${idx}_SYMBOLS)
      get_property(sort    GLOBAL PROPERTY ${section}_SETTING_${idx}_SORT)
      get_property(offset  GLOBAL PROPERTY ${section}_SETTING_${idx}_OFFSET)
      if(DEFINED offset)
        foreach(symbol ${symbols})
          list(POP_FRONT steering_postfixes postfix)
          math(EXPR offset_dec "${offset}")
          set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C
            "Image$$${name_clean}_${offset_dec}$$${postfix}"
          )
          set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_FILE
            "RESOLVE ${symbol} AS Image$$${name_clean}_${offset_dec}$$${postfix}\n"
          )
        endforeach()
      elseif(sort)
        foreach(symbol ${symbols})
          list(POP_FRONT steering_postfixes postfix)
          set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C
            "Image$$${name_clean}_${idx}$$${postfix}"
          )
          set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_FILE
            "RESOLVE ${symbol} AS Image$$${name_clean}_${idx}$$${postfix}\n"
          )
        endforeach()
      elseif(DEFINED symbols AND ${length} EQUAL 1 AND noinput)
        set(steering_postfixes Base Limit)
        foreach(symbol ${symbols})
          list(POP_FRONT steering_postfixes postfix)
          set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C
            "Image$$${name_clean}$$${postfix}"
          )
          set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_FILE
            "RESOLVE ${symbol} AS Image$$${name_clean}$$${postfix}\n"
          )
        endforeach()
      endif()
    endforeach()

    if("${type}" STREQUAL BSS)
      set(ZI "$$ZI")
    endif()

    # Symbols translation here.
    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C "Image$$${name_clean}${ZI}$$Base")
    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C "Image$$${name_clean}${ZI}$$Length")
    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C "Load$$${name_clean}${ZI}$$Base")

    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_FILE
      "RESOLVE __${name_clean}_start AS Image$$${name_clean}${ZI}$$Base\n"
      "RESOLVE __${name_clean}_load_start AS Load$$${name_clean}${ZI}$$Base\n"
      "EXPORT  __${name_clean}_start AS __${name_clean}_start\n"
    )

    get_property(symbol_val GLOBAL PROPERTY SYMBOL_TABLE___${name_clean}_end)
    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C "Image$$${symbol_val}${ZI}$$Limit")
    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_FILE
        "RESOLVE __${name_clean}_end AS Image$$${symbol_val}${ZI}$$Limit\n"
      )

    if("${symbol_val}" STREQUAL "${name_clean}")
      set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_FILE
        "RESOLVE __${name_clean}_size AS Image$$${name_clean}${ZI}$$Length\n"
      )
    else()
      create_symbol(OBJECT ${REGION_OBJECT} SYMBOL __${name_clean}_size
        EXPR "(ImageLimit(${symbol_val}${ZI}) - ImageBase(${name_clean}${ZI}))"
      )
    endif()
    set(ZI)

  endforeach()

  get_property(groups GLOBAL PROPERTY ${REGION_OBJECT}_GROUP_LIST_ORDERED)
  foreach(group ${groups})
    get_property(name GLOBAL PROPERTY ${group}_NAME)
    string(TOLOWER ${name} name)

    get_objects(LIST sections OBJECT ${group} TYPE SECTION)
    list(GET sections 0 section)
    get_property(first_section_name GLOBAL PROPERTY ${section}_NAME_CLEAN)
    list(POP_BACK sections section)
    get_property(last_section_name GLOBAL PROPERTY ${section}_NAME_CLEAN)

    # Symbols translation here.
    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C "Image$$${first_section_name}$$Base")
    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C "Load$$${first_section_name}$$Base")
    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C "Image$$${last_section_name}$$Limit")

    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_FILE
      "RESOLVE __${name}_start AS Image$$${first_section_name}$$Base\n"
      "EXPORT  __${name}_start AS __${name}_start\n"
      "RESOLVE __${name}_load_start AS Load$$${first_section_name}$$Base\n"
      "EXPORT  __${name}_load_start AS __${name}_load_start\n"
    )

    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_FILE
        "RESOLVE __${name}_end AS Image$$${last_section_name}$$Limit\n"
      )

    create_symbol(OBJECT ${REGION_OBJECT} SYMBOL __${name}_size
      EXPR "(ImageLimit(${last_section_name}) - ImageBase(${first_section_name}))"
    )
  endforeach()

  get_property(symbols GLOBAL PROPERTY ${REGION_OBJECT}_SYMBOLS)
  foreach(symbol ${symbols})
    get_property(name GLOBAL PROPERTY ${symbol}_NAME)
    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_C "Image$$${name}$$Base")

    set_property(GLOBAL APPEND PROPERTY SYMBOL_STEERING_FILE
      "RESOLVE ${name} AS Image$$${name}$$Base\n"
    )
  endforeach()

endfunction()

#
# String functions - start
#

function(system_to_string)
  cmake_parse_arguments(STRING "" "OBJECT;STRING" "" ${ARGN})

  get_property(name    GLOBAL PROPERTY ${STRING_OBJECT}_NAME)
  get_property(regions GLOBAL PROPERTY ${STRING_OBJECT}_REGIONS)
  get_property(format  GLOBAL PROPERTY ${STRING_OBJECT}_FORMAT)

  foreach(region ${regions})
    get_property(empty GLOBAL PROPERTY ${region}_EMPTY)
    if(NOT empty)
      to_string(OBJECT ${region} STRING ${STRING_STRING})
    endif()
  endforeach()

  set(${STRING_STRING} ${${STRING_STRING}} PARENT_SCOPE)
endfunction()

function(group_to_string)
  cmake_parse_arguments(STRING "" "OBJECT;STRING" "" ${ARGN})

  get_property(type GLOBAL PROPERTY ${STRING_OBJECT}_OBJ_TYPE)
  if(${type} STREQUAL REGION)
    get_property(name GLOBAL PROPERTY ${STRING_OBJECT}_NAME)
    get_property(address GLOBAL PROPERTY ${STRING_OBJECT}_ADDRESS)
    get_property(size GLOBAL PROPERTY ${STRING_OBJECT}_SIZE)
    set(${STRING_STRING} "${${STRING_STRING}}\n${name} ${address} NOCOMPRESS ${size}\n{\n")
  endif()

  get_property(sections GLOBAL PROPERTY ${STRING_OBJECT}_SECTIONS_FIXED)
  foreach(section ${sections})
    to_string(OBJECT ${section} STRING ${STRING_STRING})
  endforeach()

  get_property(groups GLOBAL PROPERTY ${STRING_OBJECT}_GROUPS)
  foreach(group ${groups})
    to_string(OBJECT ${group} STRING ${STRING_STRING})
  endforeach()

  get_property(sections GLOBAL PROPERTY ${STRING_OBJECT}_SECTIONS)
  foreach(section ${sections})
    to_string(OBJECT ${section} STRING ${STRING_STRING})
  endforeach()

  get_parent(OBJECT ${STRING_OBJECT} PARENT parent TYPE SYSTEM)
  get_property(regions GLOBAL PROPERTY ${parent}_REGIONS)
  list(REMOVE_ITEM regions ${STRING_OBJECT})
  foreach(region ${regions})
    get_property(vma GLOBAL PROPERTY ${region}_NAME)
    get_property(sections GLOBAL PROPERTY ${STRING_OBJECT}_${vma}_SECTIONS_FIXED)
    foreach(section ${sections})
      to_string(OBJECT ${section} STRING ${STRING_STRING})
    endforeach()

    get_property(groups GLOBAL PROPERTY ${STRING_OBJECT}_${vma}_GROUPS)
    foreach(group ${groups})
      to_string(OBJECT ${group} STRING ${STRING_STRING})
    endforeach()

    get_property(sections GLOBAL PROPERTY ${STRING_OBJECT}_${vma}_SECTIONS)
    foreach(section ${sections})
      to_string(OBJECT ${section} STRING ${STRING_STRING})
    endforeach()
  endforeach()

  get_property(symbols GLOBAL PROPERTY ${STRING_OBJECT}_SYMBOLS)
  foreach(symbol ${symbols})
    to_string(OBJECT ${symbol} STRING ${STRING_STRING})
  endforeach()

  if(${type} STREQUAL REGION)
    set(${STRING_STRING} "${${STRING_STRING}}\n}\n")
  endif()
  set(${STRING_STRING} ${${STRING_STRING}} PARENT_SCOPE)
endfunction()


function(section_to_string)
  cmake_parse_arguments(STRING "" "SECTION;STRING" "" ${ARGN})

  get_property(name     GLOBAL PROPERTY ${STRING_SECTION}_NAME)
  get_property(address  GLOBAL PROPERTY ${STRING_SECTION}_ADDRESS)
  get_property(type     GLOBAL PROPERTY ${STRING_SECTION}_TYPE)
  get_property(align    GLOBAL PROPERTY ${STRING_SECTION}_ALIGN)
  get_property(subalign GLOBAL PROPERTY ${STRING_SECTION}_SUBALIGN)
  get_property(endalign GLOBAL PROPERTY ${STRING_SECTION}_ENDALIGN)
  get_property(vma      GLOBAL PROPERTY ${STRING_SECTION}_VMA)
  get_property(lma      GLOBAL PROPERTY ${STRING_SECTION}_LMA)
  get_property(noinput  GLOBAL PROPERTY ${STRING_SECTION}_NOINPUT)
  get_property(noinit   GLOBAL PROPERTY ${STRING_SECTION}_NOINIT)

  string(REGEX REPLACE "^[\.]" "" name_clean "${name}")
  string(REPLACE "." "_" name_clean "${name_clean}")

  set(TEMP "  ${name_clean}")
  if(DEFINED address)
    set(TEMP "${TEMP} ${address}")
  else()
    set(TEMP "${TEMP} +0")
  endif()

  if(noinit)
    # Currently we simply uses offset +0, but we must support offset defined
    # externally.
    set(TEMP "${TEMP} UNINIT")
  endif()

  if(subalign)
    # Currently we simply uses offset +0, but we must support offset defined
    # externally.
    set(TEMP "${TEMP} ALIGN ${subalign}")
  endif()

  if(NOT noinput)
    set(TEMP "${TEMP}\n  {")

    if("${type}" STREQUAL NOLOAD)
      set(TEMP "${TEMP}\n    *.o(${name}*)")
      set(TEMP "${TEMP}\n    *.o(${name}*.*)")
    elseif(VMA_FLAGS)
      # ToDo: Proper names as provided by armclang
#      set(TEMP "${TEMP}\n    *.o(${SEC_NAME}*, +${VMA_FLAGS})")
#      set(TEMP "${TEMP}\n    *.o(${SEC_NAME}*.*, +${VMA_FLAGS})")
      set(TEMP "${TEMP}\n    *.o(${name}*)")
      set(TEMP "${TEMP}\n    *.o(${name}*.*)")
    else()
      set(TEMP "${TEMP}\n    *.o(${name}*)")
      set(TEMP "${TEMP}\n    *.o(${name}*.*)")
    endif()
  else()
    set(empty TRUE)
  endif()

  get_property(indicies GLOBAL PROPERTY ${STRING_SECTION}_SETTINGS_INDICIES)
  foreach(idx ${indicies})
    get_property(align    GLOBAL PROPERTY ${STRING_SECTION}_SETTING_${idx}_ALIGN)
    get_property(any      GLOBAL PROPERTY ${STRING_SECTION}_SETTING_${idx}_ANY)
    get_property(first    GLOBAL PROPERTY ${STRING_SECTION}_SETTING_${idx}_FIRST)
    get_property(keep     GLOBAL PROPERTY ${STRING_SECTION}_SETTING_${idx}_KEEP)
    get_property(sort     GLOBAL PROPERTY ${STRING_SECTION}_SETTING_${idx}_SORT)
    get_property(flags    GLOBAL PROPERTY ${STRING_SECTION}_SETTING_${idx}_FLAGS)
    get_property(input    GLOBAL PROPERTY ${STRING_SECTION}_SETTING_${idx}_INPUT)
    get_property(offset   GLOBAL PROPERTY ${STRING_SECTION}_SETTING_${idx}_OFFSET)
    if(DEFINED offset)
      set(section_close TRUE)
      math(EXPR offset_dec "${offset} + 0")
      if(empty)
        set(TEMP "${TEMP} EMPTY 0x0\n  {")
        set(empty FALSE)
      endif()
      set(last_index ${offset_dec})
      if(sort)
        set(sorttype "SORTTYPE ${SORT_TYPE_${sort}}")
      endif()
      set(TEMP "${TEMP}\n  }")
      set(TEMP "${TEMP}\n  ${name_clean}_${offset_dec} (ImageBase(${name_clean}) + ${offset}) ${sorttype}\n {")
    elseif(sort)
      set(section_close TRUE)
      if(empty)
        set(TEMP "${TEMP} EMPTY 0x0\n  {")
        set(empty FALSE)
      endif()
      set(last_index ${idx})
      set(TEMP "${TEMP}\n  }")
      set(TEMP "${TEMP}\n  ${name_clean}_${idx} +0 SORTTYPE ${SORT_TYPE_${sort}}\n  {")
    endif()

    if(empty)
      set(TEMP "${TEMP}\n  {")
      set(empty FALSE)
    endif()

    foreach(setting ${input})
      #set(SETTINGS ${SETTINGS_INPUT})

#      # ToDo: The code below had en error in original implementation, causing
#      #       settings not to be applied
#      #       Verify behaviour and activate if working as intended.
#      if(align)
#        set(setting "${setting}, OVERALIGN ${align}")
#      endif()

      #if(SETTINGS_KEEP)
      # armlink has --keep=<section_id>, but is there an scatter equivalant ?
      #endif()

      if(first)
        set(setting "${setting}, +First")
        set(first "")
      endif()

      set(TEMP "${TEMP}\n    *.o(${setting})")
    endforeach()

    if(any)
      if(NOT flags)
        message(FATAL_ERROR ".ANY requires flags to be set.")
      endif()
      string(REPLACE ";" " " flags "${flags}")

      set(TEMP "${TEMP}\n    .ANY (${flags})")
    endif()
  endforeach()

  if(section_close OR DEFINED endalign)
    set(section_close)
    set(TEMP "${TEMP}\n  }")

    if(DEFINED endalign)
      if(DEFINED last_index)
        set(align_expr "AlignExpr(ImageLimit(${name_clean}_${last_index}), ${endalign}) FIXED")
      else()
        set(align_expr "AlignExpr(ImageLimit(${name_clean}), ${endalign}) FIXED")
      endif()
    else()
      set(align_expr "+0")
    endif()

    set(TEMP "${TEMP}\n  ${name_clean}_end ${align_expr} EMPTY 0x0\n  {")
    set(last_index)
  endif()

  set(TEMP "${TEMP}")
  # ToDo: add patterns here.

  if("${type}" STREQUAL BSS)
    set(ZI "$$ZI")
  endif()

  set(TEMP "${TEMP}\n  }")

  set(${STRING_STRING} "${${STRING_STRING}}\n${TEMP}\n" PARENT_SCOPE)
endfunction()

function(symbol_to_string)
  cmake_parse_arguments(STRING "" "SYMBOL;STRING" "" ${ARGN})

  get_property(name     GLOBAL PROPERTY ${STRING_SYMBOL}_NAME)
  get_property(expr     GLOBAL PROPERTY ${STRING_SYMBOL}_EXPR)
  get_property(size     GLOBAL PROPERTY ${STRING_SYMBOL}_SIZE)
  get_property(symbol   GLOBAL PROPERTY ${STRING_SYMBOL}_SYMBOL)
  get_property(subalign GLOBAL PROPERTY ${STRING_SYMBOL}_SUBALIGN)

  string(REPLACE "\\" "" expr "${expr}")
  string(REGEX MATCHALL "%([^%]*)%" match_res ${expr})

  foreach(match ${match_res})
    string(REPLACE "%" "" match ${match})
    get_property(symbol_val GLOBAL PROPERTY SYMBOL_TABLE_${match})
    string(REPLACE "%${match}%" "ImageBase(${symbol_val})" expr ${expr})
  endforeach()

  if(DEFINED subalign)
    set(subalign "ALIGN ${subalign}")
  endif()

  if(NOT DEFINED size)
    set(size "0x0")
  endif()

  set(${STRING_STRING}
    "${${STRING_STRING}}\n  ${symbol} ${expr} ${subalign} ${size} { }\n"
    PARENT_SCOPE
  )
endfunction()

include(${CMAKE_CURRENT_LIST_DIR}/../linker_script_common.cmake)

if(DEFINED STEERING_C)
  get_property(symbols_c GLOBAL PROPERTY SYMBOL_STEERING_C)
  file(WRITE ${STEERING_C}  "/* AUTO-GENERATED - Do not modify\n")
  file(APPEND ${STEERING_C} " * AUTO-GENERATED - All changes will be lost\n")
  file(APPEND ${STEERING_C} " */\n")
  foreach(symbol ${symbols_c})
    file(APPEND ${STEERING_C} "extern char ${symbol}[];\n")
  endforeach()

  file(APPEND ${STEERING_C} "\nint __armlink_symbol_steering(void) {\n")
  file(APPEND ${STEERING_C} "\treturn\n")
  foreach(symbol ${symbols_c})
    file(APPEND ${STEERING_C} "\t\t${OPERAND} (int)${symbol}\n")
    set(OPERAND "&")
  endforeach()
  file(APPEND ${STEERING_C} "\t;\n}\n")
endif()

if(DEFINED STEERING_FILE)
  get_property(steering_content GLOBAL PROPERTY SYMBOL_STEERING_FILE)
  file(WRITE ${STEERING_FILE}  "; AUTO-GENERATED - Do not modify\n")
  file(APPEND ${STEERING_FILE} "; AUTO-GENERATED - All changes will be lost\n")
  file(APPEND ${STEERING_FILE} ${steering_content})
endif()
