/*
  This file is part of TALER
  Copyright (C) 2014, 2015, 2016 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 pq/pq_result_helper.c
 * @brief functions to initialize parameter arrays
 * @author Christian Grothoff
 */
#include "platform.h"
#include 
#include "taler_pq_lib.h"
/**
 * Extract a currency amount from a query result according to the
 * given specification.
 *
 * @param result the result to extract the amount from
 * @param row which row of the result to extract the amount from (needed as results can have multiple rows)
 * @param currency currency to use for @a r_amount_nbo
 * @param val_name name of the column with the amount's "value", must include the substring "_val".
 * @param frac_name name of the column with the amount's "fractional" value, must include the substring "_frac".
 * @param[out] r_amount_nbo where to store the amount, in network byte order
 * @return
 *   #GNUNET_YES if all results could be extracted
 *   #GNUNET_NO if at least one result was NULL
 *   #GNUNET_SYSERR if a result was invalid (non-existing field)
 */
static enum GNUNET_GenericReturnValue
extract_amount_nbo_helper (PGresult *result,
                           int row,
                           const char *currency,
                           const char *val_name,
                           const char *frac_name,
                           struct TALER_AmountNBO *r_amount_nbo)
{
  int val_num;
  int frac_num;
  int len;
  /* These checks are simply to check that clients obey by our naming
     conventions, and not for any functional reason */
  GNUNET_assert (NULL !=
                 strstr (val_name,
                         "_val"));
  GNUNET_assert (NULL !=
                 strstr (frac_name,
                         "_frac"));
  /* Set return value to invalid in case we don't finish */
  memset (r_amount_nbo,
          0,
          sizeof (struct TALER_AmountNBO));
  val_num = PQfnumber (result,
                       val_name);
  frac_num = PQfnumber (result,
                        frac_name);
  if (val_num < 0)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Field `%s' does not exist in result\n",
                val_name);
    return GNUNET_SYSERR;
  }
  if (frac_num < 0)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Field `%s' does not exist in result\n",
                frac_name);
    return GNUNET_SYSERR;
  }
  if ( (PQgetisnull (result,
                     row,
                     val_num)) ||
       (PQgetisnull (result,
                     row,
                     frac_num)) )
  {
    return GNUNET_NO;
  }
  /* Note that Postgres stores value in NBO internally,
     so no conversion needed in this case */
  r_amount_nbo->value = *(uint64_t *) PQgetvalue (result,
                                                  row,
                                                  val_num);
  r_amount_nbo->fraction = *(uint32_t *) PQgetvalue (result,
                                                     row,
                                                     frac_num);
  len = GNUNET_MIN (TALER_CURRENCY_LEN - 1,
                    strlen (currency));
  memcpy (r_amount_nbo->currency,
          currency,
          len);
  return GNUNET_OK;
}
/**
 * Extract data from a Postgres database @a result at row @a row.
 *
 * @param cls closure, a `const char *` giving the currency
 * @param result where to extract data from
 * @param row row to extract data from
 * @param fname name (or prefix) of the fields to extract from
 * @param[in,out] dst_size where to store size of result, may be NULL
 * @param[out] dst where to store the result
 * @return
 *   #GNUNET_YES if all results could be extracted
 *   #GNUNET_NO if at least one result was NULL
 *   #GNUNET_SYSERR if a result was invalid (non-existing field)
 */
static enum GNUNET_GenericReturnValue
extract_amount_nbo (void *cls,
                    PGresult *result,
                    int row,
                    const char *fname,
                    size_t *dst_size,
                    void *dst)
{
  const char *currency = cls;
  char *val_name;
  char *frac_name;
  enum GNUNET_GenericReturnValue ret;
  if (sizeof (struct TALER_AmountNBO) != *dst_size)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  GNUNET_asprintf (&val_name,
                   "%s_val",
                   fname);
  GNUNET_asprintf (&frac_name,
                   "%s_frac",
                   fname);
  ret = extract_amount_nbo_helper (result,
                                   row,
                                   currency,
                                   val_name,
                                   frac_name,
                                   dst);
  GNUNET_free (val_name);
  GNUNET_free (frac_name);
  return ret;
}
/**
 * Currency amount expected.
 *
 * @param name name of the field in the table
 * @param currency the currency to use for @a amount
 * @param[out] amount where to store the result
 * @return array entry for the result specification to use
 */
struct GNUNET_PQ_ResultSpec
TALER_PQ_result_spec_amount_nbo (const char *name,
                                 const char *currency,
                                 struct TALER_AmountNBO *amount)
{
  struct GNUNET_PQ_ResultSpec res = {
    .conv = &extract_amount_nbo,
    .cls = (void *) currency,
    .dst = (void *) amount,
    .dst_size = sizeof (*amount),
    .fname = name
  };
  return res;
}
/**
 * Extract data from a Postgres database @a result at row @a row.
 *
 * @param cls closure, a `const char *` giving the currency
 * @param result where to extract data from
 * @param row row to extract data from
 * @param fname name (or prefix) of the fields to extract from
 * @param[in,out] dst_size where to store size of result, may be NULL
 * @param[out] dst where to store the result
 * @return
 *   #GNUNET_YES if all results could be extracted
 *   #GNUNET_NO if at least one result was NULL
 *   #GNUNET_SYSERR if a result was invalid (non-existing field)
 */
static enum GNUNET_GenericReturnValue
extract_amount (void *cls,
                PGresult *result,
                int row,
                const char *fname,
                size_t *dst_size,
                void *dst)
{
  const char *currency = cls;
  struct TALER_Amount *r_amount = dst;
  char *val_name;
  char *frac_name;
  struct TALER_AmountNBO amount_nbo;
  enum GNUNET_GenericReturnValue ret;
  if (sizeof (struct TALER_AmountNBO) != *dst_size)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  GNUNET_asprintf (&val_name,
                   "%s_val",
                   fname);
  GNUNET_asprintf (&frac_name,
                   "%s_frac",
                   fname);
  ret = extract_amount_nbo_helper (result,
                                   row,
                                   currency,
                                   val_name,
                                   frac_name,
                                   &amount_nbo);
  if (GNUNET_OK == ret)
    TALER_amount_ntoh (r_amount,
                       &amount_nbo);
  else
    memset (r_amount,
            0,
            sizeof (struct TALER_Amount));
  GNUNET_free (val_name);
  GNUNET_free (frac_name);
  return ret;
}
/**
 * Currency amount expected.
 *
 * @param name name of the field in the table
 * @param currency the currency to use for @a amount
 * @param[out] amount where to store the result
 * @return array entry for the result specification to use
 */
struct GNUNET_PQ_ResultSpec
TALER_PQ_result_spec_amount (const char *name,
                             const char *currency,
                             struct TALER_Amount *amount)
{
  struct GNUNET_PQ_ResultSpec res = {
    .conv = &extract_amount,
    .cls = (void *) currency,
    .dst = (void *) amount,
    .dst_size = sizeof (*amount),
    .fname = name
  };
  return res;
}
/**
 * Extract data from a Postgres database @a result at row @a row.
 *
 * @param cls closure
 * @param result where to extract data from
 * @param row row to extract data from
 * @param fname name (or prefix) of the fields to extract from
 * @param[in,out] dst_size where to store size of result, may be NULL
 * @param[out] dst where to store the result
 * @return
 *   #GNUNET_YES if all results could be extracted
 *   #GNUNET_NO if at least one result was NULL
 *   #GNUNET_SYSERR if a result was invalid (non-existing field)
 */
static enum GNUNET_GenericReturnValue
extract_json (void *cls,
              PGresult *result,
              int row,
              const char *fname,
              size_t *dst_size,
              void *dst)
{
  json_t **j_dst = dst;
  const char *res;
  int fnum;
  json_error_t json_error;
  size_t slen;
  (void) cls;
  (void) dst_size;
  fnum = PQfnumber (result,
                    fname);
  if (fnum < 0)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Field `%s' does not exist in result\n",
                fname);
    return GNUNET_SYSERR;
  }
  if (PQgetisnull (result,
                   row,
                   fnum))
    return GNUNET_NO;
  slen = PQgetlength (result,
                      row,
                      fnum);
  res = (const char *) PQgetvalue (result,
                                   row,
                                   fnum);
  *j_dst = json_loadb (res,
                       slen,
                       JSON_REJECT_DUPLICATES,
                       &json_error);
  if (NULL == *j_dst)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to parse JSON result for field `%s': %s (%s)\n",
                fname,
                json_error.text,
                json_error.source);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
/**
 * Function called to clean up memory allocated
 * by a #GNUNET_PQ_ResultConverter.
 *
 * @param cls closure
 * @param rd result data to clean up
 */
static void
clean_json (void *cls,
            void *rd)
{
  json_t **dst = rd;
  (void) cls;
  if (NULL != *dst)
  {
    json_decref (*dst);
    *dst = NULL;
  }
}
/**
 * json_t expected.
 *
 * @param name name of the field in the table
 * @param[out] jp where to store the result
 * @return array entry for the result specification to use
 */
struct GNUNET_PQ_ResultSpec
TALER_PQ_result_spec_json (const char *name,
                           json_t **jp)
{
  struct GNUNET_PQ_ResultSpec res = {
    .conv = &extract_json,
    .cleaner = &clean_json,
    .dst = (void *) jp,
    .fname  = name
  };
  return res;
}
/**
 * Extract data from a Postgres database @a result at row @a row.
 *
 * @param cls closure
 * @param result where to extract data from
 * @param row the row to extract data from
 * @param fname name (or prefix) of the fields to extract from
 * @param[in,out] dst_size where to store size of result, may be NULL
 * @param[out] dst where to store the result
 * @return
 *   #GNUNET_YES if all results could be extracted
 *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
 */
static enum GNUNET_GenericReturnValue
extract_round_time (void *cls,
                    PGresult *result,
                    int row,
                    const char *fname,
                    size_t *dst_size,
                    void *dst)
{
  struct GNUNET_TIME_Absolute *udst = dst;
  const struct GNUNET_TIME_AbsoluteNBO *res;
  struct GNUNET_TIME_Absolute tmp;
  int fnum;
  (void) cls;
  fnum = PQfnumber (result,
                    fname);
  if (fnum < 0)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  if (PQgetisnull (result,
                   row,
                   fnum))
    return GNUNET_NO;
  GNUNET_assert (NULL != dst);
  if (sizeof (struct GNUNET_TIME_Absolute) != *dst_size)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  res = (struct GNUNET_TIME_AbsoluteNBO *) PQgetvalue (result,
                                                       row,
                                                       fnum);
  tmp = GNUNET_TIME_absolute_ntoh (*res);
  GNUNET_break (GNUNET_OK ==
                GNUNET_TIME_round_abs (&tmp));
  *udst = tmp;
  return GNUNET_OK;
}
/**
 * Rounded absolute time expected.
 * In contrast to #GNUNET_PQ_query_param_absolute_time_nbo(),
 * this function ensures that the result is rounded and can
 * be converted to JSON.
 *
 * @param name name of the field in the table
 * @param[out] at where to store the result
 * @return array entry for the result specification to use
 */
struct GNUNET_PQ_ResultSpec
TALER_PQ_result_spec_absolute_time (const char *name,
                                    struct GNUNET_TIME_Absolute *at)
{
  struct GNUNET_PQ_ResultSpec res = {
    .conv = &extract_round_time,
    .dst = (void *) at,
    .dst_size = sizeof (struct GNUNET_TIME_Absolute),
    .fname = name
  };
  return res;
}
/**
 * Extract data from a Postgres database @a result at row @a row.
 *
 * @param cls closure
 * @param result where to extract data from
 * @param row the row to extract data from
 * @param fname name (or prefix) of the fields to extract from
 * @param[in,out] dst_size where to store size of result, may be NULL
 * @param[out] dst where to store the result
 * @return
 *   #GNUNET_YES if all results could be extracted
 *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
 */
static enum GNUNET_GenericReturnValue
extract_round_time_nbo (void *cls,
                        PGresult *result,
                        int row,
                        const char *fname,
                        size_t *dst_size,
                        void *dst)
{
  struct GNUNET_TIME_AbsoluteNBO *udst = dst;
  const struct GNUNET_TIME_AbsoluteNBO *res;
  struct GNUNET_TIME_Absolute tmp;
  int fnum;
  (void) cls;
  fnum = PQfnumber (result,
                    fname);
  if (fnum < 0)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  if (PQgetisnull (result,
                   row,
                   fnum))
    return GNUNET_NO;
  GNUNET_assert (NULL != dst);
  if (sizeof (struct GNUNET_TIME_AbsoluteNBO) != *dst_size)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  res = (struct GNUNET_TIME_AbsoluteNBO *) PQgetvalue (result,
                                                       row,
                                                       fnum);
  tmp = GNUNET_TIME_absolute_ntoh (*res);
  GNUNET_break (GNUNET_OK ==
                GNUNET_TIME_round_abs (&tmp));
  *udst = GNUNET_TIME_absolute_hton (tmp);
  return GNUNET_OK;
}
/**
 * Rounded absolute time in network byte order expected.
 * In contrast to #GNUNET_PQ_query_param_absolute_time_nbo(),
 * this function ensures that the result is rounded and can
 * be converted to JSON.
 *
 * @param name name of the field in the table
 * @param[out] at where to store the result
 * @return array entry for the result specification to use
 */
struct GNUNET_PQ_ResultSpec
TALER_PQ_result_spec_absolute_time_nbo (const char *name,
                                        struct GNUNET_TIME_AbsoluteNBO *at)
{
  struct GNUNET_PQ_ResultSpec res = {
    .conv = &extract_round_time_nbo,
    .dst = (void *) at,
    .dst_size = sizeof (struct GNUNET_TIME_AbsoluteNBO),
    .fname = name
  };
  return res;
}
/* end of pq_result_helper.c */