/****************************************************************************
 *   Copyright (c) 2017 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
 *                                                                          *
 *   This file is part of the FreeCAD CAx development system.               *
 *                                                                          *
 *   This library is free software; you can redistribute it and/or          *
 *   modify it under the terms of the GNU Library General Public            *
 *   License as published by the Free Software Foundation; either           *
 *   version 2 of the License, or (at your option) any later version.       *
 *                                                                          *
 *   This library  is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *   GNU Library General Public License for more details.                   *
 *                                                                          *
 *   You should have received a copy of the GNU Library General Public      *
 *   License along with this library; see the file COPYING.LIB. If not,     *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,          *
 *   Suite 330, Boston, MA  02111-1307, USA                                 *
 *                                                                          *
 ****************************************************************************/

#ifndef PARAMS_HELPER_H
#define PARAMS_HELPER_H

/** \page ParamPage Parameter helper macros
 * Collections of macros for managing groups of parameters.
 *
 * \section Motivation 
 *
 * For an application like FreeCAD, there are often cases where the same set of
 * parameters are referred in dozons of different places. The macros here is
 * designed to help managing those parameters, so that you can define groups of
 * parameters once, and refer them everywhere in groups with simple macro calls for
 * all kinds of purposes. Any changing, adding and removing of parameters in the
 * group become much easier. And by everywhere, I mean \ref ParamCommon 
 * "class definition, impelentation", \ref ParamProperty "document object properties",
 * \ref ParamPy "python c++ classes", and even \ref ParamDoc "doc string",
 * pretty much everything except the python code, which although not implemented
 * yet, is in fact also possible to be done using C preprocessor (No one says C
 * preprocessor must produce C code :). It is also possible (not implemented
 * yet) to use macros to generate python wrapper class instead of using
 * FreeCAD's current xml python export.
 *
 * \section Debugging
 *
 * Extensive use of macros has one noticeable disadvantage, though. If some thing
 * goes wrong, the compiler error message is kind of cryptic. If so, first
 * double check your macro definition of the parameter is correctly, not missing
 * or having extra parathesis or comma.  Then, you can use the CMake
 * intermeidate file target to get the preprocessor output for checking. For
 * example, for a file located at \c src/Mod/Path/App/Area.cpp,
 * \code{.sh}
 *     cd <your_build_dir>/src/Mod/Path/App
 *     make Area.cpp.i
 * \endcode
 *
 * The preprocessed intermediate output will be at,
 * \code{.sh}
 *     <your_build_dir>/src/Mod/Path/App.CMakeFiles/Path.dir/Area.cpp.i
 * \endcode
 *
 * \section Intrudction of Boost.Preprocessor
 *
 * The macros here make heavy use of the awsome
 * [Boost.Preprocessor](http://www.boost.org/libs/preprocessor/) (short for
 * Boost.PP).  Here are some brief introduction on Boost.PP conecept in order to
 * explain why this marco library is designed the way it is.
 *
 * In Boost.PP, a sequence is defined as,
 * \code{.sh}
 *     (a)(b)(c)...
 * \endcode
 *
 * A sequence cannot be empty. Thus, \c () is not a sequence. And also those
 * <tt>a, b, c</tt> here cannot directly contain <tt>,</tt>. These restriction
 * is due to the fact that <tt>( ) ,</tt> are among those very few special
 * characters recognized by the preprocssor. \c a can itself be a sequence or
 * other Boost.PP types, so by right, our parameter can be defined as something
 * like
 * \code{.sh}
 *     ((type)(name)(default)...)
 * \endcode
 *
 * A bit awkward to write. So another Boost.PP type is chosen, tuple, to define
 * each individual parameter. A tuple is defined as
 * \code{.sh}
 *     (a,b,c ...)
 * \endcode
 *
 * This is why the parameter definition requires a double parathesis, as shown
 * in the following section.
 *
 * \section Library Overview
 *
 * In this macro library, a parameter is defined using a tuple inside a sequence,
 * \code{.sh}
 *     ((<type>, <arg>, <name>, <default>, <doc>, <seq>, <info>))
 * \endcode
 *
 * - \c type is the type of the parameter. Currently only five types of
 *   parameters are defined, <tt>short, long, double, bool, enum, enum2</tt>.
 *   \enum2 type is the same as \enum with additional information to be able to
 *   map to a user defined C enum type. To add more types, search this file for
 *   keyword \a _short, and supply all relevant macros.  It's quite trivial
 *   actually.
 *
 * - \c arg is the argument name. It is intended to be used as function argument.
 *   By convention, the name shall be all small cases, but that's not required.
 *   This \c arg can be repurposed, if the parameter is not going to be used as
 *   function argument.  The #AREA_PARAMS_CAREA parameters repurposed this field
 *   to CArea internal setting variables to implement save, apply and restore
 *   function using CAreaConfig class.
 *
 * - \c name is normally a %CamelCase name which are used as member variable and
 *   property name. Because of this, make sure the names are unique to avoid
 *   conflicts.
 *
 * - \c default is the default value of this parameter. Right now, you must
 *   supply a default value. Boost.PP has trouble dealing with empty values.
 *   Remember that a sequence cannot be empty. Neight can tuple. Only array,
 *   something like <tt>(0,())</tt> for an empty array. It is awkward to write,
 *   and didn't add much functionality I want, hence the restriction of
 *   non-empty defaults here.
 *
 * - \c doc is a string to describe the variable.
 *
 * - \c seq \anchor ParamSeq. Right now this field is used by \c enum and
 *   \c enum2 type parameter to define its enumerations. As the name suggests,
 *   it must be a sequence.  It is not a tuple because looping through tuple is
 *   not as easy as sequence. Other type of parameter do not need to have this
 *   field
 *
 * - \c info is used to provide the supplimentery information for \c enum2 type
 *   of parameter, which can be converted to a user defined enum type by
 *   #PARAM_ENUM_CONVERT. \c info must be a tuple, with the user defined enum
 *   type as the first element, and a prefix as the second element. For \c enum2
 *   type of parameter, this field is mandatory.
 *
 * The common usage is that you define a macro of a group of parameters. And use
 * the macro helper here to do operation on each parameter in the group. See
 * AreaParams.h file for an example of parameter definitions.
 *
 * Area.h, Area.cpp, FeatureArea.h, FeatureArea.cpp for usage of variouse macros.
 *
 * See struct AreaDoc for an example of doc string generation.
 * 
 * Each field of the parameter can be referred to with various 
 * \ref ParamAccessor "various accessor macros", and can be easily 
 * \ref ParamStringizer "stringified".
 *
 * \anchor ParamField You can also use #PARAM_FIELD(_field,_param) to refer to
 * each field, where \a _field is one of <tt>TYPE, ARG, NAME, DEF, DOC, or SEQ</tt>. 
 * And #PARAM_FIELD_STR to stringify.
 *
 * Here \a _param is the parameter definition described above in the form of a
 * Boost.PP tuple, and is usually supplied by various \ref ParamLooper "looper macros"
 *
 * You can of course directly use various Boost.PP sequence looper to pass
 * additional arguments to the operation macro. See #PARAM_PY_DICT_SET_VALUE for
 * an example of using tuple, and the more complex example #PARAM_ENUM_CONVERT
 *
 * Note that when generating comma separated list, the first and last comma are
 * conveniently omitted, so that the macros can be mixed with others intuitively
 */

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/seq.hpp>
#include <boost/preprocessor/seq/elem.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/pop_front.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/logical/not.hpp>
#include <boost/preprocessor/tuple/elem.hpp>
#include <boost/preprocessor/tuple/enum.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/comparison/greater.hpp>

/** \defgroup ParamHelper Parameters helper macros
 * Collections of macros for managing groups of parameters */

/**
 * \defgroup ParamAccessor Field accessors
 * To abstract parameter field details 
 * \ingroup ParamHelper
 * @{
 */
#define PARAM_ITYPE 0
#define PARAM_IARG  1
#define PARAM_INAME 2
#define PARAM_IDEF  3
#define PARAM_IDOC  4
#define PARAM_ISEQ  5
#define PARAM_IPROP  5
#define PARAM_IINFO 6

#define PARAM_FIELD(_idx,_param) BOOST_PP_TUPLE_ELEM(PARAM_I##_idx,_param)

#define PARAM_FTYPE(_param) PARAM_FIELD(TYPE,_param)
#define PARAM_FARG(_param) PARAM_FIELD(ARG,_param)
#define PARAM_FNAME(_param) PARAM_FIELD(NAME,_param)
#define PARAM_FDEF(_param) PARAM_FIELD(DEF,_param)
#define PARAM_FDOC(_param) PARAM_FIELD(DOC,_param)
#define PARAM_FSEQ(_param) PARAM_FIELD(SEQ,_param)
#define PARAM_FPROP(_param) PARAM_FIELD(PROP,_param)
#define PARAM_FINFO(_param) PARAM_FIELD(INFO,_param)
#define PARAM_FENUM_TYPE(_param) BOOST_PP_TUPLE_ELEM(0,PARAM_FINFO(_param))
#define PARAM_FENUM_PREFIX(_param) BOOST_PP_TUPLE_ELEM(1,PARAM_FINFO(_param))
/** @} */


/**
 * \defgroup ParamStringizer Field stringizers
 * \ingroup ParamHelper
 * @{ */
#define PARAM_FIELD_STR(_idx,_param) \
    BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(PARAM_I##_idx,_param))

#define PARAM_FTYPE_STR(_param) PARAM_FIELD_STR(TYPE,_param)
#define PARAM_FARG_STR(_param) PARAM_FIELD_STR(ARG,_param)
#define PARAM_FNAME_STR(_param) PARAM_FIELD_STR(NAME,_param)
#define PARAM_FDEF_STR(_param) PARAM_FIELD_STR(DEF,_param)
/** @} */

/** Helper for #PARAM_FSEQ_STR */
#define PARAM_FSEQ_STR_(_i,_elem) \
    BOOST_PP_COMMA_IF(_i) BOOST_PP_STRINGIZE(_elem)

/** \c SEQ stringizer will stringify each element separately 
 *
 * Expands to:
 *      #seq[0], #seq[1] ...
 * \ingroup ParamHelper
 */
#define PARAM_FSEQ_STR(_param) \
    PARAM_FOREACH_I(PARAM_FSEQ_STR_,PARAM_FSEQ(_param))


/** \defgroup ParamLooper Looper macros
 * Macros for looping through sequence to parameters
 * \ingroup ParamHelper
 */

/** Helper for #PARAM_FOREACH */
#define PARAM_FOREACH_(_,_op,_param) _op(_param)

/** Apply macro \a _op to each parameter in sequence \a _seq
 *
 * Operation macro \a _op should be defined as,
 * \code
 *      _op(_param)
 * \endcode
 * \ingroup ParamLooper
 */
#define PARAM_FOREACH(_op,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_FOREACH_,_op,_seq)

/** Helper for #PARAM_FOREACH_I */
#define PARAM_FOREACH_I_(_,_op,_i,_param) _op(_i,_param)

/** Apply macro \a _op to each parameter in sequence \a _seq with additional index 
 *
 * Operation macro \a _op should be defined as,
 * \code
 *      _op(_i,_param)
 * \endcode
 * \ingroup ParamLooper
 * */
#define PARAM_FOREACH_I(_op,_seq) \
    BOOST_PP_SEQ_FOR_EACH_I(PARAM_FOREACH_I_,_op,_seq)


/** Helper for #PARAM_TYPED_FOREACH */
#define PARAM_TYPED_FOREACH_(_1,_op,_param) \
    PARAM_TYPED(_op,_param)(_param)

/** Type depended macro construction 
 *
 * Convert macro \a _op to \a _op##\<type\>. Note that it only converts the 
 * macro name, not contsucts a macro call. To expand to a macro call, simply
 * \code
 *      PARAM_TYPED(_op,_param)(_param)
 * \endcode
 * \ingroup ParamLooper
 */
#define PARAM_TYPED(_op,_param) \
    BOOST_PP_CAT(_op,PARAM_FTYPE(_param))

/** Apply type dependent macro call to a sequence of parameters 
 *
 * \a _op will be converted to \a _op##\<type\> for each parameter
 * \ingroup ParamLooper
 */
#define PARAM_TYPED_FOREACH(_op,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_TYPED_FOREACH_,_op,_seq)


/** \defgroup ParamCommon Common helpers 
 * \ingroup ParamHelper
 */

#define PARAM_TYPE_short     short
#define PARAM_TYPE_long      long
#define PARAM_TYPE_double    double
#define PARAM_TYPE_bool      bool
#define PARAM_TYPE_enum      short
#define PARAM_TYPE_enum2     short

/** Obtain parameter type
 *
 * The main purpose is to alias enum type to short 
 * \ingroup ParamCommon
 */
#define PARAM_TYPE(_param) \
    PARAM_TYPED(PARAM_TYPE_,_param)


/** Helper for #PARAM_DECLARE */
#define PARAM_DECLARE_(_1,_src,_param) \
    PARAM_TYPE(_param) _src(_param);

/**
 * Delcares parameters using the given field as name
 *
 * \arg \c _src: \anchor ParamSrc Macro to generate source variable. The
 * signature must be <tt>_src(_param)<\tt>, where \c _param is the tuple
 * defining the parameter.  You pass any of the \ref ParamAccessor "parameter
 * accessors" to directly access the field. Or, supply your own macro to append
 * any prefix as you like. For example:
 * \code{.unparsed}
 *      #define MY_SRC(_param) BOOST_PP_CAT(my,PARAM_FNAME(_param))
 *      ->
 *          my##<name>
 * \endcode 
 *
 * Expands to:
 * \code{.unparsed}
 *         type1 _src(_param1);type2 _src(_param2); ...
 * \endcode 
 * \ingroup ParamCommon
 */
#define PARAM_DECLARE(_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_,_src,_seq)
 

/** Helper for #PARAM_DECLARE_INIT */
#define PARAM_DECLARE_INIT_(_1,_src,_param) \
    PARAM_TYPE(_param) _src(_param) = PARAM_FDEF(_param);

/**
 * Delcares parameters with initialization to default using the given field as
 * name
 *
 * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for
 * more details
 *
 * Expands to:
 * \code{.unparsed}
 *         type1 _src(_param1)=_def1;type2 _src(_param2)=_def2; ...
 * \endcode 
 * \ingroup ParamCommon
 */
#define PARAM_DECLARE_INIT(_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_INIT_,_src,_seq)


#define PARAM_ENUM_DECLARE_enum_(_1,_name,_i,_elem) \
     BOOST_PP_COMMA_IF(_i) BOOST_PP_CAT(_name,_elem)

#define PARAM_ENUM_DECLARE_enum(_param) \
    enum {BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_DECLARE_enum_,PARAM_FNAME(_param),PARAM_FSEQ(_param))};

#define PARAM_ENUM_DECLARE_short(_param)
#define PARAM_ENUM_DECLARE_long(_param)
#define PARAM_ENUM_DECLARE_double(_param)
#define PARAM_ENUM_DECLARE_bool(_param)
#define PARAM_ENUM_DECLARE_enum2 PARAM_ENUM_DECLARE_enum

/** \defgroup ParamEnumHelper Enum convert helpers 
 * \ingroup ParamCommon
 */

/** Make anonymous \c enum type
 *
 * Make anonymous \c enum type for \c enum type parameters in \a _seq. All other
 * types are ignored. The enum member is prefixed with \a _name. Expand to:
 * \code{.unparsed}
 *      enum {_name1##_seq1[0], _name1##_seq1[1] ...};
 *      enum {_name2##_seq2[0], _name2##_seq2[1] ...};
 *      ...
 * \endcode
 * \ingroup ParamEnumHelper*/
#define PARAM_ENUM_DECLARE(_seq) \
    PARAM_TYPED_FOREACH(PARAM_ENUM_DECLARE_,_seq)


/** \addgroup ParamEnumHelper Enum convert helpers
 * @{ */
#define PARAM_ENUM_CONVERT_short(...)
#define PARAM_ENUM_CONVERT_long(...)
#define PARAM_ENUM_CONVERT_double(...)
#define PARAM_ENUM_CONVERT_bool(...)
#define PARAM_ENUM_CONVERT_enum(...)
#define PARAM_ENUM_CONVERT_enum2 PARAM_ENUM_CONVERT_SINGLE

#define PARAM_ENUM_CONVERT_enum_(_dst,_name,_prefix,_elem) \
        case BOOST_PP_CAT(_name,_elem):\
            _dst = BOOST_PP_CAT(_prefix,_elem);\
            break;

#define PARAM_ENUM_CONVERT__(_1,_args,_i,_elem) \
        PARAM_ENUM_CONVERT_enum_(BOOST_PP_TUPLE_ELEM(0,_args),\
                                 BOOST_PP_TUPLE_ELEM(1,_args),\
                                 BOOST_PP_TUPLE_ELEM(2,_args),\
                                 _elem);

#define PARAM_ENUM_CONVERT_(_1,_args,_param) \
        PARAM_TYPED(PARAM_ENUM_CONVERT_,_param)(BOOST_PP_TUPLE_ELEM(0,_args),\
                                            BOOST_PP_TUPLE_ELEM(1,_args),\
                                            BOOST_PP_TUPLE_ELEM(2,_args),\
                                            _param)

/** Convert single enum parameter value into user defined enum type 
 *
 * This macro is used by #PARAM_ENUM_CONVERT to convert each parameter, but
 * you can use it directly for a single parameter. Check #PARAM_NUM_CONVERT
 * for more detail. Make sure the outer parathesis of \c _param is stripped,
 * i.e. not double but single parathesis
 */
#define PARAM_ENUM_CONVERT_SINGLE(_src,_dst,_default,_param) \
        PARAM_FENUM_TYPE(_param) _dst(_param);\
        switch(_src(_param)) {\
        BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_CONVERT__,\
                (_dst(_param),PARAM_FNAME(_param),PARAM_FENUM_PREFIX(_param)),PARAM_FSEQ(_param))\
        default: \
            _default(_param);\
        }

/** Default handling in #PARAM_ENUM_CONVERT and #PARAM_ENUM_CHECK*/
#define PARAM_ENUM_EXCEPT(_param) \
    throw Base::ValueError("invalid value for enum " PARAM_FNAME_STR(_param))

/** @} */

/* Convert ParamHelper defined enum type to user defined ones
 *
 * This assumes the user defined enum type is given in \ref ParamSeq "seq_type"
 * of the parameter definition, and it has the same postfix as the ones
 * specified in \ref ParamSeq "seq" member of the parameter definition. See 
 * \ref ParamEnumHelper "here" for implementations
 *
 * \ingroup ParamEnumHelper
 *
 * \arg \c _src: Macro to generate source variable. The signature must be
 * <tt>_src(_param)<\tt>, where \c _param is the tuple defining the parameter.
 * You pass any of the \ref ParamAccessor "parameter accessors" to directly
 * access the field. Or, supply your own macro to append any prefix as you
 * like.
 * \arg \c _dst: Same as above.
 * \arg \c _default: A macro to call for invalid value. Signature should be 
 * <tt>_default(_param)<\tt>, where \c _param is the parameter definition. You
 * can use #PARAM_ENUM_EXCEPT to throw Base::ValueError exception in FreeCAD
 * \arg \c _seq: Parameter sequence
 *
 * For example, with the following parameter definition
 * \code{.unparsed}
 * #define MY_PARAM_TEST \
 *      ((enum,test1,Test1,0,"it's a test",(Foo)(Bar),(MyEnum1,myEnum1)) \
 *      ((enum,test2,Test2,0,"it's a test",(Foo)(Bar),(MyEnum2,myEnum2)))
 *
 *  #define MY_DST(_param) BOOST_PP_CAT(my,PARAM_FNAME(_param))
 * \code{.unparsed}
 *
 * calling
 * \code{.unparsed}
 *      PARAM_ENUM_CONVERT(PARAM_FNAME,MY_DST,My,PARAM_ENUM_EXCEP,MY_PARAM_TEST)
 * \code{.unparsed}
 *
 * expands to
 * \code{.unparsed}
 *      MyEnum1 myTest1; 
 *      switch(Test1) {
 *      case Test1Foo:
 *          myTest1 = myEnum1Foo;
 *          break;
 *      case Test1Bar:
 *          myTest1 = myEnum1Bar;
 *          break;
 *      default:
 *          throw Base::ValueError("invalid value for enum Test1");
 *      }
 *      MyEnum2 myTest2; 
 *      switch(Test2) {
 *      case Test1Foo:
 *          myTest2 = myEnum2Foo;
 *          break;
 *      case Test2Bar:
 *          myTest2 = myEnum2Bar;
 *          break;
 *      default:
 *          throw Base::ValueError("invalid value for enum Test2");
 *      }
 * \endcode
 *
 * The above code assumes you've already defined \a Test1 and \a Test2 some
 * where as the source variable.
 */
#define PARAM_ENUM_CONVERT(_src,_dst,_default,_seq) \
        BOOST_PP_SEQ_FOR_EACH(PARAM_ENUM_CONVERT_,(_src,_dst,_default),_seq)


#define PARAM_ENUM_CHECK_short(...)
#define PARAM_ENUM_CHECK_long(...)
#define PARAM_ENUM_CHECK_double(...)
#define PARAM_ENUM_CHECK_bool(...)
#define PARAM_ENUM_CHECK_enum PARAM_ENUM_CHECK_SINGLE
#define PARAM_ENUM_CHECK_enum2 PARAM_ENUM_CHECK_SINGLE

#define PARAM_ENUM_CHECK_enum_(_1,_name,_i,_elem) \
        case BOOST_PP_CAT(_name,_elem): break;

#define PARAM_ENUM_CHECK_(_1,_args,_param) \
        PARAM_TYPED(PARAM_ENUM_CHECK_,_param)(BOOST_PP_TUPLE_ELEM(0,_args),\
                                              BOOST_PP_TUPLE_ELEM(1,_args),\
                                              _param)

#define PARAM_ENUM_CHECK_SINGLE(_src,_default,_param) \
        switch(_src(_param)) {\
        BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_CHECK_enum_,\
                                PARAM_FNAME(_param),PARAM_FSEQ(_param))\
        default: \
            _default(_param);\
        }

/* Validate enum type parameters
 *
 * This macro validates the value a variable of enum type parameters. See 
 * similar macro #PARAM_ENUM_CONVERT for detail usage.
 *
 * \ingroup ParamEnumHelper
 *
 * \arg \c _src: Macro to generate source variable. The signature must be
 * <tt>_src(_param)<\tt>, where \c _param is the tuple defining the parameter.
 * You pass any of the \ref ParamAccessor "parameter accessors" to directly
 * access the field. Or, supply your own macro to append any prefix as you
 * like.
 *
 * \arg \c _default: A macro to call for invalid value. Signature should be 
 * <tt>_default(_param)<\tt>, where \c _param is the parameter definition. You
 * can use #PARAM_ENUM_EXCEPT to throw Base::ValueError exception in FreeCAD
 *
 * \arg \c _seq: Parameter sequence
 */
#define PARAM_ENUM_CHECK(_src,_default,_seq) \
        BOOST_PP_SEQ_FOR_EACH(PARAM_ENUM_CHECK_,(_src,_default),_seq)


#define PARAM_ENUM_STRING_DECLARE_short(...)
#define PARAM_ENUM_STRING_DECLARE_long(...)
#define PARAM_ENUM_STRING_DECLARE_double(...)
#define PARAM_ENUM_STRING_DECLARE_bool(...)
#define PARAM_ENUM_STRING_DECLARE_enum2 PARAM_ENUM_STRING_DECLARE_enum

/** Helper for #PARAM_ENUM_STRING_DECLARE */
#define PARAM_ENUM_STRING_DECLARE_enum(_prefix,_param) \
    BOOST_PP_CAT(_prefix,PARAM_FNAME(_param))[] = {PARAM_FSEQ_STR(_param),NULL};

/** Helper for #PARAM_ENUM_STRING_DECLARE */
#define PARAM_ENUM_STRING_DECLARE_(_1,_prefix,_param) \
    PARAM_TYPED(PARAM_ENUM_STRING_DECLARE_,_param)(_prefix,_param)

/** Make \c enum string list
 *
 * Roughly translated:
 * \code{.unparsed}
 *      _prefix##_name1[] = {#seq1[0], #seq1[1], ...,NULL};
 *      _prefix##_name2[] = {#seq2[0], #seq2[1], ...,NULL};
 *      ...
 * \endcode
 * Example usage:
 *      PARAM_ENUM_STRING_DECLARE(static const char *Enum, MyParamsSeq) 
 * \ingroup ParamEnumHelper
 */
#define PARAM_ENUM_STRING_DECLARE(_prefix,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_ENUM_STRING_DECLARE_,_prefix,_seq)


/** Helper for #PARAM_INIT */
#define PARAM_INIT_(_,_src,_i,_param) \
    BOOST_PP_COMMA_IF(_i) _src(_param)(PARAM_FDEF(_param))

/** Constructor initialization
 *
 * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for
 * more details
 *
 * Expand to,
 * \code{.unparsed}
 *       _src(_param1)(def1), _src(_param1)(def2)...  
 * \endcode
 * \ingroup ParamCommon
 */
#define PARAM_INIT(_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH_I(PARAM_INIT_,_src,_seq)


/** Helper for #PARAM_OP */
#define PARAM_OP_(_,_args,_param) \
     BOOST_PP_TUPLE_ELEM(0,_args)(_param) BOOST_PP_TUPLE_ELEM(1,_args) \
            BOOST_PP_TUPLE_ELEM(2,_args)(_param);

/** Perform operation on two instance of each parameter in a sequence
 *
 * \arg \c _src: Macro to generate source variable. The signature must be
 * <tt>_src(_param)<\tt>, where \c _param is the tuple defining the parameter.
 * You pass any of the \ref ParamAccessor "parameter accessors" to directly
 * access the field. Or, supply your own macro to append any prefix as you
 * like.
 * \arg \c _op: a boolean operator
 * \arg \c _dst: Same as \c _src above.
 *
 * Expands to:
 * \code{.unparsed}
 *      _src(_param1) _op _src(_param2);
 * \endcode
 *
 * \ingroup ParamCommon
 */
#define PARAM_OP(_src,_op,_dst,_seq) \
     BOOST_PP_SEQ_FOR_EACH(PARAM_COPY_,(_src,_op,_dst),_seq)


/** Helper for #PARAM_ARGS_DEF */
#define PARAM_ARGS_DEF_(_,_src,_i,_param) \
    BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) _src(_param)=PARAM_FDEF(_param)

/** Delcare the parameters as function argument list with defaults.
 *
 * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for
 * more details
 *
 * Expand to:
 * \code{.unparsed}
 *      type1 _src(_param1)=def1, type2 _src(_param1)=def2 ...
 * \endcode
 * \ingroup ParamCommon
 */
#define PARAM_ARGS_DEF(_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_DEF_,_src,_seq)


/** Helper for #PARAM_ARGS */
#define PARAM_ARGS_(_,_src,_i,_param) \
    BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) _src(_param)

/** Delcare the parameters as function argument list without defaults. 
 *
 * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for
 * more details
 *
 * Expand to:
 * \code{.unparsed}
 *      type1 _src(_param1), type2 _src(_param2) ... 
 * \endcode
 * \ingroup ParamCommon
 */
#define PARAM_ARGS(_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_,_src,_seq)


/** \defgroup ParamPy Python helper
 * Helper macros for Python bindings
 * \ingroup ParamHelper
 */

/** \defgroup ParamDoc Python doc helper
 * Generate argument doc string for Python 
 * \ingroup ParamPy
 */

/** Helper for #PARAM_PY_DOC_enum */
#define PARAM_PY_DOC_enum_(_i,_elem) \
    BOOST_PP_IF(_i,","," ") #_i "=" #_elem

/** Generate doc for an enum parameter */
#define PARAM_PY_DOC_enum(_field,_param) \
    "\n* " PARAM_FIELD_STR(_field,_param) "(" PARAM_FDEF_STR(_param)"):" \
        PARAM_FOREACH_I(PARAM_PY_DOC_enum_, PARAM_FSEQ(_param)) ". " \
        PARAM_FDOC(_param) "\n"

/* Generate doc for other type of parameter */
#define PARAM_PY_DOC_short(_field,_param) \
    "\n* " PARAM_FIELD_STR(_field,_param) "(" PARAM_FDEF_STR(_param)"): " \
            PARAM_FDOC(_param) "\n"
#define PARAM_PY_DOC_long PARAM_PY_DOC_short
#define PARAM_PY_DOC_double PARAM_PY_DOC_short
#define PARAM_PY_DOC_bool PARAM_PY_DOC_short
#define PARAM_PY_DOC_enum2 PARAM_PY_DOC_enum

#define PARAM_PY_DOC_(_,_field,_param) \
    PARAM_TYPED(PARAM_PY_DOC_,_param)(_field,_param)

/* Generate document of a sequence of parameters 
 * \ingroup ParamDoc
 */
#define PARAM_PY_DOC(_field,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DOC_,_field,_seq)


/** Helper for #PARAM_PY_ARGS_DOC */
#define PARAM_PY_ARGS_DOC_(_,_field,_i,_param) \
    BOOST_PP_IF(_i,", "," ") PARAM_FIELD_STR(_field,_param) "=" PARAM_FDEF_STR(_param)

/** Generate argument list string 
 * \arg \c _field: specifies the \ref ParamField "field" to use as name
 *
 * Expand to a single string:
 * \code{.unparsed}
 *      "_field1=_def1,_field2=_def2 ..."
 * \endcode
 *
 * \ingroup ParamDoc
 */
#define PARAM_PY_ARGS_DOC(_field,_seq) \
    BOOST_PP_SEQ_FOR_EACH_I(PARAM_PY_ARGS_DOC_,_field,_seq)


/** Helper for #PARAM_FIELDS */
#define PARAM_FIELDS_(_1,_src,_i,_param) \
    BOOST_PP_COMMA_IF(_i) _src(_param)

/** Expand to a list of the given field in the parameter sequence 
 *
 * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for
 * more details
 *
 * For example, PARAM_FIELDS(PARAM_FARG, _seq) expands to:
 * \code{.unparsed}
 *      arg1,arg2 ...
 * \endcode
 * \ingroup ParamCommon ParamPy
 */
#define PARAM_FIELDS(_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH_I(PARAM_FIELDS_,_src,_seq)


#define PARAM_PY_CAST_short(_v)  (_v)
#define PARAM_PY_CAST_long(_v)   (_v)
#define PARAM_PY_CAST_double(_v) (_v)
#define PARAM_PY_CAST_bool(_v)   ((_v)?Py_True:Py_False)
#define PARAM_PY_CAST_enum(_v)   (_v)
#define PARAM_PY_CAST_enum2(_v)   (_v)

#define PARAM_CAST_PY_short(_v)  (_v)
#define PARAM_CAST_PY_long(_v)   (_v)
#define PARAM_CAST_PY_double(_v) (_v)
#define PARAM_CAST_PY_bool(_v)   (PyObject_IsTrue(_v)?true:false)
#define PARAM_CAST_PY_enum(_v)   (_v)
#define PARAM_CAST_PY_enum2(_v)   (_v)


/** Helper for #PARAM_PY_FIELDS */
#define PARAM_PY_FIELDS_(_1,_src,_i,_param) \
    BOOST_PP_COMMA_IF(_i) PARAM_TYPED(PARAM_CAST_PY_,_param)(_src(_param))

/** Expand to a comma separated list of the given field in the sequence
 *
 * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for
 * more details
 *
 * The field will be casted from python C to C type 
 * \ingroup ParamCommon ParamPy
 */
#define PARAM_PY_FIELDS(_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH_I(PARAM_PY_FIELDS_,_src,_seq)


/** Helper for #PARAM_FIELD_STRINGS */
#define PARAM_FIELD_STRINGS_(_1,_field,_i,_param) \
    BOOST_PP_COMMA_IF(_i) PARAM_FIELD_STR(_field,_param)

/** Expand to a list of stringified fields
 * \ingroup ParamStringizer ParamPy
 */
#define PARAM_FIELD_STRINGS(_field,_seq) \
    BOOST_PP_SEQ_FOR_EACH_I(PARAM_FIELD_STRINGS_,_field,_seq)


#define PARAM_PYARG_short  "h"
#define PARAM_PYARG_long   "l"
#define PARAM_PYARG_double "d"
#define PARAM_PYARG_bool   "O"
#define PARAM_PYARG_enum   "h"
#define PARAM_PYARG_enum2  "h"

/** Helper for #PARAM_PY_KWDS */
#define PARAM_PY_KWDS_(_param) \
    PARAM_TYPED(PARAM_PYARG_,_param)

/** Generate a format string for kewords based argument 
 * \ingroup ParamPy
 */
#define PARAM_PY_KWDS(_seq) \
    PARAM_FOREACH(PARAM_PY_KWDS_,_seq)

#define PARAM_PY_TYPE_short     short
#define PARAM_PY_TYPE_long      long
#define PARAM_PY_TYPE_double    double
#define PARAM_PY_TYPE_bool      PyObject*
#define PARAM_PY_TYPE_enum      short
#define PARAM_PY_TYPE_enum2     short

/** Helper for #PARAM_PY_DECLARE */
#define PARAM_PY_DECLARE_(_1,_src,_param) \
    PARAM_TYPED(PARAM_PY_TYPE_,_param) _src(_param);

/** Declare field variables for Python C type without initialization 
 * \ingroup ParamPy
 */
#define PARAM_PY_DECLARE(_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_,_src,_seq)

#define PARAM_PY_INIT_short(_v)     _v
#define PARAM_PY_INIT_long(_v)      _v
#define PARAM_PY_INIT_double(_v)    _v
#define PARAM_PY_INIT_bool(_v)      ((_v)?Py_True:Py_False)
#define PARAM_PY_INIT_enum(_v)      _v
#define PARAM_PY_INIT_enum2(_v)      _v

/** Helper for #PARAM_PY_DECLARE_INIT */
#define PARAM_PY_DECLARE_INIT_(_1,_src,_param) \
    PARAM_TYPED(PARAM_PY_TYPE_,_param) _src(_param) = \
        PARAM_TYPED(PARAM_PY_INIT_,_param)(PARAM_FDEF(_param));

/** Declare field variables of Python c type with initialization to default
 * \ingroup ParamPy
 */
#define PARAM_PY_DECLARE_INIT(_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_INIT_,_src,_seq)


/** Helper for #PARAM_REF */
#define PARAM_REF_(_1,_src,_i,_param) \
    BOOST_PP_COMMA_IF(_i) &_src(_param)

/** Generate a list of field references
 *
 * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for
 *
 * more details
 * Expand to:
 * \code{.unparsed}
 *      &_src(_param1), &_src(_param1) ... 
 * \endcode
 * \ingroup ParamPy
 */
#define PARAM_REF(_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH_I(PARAM_REF_,_src,_seq)

#if PY_MAJOR_VERSION < 3
    #define PARAM_CAST_PYOBJ_short(_v)   PyInt_FromLong(_v)
    #define PARAM_CAST_PYOBJ_long(_v)    PyInt_FromLong(_v)
#else
    #define PARAM_CAST_PYOBJ_short(_v)   PyLong_FromLong(_v)
    #define PARAM_CAST_PYOBJ_long(_v)    PyLong_FromLong(_v)
#endif

#define PARAM_CAST_PYOBJ_double(_v)  PyFloat_FromDouble(_v)
#define PARAM_CAST_PYOBJ_bool(_v)    ((_v)?Py_True:Py_False)
#define PARAM_CAST_PYOBJ_enum        PARAM_CAST_PYOBJ_short
#define PARAM_CAST_PYOBJ_enum2       PARAM_CAST_PYOBJ_short


/** Stringize field to a Python string 
 * \ingroup ParamPy ParamStringizer
 */
#if PY_MAJOR_VERSION < 3
#define PARAM_PY_STR(_field,_param)  \
    PyString_FromString(PARAM_FIELD_STR(_field,_param))
#else
#define PARAM_PY_STR(_field,_param)  \
    PyUnicode_FromString(PARAM_FIELD_STR(_field,_param))
#endif
/** Helper for #PARAM_PY_DICT_SET_VALUE */
#define PARAM_PY_DICT_SET_VALUE_(_1,_args,_param) \
    PyDict_SetItem(BOOST_PP_TUPLE_ELEM(0,_args), \
            PARAM_PY_STR(BOOST_PP_TUPLE_ELEM(1,_args),_param),\
            PARAM_TYPED(PARAM_CAST_PYOBJ_,_param)(\
                BOOST_PP_TUPLE_ELEM(2,_args)(_param)));

/** Populate a Python dict with a structure variable
 *
 * \arg \c _dict: the Python dictionary object
 * \arg \c _field: specifies the \ref ParamField "field" to use as key
 * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for
 * more details
 *
 * Roughly translated to:
 * \code{.unparsed}
 *      PyDict_SetItem(_dict,#_field1,_src(_param));
 *      PyDict_SetItem(_dict,#_field2,_src(_param)); 
 *      ... 
 * \endcode
 * \ingroup ParamPy
 */
#define PARAM_PY_DICT_SET_VALUE(_dict,_field,_src,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_VALUE_,(_dict,_field,_src),_seq)


#define PARAM_PY_DICT_DOC_enum_(_i,_elem) \
    BOOST_PP_IF(_i,","," ") #_i "=" #_elem

/** Generate doc for an enum parameter */
#define PARAM_PY_DICT_DOC_enum(_param) \
    "(" PARAM_FDEF_STR(_param) ") - " \
        PARAM_FOREACH_I(PARAM_PY_DOC_enum_, PARAM_FSEQ(_param)) ".\n" \
        PARAM_FDOC(_param) "\n"

/* Generate doc for other type of parameter */
#define PARAM_PY_DICT_DOC_(_param) \
    "(" PARAM_FDEF_STR(_param) ") - " PARAM_FDOC(_param) "\n"


#define PARAM_PY_DICT_DOC_short PARAM_PY_DICT_DOC_
#define PARAM_PY_DICT_DOC_long PARAM_PY_DICT_DOC_
#define PARAM_PY_DICT_DOC_double PARAM_PY_DICT_DOC_
#define PARAM_PY_DICT_DOC_bool PARAM_PY_DICT_DOC_
#define PARAM_PY_DICT_DOC_enum2 PARAM_PY_DICT_DOC_enum

/** Helper for #PARAM_PY_DICT_SET_DOC */
#if PY_MAJOR_VERSION < 3
#define PARAM_PY_DICT_SET_DOC_(_1,_args,_param) \
    PyDict_SetItem(BOOST_PP_TUPLE_ELEM(0,_args), \
            PARAM_PY_STR(BOOST_PP_TUPLE_ELEM(1,_args),_param),\
            PyString_FromString(PARAM_TYPED(PARAM_PY_DICT_DOC_,_param)(_param)));
#else
#define PARAM_PY_DICT_SET_DOC_(_1,_args,_param) \
    PyDict_SetItem(BOOST_PP_TUPLE_ELEM(0,_args), \
            PARAM_PY_STR(BOOST_PP_TUPLE_ELEM(1,_args),_param),\
            PyUnicode_FromString(PARAM_TYPED(PARAM_PY_DICT_DOC_,_param)(_param)));
#endif

/** Populate a Python dict with the doc field of the parameter sequence
 *
 * \arg \c _dict: the Python dictionary object
 * \arg \c _field: specifies the \ref ParamField "field" to use as key
 *
 * Roughly translated to:
 * \code{.unparsed}
 *      PyDict_SetItem(_dict,#_field1,doc1);
 *      PyDict_SetItem(_dict,#_field1,doc2); 
 *      ... 
 * \endcode
 * \ingroup ParamDoc
 */
#define PARAM_PY_DICT_SET_DOC(_dict,_field,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_DOC_,(_dict,_field),_seq)


/** \defgroup ParamProperty Property Macros
 * Helper macros for FreeCAD properties 
 * \ingroup ParamHelper
 * @{*/
#define PARAM_PROP_bool(_param) App::PropertyBool PARAM_FNAME(_param)
#define PARAM_PROP_double(_param) PARAM_FPROP(_param) PARAM_FNAME(_param)
#define PARAM_PROP_short(_param) App::PropertyInteger PARAM_FNAME(_param)
#define PARAM_PROP_long(_param) App::PropertyInteger PARAM_FNAME(_param)
#define PARAM_PROP_enum(_param) App::PropertyEnumeration PARAM_FNAME(_param)
#define PARAM_PROP_enum2(_param) App::PropertyEnumeration PARAM_FNAME(_param)
/** @} */

/** Helper for #PARAM_PROP_DECLARE */
#define PARAM_PROP_DECLARE_(_param) \
    PARAM_TYPED(PARAM_PROP_,_param)(_param);

/** Declare FreeCAD properties 
 * \ingroup ParamProperty
 */
#define PARAM_PROP_DECLARE(_seq) \
    PARAM_FOREACH(PARAM_PROP_DECLARE_,_seq)

/** Replace FreeCAD #ADD_PROPERTY_TYPE to fix singifying macro */
#define PARAM_ADD_PROPERTY_TYPE(_prop_, _defaultval_, _group_,_type_,_Docu_) \
  do { \
    this->_prop_.setValue _defaultval_;\
    this->_prop_.setContainer(this); \
    propertyData.addProperty(static_cast<App::PropertyContainer*>(this), BOOST_PP_STRINGIZE(_prop_), &this->_prop_, (_group_),(_type_),(_Docu_)); \
  } while (0)

/** Generic property adding */
#define PARAM_PROP_ADD_(_group,_param) \
    PARAM_ADD_PROPERTY_TYPE(PARAM_FNAME(_param), (PARAM_FDEF(_param)),\
            _group,App::Prop_None,PARAM_FDOC(_param));

#define PARAM_PROP_ADD_short PARAM_PROP_ADD_
#define PARAM_PROP_ADD_long PARAM_PROP_ADD_
#define PARAM_PROP_ADD_double PARAM_PROP_ADD_
#define PARAM_PROP_ADD_bool PARAM_PROP_ADD_
#define PARAM_PROP_ADD_enum2 PARAM_PROP_ADD_enum

/** Add \c enum type parameter as property */
#define PARAM_PROP_ADD_enum(_group,_param) \
    PARAM_ADD_PROPERTY_TYPE(PARAM_FNAME(_param), ((long)PARAM_FDEF(_param)),\
            _group,App::Prop_None,PARAM_FDOC(_param));

/** Helper for #PARAM_PROP_ADD */
#define PARAM_PROP_ADD_TYPED(_1,_group,_i,_param) \
    PARAM_TYPED(PARAM_PROP_ADD_,_param)(_group,_param)

/** Add FreeCAD properties
 * \ingroup ParamProperty
 */
#define PARAM_PROP_ADD(_group,_seq) \
    BOOST_PP_SEQ_FOR_EACH_I(PARAM_PROP_ADD_TYPED,_group,_seq)

#define PARAM_PROP_SET_ENUM_short(...)
#define PARAM_PROP_SET_ENUM_long(...)
#define PARAM_PROP_SET_ENUM_bool(...)
#define PARAM_PROP_SET_ENUM_double(...)
#define PARAM_PROP_SET_ENUM_enum2 PARAM_PROP_SET_ENUM_enum

/** Setup \c enum type parameter */
#define PARAM_PROP_SET_ENUM_enum(_prefix,_param) \
    PARAM_FNAME(_param).setEnums(BOOST_PP_CAT(_prefix,PARAM_FNAME(_param)));
 
/** Helper for #PARAM_PROP_SET_ENUM */
#define PARAM_PROP_SET_ENUM_TYPED(_1,_prefix,_param) \
    PARAM_TYPED(PARAM_PROP_SET_ENUM_,_param)(_prefix,_param)

/* Setup the \c enum string list for \c enum type properties
 * \ingroup ParamProperty
 */
#define PARAM_PROP_SET_ENUM(_prefix,_seq) \
    BOOST_PP_SEQ_FOR_EACH(PARAM_PROP_SET_ENUM_TYPED,_prefix,_seq)


/** Helper for #PARAM_PROP_ARGS */
#define PARAM_PROP_ARGS_(_i,_param) \
    BOOST_PP_COMMA_IF(_i) PARAM_FNAME(_param).getValue()

/** Expand the property list as function arguments 
 *
 * Expand to:
 * \code{.unparsed}
 *      name1.getValue(), name2.getValue() ...
 * \endcode
 * \ingroup ParamProperty
 */
#define PARAM_PROP_ARGS(_seq) \
    PARAM_FOREACH_I(PARAM_PROP_ARGS_,_seq)


/** Helper for #PARAM_PROP_TOUCHED */
#define PARAM_PROP_TOUCHED_(_param) \
    if(PARAM_FNAME(_param).isTouched()) return 1;
    
/** Returns 1 if any properties is touched 
 *
 * Expand to:
 * \code{.unparsed}
 *      if(name1.isTouched()) return 1;
 *      if(name2.isTouched()) return 1;
 *      ...
 * \ingroup ParamProperty
 */
#define PARAM_PROP_TOUCHED(_seq) \
    PARAM_FOREACH(PARAM_PROP_TOUCHED_,_seq)

#endif // PARAMS_HELPER_H
