/*
  This file is part of TALER
  (C) 2015, 2016, 2017 GNUnet e.V. and Inria
  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 
*/
/**
 * @file wire/test_wire_plugin_transactions_test.c
 * @brief Tests performing actual transactions with the TEST wire plugin against FAKEBANK
 * @author Christian Grothoff
 */
#include "platform.h"
#include "taler_util.h"
#include "taler_wire_lib.h"
#include "taler_wire_plugin.h"
#include "taler_fakebank_lib.h"
#include 
#include 
/**
 * When does the test timeout? Right now, we expect this to be very
 * fast.
 */
#define TIMEOUT GNUNET_TIME_UNIT_SECONDS
/**
 * Input for the wire transfer details.
 */
static const char *json_proto =
  "{  \"type\":\"test\", \"bank_uri\":\"http://localhost:8088/\", \"account_number\":42 }";
/**
 * Private key used to sign wire details.
 */
static struct TALER_MasterPrivateKeyP priv_key;
/**
 * Public key matching #priv_key.
 */
static struct TALER_MasterPublicKeyP pub_key;
/**
 * Our configuration.
 */
static struct GNUNET_CONFIGURATION_Handle *cfg;
/**
 * Set to #GNUNET_SYSERR if the test failed.
 */
static int global_ret;
/**
 * The 'test' plugin that we are using for the test.
 */
static struct TALER_WIRE_Plugin *plugin;
/**
 * Active preparation handle, or NULL if not active.
 */
static struct TALER_WIRE_PrepareHandle *ph;
/**
 * Active execution handle, or NULL if not active.
 */
static struct TALER_WIRE_ExecuteHandle *eh;
/**
 * Handle to the bank.
 */
static struct TALER_FAKEBANK_Handle *fb;
/**
 * Handle to the history request.
 */
static struct TALER_WIRE_HistoryHandle *hh;
/**
 * Handle for the timeout task.
 */
static struct GNUNET_SCHEDULER_Task *tt;
/**
 * Which serial ID do we expect to get from /history?
 */
static uint64_t serial_target;
/**
 * Wire transfer identifier we are using.
 */
static struct TALER_WireTransferIdentifierRawP wtid;
/**
 * Function called on shutdown (regular, error or CTRL-C).
 *
 * @param cls NULL
 */
static void
do_shutdown (void *cls)
{
  TALER_FAKEBANK_stop (fb);
  fb = NULL;
  if (NULL != eh)
  {
    plugin->execute_wire_transfer_cancel (plugin->cls,
                                          eh);
    eh = NULL;
  }
  if (NULL != ph)
  {
    plugin->prepare_wire_transfer_cancel (plugin->cls,
                                          ph);
    ph = NULL;
  }
  if (NULL != hh)
  {
    plugin->get_history_cancel (plugin->cls,
                                hh);
    hh = NULL;
  }
  if (NULL != tt)
  {
    GNUNET_SCHEDULER_cancel (tt);
    tt = NULL;
  }
  TALER_WIRE_plugin_unload (plugin);
}
/**
 * Function called on timeout.
 *
 * @param cls NULL
 */
static void
timeout_cb (void *cls)
{
  tt = NULL;
  GNUNET_break (0);
  global_ret = GNUNET_SYSERR;
  GNUNET_SCHEDULER_shutdown ();
}
/**
 * Callbacks of this type are used to serve the result of asking
 * the bank for the transaction history.
 *
 * @param cls closure
 * @param dir direction of the transfer
 * @param row_off identification of the position at which we are querying
 * @param row_off_size number of bytes in @a row_off
 * @param details details about the wire transfer
 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
 */
static int
history_result_cb (void *cls,
                   enum TALER_BANK_Direction dir,
                   const void *row_off,
                   size_t row_off_size,
                   const struct TALER_WIRE_TransferDetails *details)
{
  uint64_t *serialp;
  uint64_t serialh;
  struct TALER_Amount amount;
  hh = NULL;
  if ( (TALER_BANK_DIRECTION_NONE == dir) &&
       (GNUNET_OK == global_ret) )
  {
    GNUNET_SCHEDULER_shutdown ();
    return GNUNET_OK;
  }
  if (sizeof (uint64_t) != row_off_size)
  {
    GNUNET_break (0);
    global_ret = GNUNET_SYSERR;
    GNUNET_SCHEDULER_shutdown ();
    return GNUNET_SYSERR;
  }
  serialp = (uint64_t *) row_off;
  serialh = GNUNET_ntohll (*serialp);
  if (serialh != serial_target)
  {
    GNUNET_break (0);
    global_ret = GNUNET_SYSERR;
    GNUNET_SCHEDULER_shutdown ();
    return GNUNET_SYSERR;
  }
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("KUDOS:5.01",
                                         &amount));
  if (0 != TALER_amount_cmp (&amount,
                             &details->amount))
  {
    GNUNET_break (0);
    global_ret = GNUNET_SYSERR;
    GNUNET_SCHEDULER_shutdown ();
    return GNUNET_SYSERR;
  }
  if (0 != memcmp (&wtid,
                   &details->reserve_pub,
                   GNUNET_MIN (sizeof (struct TALER_ReservePublicKeyP),
                               sizeof (wtid))))
  {
    GNUNET_break (0);
    global_ret = GNUNET_SYSERR;
    GNUNET_SCHEDULER_shutdown ();
    return GNUNET_SYSERR;
  }
  global_ret = GNUNET_OK;
  return GNUNET_OK;
}
/**
 * Function called with the result from the execute step.
 *
 * @param cls closure
 * @param success #GNUNET_OK on success, #GNUNET_SYSERR on failure
 * @param serial_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error
 * @param emsg NULL on success, otherwise an error message
 */
static void
confirmation_cb (void *cls,
                 int success,
                 uint64_t serial_id,
                 const char *emsg)
{
  eh = NULL;
  if (GNUNET_OK != success)
  {
    GNUNET_break (0);
    global_ret = GNUNET_SYSERR;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  serial_target = serial_id;
  hh = plugin->get_history (plugin->cls,
                            TALER_BANK_DIRECTION_BOTH,
                            NULL, 0,
                            5,
                            &history_result_cb,
                            NULL);
}
/**
 * Callback with prepared transaction.
 *
 * @param cls closure
 * @param buf transaction data to persist, NULL on error
 * @param buf_size number of bytes in @a buf, 0 on error
 */
static void
prepare_cb (void *cls,
            const char *buf,
            size_t buf_size)
{
  ph = NULL;
  if (NULL == buf)
  {
    GNUNET_break (0);
    global_ret = GNUNET_SYSERR;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  plugin->execute_wire_transfer (plugin->cls,
                                 buf,
                                 buf_size,
                                 &confirmation_cb,
                                 NULL);
}
/**
 * Run the test.
 *
 * @param cls NULL
 */
static void
run (void *cls)
{
  json_t *wire;
  struct TALER_Amount amount;
  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
                                 NULL);
  tt = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
                                     &timeout_cb,
                                     NULL);
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                              &wtid,
                              sizeof (wtid));
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("KUDOS:5.01",
                                         &amount));
  wire = json_loads (json_proto,
                     0,
                     NULL);
  fb = TALER_FAKEBANK_start (8088);
  ph = plugin->prepare_wire_transfer (plugin->cls,
                                      wire,
                                      &amount,
                                      "https://exchange.net/",
                                      &wtid,
                                      &prepare_cb,
                                      NULL);
  json_decref (wire);
}
int
main (int argc,
      const char *const argv[])
{
  struct GNUNET_CRYPTO_EddsaPrivateKey *pk;
  GNUNET_log_setup ("test-wire-plugin-transactions-test",
                    "WARNING",
                    NULL);
  cfg = GNUNET_CONFIGURATION_create ();
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CONFIGURATION_load (cfg,
                                            "test_wire_plugin_transactions_test.conf"));
  pk = GNUNET_CRYPTO_eddsa_key_create_from_file ("test_wire_plugin_key.priv");
  priv_key.eddsa_priv = *pk;
  GNUNET_free (pk);
  GNUNET_CRYPTO_eddsa_key_get_public (&priv_key.eddsa_priv,
                                      &pub_key.eddsa_pub);
  global_ret = GNUNET_OK;
  plugin = TALER_WIRE_plugin_load (cfg,
                                   "test");
  GNUNET_assert (NULL != plugin);
  GNUNET_SCHEDULER_run (&run,
                        NULL);
  GNUNET_CONFIGURATION_destroy (cfg);
  if (GNUNET_OK != global_ret)
    return 1;
  return 0;
}
/* end of test_wire_plugin_transactions_test.c */