/*
  This file is part of Anastasis
  Copyright (C) 2020, 2021, 2022 Anastasis SARL

  Anastasis 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.

  Anastasis 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
  Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file reducer/anastasis_api_redux_serialize.c
 * @brief write a reducer state back out as JSON
 * @author Christian Grothoff
 *
 * The inverse of anastasis_api_redux_parse.c.  Fields that are not set are
 * omitted rather than serialized as null, matching what the JSON-based
 * implementation this replaced produced.
 */
#include "platform.h"
#include "anastasis_redux.h"
#include "anastasis_api_redux.h"
#include "anastasis_api_redux_state.h"
#include <taler/taler_json_lib.h>


/**
 * Add @a val to @a obj under @a key, unless @a val is NULL.
 *
 * @param[in,out] obj object to extend
 * @param key field name
 * @param[in] val value to add, reference is consumed
 */
static void
set_opt (json_t *obj,
         const char *key,
         json_t *val)
{
  if (NULL == val)
    return;
  GNUNET_assert (0 ==
                 json_object_set_new (obj,
                                      key,
                                      val));
}


/**
 * Serialize the `continents` array.
 *
 * @param common state to serialize
 * @return JSON array
 */
static json_t *
serialize_continents (const struct ANASTASIS_ReduxCommon *common)
{
  json_t *arr;

  arr = json_array ();
  GNUNET_assert (NULL != arr);
  for (unsigned int i = 0; i < common->continents_len; i++)
  {
    const struct ANASTASIS_ReduxContinent *c = &common->continents[i];
    json_t *co;

    if (c->bare)
    {
      GNUNET_assert (0 ==
                     json_array_append_new (arr,
                                            json_string (c->name)));
      continue;
    }
    co = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("name",
                               c->name));
    set_opt (co,
             "name_i18n",
             json_incref (c->name_i18n));
    GNUNET_assert (0 ==
                   json_array_append_new (arr,
                                          co));
  }
  return arr;
}


/**
 * Serialize the `countries` array.
 *
 * @param common state to serialize
 * @return JSON array
 */
static json_t *
serialize_countries (const struct ANASTASIS_ReduxCommon *common)
{
  json_t *arr;

  arr = json_array ();
  GNUNET_assert (NULL != arr);
  for (unsigned int i = 0; i < common->countries_len; i++)
  {
    const struct ANASTASIS_ReduxCountry *c = &common->countries[i];
    json_t *co;

    co = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("code",
                               c->code),
      GNUNET_JSON_pack_string ("name",
                               c->name),
      GNUNET_JSON_pack_string ("continent",
                               c->continent),
      GNUNET_JSON_pack_conditional (
        NULL != c->call_code,
        GNUNET_JSON_pack_string ("call_code",
                                 c->call_code)));
    set_opt (co,
             "name_i18n",
             json_incref (c->name_i18n));
    set_opt (co,
             "continent_i18n",
             json_incref (c->continent_i18n));
    if (NULL != c->currency)
      set_opt (co,
               "currency",
               json_string (c->currency));
    GNUNET_assert (0 ==
                   json_array_append_new (arr,
                                          co));
  }
  return arr;
}


/**
 * Serialize the `required_attributes` array.
 *
 * @param common state to serialize
 * @return JSON array
 */
static json_t *
serialize_required_attributes (const struct ANASTASIS_ReduxCommon *common)
{
  json_t *arr;

  arr = json_array ();
  GNUNET_assert (NULL != arr);
  for (unsigned int i = 0; i < common->required_attributes_len; i++)
  {
    const struct ANASTASIS_ReduxAttributeSpec *a
      = &common->required_attributes[i];
    json_t *ao;

    ao = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("type",
                               a->type),
      GNUNET_JSON_pack_string ("name",
                               a->name),
      GNUNET_JSON_pack_string ("label",
                               a->label),
      GNUNET_JSON_pack_string ("widget",
                               a->widget),
      GNUNET_JSON_pack_conditional (
        NULL != a->uuid,
        GNUNET_JSON_pack_string ("uuid",
                                 a->uuid)),
      GNUNET_JSON_pack_conditional (
        NULL != a->tooltip,
        GNUNET_JSON_pack_string ("tooltip",
                                 a->tooltip)),
      GNUNET_JSON_pack_conditional (
        NULL != a->validation_regex,
        GNUNET_JSON_pack_string ("validation-regex",
                                 a->validation_regex)),
      GNUNET_JSON_pack_conditional (
        NULL != a->validation_logic,
        GNUNET_JSON_pack_string ("validation-logic",
                                 a->validation_logic)),
      GNUNET_JSON_pack_conditional (
        NULL != a->autocomplete,
        GNUNET_JSON_pack_string ("autocomplete",
                                 a->autocomplete)),
      GNUNET_JSON_pack_conditional (
        a->have_optional,
        GNUNET_JSON_pack_bool ("optional",
                               a->optional)));
    set_opt (ao,
             "label_i18n",
             json_incref (a->label_i18n));
    GNUNET_assert (0 ==
                   json_array_append_new (arr,
                                          ao));
  }
  return arr;
}


/**
 * Serialize the `authentication_providers` object.
 *
 * @param common state to serialize
 * @return JSON object
 */
static json_t *
serialize_providers (const struct ANASTASIS_ReduxCommon *common)
{
  json_t *obj;

  obj = json_object ();
  GNUNET_assert (NULL != obj);
  for (unsigned int i = 0; i < common->providers_len; i++)
  {
    const struct ANASTASIS_ReduxProvider *p = &common->providers[i];
    const char *status;
    json_t *po;

    switch (p->status)
    {
    case ANASTASIS_RPS_NOT_CONTACTED:
      status = "not-contacted";
      break;
    case ANASTASIS_RPS_OK:
      status = "ok";
      break;
    case ANASTASIS_RPS_DISABLED:
      status = "disabled";
      break;
    case ANASTASIS_RPS_ERROR:
      status = "error";
      break;
    default:
      GNUNET_assert (0);
    }
    if (ANASTASIS_RPS_ERROR == p->status)
    {
      po = GNUNET_JSON_PACK (
        GNUNET_JSON_pack_string ("status",
                                 status),
        GNUNET_JSON_pack_uint64 ("error_code",
                                 p->error.ec),
        GNUNET_JSON_pack_uint64 ("http_status",
                                 p->error.http_status));
    }
    else if (! p->have_config)
    {
      po = GNUNET_JSON_PACK (
        GNUNET_JSON_pack_string ("status",
                                 status));
    }
    else
    {
      const struct ANASTASIS_ReduxProviderConfig *cfg = &p->config;
      json_t *methods;

      methods = json_array ();
      GNUNET_assert (NULL != methods);
      for (unsigned int j = 0; j < cfg->methods_len; j++)
      {
        const struct ANASTASIS_ReduxMethodSpec *ms = &cfg->methods[j];

        GNUNET_assert (0 ==
                       json_array_append_new (
                         methods,
                         GNUNET_JSON_PACK (
                           GNUNET_JSON_pack_string ("type",
                                                    ms->type),
                           TALER_JSON_pack_amount ("usage_fee",
                                                   &ms->usage_fee),
                           TALER_JSON_pack_amount_list ("usage_fees",
                                                        &ms->usage_fees))));
      }
      {
        json_t *currencies = json_array ();

        GNUNET_assert (NULL != currencies);
        for (unsigned int j = 0; j < cfg->currencies_len; j++)
          GNUNET_assert (0 ==
                         json_array_append_new (
                           currencies,
                           json_string (cfg->currencies[j])));
        po = GNUNET_JSON_PACK (
          GNUNET_JSON_pack_string ("status",
                                   status),
          GNUNET_JSON_pack_array_steal ("methods",
                                        methods),
          GNUNET_JSON_pack_array_steal ("currencies",
                                        currencies),
          TALER_JSON_pack_amount ("annual_fee",
                                  &cfg->annual_fee),
          TALER_JSON_pack_amount_list ("annual_fees",
                                       &cfg->annual_fees),
          TALER_JSON_pack_amount ("truth_upload_fee",
                                  &cfg->truth_upload_fee),
          TALER_JSON_pack_amount_list ("truth_upload_fees",
                                       &cfg->truth_upload_fees),
          TALER_JSON_pack_amount ("liability_limit",
                                  &cfg->liability_limit),
          TALER_JSON_pack_amount_list ("liability_limits",
                                       &cfg->liability_limits),
          GNUNET_JSON_pack_string ("business_name",
                                   cfg->business_name),
          GNUNET_JSON_pack_uint64 ("storage_limit_in_megabytes",
                                   cfg->storage_limit_in_megabytes),
          GNUNET_JSON_pack_data_auto ("provider_salt",
                                      &cfg->provider_salt),
          GNUNET_JSON_pack_uint64 ("http_status",
                                   cfg->http_status),
          GNUNET_JSON_pack_conditional (
            NULL != cfg->currency,
            GNUNET_JSON_pack_string ("currency",
                                     cfg->currency)));
      }
    }
    GNUNET_assert (0 ==
                   json_object_set_new (obj,
                                        p->url.url,
                                        po));
  }
  return obj;
}


/**
 * Add the fields shared by the backup and recovery variants to @a obj.
 *
 * @param[in,out] obj object to extend
 * @param common state to serialize
 */
static void
serialize_common (json_t *obj,
                  const struct ANASTASIS_ReduxCommon *common)
{
  if (common->have_continents)
    set_opt (obj,
             "continents",
             serialize_continents (common));
  if (common->have_countries)
    set_opt (obj,
             "countries",
             serialize_countries (common));
  if (common->have_required_attributes)
    set_opt (obj,
             "required_attributes",
             serialize_required_attributes (common));
  if (common->have_providers)
    set_opt (obj,
             "authentication_providers",
             serialize_providers (common));
  if (NULL != common->identity_attributes)
    set_opt (obj,
             "identity_attributes",
             json_incref (common->identity_attributes));
  if (common->have_currencies)
  {
    json_t *arr;

    arr = json_array ();
    GNUNET_assert (NULL != arr);
    for (unsigned int i = 0; i < common->currencies_len; i++)
      GNUNET_assert (0 ==
                     json_array_append_new (
                       arr,
                       json_string (common->currencies[i])));
    set_opt (obj,
             "currencies",
             arr);
  }
  if (NULL != common->selected_continent)
    set_opt (obj,
             "selected_continent",
             json_string (common->selected_continent));
  if (NULL != common->selected_country)
    set_opt (obj,
             "selected_country",
             json_string (common->selected_country));
  if (NULL != common->preferred_currency)
    set_opt (obj,
             "preferred_currency",
             json_string (common->preferred_currency));
}


/**
 * Serialize the backup-specific fields into @a obj.
 *
 * @param[in,out] obj object to extend
 * @param b backup state to serialize
 */
static void
serialize_backup (json_t *obj,
                  const struct ANASTASIS_ReduxBackup *b)
{
  if (b->have_authentication_methods)
  {
    json_t *arr;

    arr = json_array ();
    GNUNET_assert (NULL != arr);
    for (unsigned int i = 0; i < b->authentication_methods_len; i++)
    {
      const struct ANASTASIS_ReduxAuthMethod *am
        = &b->authentication_methods[i];

      GNUNET_assert (0 ==
                     json_array_append_new (
                       arr,
                       GNUNET_JSON_PACK (
                         GNUNET_JSON_pack_string ("type",
                                                  am->type),
                         GNUNET_JSON_pack_string ("instructions",
                                                  am->instructions),
                         GNUNET_JSON_pack_data_varsize ("challenge",
                                                        am->challenge,
                                                        am->challenge_size),
                         GNUNET_JSON_pack_conditional (
                           NULL != am->mime_type,
                           GNUNET_JSON_pack_string ("mime_type",
                                                    am->mime_type)))));
    }
    set_opt (obj,
             "authentication_methods",
             arr);
  }
  if (b->have_policies)
  {
    json_t *arr;

    arr = json_array ();
    GNUNET_assert (NULL != arr);
    for (unsigned int i = 0; i < b->policies_len; i++)
    {
      const struct ANASTASIS_ReduxPolicy *p = &b->policies[i];
      json_t *methods;

      methods = json_array ();
      GNUNET_assert (NULL != methods);
      for (unsigned int j = 0; j < p->methods_len; j++)
      {
        const struct ANASTASIS_ReduxPolicyMethod *pm = &p->methods[j];
        json_t *mo;

        mo = GNUNET_JSON_PACK (
          GNUNET_JSON_pack_uint64 ("authentication_method",
                                   pm->authentication_method.idx),
          GNUNET_JSON_pack_string ("provider",
                                   pm->provider.url));
        if (NULL != pm->truth)
        {
          json_t *truth = ANASTASIS_truth_to_json (pm->truth);

          GNUNET_assert (NULL != truth);
          GNUNET_assert (0 ==
                         json_object_set_new (truth,
                                              "upload_status",
                                              json_integer (
                                                pm->upload_status)));
          GNUNET_assert (0 ==
                         json_object_set_new (mo,
                                              "truth",
                                              truth));
        }
        GNUNET_assert (0 ==
                       json_array_append_new (methods,
                                              mo));
      }
      GNUNET_assert (0 ==
                     json_array_append_new (
                       arr,
                       GNUNET_JSON_PACK (
                         GNUNET_JSON_pack_conditional (
                           p->have_recovery_cost,
                           TALER_JSON_pack_amount ("recovery_cost",
                                                   &p->recovery_cost)),
                         GNUNET_JSON_pack_array_steal ("methods",
                                                       methods))));
    }
    set_opt (obj,
             "policies",
             arr);
  }
  if (b->have_policy_providers)
  {
    json_t *arr;

    arr = json_array ();
    GNUNET_assert (NULL != arr);
    for (unsigned int i = 0; i < b->policy_providers_len; i++)
    {
      const struct ANASTASIS_ReduxPolicyProvider *pp
        = &b->policy_providers[i];

      GNUNET_assert (0 ==
                     json_array_append_new (
                       arr,
                       GNUNET_JSON_PACK (
                         GNUNET_JSON_pack_string ("provider_url",
                                                  pp->provider_url.url),
                         GNUNET_JSON_pack_conditional (
                           pp->have_payment_secret,
                           GNUNET_JSON_pack_data_auto (
                             "payment_secret",
                             &pp->payment_secret)))));
    }
    set_opt (obj,
             "policy_providers",
             arr);
  }
  if (b->have_upload_fees)
  {
    json_t *arr;

    arr = json_array ();
    GNUNET_assert (NULL != arr);
    for (unsigned int i = 0; i < b->upload_fees_len; i++)
      GNUNET_assert (0 ==
                     json_array_append_new (
                       arr,
                       GNUNET_JSON_PACK (
                         TALER_JSON_pack_amount ("fee",
                                                 &b->upload_fees[i]))));
    set_opt (obj,
             "upload_fees",
             arr);
  }
  if (b->have_payments)
  {
    json_t *arr;

    arr = json_array ();
    GNUNET_assert (NULL != arr);
    for (unsigned int i = 0; i < b->payments_len; i++)
      GNUNET_assert (0 ==
                     json_array_append_new (arr,
                                            json_string (b->payments[i])));
    set_opt (obj,
             "payments",
             arr);
  }
  if (b->have_policy_payment_requests)
  {
    json_t *arr;

    arr = json_array ();
    GNUNET_assert (NULL != arr);
    for (unsigned int i = 0; i < b->policy_payment_requests_len; i++)
    {
      const struct ANASTASIS_ReduxPolicyPaymentRequest *ppr
        = &b->policy_payment_requests[i];

      GNUNET_assert (0 ==
                     json_array_append_new (
                       arr,
                       GNUNET_JSON_PACK (
                         GNUNET_JSON_pack_string ("payto",
                                                  ppr->payto),
                         GNUNET_JSON_pack_string ("provider",
                                                  ppr->provider.url))));
    }
    set_opt (obj,
             "policy_payment_requests",
             arr);
  }
  if (b->have_success_details)
  {
    json_t *sd;

    sd = json_object ();
    GNUNET_assert (NULL != sd);
    for (unsigned int i = 0; i < b->success_details_len; i++)
    {
      const struct ANASTASIS_ReduxSuccessDetail *d = &b->success_details[i];

      GNUNET_assert (0 ==
                     json_object_set_new (
                       sd,
                       d->provider_url.url,
                       GNUNET_JSON_PACK (
                         GNUNET_JSON_pack_uint64 ("policy_version",
                                                  d->policy_version),
                         GNUNET_JSON_pack_timestamp ("policy_expiration",
                                                     d->policy_expiration))));
    }
    set_opt (obj,
             "success_details",
             sd);
  }
  if (NULL != b->core_secret)
    set_opt (obj,
             "core_secret",
             json_incref (b->core_secret));
  if (NULL != b->secret_name)
    set_opt (obj,
             "secret_name",
             json_string (b->secret_name));
  if (b->have_expiration)
    set_opt (obj,
             "expiration",
             GNUNET_JSON_from_timestamp (b->expiration));
  if (b->have_pay_arguments)
  {
    json_t *pa;

    pa = json_object ();
    GNUNET_assert (NULL != pa);
    if (b->pay_arguments.have_timeout)
      set_opt (pa,
               "timeout",
               GNUNET_JSON_from_time_rel (b->pay_arguments.timeout));
    set_opt (obj,
             "pay_arguments",
             pa);
  }
}


/**
 * Serialize the recovery information @a ri.
 *
 * @param ri recovery information to serialize
 * @return JSON object
 */
static json_t *
serialize_recovery_info (const struct ANASTASIS_ReduxRecoveryInfo *ri)
{
  json_t *challenges;
  json_t *policies;

  challenges = json_array ();
  GNUNET_assert (NULL != challenges);
  for (unsigned int i = 0; i < ri->challenges_len; i++)
  {
    const struct ANASTASIS_ReduxChallengeInfo *ci = &ri->challenges[i];

    GNUNET_assert (0 ==
                   json_array_append_new (
                     challenges,
                     GNUNET_JSON_PACK (
                       GNUNET_JSON_pack_data_auto ("uuid",
                                                   &ci->uuid),
                       GNUNET_JSON_pack_string ("type",
                                                ci->type),
                       GNUNET_JSON_pack_string ("uuid-display",
                                                ANASTASIS_CRYPTO_uuid2s (
                                                  &ci->uuid)),
                       GNUNET_JSON_pack_string ("instructions",
                                                ci->instructions),
                       GNUNET_JSON_pack_conditional (
                         NULL != ci->answer,
                         GNUNET_JSON_pack_string ("answer",
                                                  ci->answer)),
                       GNUNET_JSON_pack_conditional (
                         ci->have_payment_secret,
                         GNUNET_JSON_pack_data_auto ("payment_secret",
                                                     &ci->payment_secret)))));
  }
  policies = json_array ();
  GNUNET_assert (NULL != policies);
  for (unsigned int i = 0; i < ri->policies_len; i++)
  {
    const struct ANASTASIS_ReduxRecoveryPolicy *p = &ri->policies[i];
    json_t *pa;

    pa = json_array ();
    GNUNET_assert (NULL != pa);
    for (unsigned int j = 0; j < p->uuids_len; j++)
      GNUNET_assert (0 ==
                     json_array_append_new (
                       pa,
                       GNUNET_JSON_PACK (
                         GNUNET_JSON_pack_data_auto ("uuid",
                                                     &p->uuids[j]))));
    GNUNET_assert (0 ==
                   json_array_append_new (policies,
                                          pa));
  }
  return GNUNET_JSON_PACK (
    GNUNET_JSON_pack_array_steal ("challenges",
                                  challenges),
    GNUNET_JSON_pack_array_steal ("policies",
                                  policies),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("secret_name",
                               ri->secret_name)),
    GNUNET_JSON_pack_string ("provider_url",
                             ri->provider_url.url),
    GNUNET_JSON_pack_uint64 ("version",
                             ri->version));
}


/**
 * Serialize one challenge feedback entry.
 *
 * @param fb feedback to serialize
 * @return JSON object
 */
static json_t *
serialize_feedback (const struct ANASTASIS_ReduxFeedback *fb)
{
  json_t *fo;

  switch (fb->status)
  {
  case ANASTASIS_RFS_CODE_IN_FILE:
    fo = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("state",
                               "code-in-file"),
      GNUNET_JSON_pack_string ("filename",
                               fb->details.code_in_file.filename));
    break;
  case ANASTASIS_RFS_SEND_TO_ADDRESS:
    fo = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("state",
                               "send-to-address"),
      GNUNET_JSON_pack_conditional (
        NULL != fb->details.send_to_address.address_hint,
        GNUNET_JSON_pack_string ("address_hint",
                                 fb->details.send_to_address.address_hint)));
    break;
  case ANASTASIS_RFS_TALER_PAYMENT:
    fo = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("state",
                               "taler-payment"),
      GNUNET_JSON_pack_string ("taler_pay_uri",
                               fb->details.taler_payment.taler_pay_uri),
      GNUNET_JSON_pack_string ("provider",
                               fb->details.taler_payment.provider.url),
      GNUNET_JSON_pack_data_auto ("payment_secret",
                                  &fb->details.taler_payment.payment_secret));
    break;
  case ANASTASIS_RFS_SERVER_FAILURE:
    fo = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("state",
                               "server-failure"),
      GNUNET_JSON_pack_uint64 ("http_status",
                               fb->details.server_failure.http_status),
      GNUNET_JSON_pack_uint64 ("error_code",
                               fb->details.server_failure.ec));
    break;
  case ANASTASIS_RFS_TRUTH_UNKNOWN:
    fo = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("state",
                               "truth-unknown"),
      GNUNET_JSON_pack_uint64 ("http_status",
                               fb->details.server_failure.http_status),
      GNUNET_JSON_pack_uint64 ("error_code",
                               fb->details.server_failure.ec));
    break;
  case ANASTASIS_RFS_IBAN_INSTRUCTIONS:
    fo = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("state",
                               "iban-instructions"),
      GNUNET_JSON_pack_string ("target_iban",
                               fb->details.iban_instructions.target_iban),
      GNUNET_JSON_pack_string (
        "target_business_name",
        fb->details.iban_instructions.target_business_name),
      GNUNET_JSON_pack_string (
        "wire_transfer_subject",
        fb->details.iban_instructions.wire_transfer_subject),
      TALER_JSON_pack_amount ("challenge_amount",
                              &fb->details.iban_instructions.amount));
    break;
  case ANASTASIS_RFS_SOLVED:
    fo = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("state",
                               "solved"));
    break;
  case ANASTASIS_RFS_INCORRECT_ANSWER:
    fo = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("state",
                               "incorrect-answer"),
      GNUNET_JSON_pack_uint64 ("error_code",
                               fb->details.incorrect_answer.ec));
    break;
  case ANASTASIS_RFS_RATE_LIMIT_EXCEEDED:
    fo = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("state",
                               "rate-limit-exceeded"),
      GNUNET_JSON_pack_uint64 (
        "request_limit",
        fb->details.rate_limit_exceeded.request_limit),
      GNUNET_JSON_pack_time_rel (
        "request_frequency",
        fb->details.rate_limit_exceeded.request_frequency),
      GNUNET_JSON_pack_uint64 ("error_code",
                               fb->details.rate_limit_exceeded.ec));
    break;
  default:
    GNUNET_assert (0);
  }
  if (NULL != fb->display_hint)
    set_opt (fo,
             "display_hint",
             json_string (fb->display_hint));
  return fo;
}


/**
 * Serialize the recovery-specific fields into @a obj.
 *
 * @param[in,out] obj object to extend
 * @param rr recovery state to serialize
 */
static void
serialize_recovery (json_t *obj,
                    const struct ANASTASIS_ReduxRecovery *rr)
{
  if (NULL != rr->ri)
    set_opt (obj,
             "recovery_information",
             serialize_recovery_info (rr->ri));
  if (NULL != rr->r)
    set_opt (obj,
             "recovery_document",
             ANASTASIS_recovery_serialize (rr->r));
  if (rr->have_challenge_feedback)
  {
    json_t *cf;

    cf = json_object ();
    GNUNET_assert (NULL != cf);
    for (unsigned int i = 0; i < rr->challenge_feedback_len; i++)
    {
      const struct ANASTASIS_ReduxFeedback *fb = &rr->challenge_feedback[i];
      char uuid[sizeof (fb->uuid) * 2];
      char *end;

      end = GNUNET_STRINGS_data_to_string (&fb->uuid,
                                           sizeof (fb->uuid),
                                           uuid,
                                           sizeof (uuid));
      GNUNET_assert (NULL != end);
      *end = '\0';
      GNUNET_assert (0 ==
                     json_object_set_new (cf,
                                          uuid,
                                          serialize_feedback (fb)));
    }
    set_opt (obj,
             "challenge_feedback",
             cf);
  }
  if (rr->have_selected_challenge)
    set_opt (obj,
             "selected_challenge_uuid",
             GNUNET_JSON_from_data_auto (&rr->selected_challenge));
  if (NULL != rr->core_secret)
    set_opt (obj,
             "core_secret",
             json_incref (rr->core_secret));
}


json_t *
ANASTASIS_REDUX_state_serialize_ (const struct ANASTASIS_ReduxState *rs)
{
  json_t *obj;

  switch (rs->type)
  {
  case ANASTASIS_RT_BACKUP:
    obj = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("reducer_type",
                               "backup"),
      GNUNET_JSON_pack_string ("backup_state",
                               ANASTASIS_backup_state_to_string_ (
                                 rs->details.backup.state)));
    serialize_common (obj,
                      &rs->common);
    serialize_backup (obj,
                      &rs->details.backup);
    return obj;
  case ANASTASIS_RT_RECOVERY:
    obj = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("reducer_type",
                               "recovery"),
      GNUNET_JSON_pack_string ("recovery_state",
                               ANASTASIS_recovery_state_to_string_ (
                                 rs->details.recovery.state)));
    serialize_common (obj,
                      &rs->common);
    serialize_recovery (obj,
                        &rs->details.recovery);
    return obj;
  case ANASTASIS_RT_ERROR:
    return GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("reducer_type",
                               "error"),
      GNUNET_JSON_pack_uint64 ("code",
                               rs->details.error.code),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_string ("hint",
                                 rs->details.error.hint)),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_string ("detail",
                                 rs->details.error.detail)));
  }
  GNUNET_assert (0);
  return NULL;
}


/* end of anastasis_api_redux_serialize.c */
