/******************************************************************************
 * Copyright (c) 2024 Lawrence Livermore National Security, LLC and other
 * HYPRE Project Developers. See the top-level COPYRIGHT file for details.
 *
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#ifndef GEN_MACROS_HEADER
#define GEN_MACROS_HEADER

#include "internal/field.h"

/**
 * @brief Defines a function to set a field by name.
 *
 * @details This macro generates a function that sets the field of a structure
 * based on the provided name. The function uses a map to find the correct field
 * and then assigns the appropriate value.
 *
 * @param _funcName Name of the generated function.
 * @param _argsType Type of the argument structure.
 * @param _map Name of the map containing field information.
 * @param _numFields Number of fields in the map.
 */
#define DEFINE_SET_FIELD_BY_NAME_FUNC(_funcName, _argsType, _map, _numFields)        \
   void _funcName(void *vargs, const YAMLnode *_node)                                \
   {                                                                                 \
      _argsType *_args = (_argsType *)vargs;                                         \
      for (size_t i = 0; i < (_numFields); i++)                                      \
      {                                                                              \
         if (!strcmp((_map)[i].name, (_node)->key))                                  \
         {                                                                           \
            (_map)[i].setter((void *)((char *)(_args) + (_map)[i].offset), (_node)); \
            return;                                                                  \
         }                                                                           \
      }                                                                              \
   }

/**
 * @brief Defines a function to get valid keys.
 *
 * @details This macro generates a function that constructs a static array
 * of key names from a given map and returns it as a StrArray object type.
 *
 * @param _funcName Name of the generated function.
 * @param _numFields Number of fields in the map.
 * @param _map Name of the map containing field information.
 */
#define DEFINE_GET_VALID_KEYS_FUNC(_funcName, _numFields, _map) \
   StrArray _funcName(void)                                     \
   {                                                            \
      static const char *_keys[_numFields];                     \
      for (size_t i = 0; i < _numFields; i++)                   \
      {                                                         \
         _keys[i] = _map[i].name;                               \
      }                                                         \
      return STR_ARRAY_CREATE(_keys);                           \
   }

/**
 * @brief Declares a function to get valid values.
 *
 * @details This macro generates a forward declaration for a function
 * that retrieves valid values as a StrIntMapArray object type.
 *
 * @param _prefix Prefix used in the naming of the declared function.
 */
#define DECLARE_GET_VALID_VALUES_FUNC(_prefix) \
   StrIntMapArray _prefix##GetValidValues(const char *);

/**
 * @brief Declares a function to get valid values.
 *
 * @details This macro generates a function that retrieves valid values
 * as a StrIntMapArray object type. No restrictions are imposed on the
 * valid values, so we create a void map
 *
 * @param _prefix Prefix used in the naming of the declared function.
 */
#define hypredrv_DEFINE_VOID_GET_VALID_VALUES_FUNC(_prefix) \
   StrIntMapArray _prefix##GetValidValues(const char *key)  \
   {                                                        \
      (void)key;                                            \
      return STR_INT_MAP_ARRAY_VOID();                      \
   }

/**
 * @brief Declares a function to set default arguments.
 *
 * @param _fnPrefix Function name prefix (e.g. hypredrv_AMG).
 * @param _argsType Args struct type (e.g. AMG_args).
 */
#define DECLARE_SET_DEFAULT_ARGS_FUNC(_fnPrefix, _argsType) \
   void _fnPrefix##SetDefaultArgs(_argsType *);

/**
 * @brief Declares a function to set arguments from a YAML input.
 *
 * @param _prefix Prefix used in the naming of the declared function.
 */
#define DECLARE_SET_ARGS_FROM_YAML_FUNC(_prefix) \
   void _prefix##SetArgsFromYAML(void *, YAMLnode *);

/**
 * @brief Define a wrapper setter that also sets a "type" field on the parent struct.
 *
 * @details Useful when a YAML key (e.g., "ilu") selects a variant stored in a union
 * and the parent args struct must record the selected variant type.
 *
 * The generated function has signature compatible with FieldOffsetMap setters:
 *   void (*setter)(void*, const YAMLnode*)
 *
 * @param _func_name Name of the generated wrapper function.
 * @param _parent    Parent struct type (e.g., MGRcls_args).
 * @param _field     Field name within _parent (e.g., amg, ilu).
 * @param _type      Integer type value to write into ((_parent*)...)->type.
 * @param _setter    The real setter to invoke after setting the type.
 */
#define DEFINE_TYPED_SETTER(_func_name, _parent, _field, _type, _setter)    \
   static void _func_name(void *v, const YAMLnode *n)                       \
   {                                                                        \
      ((_parent *)((char *)v - offsetof(_parent, _field)))->type = (_type); \
      _setter(v, n);                                                        \
   }

/**
 * @brief Defines a function to set arguments from a YAML node.
 *
 * @param _prefix Struct prefix for args type (e.g. AMG).
 * @param _fnPrefix Function name prefix (e.g. hypredrv_AMG).
 */
#define DEFINE_SET_ARGS_FROM_YAML_FUNC(_prefix, _fnPrefix)                               \
   void _fnPrefix##SetArgsFromYAML(void *vargs, YAMLnode *parent)                        \
   {                                                                                     \
      _prefix##_args *args = (_prefix##_args *)vargs;                                    \
      hypredrv_YAMLSetArgsGeneric((void *)args, parent, _fnPrefix##GetValidKeys,         \
                                  _fnPrefix##GetValidValues, _fnPrefix##SetFieldByName); \
   }

/**
 * @brief Defines a function to set arguments of a generic struct.
 *
 * @param _prefix Struct prefix for args type.
 * @param _fnPrefix Function name prefix for callables.
 */
#define DEFINE_SET_ARGS_FUNC(_prefix, _fnPrefix)                    \
   void _fnPrefix##SetArgs(void *vargs, const YAMLnode *parent)     \
   {                                                                \
      _prefix##_args *args = (_prefix##_args *)vargs;               \
      _fnPrefix##SetDefaultArgs(args);                              \
      _fnPrefix##SetArgsFromYAML((void *)args, (YAMLnode *)parent); \
   }

/**
 * @brief Adds an entry in the field offset map.
 *
 * @details X-macro used to generate an entry in a field offset map array,
 *          associating a structure member with a setter. It is designed to work in
 *          conjunction with the DEFINE_FIELD_OFFSET_MAP macro
 *
 * @param _prefix The prefix to be concatenated with _field to form the structure member.
 * @param _field The field/member of the structure.
 * @param _setter The function or macro to set the value of the field/member.
 */
#define ADD_FIELD_OFFSET_ENTRY(_prefix, _field, _setter) \
   FIELD_OFFSET_MAP_ENTRY(_prefix##_args, _field, _setter),

/**
 * @brief A macro to define a field offset map for a given prefix.
 *
 * @details This macro generates a static array of FieldOffsetMap, containing
 *          entries created by ADD_FIELD_OFFSET_ENTRY macro, each associating
 *          a structure field with a setter.
 *
 * @param _prefix The prefix used to identify the structure and its fields/members.
 */
#define DEFINE_FIELD_OFFSET_MAP(_prefix) \
   static const FieldOffsetMap _prefix##_field_offset_map[] = {_prefix##_FIELDS(_prefix)};

/**
 * @def GENERATE_PREFIXED_COMPONENTS(prefix)
 *
 * @brief An X-macro for generating a series of component definitions, declarations,
 * and initializations based on the provided prefix.
 *
 * @details This is an aggregate macro that generates several utility functions,
 * declarations and a field offset map. Callable functions use the hypredrv_ prefix
 * (e.g. hypredrv_AMGSetFieldByName); struct types use the bare prefix (e.g. AMG_args).
 *
 * Usage:
 * @code
 * GENERATE_PREFIXED_COMPONENTS(AMG)
 * @endcode
 *
 * Generates hypredrv_AMGSetFieldByName, hypredrv_AMGGetValidKeys, hypredrv_AMGSetArgs,
 * etc., with AMG_args and AMG_field_offset_map for types.
 */
#define GENERATE_PREFIXED_COMPONENTS(prefix)                                        \
   DEFINE_FIELD_OFFSET_MAP(prefix)                                                  \
   DEFINE_SET_FIELD_BY_NAME_FUNC(hypredrv_##prefix##SetFieldByName, prefix##_args,  \
                                 prefix##_field_offset_map, prefix##_NUM_FIELDS)    \
   DEFINE_GET_VALID_KEYS_FUNC(hypredrv_##prefix##GetValidKeys, prefix##_NUM_FIELDS, \
                              prefix##_field_offset_map)                            \
   DECLARE_GET_VALID_VALUES_FUNC(hypredrv_##prefix)                                 \
   DECLARE_SET_DEFAULT_ARGS_FUNC(hypredrv_##prefix, prefix##_args)                  \
   DEFINE_SET_ARGS_FROM_YAML_FUNC(prefix, hypredrv_##prefix)                        \
   DEFINE_SET_ARGS_FUNC(prefix, hypredrv_##prefix)

/**
 * @brief Adds an entry in the field offset map from a 4-column field list.
 *
 * @details Applier for X-macro field lists of the form _X(_prefix, field, setter,
 *          default). Generates the same map entry as ADD_FIELD_OFFSET_ENTRY,
 *          ignoring the default value column.
 */
#define ADD_FIELD_OFFSET_ENTRY4(_prefix, _field, _setter, _default) \
   FIELD_OFFSET_MAP_ENTRY(_prefix##_args, _field, _setter),

/**
 * @brief Generates a default-value assignment from a 4-column field list entry.
 */
#define ADD_FIELD_DEFAULT_ENTRY4(_prefix, _field, _setter, _default) \
   _args->_field = (_default);

/**
 * @brief Defines a field offset map from a 4-column field list.
 *
 * @details Counterpart of DEFINE_FIELD_OFFSET_MAP for field lists that take an
 *          applier macro as first argument: _prefix##_FIELDS(_X, _prefix).
 */
#define DEFINE_FIELD_OFFSET_MAP4(_prefix)                       \
   static const FieldOffsetMap _prefix##_field_offset_map[] = { \
      _prefix##_FIELDS(ADD_FIELD_OFFSET_ENTRY4, _prefix)};

/**
 * @brief Defines a SetDefaultArgs function generated from a 4-column field list,
 *        assigning each field its default value column.
 */
#define DEFINE_SET_DEFAULT_ARGS_FROM_FIELDS(_prefix)              \
   void hypredrv_##_prefix##SetDefaultArgs(_prefix##_args *_args) \
   {                                                              \
      _prefix##_FIELDS(ADD_FIELD_DEFAULT_ENTRY4, _prefix)         \
   }

/**
 * @brief Like GENERATE_PREFIXED_COMPONENTS, but for 4-column field lists carrying
 *        default values: SetDefaultArgs is generated from the list instead of
 *        being declared for manual implementation.
 *
 * Prefer this form for flat args structs whose defaults are plain constants.
 * Use GENERATE_PREFIXED_COMPONENTS when defaults cannot live in a macro column
 * (conditional on build flags, nested sub-struct initialization, or unions).
 */
#define GENERATE_PREFIXED_COMPONENTS_WITH_DEFAULTS(prefix)                          \
   DEFINE_FIELD_OFFSET_MAP4(prefix)                                                 \
   DEFINE_SET_FIELD_BY_NAME_FUNC(hypredrv_##prefix##SetFieldByName, prefix##_args,  \
                                 prefix##_field_offset_map, prefix##_NUM_FIELDS)    \
   DEFINE_GET_VALID_KEYS_FUNC(hypredrv_##prefix##GetValidKeys, prefix##_NUM_FIELDS, \
                              prefix##_field_offset_map)                            \
   DECLARE_GET_VALID_VALUES_FUNC(hypredrv_##prefix)                                 \
   DEFINE_SET_DEFAULT_ARGS_FROM_FIELDS(prefix)                                      \
   DEFINE_SET_ARGS_FROM_YAML_FUNC(prefix, hypredrv_##prefix)                        \
   DEFINE_SET_ARGS_FUNC(prefix, hypredrv_##prefix)

/**
 * @brief Like GENERATE_PREFIXED_COMPONENTS, but uses a custom prefixSetArgsFromYAML.
 */
#define hypredrv_GENERATE_PREFIXED_COMPONENTS_CUSTOM_YAML(prefix)                   \
   DEFINE_FIELD_OFFSET_MAP(prefix)                                                  \
   DEFINE_SET_FIELD_BY_NAME_FUNC(hypredrv_##prefix##SetFieldByName, prefix##_args,  \
                                 prefix##_field_offset_map, prefix##_NUM_FIELDS)    \
   DEFINE_GET_VALID_KEYS_FUNC(hypredrv_##prefix##GetValidKeys, prefix##_NUM_FIELDS, \
                              prefix##_field_offset_map)                            \
   DECLARE_GET_VALID_VALUES_FUNC(hypredrv_##prefix)                                 \
   DECLARE_SET_DEFAULT_ARGS_FUNC(hypredrv_##prefix, prefix##_args)                  \
   DECLARE_SET_ARGS_FROM_YAML_FUNC(hypredrv_##prefix)                               \
   DEFINE_SET_ARGS_FUNC(prefix, hypredrv_##prefix)

#endif /* GEN_MACROS_HEADER */
