/*
  This file is part of TALER
  Copyright (C) 2018-2021 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 exchangedb/exchangedb_accounts.c
 * @brief Logic to parse account information from the configuration
 * @author Christian Grothoff
 */
#include "platform.h"
#include "taler_exchangedb_lib.h"


/**
 * Information we keep for each supported account of the exchange.
 */
struct WireAccount
{
  /**
   * Accounts are kept in a DLL.
   */
  struct WireAccount *next;

  /**
   * Plugins are kept in a DLL.
   */
  struct WireAccount *prev;

  /**
   * Externally visible account information.
   */
  struct TALER_EXCHANGEDB_AccountInfo ai;

  /**
   * Authentication data. Only parsed if
   * #TALER_EXCHANGEDB_ALO_AUTHDATA was set.
   */
  struct TALER_BANK_AuthenticationData auth;

  /**
   * Name of the section that configures this account.
   */
  char *section_name;

  /**
   * Name of the wire method underlying the account.
   */
  char *method;

  /**
   * Full payto://-URI of the account.
   */
  struct TALER_FullPayto payto_uri;

};


/**
 * Head of list of wire accounts of the exchange.
 */
static struct WireAccount *wa_head;

/**
 * Tail of list of wire accounts of the exchange.
 */
static struct WireAccount *wa_tail;


void
TALER_EXCHANGEDB_find_accounts (TALER_EXCHANGEDB_AccountCallback cb,
                                void *cb_cls)
{
  for (struct WireAccount *wa = wa_head;
       NULL != wa;
       wa = wa->next)
    cb (cb_cls,
        &wa->ai);
}


const struct TALER_EXCHANGEDB_AccountInfo *
TALER_EXCHANGEDB_find_account_by_method (const char *method)
{
  for (struct WireAccount *wa = wa_head;
       NULL != wa;
       wa = wa->next)
    if (0 == strcmp (method,
                     wa->method))
      return &wa->ai;
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
              "No wire account known for method `%s'\n",
              method);
  return NULL;
}


const struct TALER_EXCHANGEDB_AccountInfo *
TALER_EXCHANGEDB_find_account_by_payto_uri (
  const struct TALER_FullPayto url)
{
  char *method;
  const struct TALER_EXCHANGEDB_AccountInfo *ai;

  method = TALER_payto_get_method (url.full_payto);
  if (NULL == method)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Invalid payto:// URL `%s'\n",
                url.full_payto);
    return NULL;
  }
  ai = TALER_EXCHANGEDB_find_account_by_method (method);
  GNUNET_free (method);
  return ai;
}


/**
 * Closure for #add_account_cb().
 */
struct LoaderContext
{
  /**
   * Configuration to use.
   */
  const struct GNUNET_CONFIGURATION_Handle *cfg;

  /**
   * true if we are to load the authentication data
   * for the access to the bank account.
   */
  bool load_auth_data;

  /**
   * Load accounts enabled for CREDIT.
   */
  bool credit;

  /**
   * Load accounts enabled for DEBIT.
   */
  bool debit;

  /**
   * Loader status (set by callback).
   */
  enum GNUNET_GenericReturnValue res;
};


/**
 * Function called with information about a wire account.  Adds
 * the account to our list.
 *
 * @param cls closure, a `struct LoaderContext`
 * @param section section to parse account information from
 */
static void
add_account_cb (void *cls,
                const char *section)
{
  struct LoaderContext *lc = cls;
  const struct GNUNET_CONFIGURATION_Handle *cfg = lc->cfg;
  struct WireAccount *wa;
  char *payto_uri;
  char *method;
  bool debit;
  bool credit;
  struct TALER_FullPayto full_payto;
  char *err;

  if (0 != strncasecmp (section,
                        "exchange-account-",
                        strlen ("exchange-account-")))
    return;

  debit = (GNUNET_YES ==
           GNUNET_CONFIGURATION_get_value_yesno (lc->cfg,
                                                 section,
                                                 "ENABLE_DEBIT"));
  credit = (GNUNET_YES ==
            GNUNET_CONFIGURATION_get_value_yesno (lc->cfg,
                                                  section,
                                                  "ENABLE_CREDIT"));
  if (! ( ( (debit) &&
            (lc->debit) ) ||
          ( (credit) &&
            (lc->credit) ) ) )
    return; /* not enabled for us, skip */
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (cfg,
                                             section,
                                             "PAYTO_URI",
                                             &payto_uri))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
                               section,
                               "PAYTO_URI");
    return;
  }
  full_payto.full_payto = payto_uri;
  method = TALER_payto_get_method (payto_uri);
  if ( (NULL != (err = TALER_payto_validate (full_payto))) ||
       (NULL == method) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "payto URI in config ([%s]/PAYTO_URI) malformed: %s\n",
                section,
                err);
    lc->res = GNUNET_SYSERR;
    GNUNET_free (payto_uri);
    GNUNET_free (err);
    return;
  }
  GNUNET_assert (NULL != method);
  wa = GNUNET_new (struct WireAccount);
  wa->section_name = GNUNET_strdup (section);
  wa->payto_uri = full_payto;
  wa->method = method;
  wa->ai.debit_enabled = debit;
  wa->ai.credit_enabled = credit;
  wa->ai.auth = NULL;
  wa->ai.section_name = wa->section_name;
  wa->ai.method = wa->method;
  wa->ai.payto_uri = full_payto;
  if (lc->load_auth_data)
  {
    char *csn;

    GNUNET_asprintf (&csn,
                     "exchange-accountcredentials-%s",
                     &section[strlen ("exchange-account-")]);
    if (GNUNET_OK !=
        TALER_BANK_auth_parse_cfg (cfg,
                                   csn,
                                   &wa->auth))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
                  "Failed to load exchange account credentials from section `%s'\n",
                  csn);
      GNUNET_free (csn);
      GNUNET_free (wa->section_name);
      GNUNET_free (wa->method);
      GNUNET_free (wa);
      return;
    }
    wa->ai.auth = &wa->auth;
    GNUNET_free (csn);
  }
  GNUNET_CONTAINER_DLL_insert (wa_head,
                               wa_tail,
                               wa);
}


enum GNUNET_GenericReturnValue
TALER_EXCHANGEDB_load_accounts (
  const struct GNUNET_CONFIGURATION_Handle *cfg,
  enum TALER_EXCHANGEDB_AccountLoaderOptions options)
{
  struct LoaderContext lc = {
    .cfg = cfg,
    .debit = 0 != (options & TALER_EXCHANGEDB_ALO_DEBIT),
    .credit = 0 != (options & TALER_EXCHANGEDB_ALO_CREDIT),
    .load_auth_data = 0 != (options & TALER_EXCHANGEDB_ALO_AUTHDATA),
  };

  GNUNET_CONFIGURATION_iterate_sections (cfg,
                                         &add_account_cb,
                                         &lc);
  if (GNUNET_SYSERR == lc.res)
    return GNUNET_SYSERR;
  if (NULL == wa_head)
    return GNUNET_NO;
  return GNUNET_OK;
}


void
TALER_EXCHANGEDB_unload_accounts (void)
{
  struct WireAccount *wa;

  while (NULL != (wa = wa_head))
  {
    GNUNET_CONTAINER_DLL_remove (wa_head,
                                 wa_tail,
                                 wa);
    if (NULL != wa->ai.auth)
      TALER_BANK_auth_free (&wa->auth);
    GNUNET_free (wa->section_name);
    GNUNET_free (wa->payto_uri.full_payto);
    GNUNET_free (wa->method);
    GNUNET_free (wa);
  }
}


/* end of exchangedb_accounts.c */
