/**
 * Copyright 1999-2010 Ethan Galstad
 * Copyright 2011-2020 Centreon
 *
 * This file is part of Centreon Engine.
 *
 * Centreon Engine is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * Centreon Engine 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Centreon Engine. If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include "com/centreon/engine/macros/grab_value.hh"

#include "com/centreon/engine/configuration/applier/state.hh"
#include "com/centreon/engine/globals.hh"
#include "com/centreon/engine/logging/logger.hh"
#include "com/centreon/engine/macros.hh"
#include "com/centreon/engine/string.hh"

using namespace com::centreon::engine;
using namespace com::centreon::engine::configuration::applier;
using namespace com::centreon::engine::logging;

/**************************************
 *                                     *
 *           Local Functions           *
 *                                     *
 **************************************/

/**
 *  Get host macro.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to true if output buffer must be freed by
 *                         caller.
 *
 *  @return OK on success.
 */
static int handle_host_macro(nagios_macros* mac,
                             int macro_type,
                             std::string const& arg1,
                             std::string const& arg2,
                             std::string& output,
                             int* free_macro) {
  int retval;
  if (arg2.empty()) {
    // Find the host for on-demand macros
    // or use saved host pointer.
    host* hst = nullptr;

    if (!arg1.empty()) {
      host_map::const_iterator it(host::hosts.find(arg1));
      if (it != host::hosts.end())
        hst = it->second.get();
    } else
      hst = mac->host_ptr;

    if (hst)
      // Get the host macro value.
      retval =
          grab_standard_host_macro_r(mac, macro_type, hst, output, free_macro);
    else
      retval = ERROR;
  }
  // A host macro with a hostgroup name and delimiter.
  else {
    hostgroup* hg(nullptr);
    hostgroup_map::const_iterator it{hostgroup::hostgroups.find(arg1)};
    if (it != hostgroup::hostgroups.end())
      hg = it->second.get();

    if (hg) {
      // Concatenate macro values for all hostgroup members.
      for (host_map_unsafe::iterator it(hg->members.begin()),
           end(hg->members.end());
           it != end; ++it) {
        if (it->second) {
          // Get the macro value for this host.
          std::string buffer;
          int free_sub_macro{false};
          grab_standard_host_macro_r(mac, macro_type, it->second, buffer,
                                     &free_sub_macro);

          // Add macro value to already running macro.
          if (output.empty())
            output = buffer;
          else {
            output.append(arg2);
            output.append(buffer);
          }

          *free_macro = true;
        }
      }
      retval = OK;
    } else
      retval = ERROR;
  }
  return retval;
}

/**
 *  Get hostgroup macro.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to true if output buffer must be freed by
 *                         caller.
 *
 *  @return OK on success.
 */
static int handle_hostgroup_macro(nagios_macros* mac,
                                  int macro_type,
                                  std::string const& arg1,
                                  std::string const& arg2,
                                  std::string& output,
                                  int* free_macro) {
  (void)arg2;

  // Return value.
  int retval;

  // Use the saved hostgroup pointer
  // or find the hostgroup for on-demand macros.
  hostgroup* hg(nullptr);
  if (!arg1.empty()) {
    hostgroup_map::const_iterator it(hostgroup::hostgroups.find(arg1));
    if (it != hostgroup::hostgroups.end())
      hg = it->second.get();
  } else
    hg = mac->hostgroup_ptr;

  if (hg) {
    // Get the hostgroup macro value.
    retval = grab_standard_hostgroup_macro_r(mac, macro_type, hg, output);
    if (OK == retval)
      *free_macro = true;
  } else
    retval = ERROR;
  return retval;
}

/**
 *  Get service macro.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to true if macro must be freed by caller.
 *
 *  @return OK on success.
 */
static int handle_service_macro(nagios_macros* mac,
                                int macro_type,
                                std::string const& arg1,
                                std::string const& arg2,
                                std::string& output,
                                int* free_macro) {
  // Return value.
  int retval;

  // Use saved service pointer.
  if (arg1.empty() && arg2.empty()) {
    if (!mac->service_ptr)
      retval = ERROR;
    else
      retval = grab_standard_service_macro_r(mac, macro_type, mac->service_ptr,
                                             output, free_macro);
  }
  // Else and ondemand macro...
  else {
    // If first arg is blank, it means use the current host name.
    if (arg1.empty()) {
      if (!mac->host_ptr)
        retval = ERROR;
      else if (!arg2.empty()) {
        service_map::const_iterator found(
            service::services.find({mac->host_ptr->name(), arg2}));

        if (found == service::services.end() || !found->second)
          retval = ERROR;
        else
          // Get the service macro value.
          retval = grab_standard_service_macro_r(
              mac, macro_type, found->second.get(), output, free_macro);
      } else
        retval = ERROR;
    } else if (!arg1.empty() && !arg2.empty()) {
      // On-demand macro with both host and service name.
      service_map::const_iterator found(service::services.find({arg1, arg2}));

      if (found != service::services.end() && found->second)
        // Get the service macro value.
        retval = grab_standard_service_macro_r(
            mac, macro_type, found->second.get(), output, free_macro);
      // Else we have a service macro with a
      // servicegroup name and a delimiter...
      else {
        servicegroup_map::const_iterator sg_it{
            servicegroup::servicegroups.find(arg1)};
        if (sg_it == servicegroup::servicegroups.end() || !sg_it->second)
          retval = ERROR;
        else {
          // Concatenate macro values for all servicegroup members.
          for (service_map_unsafe::iterator it(sg_it->second->members.begin()),
               end(sg_it->second->members.end());
               it != end; ++it) {
            if (it->second != nullptr) {
              // Get the macro value for this service.
              std::string buffer;
              int free_sub_macro(false);
              grab_standard_service_macro_r(mac, macro_type, it->second, buffer,
                                            &free_sub_macro);
              if (output.empty())
                output = buffer;
              else {
                output.append(arg2);
                output.append(buffer);
              }
            }
          }
          *free_macro = true;
          retval = OK;
        }
      }
    } else
      retval = ERROR;
  }
  return retval;
}

/**
 *  Get servicegroup macro.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to true if output buffer must be freed by
 *                         caller.
 *
 *  @return OK on success.
 */
static int handle_servicegroup_macro(nagios_macros* mac,
                                     int macro_type,
                                     std::string const& arg1,
                                     std::string const& arg2,
                                     std::string& output,
                                     int* free_macro) {
  (void)arg2;

  // Return value.
  int retval;

  // Use the saved servicegroup pointer
  // or find the servicegroup for on-demand macros.
  servicegroup_map::const_iterator sg_it{
      servicegroup::servicegroups.find(arg1)};
  if (sg_it == servicegroup::servicegroups.end() || !sg_it->second)
    retval = ERROR;
  else {
    // Get the servicegroup macro value.
    retval = grab_standard_servicegroup_macro_r(mac, macro_type,
                                                sg_it->second.get(), output);
    if (OK == retval)
      *free_macro = true;
  }

  return retval;
}

/**
 *  Get contact macro.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to true if output buffer must be freed by
 *                         caller.
 *
 *  @return OK on success.
 */
static int handle_contact_macro(nagios_macros* mac,
                                int macro_type,
                                std::string const& arg1,
                                std::string const& arg2,
                                std::string& output,
                                int* free_macro) {
  // Return value.
  int retval;

  if (arg2.empty()) {
    contact* cnct;
    // Find the contact for on-demand macros
    // or use saved contact pointer.
    if (!arg1.empty()) {
      contact_map::const_iterator ct_it{contact::contacts.find(arg1)};
      if (ct_it == contact::contacts.end())
        return ERROR;
      cnct = ct_it->second.get();
    } else
      cnct = mac->contact_ptr;

    if (!cnct)
      return ERROR;

    // Get the contact macro value.
    retval = grab_standard_contact_macro_r(mac, macro_type, cnct, output);

    if (OK == retval)
      *free_macro = true;
  }
  // A contact macro with a contactgroup name and delimiter.
  else if (!arg1.empty() && !arg2.empty()) {
    contactgroup_map::iterator cg{contactgroup::contactgroups.find(arg1)};
    if (cg == contactgroup::contactgroups.end() || !cg->second)
      retval = ERROR;
    else {
      // Concatenate macro values for all contactgroup members.
      for (contact_map_unsafe::const_iterator
               it{cg->second->get_members().begin()},
           end{cg->second->get_members().end()};
           it != end; ++it) {
        if (it->second) {
          // Get the macro value for this contact.
          std::string buffer;
          grab_standard_contact_macro_r(mac, macro_type, it->second, buffer);
          // Add macro value to already running macro.
          if (output.empty())
            output = buffer;
          else {
            output.append(arg2);
            output.append(buffer);
          }
        }
      }
      *free_macro = true;
      retval = OK;
    }
  } else
    retval = ERROR;
  return retval;
}

/**
 *  Get contactgroup macro.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to true if output buffer must be freed by
 *                         caller.
 *
 *  @return OK on success.
 */
static int handle_contactgroup_macro(nagios_macros* mac [[maybe_unused]],
                                     int macro_type,
                                     const std::string& arg1,
                                     const std::string& arg2,
                                     std::string& output,
                                     int* free_macro) {
  (void)arg2;

  // Return value.
  int retval = OK;

  // Use the saved contactgroup pointer.
  // or find the contactgroup for on-demand macros.
  std::shared_ptr<com::centreon::engine::contactgroup> cg{nullptr};
  contactgroup_map::iterator cg_it{contactgroup::contactgroups.find(arg1)};
  if (cg_it == contactgroup::contactgroups.end() || !cg_it->second)
    retval = ERROR;
  else
    cg = cg_it->second;

  if (cg) {
    // Get the contactgroup macro value.
    retval = grab_standard_contactgroup_macro(macro_type, cg, output);
    if (OK == retval)
      *free_macro = true;
  }

  return retval;
}

/**
 *  Get a notification macro.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to false.
 *
 *  @return OK on success.
 */
static int handle_notification_macro(nagios_macros* mac,
                                     int macro_type,
                                     std::string const& arg1,
                                     std::string const& arg2,
                                     std::string& output,
                                     int* free_macro) {
  (void)arg1;
  (void)arg2;

  // Notification macros have already been pre-computed.
  output = mac->x[macro_type];
  *free_macro = false;

  return OK;
}

/**
 *  Get a date/time macro.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to true if output buffer must be freed by
 *                         caller.
 *
 *  @return OK on success.
 */
static int handle_datetime_macro(nagios_macros* mac,
                                 int macro_type,
                                 std::string const& arg1,
                                 std::string const& arg2,
                                 std::string& output,
                                 int* free_macro) {
  // Calculate macros.
  int retval(grab_datetime_macro_r(mac, macro_type, arg1, arg2, output));
  if (OK == retval)
    *free_macro = true;
  return retval;
}

/**
 *  Get a static macro.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to false.
 *
 *  @return OK on success.
 */
static int handle_static_macro(nagios_macros* mac,
                               int macro_type,
                               std::string const& arg1,
                               std::string const& arg2,
                               std::string& output,
                               int* free_macro) {
  (void)mac;
  (void)arg1;
  (void)arg2;

  // No need to do any more work - these are already precomputed for us.
  output = get_global_macros()->x[macro_type];
  *free_macro = false;
  return OK;
}

/**
 *  Get a summary macro.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to true if output buffer must be freed by
 *                         caller.
 *
 *  @return OK on success.
 */
static int handle_summary_macro(nagios_macros* mac,
                                int macro_type,
                                std::string const& arg1,
                                std::string const& arg2,
                                std::string& output,
                                int* free_macro) {
  (void)arg1;
  (void)arg2;

  // Generate summary macros if needed.
  if (mac->x[MACRO_TOTALHOSTSUP].empty()) {
    // Get host totals.
    unsigned int host_problems(0);
    unsigned int host_problems_unhandled(0);
    unsigned int hosts_down(0);
    unsigned int hosts_down_unhandled(0);
    unsigned int hosts_unreachable(0);
    unsigned int hosts_unreachable_unhandled(0);
    unsigned int hosts_up(0);
    for (host_map::iterator it(com::centreon::engine::host::hosts.begin()),
         end(com::centreon::engine::host::hosts.end());
         it != end; ++it) {
      // Filter totals based on contact if necessary.
      bool authorized(mac->contact_ptr ? is_contact_for_notifier(
                                             it->second.get(), mac->contact_ptr)
                                       : true);
      if (authorized) {
        bool problem(true);
        if ((it->second->get_current_state() == host::state_up) &&
            it->second->has_been_checked())
          hosts_up++;
        else if (it->second->get_current_state() == host::state_down) {
          if (it->second->get_scheduled_downtime_depth() > 0)
            problem = false;
          if (it->second->problem_has_been_acknowledged())
            problem = false;
          if (!it->second->active_checks_enabled())
            problem = false;
          if (problem)
            hosts_down_unhandled++;
          hosts_down++;
        } else if (it->second->get_current_state() == host::state_unreachable) {
          if (it->second->get_scheduled_downtime_depth() > 0)
            problem = false;
          if (it->second->problem_has_been_acknowledged())
            problem = false;
          if (!it->second->active_checks_enabled())
            problem = false;
          if (problem)
            hosts_down_unhandled++;
          hosts_unreachable++;
        }
      }
    }
    host_problems = hosts_down + hosts_unreachable;
    host_problems_unhandled =
        hosts_down_unhandled + hosts_unreachable_unhandled;

    // Get service totals.
    unsigned int service_problems(0);
    unsigned int service_problems_unhandled(0);
    unsigned int services_critical(0);
    unsigned int services_critical_unhandled(0);
    unsigned int services_ok(0);
    unsigned int services_unknown(0);
    unsigned int services_unknown_unhandled(0);
    unsigned int services_warning(0);
    unsigned int services_warning_unhandled(0);

    for (service_map::iterator it(service::services.begin()),
         end(service::services.end());
         it != end; ++it) {
      // Filter totals based on contact if necessary.
      bool authorized(mac->contact_ptr ? is_contact_for_notifier(
                                             it->second.get(), mac->contact_ptr)
                                       : true);
      if (authorized) {
        bool problem(true);
        if (it->second->get_current_state() == service::state_ok &&
            it->second->has_been_checked())
          services_ok++;
        else if (it->second->get_current_state() == service::state_warning) {
          host* temp_host{nullptr};
          host_map::const_iterator found(
              host::hosts.find(it->second->get_hostname()));
          if (found != host::hosts.end())
            temp_host = found->second.get();

          if (temp_host != nullptr &&
              (temp_host->get_current_state() == host::state_down ||
               temp_host->get_current_state() == host::state_unreachable))
            problem = false;
          if (it->second->get_scheduled_downtime_depth() > 0)
            problem = false;
          if (it->second->problem_has_been_acknowledged())
            problem = false;
          if (!it->second->active_checks_enabled())
            problem = false;
          if (problem)
            services_warning_unhandled++;
          services_warning++;
        } else if (it->second->get_current_state() == service::state_unknown) {
          host* temp_host{nullptr};
          host_map::const_iterator found(
              host::hosts.find(it->second->get_hostname()));
          if (found != host::hosts.end())
            temp_host = found->second.get();

          if (temp_host != nullptr &&
              (temp_host->get_current_state() == host::state_down ||
               temp_host->get_current_state() == host::state_unreachable))
            problem = false;
          if (it->second->get_scheduled_downtime_depth() > 0)
            problem = false;
          if (it->second->problem_has_been_acknowledged())
            problem = false;
          if (!it->second->active_checks_enabled())
            problem = false;
          if (problem)
            services_unknown_unhandled++;
          services_unknown++;
        } else if (it->second->get_current_state() == service::state_critical) {
          host* temp_host{nullptr};
          host_map::const_iterator found(
              host::hosts.find(it->second->get_hostname()));
          if (found != host::hosts.end())
            temp_host = found->second.get();

          if (temp_host != nullptr &&
              (temp_host->get_current_state() == host::state_down ||
               temp_host->get_current_state() == host::state_unreachable))
            problem = false;
          if (it->second->get_scheduled_downtime_depth() > 0)
            problem = false;
          if (it->second->problem_has_been_acknowledged())
            problem = false;
          if (!it->second->active_checks_enabled())
            problem = false;
          if (problem)
            services_critical_unhandled++;
          services_critical++;
        }
      }
    }
    service_problems = services_warning + services_critical + services_unknown;
    service_problems_unhandled = services_warning_unhandled +
                                 services_critical_unhandled +
                                 services_unknown_unhandled;

    // These macros are time-intensive to compute, and will likely be
    // r, so save them all for future use.
    mac->x[MACRO_TOTALHOSTSUP] = std::to_string(hosts_up);
    mac->x[MACRO_TOTALHOSTSDOWN] = std::to_string(hosts_down);
    mac->x[MACRO_TOTALHOSTSUNREACHABLE] = std::to_string(hosts_unreachable);
    mac->x[MACRO_TOTALHOSTSDOWNUNHANDLED] =
        std::to_string(hosts_down_unhandled);
    mac->x[MACRO_TOTALHOSTSUNREACHABLEUNHANDLED] =
        std::to_string(hosts_unreachable_unhandled);
    mac->x[MACRO_TOTALHOSTPROBLEMS] = std::to_string(host_problems);
    mac->x[MACRO_TOTALHOSTPROBLEMSUNHANDLED] =
        std::to_string(host_problems_unhandled);
    mac->x[MACRO_TOTALSERVICESOK] = std::to_string(services_ok);
    mac->x[MACRO_TOTALSERVICESWARNING] = std::to_string(services_warning);
    mac->x[MACRO_TOTALSERVICESCRITICAL] = std::to_string(services_critical);
    mac->x[MACRO_TOTALSERVICESUNKNOWN] = std::to_string(services_unknown);
    mac->x[MACRO_TOTALSERVICESWARNINGUNHANDLED] =
        std::to_string(services_warning_unhandled);
    mac->x[MACRO_TOTALSERVICESCRITICALUNHANDLED] =
        std::to_string(services_critical_unhandled);
    mac->x[MACRO_TOTALSERVICESUNKNOWNUNHANDLED] =
        std::to_string(services_unknown_unhandled);
    mac->x[MACRO_TOTALSERVICEPROBLEMS] = std::to_string(service_problems);
    mac->x[MACRO_TOTALSERVICEPROBLEMSUNHANDLED] =
        std::to_string(service_problems_unhandled);
  }

  // Return only the macro the user requested.
  output = mac->x[macro_type];

  // Tell caller to NOT free memory when done.
  *free_macro = false;

  return OK;
}

/**************************************
 *                                     *
 *         Redirection Object          *
 *                                     *
 **************************************/

// Redirection object.
struct grab_value_redirection {
  typedef std::unordered_map<unsigned int,
                             int (*)(nagios_macros*,
                                     int,
                                     std::string const&,
                                     std::string const&,
                                     std::string&,
                                     int*)>
      entry;
  entry routines;
  grab_value_redirection() {
    // Host macros.
    static unsigned int const host_ids[] = {MACRO_HOSTNAME,
                                            MACRO_HOSTALIAS,
                                            MACRO_HOSTADDRESS,
                                            MACRO_LASTHOSTCHECK,
                                            MACRO_LASTHOSTSTATECHANGE,
                                            MACRO_HOSTOUTPUT,
                                            MACRO_HOSTPERFDATA,
                                            MACRO_HOSTSTATE,
                                            MACRO_HOSTSTATEID,
                                            MACRO_HOSTATTEMPT,
                                            MACRO_HOSTEXECUTIONTIME,
                                            MACRO_HOSTLATENCY,
                                            MACRO_HOSTDURATION,
                                            MACRO_HOSTDURATIONSEC,
                                            MACRO_HOSTDOWNTIME,
                                            MACRO_HOSTSTATETYPE,
                                            MACRO_HOSTPERCENTCHANGE,
                                            MACRO_LASTHOSTUP,
                                            MACRO_LASTHOSTDOWN,
                                            MACRO_LASTHOSTUNREACHABLE,
                                            MACRO_HOSTCHECKCOMMAND,
                                            MACRO_HOSTDISPLAYNAME,
                                            MACRO_HOSTACTIONURL,
                                            MACRO_HOSTNOTESURL,
                                            MACRO_HOSTNOTES,
                                            MACRO_HOSTCHECKTYPE,
                                            MACRO_LONGHOSTOUTPUT,
                                            MACRO_HOSTNOTIFICATIONNUMBER,
                                            MACRO_HOSTNOTIFICATIONID,
                                            MACRO_HOSTEVENTID,
                                            MACRO_LASTHOSTEVENTID,
                                            MACRO_HOSTGROUPNAMES,
                                            MACRO_HOSTGROUPNAME,
                                            MACRO_MAXHOSTATTEMPTS,
                                            MACRO_TOTALHOSTSERVICES,
                                            MACRO_TOTALHOSTSERVICESOK,
                                            MACRO_TOTALHOSTSERVICESWARNING,
                                            MACRO_TOTALHOSTSERVICESUNKNOWN,
                                            MACRO_TOTALHOSTSERVICESCRITICAL,
                                            MACRO_HOSTPROBLEMID,
                                            MACRO_LASTHOSTPROBLEMID,
                                            MACRO_LASTHOSTSTATE,
                                            MACRO_LASTHOSTSTATEID,
                                            MACRO_HOSTPARENTS,
                                            MACRO_HOSTCHILDREN,
                                            MACRO_HOSTID,
                                            MACRO_HOSTTIMEZONE};
    for (unsigned int i = 0; i < sizeof(host_ids) / sizeof(*host_ids); ++i)
      routines[host_ids[i]] = &handle_host_macro;

    // Hostgroup macros.
    static unsigned int const hostgroup_ids[] = {
        MACRO_HOSTGROUPALIAS, MACRO_HOSTGROUPNOTES, MACRO_HOSTGROUPNOTESURL,
        MACRO_HOSTGROUPACTIONURL, MACRO_HOSTGROUPMEMBERS};
    for (unsigned int i = 0; i < sizeof(hostgroup_ids) / sizeof(*hostgroup_ids);
         ++i)
      routines[hostgroup_ids[i]] = &handle_hostgroup_macro;

    // Service macros.
    static unsigned int const service_ids[] = {MACRO_SERVICEDESC,
                                               MACRO_SERVICESTATE,
                                               MACRO_SERVICESTATEID,
                                               MACRO_SERVICEATTEMPT,
                                               MACRO_LASTSERVICECHECK,
                                               MACRO_LASTSERVICESTATECHANGE,
                                               MACRO_SERVICEOUTPUT,
                                               MACRO_SERVICEPERFDATA,
                                               MACRO_SERVICEEXECUTIONTIME,
                                               MACRO_SERVICELATENCY,
                                               MACRO_SERVICEDURATION,
                                               MACRO_SERVICEDURATIONSEC,
                                               MACRO_SERVICEDOWNTIME,
                                               MACRO_SERVICESTATETYPE,
                                               MACRO_SERVICEPERCENTCHANGE,
                                               MACRO_LASTSERVICEOK,
                                               MACRO_LASTSERVICEWARNING,
                                               MACRO_LASTSERVICEUNKNOWN,
                                               MACRO_LASTSERVICECRITICAL,
                                               MACRO_SERVICECHECKCOMMAND,
                                               MACRO_SERVICEDISPLAYNAME,
                                               MACRO_SERVICEACTIONURL,
                                               MACRO_SERVICENOTESURL,
                                               MACRO_SERVICENOTES,
                                               MACRO_SERVICECHECKTYPE,
                                               MACRO_LONGSERVICEOUTPUT,
                                               MACRO_SERVICENOTIFICATIONNUMBER,
                                               MACRO_SERVICENOTIFICATIONID,
                                               MACRO_SERVICEEVENTID,
                                               MACRO_LASTSERVICEEVENTID,
                                               MACRO_SERVICEGROUPNAMES,
                                               MACRO_SERVICEGROUPNAME,
                                               MACRO_MAXSERVICEATTEMPTS,
                                               MACRO_SERVICEISVOLATILE,
                                               MACRO_SERVICEPROBLEMID,
                                               MACRO_LASTSERVICEPROBLEMID,
                                               MACRO_LASTSERVICESTATE,
                                               MACRO_LASTSERVICESTATEID,
                                               MACRO_SERVICEID,
                                               MACRO_SERVICETIMEZONE};
    for (unsigned int i = 0; i < sizeof(service_ids) / sizeof(*service_ids);
         ++i)
      routines[service_ids[i]] = &handle_service_macro;

    // Servicegroup macros.
    static unsigned int const servicegroup_ids[] = {
        MACRO_SERVICEGROUPALIAS, MACRO_SERVICEGROUPNOTES,
        MACRO_SERVICEGROUPNOTESURL, MACRO_SERVICEGROUPACTIONURL,
        MACRO_SERVICEGROUPMEMBERS};
    for (unsigned int i = 0;
         i < sizeof(servicegroup_ids) / sizeof(*servicegroup_ids); ++i)
      routines[servicegroup_ids[i]] = &handle_servicegroup_macro;

    // Contact macros.
    static unsigned int const contact_ids[] = {
        MACRO_CONTACTNAME,     MACRO_CONTACTALIAS,      MACRO_CONTACTEMAIL,
        MACRO_CONTACTPAGER,    MACRO_CONTACTGROUPNAMES, MACRO_CONTACTTIMEZONE,
        MACRO_CONTACTGROUPNAME};
    for (unsigned int i = 0; i < sizeof(contact_ids) / sizeof(*contact_ids);
         ++i)
      routines[contact_ids[i]] = &handle_contact_macro;

    // Contactgroup macros.
    static unsigned int const contactgroup_ids[] = {MACRO_CONTACTGROUPALIAS,
                                                    MACRO_CONTACTGROUPMEMBERS};
    for (unsigned int i = 0;
         i < sizeof(contactgroup_ids) / sizeof(*contactgroup_ids); ++i)
      routines[contactgroup_ids[i]] = &handle_contactgroup_macro;

    // Notification macros.
    static unsigned int const notification_ids[] = {
        MACRO_NOTIFICATIONTYPE,        MACRO_NOTIFICATIONNUMBER,
        MACRO_NOTIFICATIONRECIPIENTS,  MACRO_NOTIFICATIONISESCALATED,
        MACRO_NOTIFICATIONAUTHOR,      MACRO_NOTIFICATIONAUTHORNAME,
        MACRO_NOTIFICATIONAUTHORALIAS, MACRO_NOTIFICATIONCOMMENT};
    for (unsigned int i = 0;
         i < sizeof(notification_ids) / sizeof(*notification_ids); ++i)
      routines[notification_ids[i]] = &handle_notification_macro;

    // Date/Time macros.
    static unsigned int const datetime_ids[] = {
        MACRO_LONGDATETIME, MACRO_SHORTDATETIME, MACRO_DATE,         MACRO_TIME,
        MACRO_TIMET,        MACRO_ISVALIDTIME,   MACRO_NEXTVALIDTIME};
    for (unsigned int i = 0; i < sizeof(datetime_ids) / sizeof(*datetime_ids);
         ++i)
      routines[datetime_ids[i]] = &handle_datetime_macro;

    // Static macros.
    static unsigned int const static_ids[] = {MACRO_ADMINEMAIL,
                                              MACRO_ADMINPAGER,
                                              MACRO_MAINCONFIGFILE,
                                              MACRO_STATUSDATAFILE,
                                              MACRO_RETENTIONDATAFILE,
                                              MACRO_TEMPFILE,
                                              MACRO_LOGFILE,
                                              MACRO_RESOURCEFILE,
                                              MACRO_COMMANDFILE,
                                              MACRO_HOSTPERFDATAFILE,
                                              MACRO_SERVICEPERFDATAFILE,
                                              MACRO_PROCESSSTARTTIME,
                                              MACRO_TEMPPATH,
                                              MACRO_EVENTSTARTTIME,
                                              MACRO_POLLERNAME,
                                              MACRO_POLLERID};
    for (unsigned int i = 0; i < sizeof(static_ids) / sizeof(*static_ids); ++i)
      routines[static_ids[i]] = &handle_static_macro;

    // Summary macros.
    static unsigned int const summary_ids[] = {
        MACRO_TOTALHOSTSUP,
        MACRO_TOTALHOSTSDOWN,
        MACRO_TOTALHOSTSUNREACHABLE,
        MACRO_TOTALHOSTSDOWNUNHANDLED,
        MACRO_TOTALHOSTSUNREACHABLEUNHANDLED,
        MACRO_TOTALHOSTPROBLEMS,
        MACRO_TOTALHOSTPROBLEMSUNHANDLED,
        MACRO_TOTALSERVICESOK,
        MACRO_TOTALSERVICESWARNING,
        MACRO_TOTALSERVICESCRITICAL,
        MACRO_TOTALSERVICESUNKNOWN,
        MACRO_TOTALSERVICESWARNINGUNHANDLED,
        MACRO_TOTALSERVICESCRITICALUNHANDLED,
        MACRO_TOTALSERVICESUNKNOWNUNHANDLED,
        MACRO_TOTALSERVICEPROBLEMS,
        MACRO_TOTALSERVICEPROBLEMSUNHANDLED};
    for (unsigned int i = 0; i < sizeof(summary_ids) / sizeof(*summary_ids);
         ++i)
      routines[summary_ids[i]] = &handle_summary_macro;
  }
} static const redirector;

/**************************************
 *                                     *
 *          Global Functions           *
 *                                     *
 **************************************/

/* this is the big one */
int grab_macro_value_r(nagios_macros* mac,
                       std::string const& macro_name,
                       std::string& output,
                       int* clean_options,
                       int* free_macro) {
  char* buf = nullptr;
  char* ptr = nullptr;
  char* arg[2] = {nullptr, nullptr};
  contact* temp_contact = nullptr;
  std::string temp_buffer;
  unsigned int x;
  int result = OK;

  if (macro_name.empty() || clean_options == nullptr || free_macro == nullptr)
    return ERROR;

  /* work with a copy of the original buffer */
  buf = string::dup(macro_name.c_str());

  /* BY DEFAULT, TELL CALLER TO FREE MACRO BUFFER WHEN DONE */
  *free_macro = true;

  /* see if there's an argument - if so, this is most likely an on-demand macro
   */
  if ((ptr = strchr(buf, ':'))) {
    ptr[0] = '\x0';
    ptr++;

    /* save the first argument - host name, hostgroup name, etc. */
    arg[0] = ptr;

    /* try and find a second argument */
    if ((ptr = strchr(ptr, ':'))) {
      ptr[0] = '\x0';
      ptr++;

      /* save second argument - service description or delimiter */
      arg[1] = ptr;
    }
  }

  /***** X MACROS *****/
  /* see if this is an x macro */
  for (x = 0; x < MACRO_X_COUNT; x++) {
    if (macro_x_names[x].empty())
      continue;

    if (strcmp(macro_x_names[x].c_str(), buf) == 0) {
      engine_logger(dbg_macros, most)
          << "  macros[" << x << "] (" << macro_x_names[x] << ") match.";
      macros_logger->trace("  macros[{}] ({}) match.", x, macro_x_names[x]);

      /* get the macro value */
      result = grab_macrox_value_r(mac, x, arg[0] ? arg[0] : "",
                                   arg[1] ? arg[1] : "", output, free_macro);

      /* post-processing */
      /* host/service output/perfdata and author/comment macros should get
       * cleaned */
      if ((x >= 16 && x <= 19) || (x >= 49 && x <= 52) ||
          (x >= 99 && x <= 100) || (x >= 124 && x <= 127)) {
        *clean_options |= (STRIP_ILLEGAL_MACRO_CHARS | ESCAPE_MACRO_CHARS);
        engine_logger(dbg_macros, most)
            << "  New clean options: " << *clean_options;
        macros_logger->trace("  New clean options: {}", *clean_options);
      }
      break;
    }
  }

  /* we already found the macro... */
  if (x < MACRO_X_COUNT)
    ;
  /***** ARGV MACROS *****/
  else if (macro_name.size() > 3 &&
           strncmp(macro_name.c_str(), "ARG", 3) == 0) {
    /* which arg do we want? */
    if (!absl::SimpleAtoi(macro_name.c_str() + 3, &x)) {
      macros_logger->error(
          "Error: could not grab macro value : '{}' must be a positive integer",
          macro_name.c_str() + 3);
      return ERROR;
    }

    if (!x || x > MAX_COMMAND_ARGUMENTS) {
      delete[] buf;
      return ERROR;
    }

    /* use a pre-computed macro value */
    output = mac->argv[x - 1];
    *free_macro = false;
  }
  /***** USER MACROS *****/
  else if (macro_name.size() > 4 &&
           strncmp(macro_name.c_str(), "USER", 4) == 0) {
    /* which macro do we want? */
    if (!absl::SimpleAtoi(macro_name.c_str() + 4, &x)) {
      macros_logger->error(
          "Error: could not grab macro value : '{}' must be a positive integer",
          macro_name.c_str() + 4);
      return ERROR;
    }

    if (!x || x > MAX_USER_MACROS) {
      delete[] buf;
      return ERROR;
    }

    /* use a pre-computed macro value */
    output = macro_user[x - 1];
    *free_macro = false;
  }

  /***** CONTACT ADDRESS MACROS *****/
  /* NOTE: the code below should be broken out into a separate function */
  else if (macro_name.size() > 14 &&
           strncmp(macro_name.c_str(), "CONTACTADDRESS", 14) == 0) {
    /* which address do we want? */
    if (!absl::SimpleAtoi(macro_name.c_str() + 14, &x)) {
      macros_logger->error(
          "Error: could not grab macro value : '{}' must be a positive integer",
          macro_name.c_str() + 14);
      return ERROR;
    }
    x -= 1;

    /* regular macro */
    if (arg[0] == nullptr) {
      /* use the saved pointer */
      if ((temp_contact = mac->contact_ptr) == nullptr) {
        delete[] buf;
        return ERROR;
      }

      /* get the macro value */
      result = grab_contact_address_macro(x, temp_contact, output);
    }
    /* on-demand macro */
    else {
      /* on-demand contact macro with a contactgroup and a delimiter */
      if (arg[1] != nullptr) {
        contactgroup_map::iterator cg_it{
            contactgroup::contactgroups.find(arg[0])};
        if (cg_it == contactgroup::contactgroups.end() || !cg_it->second)
          return ERROR;

        /* concatenate macro values for all contactgroup members */
        for (contact_map_unsafe::const_iterator
                 it{cg_it->second->get_members().begin()},
             end{cg_it->second->get_members().end()};
             it != end; ++it) {
          if (!it->second)
            continue;

          /* get the macro value for this contact */
          grab_contact_address_macro(x, it->second, temp_buffer);

          if (temp_buffer.empty())
            continue;

          /* add macro value to already running macro */
          if (output.empty())
            output.append(temp_buffer);
          else {
            output.append(arg[1]);
            output.append(temp_buffer);
          }
          temp_buffer = "";
        }
      }
      /* else on-demand contact macro */
      else {
        /* find the contact */
        contact_map::const_iterator it{contact::contacts.find(arg[0])};
        if (it == contact::contacts.end()) {
          delete[] buf;
          return ERROR;
        }

        /* get the macro value */
        result = grab_contact_address_macro(x, it->second.get(), output);
      }
    }
  }
  /***** CUSTOM VARIABLE MACROS *****/
  else if (macro_name[0] == '_') {
    /* get the macro value */

    result = grab_custom_macro_value_r(mac, macro_name, arg[0] ? arg[0] : "",
                                       arg[1] ? arg[1] : "", output);
  } else if (configuration::applier::state::instance().user_macros().find(
                 macro_name) !=
             configuration::applier::state::instance().user_macros().end()) {
    /*** New style user macros ***/
    output = configuration::applier::state::instance()
                 .user_macros_find(macro_name)
                 ->second;
    result = true;
  }
  /* no macro matched... */
  else {
    engine_logger(dbg_macros, basic)
        << " WARNING: Could not find a macro matching '" << macro_name << "'!";
    macros_logger->trace(" WARNING: Could not find a macro matching '{}'!",
                         macro_name);
    result = ERROR;
  }

  /* free memory */
  delete[] buf;
  return result;
}

/**
 *  Grab a macro value.
 *
 *  @param[out] mac        Macro object.
 *  @param[in]  macro_type Macro to get.
 *  @param[in]  arg1       Argument 1.
 *  @param[in]  arg2       Argument 2.
 *  @param[out] output     Output buffer.
 *  @param[out] free_macro Set to true if output buffer must be freed by
 *                         caller.
 *
 *  @return OK on success.
 */
int grab_macrox_value_r(nagios_macros* mac,
                        int macro_type,
                        std::string const& arg1,
                        std::string const& arg2,
                        std::string& output,
                        int* free_macro) {
  int retval;
  if (!mac || !free_macro)
    retval = ERROR;
  else {
    grab_value_redirection::entry::const_iterator it(
        redirector.routines.find(macro_type));
    if (redirector.routines.end() == it) {
      retval = ERROR;
      engine_logger(dbg_macros, basic)
          << "UNHANDLED MACRO #" << macro_type << "! THIS IS A BUG!";
      macros_logger->trace("UNHANDLED MACRO #{}! THIS IS A BUG!", macro_type);
    } else {
      retval = (*it->second)(mac, macro_type, arg1, arg2, output, free_macro);
    }
  }
  return retval;
}
