# @HEADER
# ************************************************************************
#
#            TriBITS: Tribal Build, Integrate, and Test System
#                    Copyright 2013 Sandia Corporation
#
# Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
# the U.S. Government retains certain rights in this software.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the Corporation nor the names of the
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ************************************************************************
# @HEADER

include(AppendSet)
include(AppendGlobalSet)
include(Split)
include(Join)
include(PrintVar)

# this macro expands produces a list of all combinations of a variable number of lists:
# called like tribits_eti_type_expansion(setvar "T=t1|t2|t3" "S=s1|s2" "V=v1")
# returns setvar="T=T1 S=s1 V=v1;T=T1 S=s2 V=v1;
#                 T=T2 S=s1 V=v1;T=T2 S=s2 V=v1;
#                 T=T3 S=s1 V=v1;T=T3 S=s2 V=v1"
function(tribits_eti_type_expansion outvar first_list)
  # extract the field name and the list of types
  if (NOT "${first_list}" MATCHES "([^=]*)=(.*)")
    set(${outvar} "TRIBITS_ETI_BAD_ARGUMENTS" PARENT_SCOPE)
    return()
  else()
    set(label "${CMAKE_MATCH_1}")
    set(tlist "${CMAKE_MATCH_2}")
  endif()
  set(eti_result "") 
  split("${tlist}" "\\|" tlist)
  if ("${${outvar}}" STREQUAL "")
    set(accumulate OFF)
  else()
    set(accumulate ON)
  endif()
  list(LENGTH tlist tlist_len)
  if (${ARGC} GREATER 2)
    tribits_eti_type_expansion(sub ${ARGN})
    foreach(t ${tlist})
      string(STRIP "${t}" t)
      set(t "{${t}}")
      foreach(s ${sub})
        list(APPEND eti_result "${label}=${t} ${s}")
      endforeach()
    endforeach()
  else()
    foreach(t ${tlist})
      string(STRIP "${t}" t)
      set(t "{${t}}")
      list(APPEND eti_result "${label}=${t}")
    endforeach()
  endif()
  if (accumulate)
    set(${outvar} "${${outvar}};${eti_result}" PARENT_SCOPE)
  else()
    set(${outvar} "${eti_result}"              PARENT_SCOPE)
  endif()
endfunction()

# Explode an ETI set into a variable number of named component fields
function(tribits_eti_explode fields inst outvar)
  foreach (field ${fields})
    if(    "${inst}" MATCHES "( |^)${field}={([^{}$=]*)}( |$)")
      list(APPEND eti_result "${CMAKE_MATCH_2}")
    elseif("${inst}" MATCHES "( |^)${field}=([^{} $=]*)( |$)")
      list(APPEND eti_result "${CMAKE_MATCH_2}")
    elseif(NOT "${inst}" MATCHES "( |^)${field}=")
      list(APPEND eti_result "TYPE-MISSING")
    else()
      set(eti_result "TRIBITS_ETI_BAD_PARSE")
      break()
    endif()
  endforeach()
  join(eti_result "|" FALSE ${eti_result})
  set(${outvar} "${eti_result}" PARENT_SCOPE)
endfunction()

# effectively, a tupled regex, wrapped in a for loop
# given a list of processed excludes (a list of pipe-separated, bracket-delimited tuples of type/regexes),
# determine whether processed_inst (a pipe-separated, bracket tuple of types) is matched
# if the instantiation matches one of the exclusions, result is set true
function(tribits_eti_check_exclusion processed_excludes processed_inst excluded)
  split("${processed_inst}" "\\|" processed_inst)
  list(LENGTH processed_inst numfields)
  math(EXPR NFm1 "${numfields}-1")
  set(${excluded} OFF PARENT_SCOPE)
  # check to see whether this is excluded or not
  list(LENGTH processed_excludes numexcl)
  foreach(excl ${processed_excludes})
    split(${excl} "\\|" excl)
    list(LENGTH excl excp_len)
    if(${excp_len} EQUAL ${numfields})
      set(lcltest ON)
      foreach(i RANGE 0 ${NFm1})
        list(GET processed_inst ${i} f)
        list(GET excl           ${i} e)
        if (NOT "${f}" STREQUAL "TYPE-MISSING" AND NOT "${f}" MATCHES "${e}")
          set(lcltest OFF)
          break()
        endif()
      endforeach()
      if(lcltest)
        set(${excluded} ON PARENT_SCOPE)
        if (${PROJECT}_VERBOSE_CONFIGURE)
          message(STATUS "-- Instantiation excluded by ${excl}")
        endif()
        return()
      endif()
    endif()
  endforeach()
endfunction()

# given a list of field names and list of macrofields, translate the
# field names in macrofields into indices corresponding to the position in
# the list of field names
function(tribits_eti_index_macro_fields etifields macrofields indexvar)
  foreach(mf ${macrofields})
    string(STRIP "${mf}" mf)
    list(FIND etifields ${mf} ind)
    if(${ind} EQUAL -1)
      message(FATAL_ERROR "Macro variable ${mf} not found in list of fields: ${etifields}")
    endif()
    list(APPEND inds ${ind})
  endforeach()
  set(${indexvar} ${inds} PARENT_SCOPE)
endfunction()

# given a macro name and a list of tuples, generate a macro string
function(tribits_eti_build_macro_string macroname tuplelist outvar)
  set(str "#define ${macroname}(INSTMACRO)")
  foreach(tuple ${tuplelist})
    string(REPLACE "|" " , " tuple "${tuple}")
    set(str "${str}\\\n\tINSTMACRO( ${tuple} )")
  endforeach()
  set(str "${str}\n")
  set(${outvar} ${str} PARENT_SCOPE)
endfunction()

# utility for adding eti support to another package's library set
function(tribits_add_eti_instantiations PACKAGE)
  if(${PROJECT_NAME}_VERBOSE_CONFIGURE)
    message(STATUS "Adding instantiations to ${PACKAGE} library set...")
  endif()
  append_global_set(${PACKAGE}_ETI_LIBRARYSET ${ARGN})
endfunction()

# utility for mangling type names to make them safe for the C preprocessor
function(tribits_eti_mangle_symbol
         mangledvar
         input)
  set(newvar "${input}")
  string(REPLACE "::" "_" newvar "${newvar}")
  string(REPLACE ","  "_" newvar "${newvar}")
  set(num_brackets 0)
  while("${newvar}" MATCHES "(.*)<([^<>]*)>(.*)")
    set(newvar "${CMAKE_MATCH_1}${num_brackets}${CMAKE_MATCH_2}${num_brackets}${CMAKE_MATCH_3}")
    math(EXPR num_brackets "${num_brackets}+1")
  endwhile()
  string(REPLACE " "  "" newvar "${newvar}")
  set(${mangledvar} "${newvar}" PARENT_SCOPE)
endfunction()

# utility for mangling type names to make them safe for the C preprocessor
# upon mangling, it automatically inserts an ifdef into the mangling macro
function(tribits_eti_mangle_symbol_augment_macro
         typedefvar
         symbolvar
         manglistvar)
  set(oldsymbol "${${symbolvar}}")
  if(NOT "${oldsymbol}" MATCHES "::|[<>, ]")
    return()
  endif()
  tribits_eti_mangle_symbol(newsymbol "${oldsymbol}")
  set(${symbolvar} "${newsymbol}" PARENT_SCOPE)
  list(FIND ${manglistvar} ${newsymbol} already_mangled)
  if(${already_mangled} EQUAL -1)
    list(APPEND ${manglistvar} ${newsymbol})
    set(${manglistvar} ${${manglistvar}} PARENT_SCOPE)
    list(APPEND ${typedefvar}  "typedef ${oldsymbol} ${newsymbol}")
    set(${typedefvar}  ${${typedefvar}}  PARENT_SCOPE)
  endif()
endfunction()

# generate the macros
function(tribits_eti_generate_macros etifields etisetvar etiexcludelist manglinglistvar typedeflistvar)
  set(manglinglist  "${${manglinglistvar}}")
  set(typedeflist   "${${typedeflistvar}}")
  split("${etifields}" "\\|" etifields)
  if(${PROJECT}_VERBOSE_CONFIGURE)
    message(STATUS "ETI fields: ${etifields}")
    message(STATUS "ETI set: ${etisetvar}")
    message(STATUS "ETI excludes: ${etiexcludelist}")
  endif()
  # we make lists of tuples first, because we want to make sure they don't have duplicates
  # this algorithm is o(N^2) in the number of instantiations in etisetvar
  math(EXPR num_macros "(${ARGC}-5)/2")
  if(${num_macros} EQUAL 0)
    return()
  endif()
  # process macro fields into a list of indices
  foreach(m RANGE 1 ${num_macros})
    math(EXPR m2   "(${m}-1)*2")
    math(EXPR m2p1 "${m2}+1")
    list(GET ARGN ${m2}   macroarg)
    list(GET ARGN ${m2p1} macrovar${m})
    string(REGEX REPLACE "^(.*)\\(.*"     "\\1" macroname${m} ${macroarg})
    string(REGEX REPLACE "^.*\\((.*)\\)$" "\\1" macrofields${m} ${macroarg})
    split("${macrofields${m}}" "," macrofields)
    tribits_eti_index_macro_fields("${etifields}" "${macrofields}" macroindex${m})
    if(${PROJECT}_VERBOSE_CONFIGURE)
      message(STATUS "Parsed macro ${macroname${m}}(${macrofields${m}}) into variable ${macrovar${m}} (index list ${macroindex${m}})")
    endif()
  endforeach()
  # process the exclusions once
  foreach(excl ${etiexcludelist})
    tribits_eti_explode("${etifields}" "${excl}" e_excl)
    if("${e_excl}" STREQUAL "TRIBITS_ETI_BAD_PARSE")
      message(FATAL_ERROR "TRIBITS_GENERATE_ETI_MACROS: exclusion did not parse: ${excl}")
    endif()
    list(APPEND processed_excludes "${e_excl}")
  endforeach()
  list(LENGTH etifields numfields)
  math(EXPR NFm1 "${numfields}-1")
  foreach(inst ${etisetvar})
    if (${PROJECT}_VERBOSE_CONFIGURE)
      message(STATUS "Processing instantiation: ${inst}") # comment
    endif()
    tribits_eti_explode("${etifields}" "${inst}" tmp)
    if("${tmp}" STREQUAL "TRIBITS_ETI_BAD_PARSE")
      message(FATAL_ERROR "TRIBITS_GENERATE_ETI_MACROS: instantiation did not parse: ${inst}")
    else()
      set(inst "${tmp}")
    endif()
    # check whether it is on the exclude list
    tribits_eti_check_exclusion("${processed_excludes}" "${inst}" excluded)
    if(NOT excluded)
      split("${inst}" "\\|" inst)
      # append tuple to list
      foreach(m RANGE 1 ${num_macros})
        set(tuple "")
        foreach(ind ${macroindex${m}})
          list(GET inst ${ind} t)
          if("${t}" STREQUAL "TYPE-MISSING")
            set(tuple "SKIP-TUPLE")
            break()
          endif()
          # mangle the types in the instantiation
          tribits_eti_mangle_symbol_augment_macro(typedeflist t manglinglist)
          list(APPEND tuple ${t})
        endforeach()
        if(NOT "${tuple}" STREQUAL "SKIP-TUPLE")
          join(tuple "|" FALSE "${tuple}")
          list(APPEND macrotuples${m} ${tuple})
        endif()
      endforeach()
    endif()
  endforeach()
  # remove duplicates from lists
  foreach(m RANGE 1 ${num_macros})
    if(DEFINED macrotuples${m})
      list(REMOVE_DUPLICATES macrotuples${m})
    endif()
  endforeach()
  # build the macro strings
  foreach(m RANGE 1 ${num_macros})
    tribits_eti_build_macro_string("${macroname${m}}" "${macrotuples${m}}" mac)
    set(${macrovar${m}} "${mac}" PARENT_SCOPE)
  endforeach()
  # build the typedef string
  set(${manglinglistvar} ${manglinglist} PARENT_SCOPE)
  set(${typedeflistvar}  ${typedeflist}  PARENT_SCOPE)
endfunction()

# generate the typedef macro
function(tribits_eti_generate_typedef_macro outputvar macroname typedeflist)
  set(mac "#define ${macroname}() ")
  foreach(td ${typedeflist})
    set(mac "${mac} \\\n\t${td};")
  endforeach()
  set(${outputvar} "${mac}" PARENT_SCOPE)
endfunction()
