# @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

# Set up to use cmake_parse_arguments() function!
include(CMakeParseArguments)
# NOTE: For CMake versions 3.5.0 and above, this module is empty so the
# natively implemented function cmake_parse_arguments() will get used!

macro(parse_arguments_deprecated_warning)
  message(WARNING "parse_arguments() is deprecated and should not be used."
     " Instead use cmake_parse_arguments()")
endmacro()

parse_arguments_deprecated_warning()

# Set PARSE_ARGUMENTS_DUMP_OUTPUT_ENABLED to TRUE to see output from parsing.

function(parse_arguments_dump_output  OUTPUT_STR)
  if (PARSE_ARGUMENTS_DUMP_OUTPUT_ENABLED)
    message("${OUTPUT_STR}")
  endif()
endfunction()

#
# @MACRO: parse_arguments()
#
# Parse a set of macro/function input arguments into different lists.  This
# allows the easy implementation of keyword-based user-defined macros and
# functions.
#
# Usage::
#
#   parse_arguments(
#     <prefix>  <argNamesList>  <optionNamesList>
#     <inputArgsList>
#     )
#
# Arguments to this macro are:
#
#   ``<prefix>``
#
#     Prefix ``<prefix>_`` added the list and option variables created listed
#     in ``<argNamesList>`` and ``<optionNamesList>``.
#
#   ``<argNamesList>``
#
#     Quoted array of list arguments (e.g. ``"<argName0>;<argName1>;..."``).
#     For each variable name ``<argNamei>``, a local variable will be created
#     in the current scope with the name ``<prefix>_<argNamei>`` which gives
#     the list of variables parsed out of ``<inputArgsList>``.
#
#   ``<optionNamesList>``
#
#     Quoted array of list options (e.g. ``"<optName0>;<optName1>;..."``)
#     typically pass in as ``${ARGN}`` in the outer function/macro.  For each
#     variable name ``<optNamei>``, a local variable will be created in the
#     current scope with the name ``<prefix>_<optNamei>`` that is either set
#     to ``TRUE`` or ``FALSE`` depending if ``<optNamei>`` appears in
#     ``<inputArgsList>`` or not.
#
#   ``<inputArgsList>``
#
#     List of arguments keyword-based arguments passed in through the outer
#     macro or function to be parsed out into the different argument and
#     option lists.
#
# What this macro does is very simple yet very useful.  What it does is to
# allow one to create one's own user-defined keyword-based macros and
# functions like is used by some built-in CMake commands.
#
# For example, consider the following user-defined macro that uses both
# positional and keyword-based arguments using ``parse_arguments()``::
#
#   macro(parse_special_vars  BASE_NAME)
#
#     parse_arguments(
#       #prefix
#       ${BASE_NAME}
#       #lists
#       "ARG0;ARG1;ARG2"
#       #options
#       "OPT0;OPT1"
#       ${ARGN}
#       )
#
#   endmacro()
#
# Calling this macro as::
#
#   parse_special_vars(MyVar ARG0 a b ARG2 c OPT1)
#
# sets the following variables in the current scope::
#
#   MyVar_ARG0="a;b"
#   MyVar_ARG1=""
#   MyVar_ARG2="c"
#   MyVar_OPT0="FALSE"
#   MyVar_OPT1="TRUE"
#
# This allows one to define user-defined macros and functions that have a
# mixture of positional arguments and keyword-based arguments like one can do
# in other languages.  The keyword-based arguments can be passed in any order
# and those that are missing are empty (or false for options) by default.
#
# Any initial arguments that are not recognized as ``<argNamesList>`` or
# ``<optionNamesList>`` keyword arguments will be put into the local variable
# ``<prefix>_DEFAULT_ARGS``.  If no arguments in ``<inputArgsList>``
# (typically ``${ARGN}``) match any in ``<argNamesList>``, then all non-option
# arguments are put into ``<prefix>_DEFAULT_ARGS``.  For example, if one
# passes in::
#
#   parse_special_vars(MyVar ARG5 a b c)
#
# you will get::
#
#   MyVar_DEFAULT_ARGS="ARG5;a;b;c"
#   MyVar_ARG0=""
#   MyVar_ARG1=""
#   MyVar_ARG2=""
#   MyVar_OPT0="FALSE"
#   MyVar_OPT1="FALSE"
#
# Multiple occurrences of keyword arguments in ``<inputArgsList>`` is allowed
# but only the last one listed will be recorded.  For example, if one calls::
#
#   parse_special_vars(MyVar ARG1 a b ARG1 c)
#
# then this will set::
#
#   MyVar_ARG0=""
#   MyVar_ARG1="c"
#   MyVar_ARG2=""
#   MyVar_OPT0="FALSE"
#   MyVar_OPT1="FALSE"
#
# This is actually consistent with the way that most argument list parsers
# behave with respect to multiple instances of the same argument so hopefully
# this will not be a surprise to anyone.
#
# If one puts an option keyword in the middle of a keyword argument list, the
# option keyword will get pulled out of the list.  For example, if one calls::
#
#   parse_special_vars(MyVar ARG0 a OPT0 c)
#
# then this will set::
#
#   MyVar_ARG0="a;c"
#   MyVar_ARG1=""
#   MyVar_ARG2=""
#   MyVar_OPT0="TRUE"
#   MyVar_OPT1="FALSE"
#
# This is confusing behavior so users would be smart not to mix option
# arguments inside of list arguments.
#
# If ``PARSE_ARGUMENTS_DUMP_OUTPUT_ENABLED`` is set to ``TRUE``, then a bunch
# of detailed debug info will be printed.  This should only be used in the
# most desperate of debug situations because it will print a *lot* of output!
#
# **PERFORMANCE:** This function will scale as::
#
#   o( (len(<argNamesList>) * len(<optionNamesList>)) * len(<inputArgsList>) )
#
# Therefore, this could scale very badly for large sets of argument and option
# names and input argument list names.
#
macro(parse_arguments prefix arg_names option_names)
   
  parse_arguments_deprecated_warning()
 
  parse_arguments_dump_output("PARSE_ARGUMENTS: prefix='${prefix}'")
  parse_arguments_dump_output("PARSE_ARGUMENTS: arg_names='${arg_names}'")
  parse_arguments_dump_output("PARSE_ARGUMENTS: option_names='${option_names}'")
  parse_arguments_dump_output("PARSE_ARGUMENTS: ARGN='${ARGN}'")

  foreach(arg_name ${arg_names})
    set(${prefix}_${arg_name})
  endforeach()

  foreach(option ${option_names})
    set(${prefix}_${option} FALSE)
  endforeach()

  set(DEFAULT_ARGS)
  set(current_arg_name DEFAULT_ARGS)
  set(current_arg_list)

  foreach(arg ${ARGN})
    set(larg_names ${arg_names})
    list(FIND larg_names "${arg}" is_arg_name)
    if (is_arg_name GREATER -1)
      set(${prefix}_${current_arg_name} "${current_arg_list}")
      parse_arguments_dump_output("PARSE_ARGUMENTS: ${prefix}_${current_arg_name} = '${${prefix}_${current_arg_name}}'" )
      set(current_arg_name "${arg}")
      set(current_arg_list)
    else()
      set(loption_names "${option_names}")
      list(FIND loption_names "${arg}" is_option)
      if (is_option GREATER -1)
        set(${prefix}_${arg} TRUE)
        parse_arguments_dump_output( "PARSE_ARGUMENTS: ${prefix}_${arg} = '${${prefix}_${arg}}'" )
      else()
        list(APPEND current_arg_list "${arg}")
      endif()
    endif()
  endforeach()

  set(${prefix}_${current_arg_name} "${current_arg_list}")
  parse_arguments_dump_output( "PARSE_ARGUMENTS: ${prefix}_${current_arg_name} = '${${prefix}_${current_arg_name}}'" )

endmacro()

# NOTE: If the above function turns out to be a performance bottle neck, there
# are a few things that could be done to improve performance.  One thing you
# could do is repalce the o(len(arg_names)) and o(len(option_names)) lookups
# with o(1) lookups by creating CMake variables of the name
# ${OUTER_FUNC_NAME}_arg_<argNamei> and then just look of that varible exists
# or not.  That should use a hash function.  That might actually slow things
# down for short lists however so we would have to measure, measure,
# measure. I would have to pass in the function/macro name to disabiguate
# the varible names.  It would really be better if cmake would provide a
# sorted list find operation.  That would make this much faster for large
# numbers of argument and option names.
