/*
  This file is part of TALER
  (C) 2024, 2025 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Lesser 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 <http://www.gnu.org/licenses/>
*/
/**
 * @file src/util/order_choice_parse.c
 * @brief shared logic for order choice parsing
 * @author Iván Ávalos
 * @author Christian Grothoff
 */
#include "platform.h"
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <stdbool.h>
#include <stdint.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_util.h>
#include "taler/taler_merchant_util.h"


/**
 * Parse JSON order choice input.
 *
 * @param[in] root JSON object containing choice input
 * @param[out] input parsed choice input, NULL if @a input is malformed
 * @param index index of choice input in inputs array
 * @return #GNUNET_SYSERR if @a input is malformed; #GNUNET_OK otherwise
 */
static enum GNUNET_GenericReturnValue
parse_order_choice_input (
  json_t *root,
  struct TALER_MERCHANT_OrderInput *input,
  size_t index)
{
  const char *ename;
  unsigned int eline;
  struct GNUNET_JSON_Specification ispec[] = {
    TALER_MERCHANT_json_spec_cit ("type",
                                  &input->type),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (root,
                         ispec,
                         &ename,
                         &eline))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Failed to parse %s at %u: %s\n",
                ispec[eline].field,
                eline,
                ename);
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  switch (input->type)
  {
  case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
    GNUNET_break (0);
    break;
  case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
    {
      struct GNUNET_JSON_Specification spec[] = {
        TALER_JSON_spec_slug ("token_family_slug",
                              &input->details.token.token_family_slug),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_uint ("count",
                                 &input->details.token.count),
          NULL),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             spec,
                             &ename,
                             &eline))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Failed to parse %s at %u: %s\n",
                    spec[eline].field,
                    eline,
                    ename);
        GNUNET_break_op (0);
        return GNUNET_SYSERR;
      }

      return GNUNET_OK;
    }
  }

  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
              "Field 'type' invalid in input #%u\n",
              (unsigned int) index);
  GNUNET_break_op (0);
  return GNUNET_SYSERR;
}


/**
 * Parse JSON order choice output.
 *
 * @param[in] root JSON object containing choice output
 * @param[out] output parsed choice output, NULL if @a output is malformed
 * @param index index of choice output in outputs array
 * @return #GNUNET_SYSERR if @a output is malformed; #GNUNET_OK otherwise
 */
static enum GNUNET_GenericReturnValue
parse_order_choice_output (
  json_t *root,
  struct TALER_MERCHANT_OrderOutput *output,
  size_t index)
{
  const char *ename;
  unsigned int eline;
  struct GNUNET_JSON_Specification ispec[] = {
    TALER_MERCHANT_json_spec_cot ("type",
                                  &output->type),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (root,
                         ispec,
                         &ename,
                         &eline))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Failed to parse %s at %u: %s\n",
                ispec[eline].field,
                eline,
                ename);
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  switch (output->type)
  {
  case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
    GNUNET_break (0);
    break;
  case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
    {
      struct GNUNET_JSON_Specification spec[] = {
        TALER_JSON_spec_slug ("token_family_slug",
                              &output->details.token.token_family_slug),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_uint ("count",
                                 &output->details.token.count),
          NULL),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_timestamp ("valid_at",
                                      &output->details.token.valid_at),
          NULL),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             spec,
                             &ename,
                             &eline))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Failed to parse %s at %u: %s\n",
                    spec[eline].field,
                    eline,
                    ename);
        GNUNET_break_op (0);
        return GNUNET_SYSERR;
      }

      return GNUNET_OK;
    }
  case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
    {
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_mark_optional (
          TALER_JSON_spec_amount_any ("amount",
                                      &output->details.donation_receipt.amount),
          &output->details.donation_receipt.no_amount),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             spec,
                             &ename,
                             &eline))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Failed to parse %s at %u: %s\n",
                    spec[eline].field,
                    eline,
                    ename);
        GNUNET_break_op (0);
        return GNUNET_SYSERR;
      }
      return GNUNET_OK;
    }
  }

  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
              "Field 'type' invalid in output #%u\n",
              (unsigned int) index);
  GNUNET_break_op (0);
  return GNUNET_SYSERR;
}


/**
 * Parse given JSON object to choices array.
 *
 * @param cls closure, pointer to array length
 * @param root the json array representing the choices
 * @param[out] ospec where to write the data
 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
 */
static enum GNUNET_GenericReturnValue
parse_order_choices (
  void *cls,
  json_t *root,
  struct GNUNET_JSON_Specification *ospec)
{
  struct TALER_MERCHANT_OrderChoice **choices = ospec->ptr;
  unsigned int *choices_len = cls;

  if (! json_is_array (root))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  if (0 == json_array_size (root))
  {
    /* empty list of choices is not allowed */
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  *choices = NULL;
  *choices_len = 0;
  GNUNET_array_grow (*choices,
                     *choices_len,
                     json_array_size (root));

  for (unsigned int i = 0; i < *choices_len; i++)
  {
    struct TALER_MERCHANT_OrderChoice *choice = &(*choices)[i];
    const json_t *jinputs = NULL;
    const json_t *joutputs = NULL;
    struct GNUNET_JSON_Specification spec[] = {
      TALER_JSON_spec_amount_any ("amount",
                                  &choice->amount),
      GNUNET_JSON_spec_mark_optional (
        TALER_JSON_spec_amount_any ("tip",
                                    &choice->tip),
        &choice->no_tip),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string_copy ("description",
                                      &choice->description),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_object_copy ("description_i18n",
                                      &choice->description_i18n),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        TALER_JSON_spec_amount_any ("max_fee",
                                    &choice->max_fee),
        &choice->no_max_fee),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_bool ("editable_amount",
                               &choice->editable_amount),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_array_const ("inputs",
                                      &jinputs),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_array_const ("outputs",
                                      &joutputs),
        NULL),
      GNUNET_JSON_spec_end ()
    };
    const char *ename;
    unsigned int eline;

    if (GNUNET_OK !=
        GNUNET_JSON_parse (json_array_get (root, i),
                           spec,
                           &ename,
                           &eline))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed to parse %s at %u: %s\n",
                  spec[eline].field,
                  eline,
                  ename);
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    if ( (! choice->no_max_fee) &&
         (GNUNET_OK !=
          TALER_amount_cmp_currency (&choice->amount,
                                     &choice->max_fee)) )
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Fee currency does not match amount currency in choice #%u\n",
                  i);
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    if ( (! choice->no_tip) &&
         (GNUNET_OK !=
          TALER_amount_cmp_currency (&choice->amount,
                                     &choice->tip)) )
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Tip currency does not match amount currency in choice #%u\n",
                  i);
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }

    if (NULL != jinputs)
    {
      const json_t *jinput;
      size_t idx;

      json_array_foreach ((json_t *) jinputs, idx, jinput)
      {
        struct TALER_MERCHANT_OrderInput input = {
          .details.token.count = 1
        };

        if (GNUNET_OK !=
            parse_order_choice_input ((json_t *) jinput,
                                      &input,
                                      idx))
        {
          GNUNET_break (0);
          return GNUNET_SYSERR;
        }
        switch (input.type)
        {
        case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
          GNUNET_break_op (0);
          return GNUNET_SYSERR;
        case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
          /* Ignore inputs tokens with 'count' field set to 0 */
          if (0 == input.details.token.count)
            continue;
          break;
        }
        GNUNET_array_append (choice->inputs,
                             choice->inputs_len,
                             input);
      }
    }

    if (NULL != joutputs)
    {
      const json_t *joutput;
      size_t idx;
      json_array_foreach ((json_t *) joutputs, idx, joutput)
      {
        struct TALER_MERCHANT_OrderOutput output = {
          .details.token.count = 1
        };

        if (GNUNET_OK !=
            parse_order_choice_output ((json_t *) joutput,
                                       &output,
                                       idx))
        {
          GNUNET_break (0);
          return GNUNET_SYSERR;
        }
        switch (output.type)
        {
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
          GNUNET_break_op (0);
          return GNUNET_SYSERR;
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
          /* Ignore output tokens with 'count' field set to 0 */
          if (0 == output.details.token.count)
            continue;
          break;
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
          break;
        }
        GNUNET_array_append (choice->outputs,
                             choice->outputs_len,
                             output);
      }
    }
  }

  return GNUNET_OK;
}


struct GNUNET_JSON_Specification
TALER_MERCHANT_spec_order_choices (
  const char *name,
  struct TALER_MERCHANT_OrderChoice **choices,
  unsigned int *choices_len)
{
  struct GNUNET_JSON_Specification ret = {
    .cls = (void *) choices_len,
    .parser = &parse_order_choices,
    .field = name,
    .ptr = choices,
  };

  return ret;
}


void
TALER_MERCHANT_order_choice_free (
  struct TALER_MERCHANT_OrderChoice *choice)
{
  for (unsigned int i = 0; i < choice->outputs_len; i++)
  {
    struct TALER_MERCHANT_OrderOutput *output = &choice->outputs[i];

    switch (output->type)
    {
    case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
      GNUNET_break (0);
      break;
    case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
      break;
    case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
      break;
#if FUTURE
    case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN:
      GNUNET_free (output->details.coin.exchange_url);
      break;
#endif
    }
  }
  GNUNET_free (choice->description);
  if (NULL != choice->description_i18n)
  {
    json_decref (choice->description_i18n);
    choice->description_i18n = NULL;
  }
  GNUNET_free (choice->inputs);
  GNUNET_free (choice->outputs);
}
