/*
   This file is part of TALER
   Copyright (C) 2021-2022 Taler Systems SA

   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
   Foundation; either version 3, or (at your option) any later version.

   TALER 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
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * @file age_restriction.c
 * @brief Utility functions regarding age restriction
 * @author Özgür Kesim
 */
#include "platform.h"
#include "taler_util.h"
#include "taler_extensions.h"
#include "stdint.h"

/* ==================================================
 *
 * Age Restriction  TALER_Extension implementation
 *
 * ==================================================
 */

/**
 * @brief local configuration
 */

static struct TALER_AgeRestrictionConfig AR_config = {0};

/**
 * @brief implements the TALER_Extension.disable interface.
 *
 * @param ext Pointer to the current extension
 */
static void
age_restriction_disable (
  struct TALER_Extension *ext)
{
  if (NULL == ext)
    return;

  ext->enabled = false;
  ext->config = NULL;

  AR_config.mask.bits = 0;
  AR_config.num_groups = 0;
}


/**
 * @brief implements the TALER_Extension.load_config interface.
 *
 * @param ext if NULL, only tests the configuration
 * @param jconfig the configuration as json
 */
static enum GNUNET_GenericReturnValue
age_restriction_load_config (
  const json_t *jconfig,
  struct TALER_Extension *ext)
{
  struct TALER_AgeMask mask = {0};
  enum GNUNET_GenericReturnValue ret;

  ret = TALER_JSON_parse_age_groups (jconfig, &mask);
  if (GNUNET_OK != ret)
    return ret;

  /* only testing the parser */
  if (ext == NULL)
    return GNUNET_OK;

  if (TALER_Extension_AgeRestriction != ext->type)
    return GNUNET_SYSERR;

  if (mask.bits > 0)
  {
    /* if the mask is not zero, the first bit MUST be set */
    if (0 == (mask.bits & 1))
      return GNUNET_SYSERR;

    AR_config.mask.bits = mask.bits;
    AR_config.num_groups = __builtin_popcount (mask.bits) - 1;
  }

  ext->config = &AR_config;
  ext->enabled = true;

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "loaded new age restriction config with age groups: %s\n",
              TALER_age_mask_to_string (&mask));

  return GNUNET_OK;
}


/**
 * @brief implements the TALER_Extension.manifest interface.
 *
 * @param ext if NULL, only tests the configuration
 * @return configuration as json_t* object, maybe NULL
 */
static json_t *
age_restriction_manifest (
  const struct TALER_Extension *ext)
{
  json_t *conf;

  GNUNET_assert (NULL != ext);

  if (NULL == ext->config)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "age restriction not configured");
    return json_null ();
  }

  conf = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_string ("age_groups",
                             TALER_age_mask_to_string (&AR_config.mask))
    );
  return GNUNET_JSON_PACK (
    GNUNET_JSON_pack_bool ("critical",
                           ext->critical),
    GNUNET_JSON_pack_string ("version",
                             ext->version),
    GNUNET_JSON_pack_object_steal ("config",
                                   conf)
    );
}


/* The extension for age restriction */
struct TALER_Extension TE_age_restriction = {
  .type = TALER_Extension_AgeRestriction,
  .name = "age_restriction",
  .critical = false,
  .version = "1",
  .enabled = false, /* disabled per default */
  .config = NULL,
  .disable = &age_restriction_disable,
  .load_config = &age_restriction_load_config,
  .manifest = &age_restriction_manifest,

  /* This extension is not a policy extension */
  .create_policy_details = NULL,
  .policy_get_handler = NULL,
  .policy_post_handler = NULL,
};


/**
 * @brief implements the init() function for GNUNET_PLUGIN_load
 *
 * @param arg Pointer to the GNUNET_CONFIGURATION_Handle
 * @return pointer to TALER_Extension on success or NULL otherwise.
 */
void *
libtaler_extension_age_restriction_init (void *arg);

/* Declaration used to squash compiler warning */
void *
libtaler_extension_age_restriction_init (void *arg)
{
  const struct GNUNET_CONFIGURATION_Handle *cfg = arg;
  char *groups = NULL;
  struct TALER_AgeMask mask = {0};

  if ((GNUNET_YES !=
       GNUNET_CONFIGURATION_have_value (cfg,
                                        TALER_EXTENSION_SECTION_AGE_RESTRICTION,
                                        "ENABLED"))
      ||
      (GNUNET_YES !=
       GNUNET_CONFIGURATION_get_value_yesno (cfg,
                                             TALER_EXTENSION_SECTION_AGE_RESTRICTION,
                                             "ENABLED")))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "[age restriction] no section %s found in configuration\n",
                TALER_EXTENSION_SECTION_AGE_RESTRICTION);

    return NULL;
  }

  /* Age restriction is enabled, extract age groups */
  if ((GNUNET_YES ==
       GNUNET_CONFIGURATION_have_value (cfg,
                                        TALER_EXTENSION_SECTION_AGE_RESTRICTION,
                                        "AGE_GROUPS"))
      &&
      (GNUNET_YES !=
       GNUNET_CONFIGURATION_get_value_string (cfg,
                                              TALER_EXTENSION_SECTION_AGE_RESTRICTION,
                                              "AGE_GROUPS",
                                              &groups)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "[age restriction] AGE_GROUPS in %s is not a string\n",
                TALER_EXTENSION_SECTION_AGE_RESTRICTION);

    return NULL;
  }

  if (NULL == groups)
    groups = GNUNET_strdup (TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS);

  if (GNUNET_OK != TALER_parse_age_group_string (groups, &mask))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "[age restriction] couldn't parse age groups: '%s'\n",
                groups);
    return NULL;
  }

  AR_config.mask = mask;
  AR_config.num_groups = __builtin_popcount (mask.bits) - 1;   /* no underflow, first bit always set */

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "[age restriction] setting age mask to %s with #groups: %d\n",
              TALER_age_mask_to_string (&AR_config.mask),
              __builtin_popcount (AR_config.mask.bits) - 1);

  TE_age_restriction.config = &AR_config;

  /* Note: we do now have TE_age_restriction_config set, however the extension
   * is not yet enabled! For age restriction to become active, load_config must
   * have been called. */

  GNUNET_free (groups);
  return &TE_age_restriction;
}


/**
 * @brief implements the done() function for GNUNET_PLUGIN_load
 *
 * @param arg unused
 * @return pointer to TALER_Extension on success or NULL otherwise.
 */
void *
libtaler_extension_age_restriction_done (void *arg);

/* Declaration used to squash compiler warning */
void *
libtaler_extension_age_restriction_done (void *arg)
{
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "[age restriction] disabling and unloading");
  AR_config.mask.bits = 0;
  AR_config.num_groups = 0;
  return NULL;
}


/* end of age_restriction.c */
