/*
  This file is part of TALER
  Copyright (C) 2018 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
  
*/
/**
 * @file bank-lib/testing_api_cmd_history.c
 * @brief command to check the /history API from the bank.
 * @author Marcello Stanisci
 */
#include "platform.h"
#include "taler_json_lib.h"
#include 
#include "taler_exchange_service.h"
#include "taler_testing_lib.h"
#include "taler_testing_bank_lib.h"
#include "taler_fakebank_lib.h"
#include "taler_bank_service.h"
#include "taler_fakebank_lib.h"
/**
 * State for a "history" CMD.
 */
struct HistoryState
{
  /**
   * Base URL of the bank offering the "history" operation.
   */
  const char *bank_url;
  /**
   * Account number to ask the history for.
   */
  uint64_t account_no;
  /**
   * Which type of records we are interested: in-transfers
   * / out-transfers / rejected transfers.
   */
  enum TALER_BANK_Direction direction;
  /**
   * First row number we want in the result.
   */
  const char *start_row_reference;
  /**
   * How many rows we want in the result, _at most_.  In
   * the case of /history-range, we fake this value with
   * INT64_MAX.
   */
  long long num_results;
  /**
   * Handle to a pending "history" operation.
   */
  struct TALER_BANK_HistoryHandle *hh;
  /**
   * Expected number of results (= rows).
   */
  uint64_t results_obtained;
  /**
   * Set to GNUNET_YES if the callback detects something
   * unexpected.
   */
  int failed;
  /**
   * If GNUNET_YES, this parameter will ask for results in
   * chronological order.
   */
  unsigned int ascending;
  /**********************************
   * Following defs are specific to *
   * the "/history-range" version.  *
   **********************************/
  /**
   * Last row number we want in the result.  Only used
   * as a trait source when using the /history-range API.
   */
  const char *end_row_reference;
  /**
   * Start date for /history-range.
   */
  struct GNUNET_TIME_Absolute start_date;
  /**
   * End date for /history-range.
   */
  struct GNUNET_TIME_Absolute end_date;
};
/**
 * Item in the transaction history, as reconstructed from the
 * command history.
 */
struct History
{
  /**
   * Wire details.
   */
  struct TALER_BANK_TransferDetails details;
  /**
   * Serial ID of the wire transfer.
   */
  uint64_t row_id;
  /**
   * Direction of the transfer.
   */
  enum TALER_BANK_Direction direction;
};
/**
 * Array mapping bank account numbers to login credentials.
 */
extern struct TALER_BANK_AuthenticationData AUTHS[];
/**
 * Offer internal data to other commands.
 *
 * @param cls closure.
 * @param ret[out] set to the wanted data.
 * @param trait name of the trait.
 * @param index index number of the traits to be returned.
 *
 * @return #GNUNET_OK on success
 */
static int
history_traits (void *cls,
                const void **ret,
                const char *trait,
                unsigned int index)
{
  /* Must define this function because some callbacks
   * look for certain traits on _all_ the commands. */
  return GNUNET_SYSERR;
}
/**
 * Test if the CMD at offset @a off has been /rejected, and
 * is indeed a wire transfer CMD.
 *
 * @param is interpreter state (where we are right now)
 * @param off offset of the command to test for rejection.
 *
 * @return GNUNET_YES if the command at @a off was cancelled.
 */
static int
test_cancelled (struct TALER_TESTING_Interpreter *is,
                unsigned int off)
{
  const char *rejected_reference;
  const struct TALER_TESTING_Command *current_cmd;
  current_cmd = &is->commands[off];
  TALER_LOG_INFO ("Is `%s' rejected?\n",
                  current_cmd->label);
  for (unsigned int i = 0; iip; i++)
  {
    const struct TALER_TESTING_Command *c = &is->commands[i];
    /* XXX: Errors reported here are NOT fatal */
    /* Rejected wire transfers have a non-NULL reference to a
     * reject command to mark them as rejected. So errors
     * about "reject traits" not found are NOT fatal here */
    if (GNUNET_OK != TALER_TESTING_get_trait_rejected
          (c, 0, &rejected_reference))
      continue;
    TALER_LOG_INFO ("Command `%s' was rejected by `%s'.\n",
                    current_cmd->label,
                    c->label);
    if (0 == strcmp (rejected_reference,
                     current_cmd->label))
      return GNUNET_YES;
  }
  return GNUNET_NO;
}
/**
 * Free history @a h of length @a h_len.
 *
 * @param h history array to free.
 * @param h_len number of entries in @a h.
 */
static void
free_history (struct History *h,
              uint64_t h_len)
{
  for (uint64_t off = 0; offstart_date.abs_value_us)
  {
    const struct GNUNET_TIME_Absolute *timestamp;
    GNUNET_assert (GNUNET_OK ==
                   TALER_TESTING_get_trait_absolute_time (pos,
                                                          0,
                                                          ×tamp));
    GNUNET_assert (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
                   hs->end_date.abs_value_us);
    return timestamp->abs_value_us >= hs->end_date.abs_value_us;
  }
  return total >= hs->num_results;
}
/**
 * This function constructs the list of history elements that
 * interest the account number of the caller.  It has two main
 * loops: the first to figure out how many history elements have
 * to be allocated, and the second to actually populate every
 * element.
 *
 * This command has a limitation currently: it orders the history
 * list with descending elements if and only if the 'delta' was
 * given negative; and will order the list with ascending elements
 * if and only if the 'delta' was given positive.  Therefore,
 * for now it is NOT possible to test such a "/history" request:
 * "/history?auth=basic&direction=both&delta=10&ordering=descending"
 *
 * @param is interpreter state (supposedly having the
 *        current CMD pointing at a "history" CMD).
 * @param[out] rh history array to initialize.
 *
 * @return number of entries in @a rh.
 */
static uint64_t
build_history (struct TALER_TESTING_Interpreter *is,
               struct History **rh)
{
  struct HistoryState *hs = is->commands[is->ip].cls;
  uint64_t total;
  struct History *h;
  const struct TALER_TESTING_Command *add_incoming_cmd;
  int inc;
  unsigned int start;
  unsigned int end;
  /**
   * @var turns GNUNET_YES whenever either no 'start' value was
   *      given for the history query, or the given value is found
   *      in the list of all the CMDs.
   */
  int ok;
  const uint64_t *row_id_start = NULL;
  if (NULL != hs->start_row_reference)
  {
    TALER_LOG_INFO
      ("`%s': start row given via reference `%s'\n",
      TALER_TESTING_interpreter_get_current_label  (is),
      hs->start_row_reference);
    add_incoming_cmd = TALER_TESTING_interpreter_lookup_command
                         (is, hs->start_row_reference);
    GNUNET_assert (NULL != add_incoming_cmd);
    GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_uint64
                     (add_incoming_cmd, 0, &row_id_start));
  }
  GNUNET_assert ((0 != hs->num_results) || /* "/history" */
                 (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != /* "/history-range" */
                  hs->start_date.abs_value_us));
  if (0 == is->ip)
  {
    TALER_LOG_DEBUG ("Checking history at first CMD..\n");
    *rh = NULL;
    return 0;
  }
  /* AKA 'delta'.  */
  if (hs->num_results > 0)
  {
    inc = 1;  /* _inc_rement */
    start = 0;
    end = is->ip - 1;
  }
  else
  {
    inc = -1;
    start = is->ip - 1;
    end = 0;
  }
  total = 0;
  ok = GNUNET_NO;
  if (NULL == row_id_start)
    ok = GNUNET_YES;
  /* This loop counts how many commands _later than "start"_ belong
   * to the history of the caller.  This is stored in the @var total
   * variable.  */
  for (unsigned int off = start; off != end + inc; off += inc)
  {
    const struct TALER_TESTING_Command *pos = &is->commands[off];
    int cancelled;
    const uint64_t *row_id;
    /**
     * The following command allows us to skip over those CMDs
     * that do not offer a "row_id" trait.  Such skipped CMDs are
     * not interesting for building a history.
     */
    if (GNUNET_OK != TALER_TESTING_get_trait_uint64 (pos,
                                                     0,
                                                     &row_id))
      continue;
    /* Seek "/history" starting row.  */
    if (NULL != row_id_start)
    {
      if (*row_id_start == *row_id)
      {
        /* Doesn't count, start is excluded from output. */
        total = 0;
        ok = GNUNET_YES;
        continue;
      }
    }
    /* Seek "/history-range" starting row, _if_ that's the case */
    if ((GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
         hs->start_date.abs_value_us) && (GNUNET_YES != ok))
    {
      const struct GNUNET_TIME_Absolute *timestamp;
      TALER_TESTING_get_trait_absolute_time (pos,
                                             0,
                                             ×tamp);
      TALER_LOG_DEBUG
        ("Seeking first row, start vs timestamp: %llu vs %llu\n",
        (long long unsigned int) hs->start_date.abs_value_us,
        (long long unsigned int) timestamp->abs_value_us);
      if (hs->start_date.abs_value_us <= timestamp->abs_value_us)
      {
        total = 0;
        ok = GNUNET_YES;
        continue;
      }
    }
    /* when 'start' was _not_ given, then ok == GNUNET_YES */
    if (GNUNET_NO == ok)
      continue; /* skip until we find the marker */
    TALER_LOG_DEBUG ("Found first row\n");
    if (build_history_hit_limit (total,
                                 hs,
                                 pos))
    {
      TALER_LOG_DEBUG ("Hit history limit\n");
      break;
    }
    cancelled = test_cancelled (is, off);
    if ( (GNUNET_YES == cancelled) &&
         (0 == (hs->direction & TALER_BANK_DIRECTION_CANCEL)) )
    {
      TALER_LOG_INFO ("Ignoring canceled wire"
                      " transfer from history\n");
      continue;
    }
    const uint64_t *credit_account_no;
    const uint64_t *debit_account_no;
    GNUNET_assert
      (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT
        (pos, &credit_account_no));
    GNUNET_assert
      (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT
        (pos, &debit_account_no));
    TALER_LOG_INFO ("Potential history element:"
                    " %llu->%llu; my account: %llu\n",
                    (unsigned long long) *debit_account_no,
                    (unsigned long long) *credit_account_no,
                    (unsigned long long) hs->account_no);
    if ( ( (0 != (hs->direction & TALER_BANK_DIRECTION_CREDIT)) &&
           (hs->account_no == *credit_account_no)) ||
         ( (0 != (hs->direction & TALER_BANK_DIRECTION_DEBIT)) &&
           (hs->account_no == *debit_account_no)) )
    {
      TALER_LOG_INFO ("+1 my history\n");
      total++; /* found matching record */
    }
  }
  GNUNET_assert (GNUNET_YES == ok);
  if (0 == total)
  {
    TALER_LOG_DEBUG ("Checking history at first CMD.. (2)\n");
    *rh = NULL;
    return 0;
  }
  GNUNET_assert (total < UINT_MAX);
  h = GNUNET_new_array ((unsigned int) total,
                        struct History);
  total = 0;
  ok = GNUNET_NO;
  if (NULL == row_id_start)
    ok = GNUNET_YES;
  /**
   * This loop _only_ populates the array of history elements.
   */
  for (unsigned int off = start; off != end + inc; off += inc)
  {
    const struct TALER_TESTING_Command *pos = &is->commands[off];
    int cancelled;
    const uint64_t *row_id;
    char *bank_hostname;
    const uint64_t *credit_account_no;
    const uint64_t *debit_account_no;
    if (GNUNET_OK != TALER_TESTING_GET_TRAIT_ROW_ID
          (pos, &row_id))
      continue;
    if (NULL != row_id_start)
    {
      if (*row_id_start == *row_id)
      {
        /**
         * Warning: this zeroing is superfluous, as
         * total doesn't get incremented if 'start'
         * was given and couldn't be found.
         */
        total = 0;
        ok = GNUNET_YES;
        continue;
      }
    }
    /* Seek "/history-range" starting row, _if_ that's the case */
    if ((GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
         hs->start_date.abs_value_us) && (GNUNET_YES != ok))
    {
      const struct GNUNET_TIME_Absolute *timestamp;
      TALER_TESTING_get_trait_absolute_time (pos,
                                             0,
                                             ×tamp);
      TALER_LOG_DEBUG
        ("Seeking first row, start vs timestamp (2): %llu vs %llu\n",
        (long long unsigned int) hs->start_date.abs_value_us,
        (long long unsigned int) timestamp->abs_value_us);
      if (hs->start_date.abs_value_us <= timestamp->abs_value_us)
      {
        total = 0;
        ok = GNUNET_YES;
        continue;
      }
    }
    TALER_LOG_INFO ("Found first row (2)\n");
    if (GNUNET_NO == ok)
    {
      TALER_LOG_INFO ("Skip on `%s'\n",
                      pos->label);
      continue; /* skip until we find the marker */
    }
    if (build_history_hit_limit (total,
                                 hs,
                                 pos))
    {
      TALER_LOG_INFO ("Hit history limit (2)\n");
      break;
    }
    GNUNET_assert
      (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT
        (pos, &credit_account_no));
    GNUNET_assert
      (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT
        (pos, &debit_account_no));
    TALER_LOG_INFO ("Potential history bit:"
                    " %llu->%llu; my account: %llu\n",
                    (unsigned long long) *debit_account_no,
                    (unsigned long long) *credit_account_no,
                    (unsigned long long) hs->account_no);
    /**
     * Discard transactions where the audited account played
     * _both_ the credit and the debit roles, but _only if_
     * the audit goes on both directions..  This needs more
     * explaination!
     */
    if ( ( (0 != (hs->direction & TALER_BANK_DIRECTION_CREDIT)) &&
           (hs->account_no == *credit_account_no)) &&
         ( (0 != (hs->direction & TALER_BANK_DIRECTION_DEBIT)) &&
           (hs->account_no == *debit_account_no)) )
    {
      GNUNET_break (0);
      continue;
    }
    cancelled = test_cancelled (is, off);
    if ( (GNUNET_YES == cancelled) &&
         (0 == (hs->direction & TALER_BANK_DIRECTION_CANCEL)) )
    {
      TALER_LOG_WARNING ("`%s' was cancelled\n",
                         TALER_TESTING_interpreter_get_current_label
                           (is));
      continue;
    }
    bank_hostname = strchr (hs->bank_url, ':');
    GNUNET_assert (NULL != bank_hostname);
    bank_hostname += 3;
    /* Next two blocks only put the 'direction' and 'banking'
     * information.  */
    /* Asked for credit, and account got the credit.  */
    if ( (0 != (hs->direction & TALER_BANK_DIRECTION_CREDIT)) &&
         (hs->account_no == *credit_account_no))
    {
      h[total].direction = TALER_BANK_DIRECTION_CREDIT;
      if (GNUNET_YES == cancelled)
        h[total].direction |= TALER_BANK_DIRECTION_CANCEL;
      GNUNET_asprintf
        (&h[total].details.account_url,
        ('/' == bank_hostname[strlen (bank_hostname) - 1])
        ? "payto://x-taler-bank/%s%llu"
        : "payto://x-taler-bank/%s/%llu",
        bank_hostname,
        (unsigned long long) *debit_account_no);
    }
    /* Asked for debit, and account got the debit.  */
    if ( (0 != (hs->direction & TALER_BANK_DIRECTION_DEBIT)) &&
         (hs->account_no == *debit_account_no))
    {
      h[total].direction = TALER_BANK_DIRECTION_DEBIT;
      if (GNUNET_YES == cancelled)
        h[total].direction |= TALER_BANK_DIRECTION_CANCEL;
      GNUNET_asprintf
        (&h[total].details.account_url,
        ('/' == bank_hostname[strlen (bank_hostname) - 1])
        ? "payto://x-taler-bank/%s%llu"
        : "payto://x-taler-bank/%s/%llu",
        bank_hostname,
        (unsigned long long) *credit_account_no);
    }
    /* This block _completes_ the information of the current item,
     * with amount / subject / exchange URL.  */
    if ( ( (0 != (hs->direction & TALER_BANK_DIRECTION_CREDIT)) &&
           (hs->account_no == *credit_account_no)) ||
         ( (0 != (hs->direction & TALER_BANK_DIRECTION_DEBIT)) &&
           (hs->account_no == *debit_account_no)) )
    {
      const struct TALER_Amount *amount;
      const char *subject;
      const char *exchange_url;
      GNUNET_assert
        (GNUNET_OK == TALER_TESTING_get_trait_amount_obj
          (pos, 0, &amount));
      GNUNET_assert
        (GNUNET_OK == TALER_TESTING_get_trait_transfer_subject
          (pos, 0, &subject));
      GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_url
                       (pos, 0, &exchange_url));
      h[total].details.amount = *amount;
      h[total].row_id = *row_id;
      GNUNET_asprintf (&h[total].details.wire_transfer_subject,
                       "%s %s",
                       subject,
                       exchange_url);
      TALER_LOG_INFO ("+1-bit of my history\n");
      total++;
    }
  }
  *rh = h;
  return total;
}
/**
 * Compute how many results we expect to be returned for
 * the current command at @a is.
 *
 * @param is the interpreter state to inspect.
 * @return number of results expected.
 */
static uint64_t
compute_result_count (struct TALER_TESTING_Interpreter *is)
{
  uint64_t total;
  struct History *h;
  total = build_history (is, &h);
  free_history (h, total);
  return total;
}
/**
 * Check that the "/history" response matches the
 * CMD whose offset in the list of CMDs is @a off.
 *
 * @param is the interpreter state.
 * @param off the offset (of the CMD list) where the command
 *        to check is.
 * @param dir the expected direction of the transaction.
 * @param details the expected transaction details.
 *
 * @return #GNUNET_OK if the transaction is what we expect.
 */
static int
check_result (struct TALER_TESTING_Interpreter *is,
              unsigned int off,
              enum TALER_BANK_Direction dir,
              const struct TALER_BANK_TransferDetails *details)
{
  uint64_t total;
  struct History *h;
  total = build_history (is, &h);
  if (off >= total)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Test says history has at most %u"
                " results, but got result #%u to check\n",
                (unsigned int) total,
                off);
    print_expected (h, total, off);
    return GNUNET_SYSERR;
  }
  if (h[off].direction != dir)
  {
    GNUNET_break (0);
    print_expected (h, total, off);
    free_history (h,
                  total);
    return GNUNET_SYSERR;
  }
  if ( (0 != strcmp (h[off].details.wire_transfer_subject,
                     details->wire_transfer_subject)) ||
       (0 != TALER_amount_cmp (&h[off].details.amount,
                               &details->amount)) ||
       (0 != strcasecmp (h[off].details.account_url,
                         details->account_url)) )
  {
    GNUNET_break (0);
    print_expected (h, total, off);
    free_history (h,
                  total);
    return GNUNET_SYSERR;
  }
  free_history (h,
                total);
  return GNUNET_OK;
}
/**
 * This callback will (1) check that the HTTP response code
 * is acceptable and (2) that the history is consistent.  The
 * consistency is checked by going through all the past CMDs,
 * reconstructing then the expected history as of those, and
 * finally check it against what the bank returned.
 *
 * @param cls closure.
 * @param http_status HTTP response code, #MHD_HTTP_OK (200)
 *        for successful status request 0 if the bank's reply is
 *        bogus (fails to follow the protocol),
 *        #MHD_HTTP_NO_CONTENT if there are no more results; on
 *        success the last callback is always of this status
 *        (even if `abs(num_results)` were already returned).
 * @param ec taler status code.
 * @param dir direction of the transfer.
 * @param row_id monotonically increasing counter corresponding to
 *        the transaction.
 * @param details details about the wire transfer.
 * @param json detailed response from the HTTPD, or NULL if
 *        reply was not in JSON.
 */
static void
history_cb (void *cls,
            unsigned int http_status,
            enum TALER_ErrorCode ec,
            enum TALER_BANK_Direction dir,
            uint64_t row_id,
            const struct TALER_BANK_TransferDetails *details,
            const json_t *json)
{
  struct TALER_TESTING_Interpreter *is = cls;
  struct HistoryState *hs = is->commands[is->ip].cls;
  /*NOTE: "204 No Content" is used to signal the end of results.*/
  if (MHD_HTTP_NO_CONTENT == http_status)
  {
    hs->hh = NULL;
    if ( (hs->results_obtained != compute_result_count (is)) ||
         (GNUNET_YES == hs->failed) )
    {
      uint64_t total;
      struct History *h;
      GNUNET_break (0);
      total = build_history (is, &h);
      GNUNET_log
        (GNUNET_ERROR_TYPE_ERROR,
        "Expected history of length %llu, got %llu;"
        " HTTP status code: %u, failed: %d\n",
        (unsigned long long) total,
        (unsigned long long) hs->results_obtained,
        http_status,
        hs->failed);
      print_expected (h, total, UINT_MAX);
      free_history (h, total);
      TALER_TESTING_interpreter_fail (is);
      return;
    }
    TALER_TESTING_interpreter_next (is);
    return;
  }
  if (MHD_HTTP_OK != http_status)
  {
    hs->hh = NULL;
    GNUNET_log
      (GNUNET_ERROR_TYPE_ERROR,
      "Unwanted response code from /history[-range]: %u\n",
      http_status);
    TALER_TESTING_interpreter_fail (is);
    return;
  }
  /* check current element */
  if (GNUNET_OK != check_result (is,
                                 hs->results_obtained,
                                 dir,
                                 details))
  {
    GNUNET_break (0);
    {
      char *acc;
      acc = json_dumps (json,
                        JSON_COMPACT);
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Result %u was `%s'\n",
                  (unsigned int) hs->results_obtained++,
                  acc);
      if (NULL != acc)
        free (acc);
    }
    hs->failed = GNUNET_YES;
    return;
  }
  hs->results_obtained++;
}
/**
 * Run the command.
 *
 * @param cls closure.
 * @param cmd the command to execute.
 * @param is the interpreter state.
 */
static void
history_run (void *cls,
             const struct TALER_TESTING_Command *cmd,
             struct TALER_TESTING_Interpreter *is)
{
  struct HistoryState *hs = cls;
  uint64_t row_id = UINT64_MAX;
  const uint64_t *row_id_ptr = &row_id;
  struct TALER_BANK_AuthenticationData *auth;
  /* Get row_id from trait. */
  if (NULL != hs->start_row_reference)
  {
    const struct TALER_TESTING_Command *history_cmd;
    history_cmd = TALER_TESTING_interpreter_lookup_command
                    (is, hs->start_row_reference);
    if (NULL == history_cmd)
      TALER_TESTING_FAIL (is);
    if (GNUNET_OK != TALER_TESTING_get_trait_uint64 (history_cmd,
                                                     0,
                                                     &row_id_ptr))
      TALER_TESTING_FAIL (is);
    row_id = *row_id_ptr;
    TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
                     (unsigned long long) row_id);
  }
  auth = &AUTHS[hs->account_no - 1];
  hs->hh = TALER_BANK_history (is->ctx,
                               hs->bank_url,
                               auth,
                               hs->account_no,
                               hs->direction,
                               hs->ascending,
                               row_id,
                               hs->num_results,
                               &history_cb,
                               is);
  GNUNET_assert (NULL != hs->hh);
}
/**
 * Run the command.
 *
 * @param cls closure.
 * @param cmd the command to execute.
 * @param is the interpreter state.
 */
static void
history_range_run (void *cls,
                   const struct TALER_TESTING_Command *cmd,
                   struct TALER_TESTING_Interpreter *is)
{
  struct HistoryState *hs = cls;
  const struct GNUNET_TIME_Absolute *start_date;
  const struct GNUNET_TIME_Absolute *end_date;
  struct TALER_BANK_AuthenticationData *auth;
  if (NULL != hs->start_row_reference)
  {
    const struct TALER_TESTING_Command *history_cmd;
    history_cmd = TALER_TESTING_interpreter_lookup_command
                    (is, hs->start_row_reference);
    if (NULL == history_cmd)
      TALER_TESTING_FAIL (is);
    if (GNUNET_OK != TALER_TESTING_get_trait_absolute_time
          (history_cmd, 0, &start_date))
      TALER_TESTING_FAIL (is);
    hs->start_date = *start_date;
  }
  else
  {
    /* no trait wanted.  */
    GNUNET_assert (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
                   hs->start_date.abs_value_us);
    start_date = &hs->start_date;
  }
  if (NULL != hs->end_row_reference)
  {
    const struct TALER_TESTING_Command *history_cmd;
    history_cmd = TALER_TESTING_interpreter_lookup_command
                    (is, hs->end_row_reference);
    if (NULL == history_cmd)
      TALER_TESTING_FAIL (is);
    if (GNUNET_OK != TALER_TESTING_get_trait_absolute_time
          (history_cmd, 0, &end_date))
      TALER_TESTING_FAIL (is);
    hs->end_date = *end_date;
  }
  else
  {
    /* no trait wanted.  */
    GNUNET_assert (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
                   hs->end_date.abs_value_us);
    end_date = &hs->end_date;
  }
  auth = &AUTHS[hs->account_no - 1];
  hs->hh = TALER_BANK_history_range (is->ctx,
                                     hs->bank_url,
                                     auth,
                                     hs->account_no,
                                     hs->direction,
                                     hs->ascending,
                                     *start_date,
                                     *end_date,
                                     &history_cb,
                                     is);
  GNUNET_assert (NULL != hs->hh);
}
/**
 * Free the state from a "history" CMD, and possibly cancel
 * a pending operation thereof.
 *
 * @param cls closure.
 * @param cmd the command which is being cleaned up.
 */
static void
history_cleanup
  (void *cls,
  const struct TALER_TESTING_Command *cmd)
{
  struct HistoryState *hs = cls;
  if (NULL != hs->hh)
  {
    TALER_LOG_WARNING ("/history did not complete\n");
    TALER_BANK_history_cancel (hs->hh);
  }
  GNUNET_free (hs);
}
/**
 * Make a "history" CMD.
 *
 * @param label command label.
 * @param bank_url base URL of the bank offering the "history"
 *        operation.
 * @param account_no bank account number to ask the history for.
 * @param direction which direction this operation is interested.
 * @param ascending if GNUNET_YES, the bank will return the rows
 *        in ascending (= chronological) order.
 * @param start_row_reference reference to a command that can
 *        offer a row identifier, to be used as the starting row
 *        to accept in the result.
 * @param num_result how many rows we want in the result.
 *
 * @return the command.
 */
struct TALER_TESTING_Command
TALER_TESTING_cmd_bank_history
  (const char *label,
  const char *bank_url,
  uint64_t account_no,
  enum TALER_BANK_Direction direction,
  unsigned int ascending,
  const char *start_row_reference,
  long long num_results)
{
  struct HistoryState *hs;
  hs = GNUNET_new (struct HistoryState);
  hs->bank_url = bank_url;
  hs->account_no = account_no;
  hs->direction = direction;
  hs->start_row_reference = start_row_reference;
  hs->num_results = num_results;
  hs->ascending = ascending;
  hs->start_date = GNUNET_TIME_UNIT_FOREVER_ABS;
  hs->end_date = GNUNET_TIME_UNIT_FOREVER_ABS;
  struct TALER_TESTING_Command cmd = {
    .label = label,
    .cls = hs,
    .run = &history_run,
    .cleanup = &history_cleanup,
    .traits = &history_traits
  };
  return cmd;
}
/**
 * Make a "history-range" CMD, picking dates from traits.
 *
 * @param label command label.
 * @param bank_url base URL of the bank offering the "history"
 *        operation.
 * @param account_no bank account number to ask the history for.
 * @param direction which direction this operation is interested.
 * @param ascending if GNUNET_YES, the bank will return the rows
 *        in ascending (= chronological) order.
 * @param start_row_reference reference to a command that can
 *        offer a absolute time to use as the 'start' argument
 *        for "/history-range".
 * @param end_row_reference reference to a command that can
 *        offer a absolute time to use as the 'end' argument
 *        for "/history-range".
 * @return the command.
 */
struct TALER_TESTING_Command
TALER_TESTING_cmd_bank_history_range
  (const char *label,
  const char *bank_url,
  uint64_t account_no,
  enum TALER_BANK_Direction direction,
  unsigned int ascending,
  const char *start_row_reference,
  const char *end_row_reference)
{
  struct HistoryState *hs;
  hs = GNUNET_new (struct HistoryState);
  hs->bank_url = bank_url;
  hs->account_no = account_no;
  hs->direction = direction;
  hs->start_row_reference = start_row_reference;
  hs->end_row_reference = end_row_reference;
  hs->ascending = ascending;
  hs->start_date = GNUNET_TIME_UNIT_FOREVER_ABS;
  hs->end_date = GNUNET_TIME_UNIT_FOREVER_ABS;
  struct TALER_TESTING_Command cmd = {
    .label = label,
    .cls = hs,
    .run = &history_range_run,
    .cleanup = &history_cleanup,
    .traits = &history_traits
  };
  return cmd;
}
/**
 * Make a "history-range" CMD, picking dates from the arguments.
 *
 * @param label command label.
 * @param bank_url base URL of the bank offering the "history"
 *        operation.
 * @param account_no bank account number to ask the history for.
 * @param direction which direction this operation is interested.
 * @param ascending if GNUNET_YES, the bank will return the rows
 *        in ascending (= chronological) order.
 * @param start_date value for the 'start' argument
 *        of "/history-range".
 * @param end_date value for the 'end' argument
 *        of "/history-range".
 * @return the command.
 */
struct TALER_TESTING_Command
TALER_TESTING_cmd_bank_history_range_with_dates
  (const char *label,
  const char *bank_url,
  uint64_t account_no,
  enum TALER_BANK_Direction direction,
  unsigned int ascending,
  struct GNUNET_TIME_Absolute start_date,
  struct GNUNET_TIME_Absolute end_date)
{
  struct HistoryState *hs;
  hs = GNUNET_new (struct HistoryState);
  hs->bank_url = bank_url;
  hs->account_no = account_no;
  hs->direction = direction;
  hs->ascending = ascending;
  hs->start_row_reference = NULL;
  hs->start_date = start_date;
  hs->end_date = end_date;
  struct TALER_TESTING_Command cmd = {
    .label = label,
    .cls = hs,
    .run = &history_range_run,
    .cleanup = &history_cleanup,
    .traits = &history_traits
  };
  return cmd;
}
/* end of testing_api_cmd_history.c */