#
# cFS Mission global CMake function definitions
#
# This is included by the top-level script and can define
# routines and variables that that are referenced in both 
# the mission (non-arch) and arch-specific builds
#

##################################################################
#
# FUNCTION: setup_globals
#
# Set up global mission configuration variables.
# This function determines the mission configuration directory and 
# also reads any startup state info from file(s) on the disk
#
function(setup_globals)

  # First read in any variables that are passed in from the parent process
  # There may be many of these and they may not all be passable via -D options
  # Although these are NOT officially cache variables, this is one of the first
  # things done and the file will be re-read with each subsequent CMake run so
  # the values will effectively persist just like cache variables do.
  if (EXISTS "${CMAKE_BINARY_DIR}/mission_archbuild_vars.cache")
  
    file(STRINGS "${CMAKE_BINARY_DIR}/mission_archbuild_vars.cache" PARENTVARS)
    set(VARNAME)
    foreach(PV ${PARENTVARS})
      if (VARNAME)
        set(${VARNAME} ${PV})
        set(${VARNAME} ${PV} PARENT_SCOPE)
        set(VARNAME)
      else()
        set(VARNAME ${PV})
      endif()
    endforeach(PV ${PARENTVARS})
    
  else()      

    # this is the parent (mission) build and variable values must be determined
    # Obtain the "real" top-level source directory and set it in parent scope
    if (NOT DEFINED MISSION_SOURCE_DIR)
      get_filename_component(MISSION_SOURCE_DIR "${CMAKE_SOURCE_DIR}/.." ABSOLUTE)
      set(MISSION_SOURCE_DIR "${MISSION_SOURCE_DIR}" CACHE PATH "Top level mission source directory")
    endif(NOT DEFINED MISSION_SOURCE_DIR)

    if (NOT "$ENV{SIMULATION}" STREQUAL "")
      set(SIMULATION "$ENV{SIMULATION}" CACHE STRING "Enable simulation mode using specified toolchain")
    endif()

    # The configuration should be in a subdirectory named "<mission>_defs".  If there is one
    # and only one of these, this is assumed to be it.  If there is more than one then the
    # user MUST specify which one is intended to be used by setting MISSIONCONFIG in the environment
    if (NOT MISSIONCONFIG)
      set(MCTEMP $ENV{MISSIONCONFIG})
      if ("${MCTEMP}" STREQUAL "")
        file(GLOB DEFDIRS RELATIVE "${MISSION_SOURCE_DIR}" "${MISSION_SOURCE_DIR}/*_defs")
        list(LENGTH DEFDIRS DDLEN)
        if (NOT DDLEN EQUAL 1)
          message(FATAL_ERROR "Unable to automatically determine the mission config directory.  Specify it with the MISSIONCONFIG variable.")
        endif (NOT DDLEN EQUAL 1)
        string(REPLACE "_defs" "" MCTEMP ${DEFDIRS})
        message(STATUS "Mission configuration ${MCTEMP} automatically selected")
      endif ("${MCTEMP}" STREQUAL "")
      # Set the MISSIONCONFIG as a CMake cache variable so it will be preserved for future runs     
      set(MISSIONCONFIG ${MCTEMP} CACHE STRING "Mission configuration selection")
    endif(NOT MISSIONCONFIG)
    
    # Export values to parent level 
    set(MISSION_DEFS "${MISSION_SOURCE_DIR}/${MISSIONCONFIG}_defs" PARENT_SCOPE)
    set(MISSION_BINARY_DIR ${CMAKE_BINARY_DIR} PARENT_SCOPE)
         
  endif()

  # Define the "_ENHANCED_BUILD_" preprocessor directive which can be used to 
  # gate code that should only be compiled when using these CMake scripts.  This
  # can perserve compatibility with the old makefiles such that the code
  # can still be built using the old method without the new features.
  add_definitions("-D_ENHANCED_BUILD_")

endfunction(setup_globals)


##################################################################
#
# FUNCTION: generate_config_includefile
#
# A function to create safe include file wrappers
#
# Rather than symlinking to the include file (which might not work the same on all platforms)
# we can create a build-specific include file that just #include's the real file
#
# This also supports "stacking" multiple component files together by specifying more than one
# source file for the wrapper.
# 
# In all cases, care must be used when updating such files -- if the timestamp is updated then
# everything that uses it will be rebuilt even if the content did not change.  So the file content
# is checked against any pre-existing files, and if the content is the same then the update is
# skipped, preserving the original timestamp.
#
function(generate_config_includefile DESTFILE SUFFIX)

  # Determine the absolute location for this wrapper file
  if (NOT IS_ABSOLUTE ${DESTFILE})
    set(DESTFILE ${CMAKE_BINARY_DIR}/${DESTFILE})
  endif (NOT IS_ABSOLUTE ${DESTFILE})
  
  set(INCL_LIST)
  set(DEST_CONTENTSTR "/* This is a wrapper file generated by the build system - DO NOT EDIT */\n")
  foreach(SRC ${ARGN})
    set(SRC_LOCAL_PATH "${MISSION_DEFS}/${SRC}_${SUFFIX}")
    # only add this if not already included (prevent double inclusion) */
    list(FIND INCL_LIST "${SRC_LOCAL_PATH}" INCL_INDX)
    if (INCL_INDX LESS 0)      
      list(APPEND INCL_LIST "${SRC_LOCAL_PATH}")
      if (EXISTS "${SRC_LOCAL_PATH}")
        file(TO_NATIVE_PATH "${MISSION_DEFS}/${SRC}_${SUFFIX}" SRC_NATIVE)
        set(DEST_CONTENTSTR "${DEST_CONTENTSTR}#include \"${SRC_NATIVE}\"\n")
      else()
        set(DEST_CONTENTSTR "${DEST_CONTENTSTR}/* ${SRC_LOCAL_PATH} does not exist */\n")
      endif (EXISTS "${SRC_LOCAL_PATH}")
    endif (INCL_INDX LESS 0)      
  endforeach(SRC ${SRCFILES})
  if (EXISTS "${DESTFILE}")
    file(READ "${DESTFILE}" DEST_EXISTING)
  else (EXISTS "${DESTFILE}")
    set(DEST_EXISTING)
  endif (EXISTS "${DESTFILE}")
  if (NOT DEST_CONTENTSTR STREQUAL DEST_EXISTING)
    file(WRITE "${DESTFILE}" "${DEST_CONTENTSTR}")
  endif (NOT DEST_CONTENTSTR STREQUAL DEST_EXISTING)
  
  # Create a copy of the generated file in a location where it can
  # be found by an editor such as Eclipse.  The user will still have to
  # add the path to the IDEs include search path, but this makes it easier
  # by at least putting them all in one spot.  This is not used for the build.
  get_filename_component(DESTBASENAME "${DESTFILE}" NAME)
  if (MISSION_BINARY_DIR)
    set(EDITOR_COPY_DIR ${MISSION_BINARY_DIR}/editor_inc)
  else()
    set(EDITOR_COPY_DIR ${CMAKE_BINARY_DIR}/editor_inc)
  endif()
  
  file(MAKE_DIRECTORY ${EDITOR_COPY_DIR})
  execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DESTFILE} ${EDITOR_COPY_DIR}/${DESTBASENAME})
  
endfunction(generate_config_includefile DESTFILE SUFFIX)


##################################################################
#
# FUNCTION: read_targetconfig
#
# Scan the list of targets and organize by target system type.
# This function sets up the following variables in the global scope:
#   TGTSYS_LIST: list of CPU architectures used in the build.  Note this
#       will always contain a "native" target (for tools at least) which 
#       is forced to be last.
#   MISSION_APPS: full list of applications specified in the whole mission 
#   SYSID_<arch>: set for each entry of TGTSYS_LIST, and indicates the
#       toolchain specified in the target file for that CPU arch.
#   TGTSYS_<arch>: set to a list of CPU numbers that utilize the same arch 
#   TGTSYS_<arch>_APPS: set for each entry of TGTSYS_LIST, and indicates the
#       full set of applications that need to built for that target architecture
#   TGTSYS_<arch>_DRIVERS: set for each entry of TGTSYS_LIST, and indicates the
#       full set of device drivers that need to built for that target architecture
#
function(read_targetconfig)

  set(TGTSYS_LIST)
  set(MISSION_APPS)
  set(MISSION_PSPMODULES)
  set(TGTID 0)
  while(1)
    math(EXPR TGTID "${TGTID} + 1")
    if (NOT DEFINED TGT${TGTID}_NAME)
      break()
    endif()
    if (NOT DEFINED TGT${TGTID}_SYSTEM)
      set(TGT${TGTID}_SYSTEM "cpu${TGTID}")
    endif()
    
    if (SIMULATION)
      # if simulation use simulation system architecture for all targets
      set(CURRSYS "${SIMULATION}")
    else (SIMULATION)
      # get the target system arch identifier string
      set(CURRSYS "${TGT${TGTID}_SYSTEM}")
    endif (SIMULATION)
    
    # make sure the string is safe for a variable name
    string(REGEX REPLACE "[^A-Za-z0-9]" "_" SYSVAR "${CURRSYS}")

    # save the unmodified name for future reference
    set(SYSID_${SYSVAR} "${CURRSYS}" PARENT_SCOPE)
    
    # Append to global lists
    list(APPEND TGTSYS_LIST "${SYSVAR}")
    list(APPEND TGTSYS_${SYSVAR} "${TGTID}")
    list(APPEND TGTSYS_${SYSVAR}_APPS ${TGT${TGTID}_APPLIST})
    list(APPEND TGTSYS_${SYSVAR}_STATICAPPS ${TGT${TGTID}_STATIC_APPLIST})
    list(APPEND TGTSYS_${SYSVAR}_PSPMODULES ${TGT${TGTID}_PSP_MODULELIST})
    list(APPEND MISSION_APPS ${TGT${TGTID}_APPLIST} ${TGT${TGTID}_STATIC_APPLIST})
    list(APPEND MISSION_PSPMODULES ${TGT${TGTID}_PSP_MODULELIST})
  
  endwhile()

  # Remove duplicate entries in the generated lists
  list(REMOVE_DUPLICATES TGTSYS_LIST)
  if (MISSION_APPS)
    list(REMOVE_DUPLICATES MISSION_APPS)
  endif (MISSION_APPS)
  if (MISSION_PSPMODULES)
    list(REMOVE_DUPLICATES MISSION_PSPMODULES)
  endif(MISSION_PSPMODULES)

  # Set the final variable value(s) in the parent scope
  set(TGTSYS_LIST ${TGTSYS_LIST} PARENT_SCOPE)
  set(MISSION_APPS ${MISSION_APPS} PARENT_SCOPE)
  set(MISSION_PSPMODULES ${MISSION_PSPMODULES} PARENT_SCOPE)
  
  foreach(SYSVAR ${TGTSYS_LIST})
    set(TGTSYS_${SYSVAR} ${TGTSYS_${SYSVAR}} PARENT_SCOPE)
    if(TGTSYS_${SYSVAR}_APPS)
      list(REMOVE_DUPLICATES TGTSYS_${SYSVAR}_APPS)
      set(TGTSYS_${SYSVAR}_APPS ${TGTSYS_${SYSVAR}_APPS} PARENT_SCOPE)
    endif(TGTSYS_${SYSVAR}_APPS)
    if(TGTSYS_${SYSVAR}_STATICAPPS)
      list(REMOVE_DUPLICATES TGTSYS_${SYSVAR}_STATICAPPS)
      set(TGTSYS_${SYSVAR}_STATICAPPS ${TGTSYS_${SYSVAR}_STATICAPPS} PARENT_SCOPE)
    endif(TGTSYS_${SYSVAR}_STATICAPPS)
    if(TGTSYS_${SYSVAR}_PSPMODULES)
      list(REMOVE_DUPLICATES TGTSYS_${SYSVAR}_PSPMODULES)
      set(TGTSYS_${SYSVAR}_PSPMODULES ${TGTSYS_${SYSVAR}_PSPMODULES} PARENT_SCOPE)
    endif(TGTSYS_${SYSVAR}_PSPMODULES)
  endforeach(SYSVAR IN LISTS TGTSYS_LIST)
  
endfunction(read_targetconfig)

##################################################################
#
# FUNCTION: get_current_cflags
#
# Convert the input string, which is a simple text string of compiler flags such
# as CMAKE_C_FLAGS or CMAKE_CXX_FLAGS, and convert it to a list of individual options
#
# In addition, the "-I" options from include_directories() and -D options from 
# add_definitions() will be added to the output list.  The contents of these will be
# obtained via the properities of the current source directory. 
#
function(get_current_cflags OUTPUT_LIST INPUT_FLAGS)

  # Start by converting the supplied string to a list 
  set(FLAGLIST)
  foreach (FLGSTR ${INPUT_FLAGS} ${ARGN})
    string(REGEX REPLACE " +" ";" TEMPFLG ${FLGSTR})
    list(APPEND FLAGLIST ${TEMPFLG})
  endforeach (FLGSTR ${INPUT_FLAGS} ${ARGN})
  
  # Append any compile definitions from the directory properties
  get_directory_property(CURRENT_DEFS COMPILE_DEFINITIONS)
  foreach(DEF ${CURRENT_DEFS})
    list(APPEND FLAGLIST "-D${DEF}")
  endforeach(DEF ${CURRENT_DEFS})
  
  # Append any include directories from the directory properties
  get_directory_property(CURRENT_INCDIRS INCLUDE_DIRECTORIES)
  foreach(INC ${CURRENT_INCDIRS})
    list(APPEND FLAGLIST "-I${INC}")
  endforeach(INC ${CURRENT_INCDIRS})

  set(${OUTPUT_LIST} ${FLAGLIST} PARENT_SCOPE)
  
endfunction(get_current_cflags OUTPUT_LIST INPUT_FLAGS)
