/*
  This file is part of TALER
  (C) 2016, 2017, 2018 Inria and GNUnet e.V.
  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/fakebank_history.c
 * @brief definitions for the "/history[-range]" layer.
 * @author Marcello Stanisci 
 */
#include "platform.h"
#include 
#include "taler_json_lib.h"
#include "fakebank.h"
/**
 * Decides whether the history builder will advance or not
 * to the next element.
 *
 * @param ha history args
 * @return GNUNET_YES/NO to advance/not-advance.
 */
int
TFH_handle_history_advance (const struct HistoryArgs *ha,
                            const struct Transaction *pos)
{
  const struct HistoryRangeIds *hri = ha->range;
  return (NULL != pos) && (0 != hri->count);
}
/**
 * Iterates on the "next" element to be processed.  To
 * be used when the current element does not get inserted in
 * the result.
 *
 * @param ha history arguments.
 * @param pos current element being processed.
 * @return the next element to be processed.
 */
struct Transaction *
TFH_handle_history_skip (const struct HistoryArgs *ha,
                         const struct Transaction *pos)
{
  const struct HistoryRangeIds *hri = ha->range;
  if (hri->count > 0)
    return pos->next;
  if (hri->count < 0)
    return pos->prev;
  return NULL;
}
/**
 * Iterates on the "next" element to be processed.  To
 * be used when the current element _gets_ inserted in the result.
 *
 * @param ha history arguments.
 * @param pos current element being processed.
 * @return the next element to be processed.
 */
struct Transaction *
TFH_handle_history_step (const struct HistoryArgs *ha,
                         const struct Transaction *pos)
{
  struct HistoryRangeIds *hri = ha->range;
  if (hri->count > 0)
  {
    hri->count--;
    return pos->next;
  }
  if (hri->count < 0)
  {
    hri->count++;
    return pos->prev;
  }
  return NULL;
}
/**
 * Decides whether the history builder will advance or not
 * to the next element.
 *
 * @param ha history args
 * @return GNUNET_YES/NO to advance/not-advance.
 */
int
TFH_handle_history_range_advance (const struct HistoryArgs *ha,
                                  const struct Transaction *pos)
{
  const struct HistoryRangeDates *hrd = ha->range;
  if ( (NULL != pos) &&
       (pos->date.abs_value_us <= hrd->end.abs_value_us) )
    return GNUNET_YES;
  return GNUNET_NO;
}
/**
 * Iterates towards the "next" element to be processed.  To
 * be used when the current element does not get inserted in
 * the result.
 *
 * @param ha history arguments.
 * @param pos current element being processed.
 * @return the next element to be processed.
 */
struct Transaction *
TFH_handle_history_range_skip (const struct HistoryArgs *ha,
                               const struct Transaction *pos)
{
  (void) ha;
  /* Transactions
   * are stored from "head"/older to "tail"/younger.  */
  return pos->next;
}
/**
 * Actual history response builder.
 *
 * @param pos first (included) element in the result set, NULL if history is empty
 * @param ha history arguments.
 * @param caller_name which function is building the history.
 * @return MHD_YES / MHD_NO, after having enqueued the response
 *         object into MHD.
 */
int
TFH_build_history_response (struct MHD_Connection *connection,
                            struct Transaction *pos,
                            struct HistoryArgs *ha,
                            Skip skip,
                            Step step,
                            CheckAdvance advance)
{
  struct HistoryElement *history_results_head = NULL;
  struct HistoryElement *history_results_tail = NULL;
  struct HistoryElement *history_element = NULL;
  json_t *history;
  json_t *jresponse;
  int ret;
  while ( (NULL != pos) &&
          advance (ha,
                   pos) )
  {
    json_t *trans;
    char *subject;
    const char *sign;
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Found transaction over %s from %llu to %llu\n",
                TALER_amount2s (&pos->amount),
                (unsigned long long) pos->debit_account,
                (unsigned long long) pos->credit_account);
    if ( (! ( ( (ha->account_number == pos->debit_account) &&
                (0 != (ha->direction & TALER_BANK_DIRECTION_DEBIT)) ) ||
              ( (ha->account_number == pos->credit_account) &&
                (0 != (ha->direction & TALER_BANK_DIRECTION_CREDIT) ) ) ) ) ||
         ( (0 == (ha->direction & TALER_BANK_DIRECTION_CANCEL)) &&
           (GNUNET_YES == pos->rejected) ) )
    {
      pos = skip (ha,
                  pos);
      continue;
    }
    GNUNET_asprintf (&subject,
                     "%s %s",
                     pos->subject,
                     pos->exchange_base_url);
    sign =
      (ha->account_number == pos->debit_account)
      ? (pos->rejected ? "cancel-" : "-")
      : (pos->rejected ? "cancel+" : "+");
    trans = json_pack
              ("{s:I, s:o, s:o, s:s, s:I, s:s}",
              "row_id", (json_int_t) pos->row_id,
              "date", GNUNET_JSON_from_time_abs (pos->date),
              "amount", TALER_JSON_from_amount (&pos->amount),
              "sign", sign,
              "counterpart", (json_int_t)
              ( (ha->account_number == pos->debit_account)
                ? pos->credit_account
                : pos->debit_account),
              "wt_subject", subject);
    GNUNET_assert (NULL != trans);
    GNUNET_free (subject);
    history_element = GNUNET_new (struct HistoryElement);
    history_element->element = trans;
    /* XXX: the ordering feature is missing.  */
    GNUNET_CONTAINER_DLL_insert_tail (history_results_head,
                                      history_results_tail,
                                      history_element);
    pos = step (ha, pos);
  }
  history = json_array ();
  if (NULL != history_results_head)
    history_element = history_results_head;
  while (NULL != history_element)
  {
    GNUNET_assert (0 ==
                   json_array_append_new (history,
                                          history_element->element));
    history_element = history_element->next;
    if (NULL != history_element)
      GNUNET_free_non_null (history_element->prev);
  }
  GNUNET_free_non_null (history_results_tail);
  if (0 == json_array_size (history))
  {
    struct MHD_Response *resp;
    json_decref (history);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Returning empty transaction history\n");
    resp = MHD_create_response_from_buffer
             (0,
             "",
             MHD_RESPMEM_PERSISTENT);
    ret = MHD_queue_response (connection,
                              MHD_HTTP_NO_CONTENT,
                              resp);
    MHD_destroy_response (resp);
    return ret;
  }
  jresponse = json_pack ("{s:o}",
                         "data",
                         history);
  if (NULL == jresponse)
  {
    GNUNET_break (0);
    return MHD_NO;
  }
  /* Finally build response object */
  {
    struct MHD_Response *resp;
    void *json_str;
    size_t json_len;
    json_str = json_dumps (jresponse,
                           JSON_INDENT (2));
    json_decref (jresponse);
    if (NULL == json_str)
    {
      GNUNET_break (0);
      return MHD_NO;
    }
    json_len = strlen (json_str);
    resp = MHD_create_response_from_buffer (json_len,
                                            json_str,
                                            MHD_RESPMEM_MUST_FREE);
    if (NULL == resp)
    {
      GNUNET_break (0);
      free (json_str);
      return MHD_NO;
    }
    (void) MHD_add_response_header (resp,
                                    MHD_HTTP_HEADER_CONTENT_TYPE,
                                    "application/json");
    ret = MHD_queue_response (connection,
                              MHD_HTTP_OK,
                              resp);
    MHD_destroy_response (resp);
  }
  return ret;
}
/**
 * Parse URL history arguments, of _both_ APIs:
 * /history and /history-range.
 *
 * @param connection MHD connection.
 * @param function_name name of the caller.
 * @param ha[out] will contain the parsed values.
 * @return GNUNET_OK only if the parsing succeedes.
 */
int
TFH_parse_history_common_args (struct MHD_Connection *connection,
                               struct HistoryArgs *ha)
{
  /**
   * @variable
   * Just check if given and == "basic", no need to keep around.
   */
  const char *auth;
  /**
   * All those will go into the structure, after parsing.
   */
  const char *direction;
  const char *cancelled;
  const char *ordering;
  const char *account_number;
  auth = MHD_lookup_connection_value (connection,
                                      MHD_GET_ARGUMENT_KIND,
                                      "auth");
  direction = MHD_lookup_connection_value (connection,
                                           MHD_GET_ARGUMENT_KIND,
                                           "direction");
  cancelled = MHD_lookup_connection_value (connection,
                                           MHD_GET_ARGUMENT_KIND,
                                           "cancelled");
  ordering = MHD_lookup_connection_value (connection,
                                          MHD_GET_ARGUMENT_KIND,
                                          "ordering");
  account_number = MHD_lookup_connection_value
                     (connection,
                     MHD_GET_ARGUMENT_KIND,
                     "account_number");
  /* Fail if one of the above failed.  */
  if ( (NULL == direction) ||
       (NULL == cancelled) ||
       ( (0 != strcasecmp (cancelled,
                           "OMIT")) &&
         (0 != strcasecmp (cancelled,
                           "SHOW")) ) ||
       ( (0 != strcasecmp (direction,
                           "BOTH")) &&
         (0 != strcasecmp (direction,
                           "CREDIT")) &&
         (0 != strcasecmp (direction,
                           "DEBIT")) ) ||
       (1 != sscanf (account_number,
                     "%llu",
                     &ha->account_number)) ||
       ( (NULL == auth) || (0 != strcasecmp (auth,
                                             "basic")) ) )
  {
    /* Invalid request, given that this is fakebank we impolitely
     * just kill the connection instead of returning a nice error.
     */
    GNUNET_break (0);
    return GNUNET_NO;
  }
  if (0 == strcasecmp (direction,
                       "CREDIT"))
  {
    ha->direction = TALER_BANK_DIRECTION_CREDIT;
  }
  else if (0 == strcasecmp (direction,
                            "DEBIT"))
  {
    ha->direction = TALER_BANK_DIRECTION_DEBIT;
  }
  else if (0 == strcasecmp (direction,
                            "BOTH"))
  {
    ha->direction = TALER_BANK_DIRECTION_BOTH;
  }
  /* Direction is invalid.  */
  else
  {
    GNUNET_break (0);
    return GNUNET_NO;
  }
  if (0 == strcasecmp (cancelled,
                       "OMIT"))
  {
    /* nothing */
  }
  else if (0 == strcasecmp (cancelled,
                            "SHOW"))
  {
    ha->direction |= TALER_BANK_DIRECTION_CANCEL;
  }
  /* Cancel-showing policy is invalid.  */
  else
  {
    GNUNET_break (0);
    return GNUNET_NO;
  }
  if ((NULL != ordering)
      && (0 == strcmp ("ascending",
                       ordering)))
    ha->ascending = GNUNET_YES;
  else
    ha->ascending = GNUNET_NO;
  return GNUNET_OK;
}