/*
  This file is part of TALER
  (C) 2024 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 util/contract_parse.c
 * @brief shared logic for contract terms parsing
 * @author Iván Ávalos
 */
#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_merchant_util.h"


/**
 * Parse merchant details of given JSON contract terms.
 *
 * @param cls closure, unused parameter
 * @param root the JSON object representing data
 * @param[out] ospec where to write the data
 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
 */
static enum GNUNET_GenericReturnValue
parse_merchant_details (void *cls,
                        json_t *root,
                        struct GNUNET_JSON_Specification *ospec)
{
  struct TALER_MERCHANT_Contract *contract = ospec->ptr;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string_copy ("name",
                                  &contract->merchant.name),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string_copy ("email",
                                    &contract->merchant.email),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string_copy ("website",
                                    &contract->merchant.website),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string_copy ("logo",
                                    &contract->merchant.logo),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_copy ("address",
                                    &contract->merchant.address),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_copy ("jurisdiction",
                                    &contract->merchant.jurisdiction),
      NULL),
    GNUNET_JSON_spec_end ()
  };
  const char *error_name;
  unsigned int error_line;

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


/**
 * Provide specification to parse given JSON object to merchant details in the
 * contract terms. All fields from @a contract are copied.
 *
 * @param name name of the merchant details field in the JSON
 * @param[out] contract where the merchant details have to be written
 */
static struct GNUNET_JSON_Specification
spec_merchant_details (const char *name,
                       struct TALER_MERCHANT_Contract *contract)
{
  struct GNUNET_JSON_Specification ret = {
    .parser = &parse_merchant_details,
    .field = name,
    .ptr = contract,
  };

  return ret;
}


/**
 * Get enum value from contract token type string.
 *
 * @param str contract token type string
 * @return enum value of token type
 */
static enum TALER_MERCHANT_ContractTokenKind
contract_token_kind_from_string (const char *str)
{
  if (0 == strcmp ("subscription",
                   str))
    return TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION;
  if (0 == strcmp ("discount",
                   str))
    return TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT;
  return TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID;
}


enum GNUNET_GenericReturnValue
TALER_MERCHANT_parse_choice_input (
  json_t *root,
  struct TALER_MERCHANT_ContractInput *input,
  size_t index,
  bool order)
{
  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[] = {
        GNUNET_JSON_spec_string ("token_family_slug",
                                 &input->details.token.token_family_slug),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_uint32 ("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;
}


enum GNUNET_GenericReturnValue
TALER_MERCHANT_parse_choice_output (
  json_t *root,
  struct TALER_MERCHANT_ContractOutput *output,
  size_t index,
  bool order)
{
  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[] = {
        GNUNET_JSON_spec_string ("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),
        (! order)
        ? GNUNET_JSON_spec_uint ("key_index",
                                 &output->details.token.key_index)
        : GNUNET_JSON_spec_end (),
        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:
    {
      const json_t *donau_urls = NULL;
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_mark_optional (
          TALER_JSON_spec_amount_any ("amount",
                                      &output->details.donation_receipt.amount),
          NULL),
        (! order)
        ? GNUNET_JSON_spec_array_const ("donau_urls",
                                        &donau_urls)
        : GNUNET_JSON_spec_end (),
        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;
      }

      GNUNET_array_grow (output->details.donation_receipt.donau_urls,
                         output->details.donation_receipt.donau_urls_len,
                         json_array_size (donau_urls));

      for (unsigned int i = 0;
           i < output->details.donation_receipt.donau_urls_len;
           i++)
      {
        const json_t *jurl;

        jurl = json_array_get (donau_urls,
                               i);
        if (! json_is_string (jurl))
        {
          GNUNET_break_op (0);
          return GNUNET_SYSERR;
        }
        output->details.donation_receipt.donau_urls[i] =
          GNUNET_strdup (json_string_value (jurl));
      }

      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_choices (
  void *cls,
  json_t *root,
  struct GNUNET_JSON_Specification *ospec)
{
  struct TALER_MERCHANT_ContractChoice **choices = ospec->ptr;
  unsigned int *choices_len = cls;

  if (! json_is_array (root))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  GNUNET_array_grow (*choices,
                     *choices_len,
                     json_array_size (root));

  for (unsigned int i = 0; i < *choices_len; i++)
  {
    struct TALER_MERCHANT_ContractChoice *choice = &(*choices)[i];
    const json_t *jinputs;
    const json_t *joutputs;
    struct GNUNET_JSON_Specification spec[] = {
      TALER_JSON_spec_amount_any ("amount",
                                  &choice->amount),
      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),
      TALER_JSON_spec_amount_any ("max_fee",
                                  &choice->max_fee),
      GNUNET_JSON_spec_array_const ("inputs",
                                    &jinputs),
      GNUNET_JSON_spec_array_const ("outputs",
                                    &joutputs),
      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;
    }

    {
      const json_t *jinput;
      size_t idx;

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

        if (GNUNET_OK !=
            TALER_MERCHANT_parse_choice_input ((json_t *) jinput,
                                               &input,
                                               idx,
                                               false))
        {
          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);
      }
    }

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

        if (GNUNET_OK !=
            TALER_MERCHANT_parse_choice_output ((json_t *) joutput,
                                                &output,
                                                idx,
                                                false))
        {
          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;
}


/**
 * Provide specification to parse given JSON array to contract terms
 * choices. All fields from @a choices elements are copied.
 *
 * @param name name of the choices field in the JSON
 * @param[out] choices where the contract choices array has to be written
 * @param[out] choices_len length of the @a choices array
 */
static struct GNUNET_JSON_Specification
spec_choices (
  const char *name,
  struct TALER_MERCHANT_ContractChoice **choices,
  unsigned int *choices_len)
{
  struct GNUNET_JSON_Specification ret = {
    .cls = (void *) choices_len,
    .parser = &parse_choices,
    .field = name,
    .ptr = choices,
  };

  return ret;
}


void
TALER_MERCHANT_contract_choice_free (
  struct TALER_MERCHANT_ContractChoice *choice)
{
  for (unsigned int i = 0; i < choice->outputs_len; i++)
  {
    struct TALER_MERCHANT_ContractOutput *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:
      for (unsigned int j = 0;
           j<output->details.donation_receipt.donau_urls_len;
           j++)
        GNUNET_free (output->details.donation_receipt.donau_urls[j]);
      GNUNET_array_grow (output->details.donation_receipt.donau_urls,
                         output->details.donation_receipt.donau_urls_len,
                         0);
      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);
}


/**
 * Parse token details of the given JSON contract token family.
 *
 * @param cls closure, unused parameter
 * @param root the JSON object representing data
 * @param[out] ospec where to write the data
 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
 */
static enum GNUNET_GenericReturnValue
parse_token_details (void *cls,
                     json_t *root,
                     struct GNUNET_JSON_Specification *ospec)
{
  struct TALER_MERCHANT_ContractTokenFamily *family = ospec->ptr;
  const char *class;
  const char *ename;
  unsigned int eline;
  struct GNUNET_JSON_Specification ispec[] = {
    GNUNET_JSON_spec_string ("class",
                             &class),
    GNUNET_JSON_spec_end ()
  };

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

  family->kind = contract_token_kind_from_string (class);

  switch (family->kind)
  {
  case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
    break;
  case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
    {
      const json_t *trusted_domains;
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_array_const ("trusted_domains",
                                      &trusted_domains),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             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;
      }

      GNUNET_array_grow (family->details.subscription.trusted_domains,
                         family->details.subscription.trusted_domains_len,
                         json_array_size (trusted_domains));

      for (unsigned int i = 0;
           i < family->details.subscription.trusted_domains_len;
           i++)
      {
        const json_t *jdomain;
        jdomain = json_array_get (trusted_domains, i);

        if (! json_is_string (jdomain))
        {
          GNUNET_break_op (0);
          return GNUNET_SYSERR;
        }

        family->details.subscription.trusted_domains[i] =
          GNUNET_strdup (json_string_value (jdomain));
      }

      return GNUNET_OK;
    }
  case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
    {
      const json_t *expected_domains;
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_array_const ("expected_domains",
                                      &expected_domains),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             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;
      }

      GNUNET_array_grow (family->details.discount.expected_domains,
                         family->details.discount.expected_domains_len,
                         json_array_size (expected_domains));

      for (unsigned int i = 0;
           i < family->details.discount.expected_domains_len;
           i++)
      {
        const json_t *jdomain;

        jdomain = json_array_get (expected_domains,
                                  i);
        if (! json_is_string (jdomain))
        {
          GNUNET_break_op (0);
          return GNUNET_SYSERR;
        }

        family->details.discount.expected_domains[i] =
          GNUNET_strdup (json_string_value (jdomain));
      }

      return GNUNET_OK;
    }
  }

  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
              "Field 'type' invalid in token family\n");
  GNUNET_break_op (0);
  return GNUNET_SYSERR;
}


/**
 * Provide specification to parse given JSON object to contract token details
 * in the contract token family. All fields from @a family are copied.
 *
 * @param name name of the token details field in the JSON
 * @param[out] family token_family where the token details have to be written
 */
static struct GNUNET_JSON_Specification
spec_token_details (const char *name,
                    struct TALER_MERCHANT_ContractTokenFamily *family)
{
  struct GNUNET_JSON_Specification ret = {
    .parser = &parse_token_details,
    .field = name,
    .ptr = family,
  };

  return ret;
}


/**
 * Parse given JSON object to token families array.
 *
 * @param cls closure, pointer to array length
 * @param root the json object representing the token families. The keys are
 *             the token family slugs.
 * @param[out] ospec where to write the data
 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
 */
static enum GNUNET_GenericReturnValue
parse_token_families (void *cls,
                      json_t *root,
                      struct GNUNET_JSON_Specification *ospec)
{
  struct TALER_MERCHANT_ContractTokenFamily **families = ospec->ptr;
  unsigned int *families_len = cls;
  json_t *jfamily;
  const char *slug;

  if (! json_is_object (root))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  json_object_foreach (root, slug, jfamily)
  {
    const json_t *keys;
    struct TALER_MERCHANT_ContractTokenFamily family = {
      .slug = GNUNET_strdup (slug)
    };
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_string_copy ("name",
                                    &family.name),
      GNUNET_JSON_spec_string_copy ("description",
                                    &family.description),
      GNUNET_JSON_spec_object_copy ("description_i18n",
                                    &family.description_i18n),
      GNUNET_JSON_spec_array_const ("keys",
                                    &keys),
      spec_token_details ("details",
                          &family),
      GNUNET_JSON_spec_bool ("critical",
                             &family.critical),
      GNUNET_JSON_spec_end ()
    };
    const char *error_name;
    unsigned int error_line;

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

    GNUNET_array_grow (family.keys,
                       family.keys_len,
                       json_array_size (keys));

    for (unsigned int i = 0; i<family.keys_len; i++)
    {
      struct TALER_MERCHANT_ContractTokenFamilyKey *key = &family.keys[i];
      struct GNUNET_JSON_Specification key_spec[] = {
        TALER_JSON_spec_token_pub (
          NULL,
          &key->pub),
        GNUNET_JSON_spec_timestamp (
          "signature_validity_start",
          &key->valid_after),
        GNUNET_JSON_spec_timestamp (
          "signature_validity_end",
          &key->valid_before),
        GNUNET_JSON_spec_end ()
      };
      const char *ierror_name;
      unsigned int ierror_line;

      if (GNUNET_OK !=
          GNUNET_JSON_parse (json_array_get (keys,
                                             i),
                             key_spec,
                             &ierror_name,
                             &ierror_line))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Failed to parse %s at %u: %s\n",
                    key_spec[ierror_line].field,
                    ierror_line,
                    ierror_name);
        GNUNET_break_op (0);
        return GNUNET_SYSERR;
      }
    }

    GNUNET_array_append (*families,
                         *families_len,
                         family);
  }

  return GNUNET_OK;
}


/**
 * Provide specification to parse given JSON array to token families in the
 * contract terms. All fields from @a families items are copied.
 *
 * @param name name of the token families field in the JSON
 * @param[out] families where the token families array has to be written
 * @param[out] families_len length of the @a families array
 */
static struct GNUNET_JSON_Specification
spec_token_families (
  const char *name,
  struct TALER_MERCHANT_ContractTokenFamily **families,
  unsigned int *families_len)
{
  struct GNUNET_JSON_Specification ret = {
    .cls = (void *) families_len,
    .parser = &parse_token_families,
    .field = name,
    .ptr = families,
  };

  return ret;
}


enum GNUNET_GenericReturnValue
TALER_MERCHANT_find_token_family_key (
  const char *slug,
  struct GNUNET_TIME_Timestamp valid_after,
  const struct TALER_MERCHANT_ContractTokenFamily *families,
  unsigned int families_len,
  struct TALER_MERCHANT_ContractTokenFamily *family,
  struct TALER_MERCHANT_ContractTokenFamilyKey *key)
{
  for (unsigned int i = 0; i < families_len; i++)
  {
    const struct TALER_MERCHANT_ContractTokenFamily *fami
      = &families[i];

    if (0 != strcmp (fami->slug,
                     slug))
      continue;
    if (NULL != family)
      *family = *fami;
    for (unsigned int k = 0; k < fami->keys_len; k++)
    {
      struct TALER_MERCHANT_ContractTokenFamilyKey *ki = &fami->keys[k];

      if (GNUNET_TIME_timestamp_cmp (ki->valid_after,
                                     ==,
                                     valid_after))
      {
        if (NULL != key)
          *key = *ki;
        return GNUNET_OK;
      }
    }
    /* matching family found, but no key. */
    return GNUNET_NO;
  }

  /* no matching family found */
  return GNUNET_SYSERR;
}


/**
 * Free all the fields in the given @a family, but not @a family itself, since
 * it is normally part of an array.
 *
 * @param[in] family contract token family to free
 */
static void
contract_token_family_free (
  struct TALER_MERCHANT_ContractTokenFamily *family)
{
  GNUNET_free (family->slug);
  GNUNET_free (family->name);
  GNUNET_free (family->description);
  if (NULL != family->description_i18n)
  {
    json_decref (family->description_i18n);
    family->description_i18n = NULL;
  }
  for (unsigned int i = 0; i < family->keys_len; i++)
    TALER_token_issue_pub_free (&family->keys[i].pub);
  GNUNET_free (family->keys);

  switch (family->kind)
  {
  case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
    break;
  case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
    for (unsigned int i = 0; i < family->details.discount.expected_domains_len;
         i++)
      GNUNET_free (family->details.discount.expected_domains[i]);
    break;
  case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
    for (unsigned int i = 0; i < family->details.subscription.
         trusted_domains_len; i++)
      GNUNET_free (family->details.subscription.trusted_domains[i]);
    break;
  }
}


/**
 * Parse contract version of given JSON contract terms.
 *
 * @param cls closure, unused parameter
 * @param root the JSON object representing data
 * @param[out] spec where to write the data
 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
 */
static enum GNUNET_GenericReturnValue
parse_contract_version (void *cls,
                        json_t *root,
                        struct GNUNET_JSON_Specification *spec)
{
  enum TALER_MERCHANT_ContractVersion *res
    = (enum TALER_MERCHANT_ContractVersion *) spec->ptr;

  (void) cls;
  if (json_is_integer (root))
  {
    json_int_t version = json_integer_value (root);

    switch (version)
    {
    case 0:
      *res = TALER_MERCHANT_CONTRACT_VERSION_0;
      return GNUNET_OK;
    case 1:
      *res = TALER_MERCHANT_CONTRACT_VERSION_1;
      return GNUNET_OK;
    }

    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  if (json_is_null (root))
  {
    *res = TALER_MERCHANT_CONTRACT_VERSION_0;
    return GNUNET_OK;
  }

  GNUNET_break_op (0);
  return GNUNET_SYSERR;
}


/**
 * Create JSON specification to parse a merchant contract
 * version.
 *
 * @param name name of the field
 * @param[out] version where to write the contract version
 * @return JSON specification object
 */
static struct GNUNET_JSON_Specification
spec_contract_version (
  const char *name,
  enum TALER_MERCHANT_ContractVersion *version)
{
  struct GNUNET_JSON_Specification ret = {
    .parser = &parse_contract_version,
    .field = name,
    .ptr = version
  };

  *version = TALER_MERCHANT_CONTRACT_VERSION_0;
  return ret;
}


/**
 * Parse v0-specific fields of @a input JSON into @a contract.
 *
 * @param[in] input the JSON contract terms
 * @param[out] contract where to write the data
 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
 */
static enum GNUNET_GenericReturnValue
parse_contract_v0 (
  json_t *input,
  struct TALER_MERCHANT_Contract *contract)
{
  struct GNUNET_JSON_Specification espec[] = {
    TALER_JSON_spec_amount_any ("amount",
                                &contract->details.v0.brutto),
    TALER_JSON_spec_amount_any ("max_fee",
                                &contract->details.v0.max_fee),
    GNUNET_JSON_spec_end ()
  };
  enum GNUNET_GenericReturnValue res;
  const char *ename;
  unsigned int eline;

  res = GNUNET_JSON_parse (input,
                           espec,
                           &ename,
                           &eline);
  if (GNUNET_OK != res)
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to parse contract v0 at field %s\n",
                ename);
    return GNUNET_SYSERR;
  }

  if (GNUNET_OK !=
      TALER_amount_cmp_currency (&contract->details.v0.max_fee,
                                 &contract->details.v0.brutto))
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "'max_fee' in database does not match currency of contract price");
    return GNUNET_SYSERR;
  }

  return res;
}


/**
 * Parse v1-specific fields of @a input JSON into @a contract.
 *
 * @param[in] input the JSON contract terms
 * @param[out] contract where to write the data
 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
 */
static enum GNUNET_GenericReturnValue
parse_contract_v1 (
  json_t *input,
  struct TALER_MERCHANT_Contract *contract)
{
  struct GNUNET_JSON_Specification espec[] = {
    spec_choices (
      "choices",
      &contract->details.v1.choices,
      &contract->details.v1.choices_len),
    spec_token_families (
      "token_families",
      &contract->details.v1.token_authorities,
      &contract->details.v1.token_authorities_len),
    GNUNET_JSON_spec_end ()
  };

  enum GNUNET_GenericReturnValue res;
  const char *ename;
  unsigned int eline;

  res = GNUNET_JSON_parse (input,
                           espec,
                           &ename,
                           &eline);
  if (GNUNET_OK != res)
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to parse contract v1 at field %s\n",
                ename);
    return GNUNET_SYSERR;
  }

  return res;
}


struct TALER_MERCHANT_Contract *
TALER_MERCHANT_contract_parse (json_t *input,
                               bool nonce_optional)
{
  struct TALER_MERCHANT_Contract *contract
    = GNUNET_new (struct TALER_MERCHANT_Contract);
  struct GNUNET_JSON_Specification espec[] = {
    spec_contract_version ("version",
                           &contract->version),
    GNUNET_JSON_spec_string_copy ("summary",
                                  &contract->summary),
    /* FIXME: do i18n_str validation in the future */
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_copy ("summary_i18n",
                                    &contract->summary_i18n),
      NULL),
    GNUNET_JSON_spec_string_copy ("order_id",
                                  &contract->order_id),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string_copy ("public_reorder_url",
                                    &contract->public_reorder_url),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string_copy ("fulfillment_url",
                                    &contract->fulfillment_url),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string_copy ("fulfillment_message",
                                    &contract->fulfillment_message),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_copy ("fulfillment_message_i18n",
                                    &contract->fulfillment_message_i18n),
      NULL),
    GNUNET_JSON_spec_array_copy ("products",
                                 &contract->products),
    GNUNET_JSON_spec_timestamp ("timestamp",
                                &contract->timestamp),
    GNUNET_JSON_spec_timestamp ("refund_deadline",
                                &contract->refund_deadline),
    GNUNET_JSON_spec_timestamp ("pay_deadline",
                                &contract->pay_deadline),
    GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
                                &contract->wire_deadline),
    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
                                 &contract->merchant_pub),
    GNUNET_JSON_spec_string_copy ("merchant_base_url",
                                  &contract->merchant_base_url),
    spec_merchant_details ("merchant",
                           contract),
    GNUNET_JSON_spec_fixed_auto ("h_wire",
                                 &contract->h_wire),
    GNUNET_JSON_spec_string_copy ("wire_method",
                                  &contract->wire_method),
    GNUNET_JSON_spec_array_copy ("exchanges",
                                 &contract->exchanges),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_copy ("delivery_location",
                                    &contract->delivery_location),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_timestamp ("delivery_date",
                                  &contract->delivery_date),
      NULL),
    (nonce_optional)
    ? GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string_copy ("nonce",
                                    &contract->nonce),
      NULL)
    : GNUNET_JSON_spec_string_copy ("nonce",
                                    &contract->nonce),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_relative_time ("auto_refund",
                                      &contract->auto_refund),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_copy ("extra",
                                    &contract->extra),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_uint8 ("minimum_age",
                              &contract->minimum_age),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  enum GNUNET_GenericReturnValue res;
  const char *ename;
  unsigned int eline;

  GNUNET_assert (NULL != input);
  res = GNUNET_JSON_parse (input,
                           espec,
                           &ename,
                           &eline);
  if (GNUNET_OK != res)
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to parse contract at field %s\n",
                ename);
    goto cleanup;
  }

  switch (contract->version)
  {
  case TALER_MERCHANT_CONTRACT_VERSION_0:
    res = parse_contract_v0 (input,
                             contract);
    break;
  case TALER_MERCHANT_CONTRACT_VERSION_1:
    res = parse_contract_v1 (input,
                             contract);
    break;
  }

  if (GNUNET_OK != res)
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to parse contract\n");
    goto cleanup;
  }
  return contract;

cleanup:
  TALER_MERCHANT_contract_free (contract);
  return NULL;
}


void
TALER_MERCHANT_contract_free (
  struct TALER_MERCHANT_Contract *contract)
{
  if (NULL == contract)
    return;
  GNUNET_free (contract->public_reorder_url);
  GNUNET_free (contract->order_id);
  GNUNET_free (contract->merchant_base_url);
  GNUNET_free (contract->merchant.name);
  GNUNET_free (contract->merchant.website);
  GNUNET_free (contract->merchant.email);
  GNUNET_free (contract->merchant.logo);
  if (NULL != contract->merchant.address)
  {
    json_decref (contract->merchant.address);
    contract->merchant.address = NULL;
  }
  if (NULL != contract->merchant.jurisdiction)
  {
    json_decref (contract->merchant.jurisdiction);
    contract->merchant.jurisdiction = NULL;
  }
  GNUNET_free (contract->summary);
  GNUNET_free (contract->fulfillment_url);
  GNUNET_free (contract->fulfillment_message);
  if (NULL != contract->fulfillment_message_i18n)
  {
    json_decref (contract->fulfillment_message_i18n);
    contract->fulfillment_message_i18n = NULL;
  }
  if (NULL != contract->products)
  {
    json_decref (contract->products);
    contract->products = NULL;
  }
  GNUNET_free (contract->wire_method);
  if (NULL != contract->exchanges)
  {
    json_decref (contract->exchanges);
    contract->exchanges = NULL;
  }
  if (NULL != contract->delivery_location)
  {
    json_decref (contract->delivery_location);
    contract->delivery_location = NULL;
  }
  GNUNET_free (contract->nonce);
  if (NULL != contract->extra)
  {
    json_decref (contract->extra);
    contract->extra = NULL;
  }

  switch (contract->version)
  {
  case TALER_MERCHANT_CONTRACT_VERSION_0:
    break;
  case TALER_MERCHANT_CONTRACT_VERSION_1:
    for (unsigned int i = 0;
         i < contract->details.v1.choices_len;
         i++)
      TALER_MERCHANT_contract_choice_free (&contract->details.v1.choices[i]);
    GNUNET_free (contract->details.v1.choices);
    for (unsigned int i = 0;
         i < contract->details.v1.token_authorities_len;
         i++)
      contract_token_family_free (&contract->details.v1.token_authorities[i]);
    GNUNET_free (contract->details.v1.token_authorities);
    break;
  }
  GNUNET_free (contract);
}
