/*
  This file is part of TALER
  Copyright (C) 2015-2020 Taler Systems SA
  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Affero 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 Affero General Public License for more details.
  You should have received a copy of the GNU Affero General Public License along with
  TALER; see the file COPYING.  If not, see 
*/
/**
 * @file taler-exchange-httpd_wire.c
 * @brief Handle /wire requests
 * @author Christian Grothoff
 */
#include "platform.h"
#include 
#include "taler-exchange-httpd_keystate.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_wire.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include 
/**
 * Cached JSON for /wire response.
 */
static json_t *wire_methods;
/**
 * Array of wire methods supported by this exchange.
 */
static json_t *wire_accounts_array;
/**
 * Object mapping wire methods to the respective fee structure.
 */
static json_t *wire_fee_object;
/**
 * Convert fee structure to JSON result to be returned
 * as part of a /wire response.
 *
 * @param af fee structure to convert
 * @return NULL on error, otherwise json data structure for /wire.
 */
static json_t *
fees_to_json (struct TALER_EXCHANGEDB_AggregateFees *af)
{
  json_t *a;
  a = json_array ();
  if (NULL == a)
  {
    GNUNET_break (0); /* out of memory? */
    return NULL;
  }
  while (NULL != af)
  {
    if ( (GNUNET_NO == GNUNET_TIME_round_abs (&af->start_date)) ||
         (GNUNET_NO == GNUNET_TIME_round_abs (&af->end_date)) )
    {
      GNUNET_break (0); /* bad timestamps, should not happen */
      json_decref (a);
      return NULL;
    }
    if (0 !=
        json_array_append_new (a,
                               json_pack ("{s:o, s:o, s:o, s:o, s:o}",
                                          "wire_fee", TALER_JSON_from_amount (
                                            &af->wire_fee),
                                          "closing_fee",
                                          TALER_JSON_from_amount (
                                            &af->closing_fee),
                                          "start_date",
                                          GNUNET_JSON_from_time_abs (
                                            af->start_date),
                                          "end_date",
                                          GNUNET_JSON_from_time_abs (
                                            af->end_date),
                                          "sig", GNUNET_JSON_from_data_auto (
                                            &af->master_sig))))
    {
      GNUNET_break (0); /* out of memory? */
      json_decref (a);
      return NULL;
    }
    af = af->next;
  }
  return a;
}
/**
 * Obtain fee structure for @a method wire transfers.
 *
 * @param method method to load fees for
 * @return JSON object (to be freed by caller) with fee structure
 */
static json_t *
get_fees (const char *method)
{
  struct TALER_EXCHANGEDB_AggregateFees *af;
  struct GNUNET_TIME_Absolute now;
  af = TALER_EXCHANGEDB_fees_read (TEH_cfg,
                                   method);
  now = GNUNET_TIME_absolute_get ();
  while ( (NULL != af) &&
          (af->end_date.abs_value_us < now.abs_value_us) )
  {
    struct TALER_EXCHANGEDB_AggregateFees *n = af->next;
    GNUNET_free (af);
    af = n;
  }
  if (NULL == af)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to find current wire transfer fees for `%s' at time %s\n",
                method,
                GNUNET_STRINGS_absolute_time_to_string (now));
    return NULL;
  }
  {
    json_t *j;
    j = fees_to_json (af);
    TALER_EXCHANGEDB_fees_free (af);
    return j;
  }
}
/**
 * Load wire fees for @a method.
 *
 * @param method wire method to load fee structure for
 * @return #GNUNET_OK on success
 */
static int
load_fee (const char *method)
{
  json_t *fees;
  if (NULL != json_object_get (wire_fee_object,
                               method))
    return GNUNET_OK; /* already have them */
  fees = get_fees (method);
  if (NULL == fees)
    return GNUNET_SYSERR;
  /* Add fees to #wire_fee_object */
  if (0 !=
      json_object_set_new (wire_fee_object,
                           method,
                           fees))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
/**
 * Initialize account; checks if @a ai has /wire information, and if so,
 * adds the /wire information (if included) to our responses.
 *
 * @param cls pointer to `int` to set to #GNUNET_SYSERR on errors
 * @param ai details about the account we should load the wire details for
 */
static void
load_account (void *cls,
              const struct TALER_EXCHANGEDB_AccountInfo *ai)
{
  int *ret = cls;
  if ( (NULL != ai->wire_response_filename) &&
       (GNUNET_YES == ai->credit_enabled) )
  {
    json_t *wire_s;
    json_error_t error;
    if (NULL == (wire_s = json_load_file (ai->wire_response_filename,
                                          JSON_REJECT_DUPLICATES,
                                          &error)))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed to parse `%s': %s at %d:%d (%d)\n",
                  ai->wire_response_filename,
                  error.text,
                  error.line,
                  error.column,
                  error.position);
      *ret = GNUNET_SYSERR;
      return;
    }
    {
      char *url;
      if (NULL == (url = TALER_JSON_wire_to_payto (wire_s)))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Wire response file `%s' malformed\n",
                    ai->wire_response_filename);
        json_decref (wire_s);
        *ret = GNUNET_SYSERR;
        return;
      }
      if (0 != strcasecmp (url,
                           ai->payto_uri))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "URL in wire response file `%s' does not match URL in configuration (%s vs %s)!\n",
                    ai->wire_response_filename,
                    url,
                    ai->payto_uri);
        json_decref (wire_s);
        GNUNET_free (url);
        *ret = GNUNET_SYSERR;
        return;
      }
      GNUNET_free (url);
    }
    /* Provide friendly error message if user forgot to sign wire response. */
    if (NULL == json_object_get (wire_s,
                                 "master_sig"))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Wire response file `%s' has not been signed."
                  " Use taler-exchange-wire to sign it.\n",
                  ai->wire_response_filename);
      json_decref (wire_s);
      *ret = GNUNET_SYSERR;
      return;
    }
    if (GNUNET_OK !=
        TALER_JSON_exchange_wire_signature_check (wire_s,
                                                  &TEH_master_public_key))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Invalid signature in `%s' for public key `%s'\n",
                  ai->wire_response_filename,
                  GNUNET_p2s (&TEH_master_public_key.eddsa_pub));
      json_decref (wire_s);
      *ret = GNUNET_SYSERR;
      return;
    }
    if (GNUNET_OK ==
        load_fee (ai->method))
    {
      if (0 !=
          json_array_append_new (wire_accounts_array,
                                 wire_s))
      {
        GNUNET_break (0);
        *ret = GNUNET_SYSERR;
      }
    }
    else
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Wire fees not specified for `%s'\n",
                  ai->method);
      *ret = GNUNET_SYSERR;
    }
  }
  else if (GNUNET_YES == ai->debit_enabled)
  {
    if (GNUNET_OK !=
        load_fee (ai->method))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Wire transfer fees for `%s' are not given correctly\n",
                  ai->method);
      *ret = GNUNET_SYSERR;
      return;
    }
  }
}
/**
 * Handle a "/wire" request.
 *
 * @param rh context of the handler
 * @param connection the MHD connection to handle
 * @param args array of additional options (must be empty for this function)
 * @return MHD result code
  */
MHD_RESULT
TEH_handler_wire (const struct TEH_RequestHandler *rh,
                  struct MHD_Connection *connection,
                  const char *const args[])
{
  (void) rh;
  (void) args;
  GNUNET_assert (NULL != wire_methods);
  return TALER_MHD_reply_json (connection,
                               wire_methods,
                               MHD_HTTP_OK);
}
/**
 * Initialize wire subsystem.
 *
 * @param cfg configuration to use
 * @return #GNUNET_OK on success, #GNUNET_SYSERR if we found no valid
 *         wire methods
 */
int
TEH_WIRE_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  wire_accounts_array = json_array ();
  if (NULL == wire_accounts_array)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  wire_fee_object = json_object ();
  if (NULL == wire_fee_object)
  {
    GNUNET_break (0);
    TEH_WIRE_done ();
    return GNUNET_SYSERR;
  }
  {
    int ret;
    ret = GNUNET_OK;
    TALER_EXCHANGEDB_find_accounts (cfg,
                                    &load_account,
                                    &ret);
    if (GNUNET_OK != ret)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Error setting up bank accounts\n");
      TEH_WIRE_done ();
      return GNUNET_SYSERR;
    }
  }
  if ( (0 == json_array_size (wire_accounts_array)) ||
       (0 == json_object_size (wire_fee_object)) )
  {
    TEH_WIRE_done ();
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "No bank accounts configured\n");
    return GNUNET_SYSERR;
  }
  wire_methods = json_pack ("{s:O, s:O, s:o}",
                            "accounts", wire_accounts_array,
                            "fees", wire_fee_object,
                            "master_public_key",
                            GNUNET_JSON_from_data_auto (
                              &TEH_master_public_key));
  if (NULL == wire_methods)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to find properly configured wire transfer method\n");
    TEH_WIRE_done ();
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
/**
 * Clean up wire subsystem.
 */
void
TEH_WIRE_done ()
{
  if (NULL != wire_methods)
  {
    json_decref (wire_methods);
    wire_methods = NULL;
  }
  if (NULL != wire_fee_object)
  {
    json_decref (wire_fee_object);
    wire_fee_object = NULL;
  }
  if (NULL != wire_accounts_array)
  {
    json_decref (wire_accounts_array);
    wire_accounts_array = NULL;
  }
}
/* end of taler-exchange-httpd_wire.c */