/*
  This file is part of TALER
  (C) 2022-2026 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify
  it under the terms of the GNU Affero 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 taler-merchant-httpd_post-templates-TEMPLATE_ID.c
 * @brief implementing POST /using-templates request handling
 * @author Priscilla HUANG
 * @author Christian Grothoff
 */
#include "taler/platform.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_post-templates-TEMPLATE_ID.h"
#include "taler-merchant-httpd_post-private-orders.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_get-exchanges.h"
#include "taler/taler_merchant_util.h"
#include <taler/taler_json_lib.h>
#include <regex.h>


/**
 * Item selected from inventory_selection.
 */
struct InventoryTemplateItemContext
{
  /**
   * Product ID as referenced in inventory.
   */
  const char *product_id;

  /**
   * Unit quantity string as provided by the client.
   */
  const char *unit_quantity;

  /**
   * Parsed integer quantity.
   */
  uint64_t quantity_value;

  /**
   * Parsed fractional quantity.
   */
  uint32_t quantity_frac;

  /**
   * Product details from the DB (includes price array).
   */
  struct TALER_MERCHANTDB_ProductDetails pd;

  /**
   * Categories referenced by the product.
   */
  uint64_t *categories;

  /**
   * Length of @e categories.
   */
  size_t num_categories;
};


/**
 * Our context.
 */
enum UsePhase
{
  /**
   * Parse request payload into context fields.
   */
  USE_PHASE_PARSE_REQUEST,

  /**
   * Fetch template details from the database.
   */
  USE_PHASE_LOOKUP_TEMPLATE,

  /**
   * Parse template.
   */
  USE_PHASE_PARSE_TEMPLATE,

  /**
   * Load additional details (like products and
   * categories) needed for verification and
   * price computation.
   */
  USE_PHASE_DB_FETCH,

  /**
   * Validate request and template compatibility.
   */
  USE_PHASE_VERIFY,

  /**
   * Compute price of the order.
   */
  USE_PHASE_COMPUTE_PRICE,

  /**
   * Handle tip.
   */
  USE_PHASE_CHECK_TIP,

  /**
   * Check if client-supplied total amount matches
   * our calculation (if we did any).
   */
  USE_PHASE_CHECK_TOTAL,

  /**
   * Construct the internal order request body.
   */
  USE_PHASE_CREATE_ORDER,

  /**
   * Submit the order to the shared order handler.
   */
  USE_PHASE_SUBMIT_ORDER,

  /**
   * Finished successfully with MHD_YES.
   */
  USE_PHASE_FINISHED_MHD_YES,

  /**
   * Finished with MHD_NO.
   */
  USE_PHASE_FINISHED_MHD_NO
};

struct UseContext
{
  /**
   * Context for our handler.
   */
  struct TMH_HandlerContext *hc;

  /**
   * Internal handler context we are passing into the
   * POST /private/orders handler.
   */
  struct TMH_HandlerContext ihc;

  /**
   * Phase we are currently in.
   */
  enum UsePhase phase;

  /**
   * Template type from the contract.
   */
  enum TALER_MERCHANT_TemplateType template_type;

  /**
   * Information set in the #USE_PHASE_PARSE_REQUEST phase.
   */
  struct
  {
    /**
     * Summary override from request, if any.
     */
    const char *summary;

    /**
     * Amount provided by the client.
     */
    struct TALER_Amount amount;

    /**
     * Tip provided by the client.
     */
    struct TALER_Amount tip;

    /**
     * True if @e amount was not provided.
     */
    bool no_amount;

    /**
     * True if @e tip was not provided.
     */
    bool no_tip;

    /**
     * Parsed fields for inventory templates.
     */
    struct
    {
      /**
       * Selected products from inventory_selection.
       */
      struct InventoryTemplateItemContext *items;

      /**
       * Length of @e items.
       */
      unsigned int items_len;

    } inventory;

    /**
     * Request details if this is a paivana instantiation.
     */
    struct
    {

      /**
       * Target website for the request.
       */
      const char *website;

      /**
       * Unique client identifier, consisting of
       * current time, "-", and the hash of a nonce,
       * the website and the current time.
       */
      const char *paivana_id;

    } paivana;

  } parse_request;

  /**
   * Information set in the #USE_PHASE_LOOKUP_TEMPLATE phase.
   */
  struct
  {

    /**
     * Our template details from the DB.
     */
    struct TALER_MERCHANTDB_TemplateDetails etp;

  } lookup_template;

  /**
   * Information set in the #USE_PHASE_PARSE_TEMPLATE phase.
   */
  struct TALER_MERCHANT_TemplateContract template_contract;

  /**
   * Information set in the #USE_PHASE_COMPUTE_PRICE phase.
   */
  struct
  {

    /**
     * Per-currency totals across selected products (without tips).
     */
    struct TALER_Amount *totals;

    /**
     * Length of @e totals.
     */
    unsigned int totals_len;

    /**
     * Array of payment choices, used with Paviana.
     */
    json_t *choices;

  } compute_price;

};


/**
 * Clean up inventory items.
 *
 * @param items_len length of @a items
 * @param[in] items item array to free
 */
static void
cleanup_inventory_items (unsigned int items_len,
                         struct InventoryTemplateItemContext items[static
                                                                   items_len])
{
  for (unsigned int i = 0; i < items_len; i++)
  {
    struct InventoryTemplateItemContext *item = &items[i];

    TALER_MERCHANTDB_product_details_free (&item->pd);
    GNUNET_free (item->categories);
  }
  GNUNET_free (items);
}


/**
 * Clean up a `struct UseContext *`
 *
 * @param[in] cls a `struct UseContext *`
 */
static void
cleanup_use_context (void *cls)
{
  struct UseContext *uc = cls;

  TALER_MERCHANTDB_template_details_free (&uc->lookup_template.etp);
  if (NULL !=
      uc->parse_request.inventory.items)
    cleanup_inventory_items (uc->parse_request.inventory.items_len,
                             uc->parse_request.inventory.items);
  GNUNET_free (uc->compute_price.totals);
  uc->compute_price.totals_len = 0;
  json_decref (uc->compute_price.choices);
  if (NULL != uc->ihc.cc)
    uc->ihc.cc (uc->ihc.ctx);
  GNUNET_free (uc->ihc.infix);
  json_decref (uc->ihc.request_body);
  GNUNET_free (uc);
}


/**
 * Finalize a template use request.
 *
 * @param[in,out] uc use context
 * @param ret handler return value
 */
static void
use_finalize (struct UseContext *uc,
              MHD_RESULT ret)
{
  uc->phase = (MHD_YES == ret)
    ? USE_PHASE_FINISHED_MHD_YES
    : USE_PHASE_FINISHED_MHD_NO;
}


/**
 * Finalize after JSON parsing result.
 *
 * @param[in,out] uc use context
 * @param res parse result
 */
static void
use_finalize_parse (struct UseContext *uc,
                    enum GNUNET_GenericReturnValue res)
{
  GNUNET_assert (GNUNET_OK != res);
  use_finalize (uc,
                (GNUNET_NO == res)
                ? MHD_YES
                : MHD_NO);
}


/**
 * Reply with error and finalize the request.
 *
 * @param[in,out] uc use context
 * @param http_status HTTP status code
 * @param ec error code
 * @param detail error detail
 */
static void
use_reply_with_error (struct UseContext *uc,
                      unsigned int http_status,
                      enum TALER_ErrorCode ec,
                      const char *detail)
{
  MHD_RESULT mret;

  mret = TALER_MHD_reply_with_error (uc->hc->connection,
                                     http_status,
                                     ec,
                                     detail);
  use_finalize (uc,
                mret);
}


/* ***************** USE_PHASE_PARSE_REQUEST **************** */

/**
 * Parse request data for inventory templates.
 *
 * @param[in,out] uc use context
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_using_templates_inventory_request (
  struct UseContext *uc)
{
  const json_t *inventory_selection;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_array_const ("inventory_selection",
                                  &inventory_selection),
    GNUNET_JSON_spec_end ()
  };
  enum GNUNET_GenericReturnValue res;

  GNUNET_assert (NULL == uc->ihc.request_body);
  res = TALER_MHD_parse_json_data (uc->hc->connection,
                                   uc->hc->request_body,
                                   spec);
  if (GNUNET_OK != res)
  {
    GNUNET_break_op (0);
    use_finalize_parse (uc,
                        res);
    return GNUNET_SYSERR;
  }

  if ( (! uc->parse_request.no_amount) &&
       (! TMH_test_exchange_configured_for_currency (
          uc->parse_request.amount.currency)) )
  {
    GNUNET_break_op (0);
    use_reply_with_error (uc,
                          MHD_HTTP_CONFLICT,
                          TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
                          "Currency is not supported by backend");
    return GNUNET_SYSERR;
  }

  for (size_t i = 0; i < json_array_size (inventory_selection); i++)
  {
    struct InventoryTemplateItemContext item = { 0 };
    struct GNUNET_JSON_Specification ispec[] = {
      GNUNET_JSON_spec_string ("product_id",
                               &item.product_id),
      GNUNET_JSON_spec_string ("quantity",
                               &item.unit_quantity),
      GNUNET_JSON_spec_end ()
    };
    const char *err_name;
    unsigned int err_line;

    res = GNUNET_JSON_parse (json_array_get (inventory_selection,
                                             i),
                             ispec,
                             &err_name,
                             &err_line);
    if (GNUNET_OK != res)
    {
      GNUNET_break_op (0);
      use_reply_with_error (uc,
                            MHD_HTTP_BAD_REQUEST,
                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
                            "inventory_selection");
      return GNUNET_SYSERR;
    }

    GNUNET_array_append (uc->parse_request.inventory.items,
                         uc->parse_request.inventory.items_len,
                         item);
  }
  return GNUNET_OK;
}


/**
 * Parse request data for paivana templates.
 *
 * @param[in,out] uc use context
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_using_templates_paivana_request (
  struct UseContext *uc)
{
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("website",
                             &uc->parse_request.paivana.website),
    GNUNET_JSON_spec_string ("paivana_id",
                             &uc->parse_request.paivana.paivana_id),
    GNUNET_JSON_spec_end ()
  };
  enum GNUNET_GenericReturnValue res;
  struct GNUNET_HashCode sh;
  unsigned long long tv;
  const char *dash;

  GNUNET_assert (NULL == uc->ihc.request_body);
  res = TALER_MHD_parse_json_data (uc->hc->connection,
                                   uc->hc->request_body,
                                   spec);
  if (GNUNET_OK != res)
  {
    GNUNET_break_op (0);
    use_finalize_parse (uc,
                        res);
    return GNUNET_SYSERR;
  }
  if (1 !=
      sscanf (uc->parse_request.paivana.paivana_id,
              "%llu-",
              &tv))
  {
    GNUNET_break_op (0);
    use_reply_with_error (uc,
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
                          "paivana_id");
    return GNUNET_SYSERR;
  }
  dash = strchr (uc->parse_request.paivana.paivana_id,
                 '-');
  GNUNET_assert (NULL != dash);
  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (dash + 1,
                                     strlen (dash + 1),
                                     &sh,
                                     sizeof (sh)))
  {
    GNUNET_break_op (0);
    use_reply_with_error (uc,
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
                          "paivana_id");
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Main function for the #USE_PHASE_PARSE_REQUEST.
 *
 * @param[in,out] uc context to update
 */
static void
handle_phase_parse_request (
  struct UseContext *uc)
{
  const char *template_type = NULL;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("template_type",
                               &template_type),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      TALER_JSON_spec_amount_any ("tip",
                                  &uc->parse_request.tip),
      &uc->parse_request.no_tip),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("summary",
                               &uc->parse_request.summary),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      TALER_JSON_spec_amount_any ("amount",
                                  &uc->parse_request.amount),
      &uc->parse_request.no_amount),
    GNUNET_JSON_spec_end ()
  };
  enum GNUNET_GenericReturnValue res;

  res = TALER_MHD_parse_json_data (uc->hc->connection,
                                   uc->hc->request_body,
                                   spec);
  if (GNUNET_OK != res)
  {
    GNUNET_break_op (0);
    use_finalize_parse (uc,
                        res);
    return;
  }
  if (NULL == template_type)
    template_type = "fixed-order";
  uc->template_type
    = TALER_MERCHANT_template_type_from_string (
        template_type);
  switch (uc->template_type)
  {
  case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    /* nothig left to do */
    uc->phase++;
    return;
  case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    res = parse_using_templates_paivana_request (uc);
    if (GNUNET_OK == res)
      uc->phase++;
    return;
  case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    res = parse_using_templates_inventory_request (uc);
    if (GNUNET_OK == res)
      uc->phase++;
    return;
  case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    break;
  }
  GNUNET_break (0);
  use_reply_with_error (
    uc,
    MHD_HTTP_BAD_REQUEST,
    TALER_EC_GENERIC_PARAMETER_MALFORMED,
    "template_type");
}


/* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */

/**
 * Main function for the #USE_PHASE_LOOKUP_TEMPLATE.
 *
 * @param[in,out] uc context to update
 */
static void
handle_phase_lookup_template (
  struct UseContext *uc)
{
  struct TMH_MerchantInstance *mi = uc->hc->instance;
  const char *template_id = uc->hc->infix;
  enum GNUNET_DB_QueryStatus qs;

  qs = TMH_db->lookup_template (TMH_db->cls,
                                mi->settings.id,
                                template_id,
                                &uc->lookup_template.etp);
  switch (qs)
  {
  case GNUNET_DB_STATUS_HARD_ERROR:
    /* Clean up and fail hard */
    GNUNET_break (0);
    use_reply_with_error (uc,
                          MHD_HTTP_INTERNAL_SERVER_ERROR,
                          TALER_EC_GENERIC_DB_FETCH_FAILED,
                          "lookup_template");
    return;
  case GNUNET_DB_STATUS_SOFT_ERROR:
    /* this should be impossible (single select) */
    GNUNET_break (0);
    use_reply_with_error (uc,
                          MHD_HTTP_INTERNAL_SERVER_ERROR,
                          TALER_EC_GENERIC_DB_FETCH_FAILED,
                          "lookup_template");
    return;
  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    /* template not found! */
    use_reply_with_error (uc,
                          MHD_HTTP_NOT_FOUND,
                          TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
                          template_id);
    return;
  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    /* all good */
    break;
  }
  if (uc->template_type !=
      TALER_MERCHANT_template_type_from_contract (
        uc->lookup_template.etp.template_contract))
  {
    GNUNET_break_op (0);
    use_reply_with_error (
      uc,
      MHD_HTTP_CONFLICT,
      TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_TYPE,
      "template_contract has different type");
    return;
  }
  uc->phase++;
}


/* ***************** USE_PHASE_PARSE_TEMPLATE **************** */


/**
 * Parse template.
 *
 * @param[in,out] uc use context
 */
static void
handle_phase_template_contract (struct UseContext *uc)
{
  const char *err_name;
  enum GNUNET_GenericReturnValue res;

  res = TALER_MERCHANT_template_contract_parse (
    uc->lookup_template.etp.template_contract,
    &uc->template_contract,
    &err_name);
  if (GNUNET_OK != res)
  {
    GNUNET_break (0);
    use_reply_with_error (uc,
                          MHD_HTTP_INTERNAL_SERVER_ERROR,
                          TALER_EC_GENERIC_DB_FETCH_FAILED,
                          err_name);
    return;
  }
  uc->phase++;
}


/* ***************** USE_PHASE_DB_FETCH **************** */

/**
 * Fetch DB data for inventory templates.
 *
 * @param[in,out] uc use context
 */
static void
handle_phase_db_fetch (struct UseContext *uc)
{
  struct TMH_MerchantInstance *mi = uc->hc->instance;

  switch (uc->template_type)
  {
  case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    uc->phase++;
    return;
  case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    uc->phase++;
    return;
  case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    break;
  case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    GNUNET_assert (0);
  }

  for (unsigned int i = 0;
       i < uc->parse_request.inventory.items_len;
       i++)
  {
    struct InventoryTemplateItemContext *item =
      &uc->parse_request.inventory.items[i];
    enum GNUNET_DB_QueryStatus qs;

    qs = TMH_db->lookup_product (TMH_db->cls,
                                 mi->settings.id,
                                 item->product_id,
                                 &item->pd,
                                 &item->num_categories,
                                 &item->categories);
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
      GNUNET_break (0);
      use_reply_with_error (uc,
                            MHD_HTTP_INTERNAL_SERVER_ERROR,
                            TALER_EC_GENERIC_DB_FETCH_FAILED,
                            "lookup_product");
      return;
    case GNUNET_DB_STATUS_SOFT_ERROR:
      GNUNET_break (0);
      use_reply_with_error (uc,
                            MHD_HTTP_INTERNAL_SERVER_ERROR,
                            TALER_EC_GENERIC_DB_FETCH_FAILED,
                            "lookup_product");
      return;
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      use_reply_with_error (uc,
                            MHD_HTTP_NOT_FOUND,
                            TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
                            item->product_id);
      return;
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      break;
    }
  }
  uc->phase++;
}


/* *************** Helpers for USE_PHASE_VERIFY ***************** */

/**
 * Check if the given product ID appears in the array of allowed_products.
 *
 * @param allowed_products JSON array of product IDs allowed by the template, may be NULL
 * @param product_id product ID to check
 * @return true if the product ID is in the list
 */
static bool
product_id_allowed (const json_t *allowed_products,
                    const char *product_id)
{
  const json_t *entry;
  size_t idx;

  if (NULL == allowed_products)
    return false;
  json_array_foreach ((json_t *) allowed_products, idx, entry)
  {
    if (! json_is_string (entry))
    {
      GNUNET_break (0);
      continue;
    }
    if (0 == strcmp (json_string_value (entry),
                     product_id))
      return true;
  }
  return false;
}


/**
 * Check if any product category is in the selected_categories list.
 *
 * @param allowed_categories JSON array of categories allowed by the template, may be NULL
 * @param num_categories length of @a categories
 * @param categories list of categories of the selected product
 * @return true if any category of the product is in the list of allowed categories matches
 */
static bool
category_allowed (const json_t *allowed_categories,
                  size_t num_categories,
                  const uint64_t categories[num_categories])
{
  const json_t *entry;
  size_t idx;

  if (NULL == allowed_categories)
    return false;
  json_array_foreach ((json_t *) allowed_categories,
                      idx,
                      entry)
  {
    uint64_t selected_id;

    if (! json_is_integer (entry))
    {
      GNUNET_break (0);
      continue;
    }
    if (0 > json_integer_value (entry))
    {
      GNUNET_break (0);
      continue;
    }
    selected_id = (uint64_t) json_integer_value (entry);
    for (size_t i = 0; i < num_categories; i++)
    {
      if (categories[i] == selected_id)
        return true;
    }
  }
  return false;
}


/**
 * Verify request data for inventory templates.
 * Checks that the selected products are allowed
 * for this template.
 *
 * @param[in,out] uc use context
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
verify_using_templates_inventory (struct UseContext *uc)
{
  if (uc->template_contract.details.inventory.choose_one &&
      (1 != uc->parse_request.inventory.items_len))
  {
    GNUNET_break_op (0);
    use_reply_with_error (uc,
                          MHD_HTTP_CONFLICT,
                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
                          "inventory_selection");
    return GNUNET_SYSERR;
  }
  if (uc->template_contract.details.inventory.selected_all)
    return GNUNET_OK;
  for (unsigned int i = 0;
       i < uc->parse_request.inventory.items_len;
       i++)
  {
    struct InventoryTemplateItemContext *item =
      &uc->parse_request.inventory.items[i];
    const char *eparam = NULL;

    if (GNUNET_OK !=
        TALER_MERCHANT_vk_process_quantity_inputs (
          TALER_MERCHANT_VK_QUANTITY,
          item->pd.allow_fractional_quantity,
          true,
          0,
          false,
          item->unit_quantity,
          &item->quantity_value,
          &item->quantity_frac,
          &eparam))
    {
      GNUNET_break_op (0);
      use_reply_with_error (uc,
                            MHD_HTTP_BAD_REQUEST,
                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
                            eparam);
      return GNUNET_SYSERR;
    }

    if (0 == item->pd.price_array_length)
    {
      GNUNET_break (0);
      use_reply_with_error (uc,
                            MHD_HTTP_INTERNAL_SERVER_ERROR,
                            TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                            "price_array");
      return GNUNET_SYSERR;
    }
  }

  for (unsigned int i = 0;
       i < uc->parse_request.inventory.items_len;
       i++)
  {
    struct InventoryTemplateItemContext *item =
      &uc->parse_request.inventory.items[i];

    if (product_id_allowed (uc->template_contract.details.inventory.
                            selected_products,
                            item->product_id))
      continue;
    if (category_allowed (
          uc->template_contract.details.inventory.selected_categories,
          item->num_categories,
          item->categories))
      continue;
    GNUNET_break_op (0);
    use_reply_with_error (
      uc,
      MHD_HTTP_CONFLICT,
      TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_PRODUCT,
      item->product_id);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Verify request data for fixed-order templates.
 * As here we cannot compute the total amount, either
 * the template or the client request must provide it.
 *
 * @param[in,out] uc use context
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
verify_using_templates_fixed (
  struct UseContext *uc)
{
  if ( (! uc->parse_request.no_amount) &&
       (! uc->template_contract.no_amount) )
  {
    GNUNET_break_op (0);
    use_reply_with_error (uc,
                          MHD_HTTP_CONFLICT,
                          TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
                          NULL);
    return GNUNET_SYSERR;
  }
  if (uc->parse_request.no_amount &&
      uc->template_contract.no_amount)
  {
    GNUNET_break_op (0);
    use_reply_with_error (uc,
                          MHD_HTTP_CONFLICT,
                          TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT,
                          NULL);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Verify request data for paivana templates.
 *
 * @param[in,out] uc use context
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
verify_using_templates_paivana (
  struct UseContext *uc)
{
  if (NULL != uc->template_contract.details.paivana.website_regex)
  {
    regex_t ex;
    bool allowed = false;

    if (0 != regcomp (&ex,
                      uc->template_contract.details.paivana.website_regex,
                      REG_NOSUB | REG_EXTENDED))
    {
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    if (0 ==
        regexec (&ex,
                 uc->parse_request.paivana.website,
                 0, NULL,
                 0))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Website `%s' allowed by template\n",
                  uc->parse_request.paivana.website);
      allowed = true;
    }
    regfree (&ex);
    if (! allowed)
      return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Verify that the client request is structurally acceptable for the specified
 * template.  Does NOT check the total amount being reasonable.
 *
 * @param[in,out] uc use context
 */
static void
handle_phase_verify (
  struct UseContext *uc)
{
  enum GNUNET_GenericReturnValue res = GNUNET_SYSERR;

  if ( (NULL != uc->parse_request.summary) &&
       (NULL != uc->template_contract.summary) )
  {
    GNUNET_break_op (0);
    use_reply_with_error (uc,
                          MHD_HTTP_CONFLICT,
                          TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT,
                          NULL);
    return;
  }
  if ( (NULL == uc->parse_request.summary) &&
       (NULL == uc->template_contract.summary) )
  {
    GNUNET_break_op (0);
    use_reply_with_error (uc,
                          MHD_HTTP_CONFLICT,
                          TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY,
                          NULL);
    return;
  }
  if ( (! uc->parse_request.no_amount) &&
       (NULL != uc->template_contract.currency) &&
       (0 != strcasecmp (uc->template_contract.currency,
                         uc->parse_request.amount.currency)) )
  {
    GNUNET_break_op (0);
    use_reply_with_error (uc,
                          MHD_HTTP_CONFLICT,
                          TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
                          uc->template_contract.currency);
    return;
  }
  switch (uc->template_type)
  {
  case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    res = verify_using_templates_fixed (uc);
    break;
  case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    res = verify_using_templates_paivana (uc);
    break;
  case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    res = verify_using_templates_inventory (uc);
    break;
  case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    GNUNET_assert (0);
  }
  if (GNUNET_OK == res)
    uc->phase++;
}


/* ***************** USE_PHASE_COMPUTE_PRICE **************** */


/**
 * Compute the line total for a product based on quantity.
 *
 * @param unit_price price per unit
 * @param quantity integer quantity
 * @param quantity_frac fractional quantity (0..TALER_MERCHANT_UNIT_FRAC_BASE-1)
 * @param[out] line_total resulting line total
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
compute_line_total (const struct TALER_Amount *unit_price,
                    uint64_t quantity,
                    uint32_t quantity_frac,
                    struct TALER_Amount *line_total)
{
  struct TALER_Amount tmp;

  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (unit_price->currency,
                                        line_total));
  if ( (0 != quantity) &&
       (0 >
        TALER_amount_multiply (line_total,
                               unit_price,
                               (uint32_t) quantity)) )
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  if (0 == quantity_frac)
    return GNUNET_OK;
  if (0 >
      TALER_amount_multiply (&tmp,
                             unit_price,
                             quantity_frac))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  TALER_amount_divide (&tmp,
                       &tmp,
                       TALER_MERCHANT_UNIT_FRAC_BASE);
  if (0 >
      TALER_amount_add (line_total,
                        line_total,
                        &tmp))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Find the price of the given @a item in the specified
 * @a currency.
 *
 * @param currency currency to search price in
 * @param item item to check prices of
 * @return NULL if a suitable price was not found
 */
static const struct TALER_Amount *
find_item_price_in_currency (
  const char *currency,
  const struct InventoryTemplateItemContext *item)
{
  for (size_t j = 0; j < item->pd.price_array_length; j++)
  {
    if (0 == strcasecmp (item->pd.price_array[j].currency,
                         currency))
      return &item->pd.price_array[j];
  }
  return NULL;
}


/**
 * Compute totals for all currencies shared across selected products.
 *
 * @param[in,out] uc use context
 * @return #GNUNET_OK on success (including no price due to no items)
 *         #GNUNET_NO if we could not find a price in any accepted currency
 *                    for all selected products
 *         #GNUNET_SYSERR on arithmetic issues (internal error)
 */
static enum GNUNET_GenericReturnValue
compute_totals_per_currency (struct UseContext *uc)
{
  const struct InventoryTemplateItemContext *items
    = uc->parse_request.inventory.items;
  unsigned int items_len = uc->parse_request.inventory.items_len;

  if (0 == items_len)
    return GNUNET_OK;
  for (size_t i = 0; i < items[0].pd.price_array_length; i++)
  {
    const struct TALER_Amount *price
      = &items[0].pd.price_array[i];
    struct TALER_Amount zero;

    if (! TMH_test_exchange_configured_for_currency (price->currency))
      continue;
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_set_zero (price->currency,
                                          &zero));
    GNUNET_array_append (uc->compute_price.totals,
                         uc->compute_price.totals_len,
                         zero);
  }
  if (0 == uc->compute_price.totals_len)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "No currency supported by our configuration in which we have prices for first selected product!\n");
    return GNUNET_NO;
  }
  /* Loop through items, ensure each currency exists and sum totals. */
  for (unsigned int i = 0; i < items_len; i++)
  {
    const struct InventoryTemplateItemContext *item = &items[i];
    unsigned int c = 0;

    while (c < uc->compute_price.totals_len)
    {
      struct TALER_Amount *total = &uc->compute_price.totals[c];
      const struct TALER_Amount *unit_price;
      struct TALER_Amount line_total;

      unit_price = find_item_price_in_currency (total->currency,
                                                item);
      if (NULL == unit_price)
      {
        /* Drop the currency: we have no price in one of
           the selected products */
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Product `%s' has no price in %s: dropping currency\n",
                    item->product_id,
                    total->currency);
        *total = uc->compute_price.totals[--uc->compute_price.totals_len];
        continue;
      }
      if (GNUNET_OK !=
          compute_line_total (unit_price,
                              item->quantity_value,
                              item->quantity_frac,
                              &line_total))
      {
        GNUNET_break (0);
        return GNUNET_SYSERR;
      }
      if (0 >
          TALER_amount_add (total,
                            total,
                            &line_total))
      {
        GNUNET_break (0);
        return GNUNET_SYSERR;
      }
      c++;
    }
  }
  if (0 == uc->compute_price.totals_len)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "No currency available in which we have prices for all selected products!\n");
    GNUNET_free (uc->compute_price.totals);
  }
  return (0 == uc->compute_price.totals_len)
    ? GNUNET_NO
    : GNUNET_OK;
}


/**
 * Compute total for only the given @a currency.
 *
 * @param items_len length of @a items
 * @param items inventory items
 * @param currency currency to total
 * @param[out] total computed total
 * @return #GNUNET_OK on success
 *         #GNUNET_NO if we could not find a price in any accepted currency
 *                    for all selected products
 *         #GNUNET_SYSERR on arithmetic issues (internal error)
 */
static enum GNUNET_GenericReturnValue
compute_inventory_total (unsigned int items_len,
                         const struct InventoryTemplateItemContext *items,
                         const char *currency,
                         struct TALER_Amount *total)
{
  GNUNET_assert (NULL != currency);
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (currency,
                                        total));
  for (unsigned int i = 0; i < items_len; i++)
  {
    const struct InventoryTemplateItemContext *item = &items[i];
    const struct TALER_Amount *unit_price;
    struct TALER_Amount line_total;

    unit_price = find_item_price_in_currency (currency,
                                              item);
    if (NULL == unit_price)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "compute_inventory_total: no price in %s for product `%s'\n",
                  currency,
                  item->product_id);
      return GNUNET_NO;
    }
    if (GNUNET_OK !=
        compute_line_total (unit_price,
                            item->quantity_value,
                            item->quantity_frac,
                            &line_total))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "compute_inventory_total: line total failed for %s in %s\n",
                  item->product_id,
                  currency);
      return GNUNET_SYSERR;
    }
    if (0 >
        TALER_amount_add (total,
                          total,
                          &line_total))
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
  }
  return GNUNET_OK;
}


/**
 * Compute total price.
 *
 * @param[in,out] uc use context
 */
static void
handle_phase_compute_price (struct UseContext *uc)
{
  const char *primary_currency;
  enum GNUNET_GenericReturnValue ret;

  switch (uc->template_type)
  {
  case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    uc->compute_price.totals
      = GNUNET_new (struct TALER_Amount);
    uc->compute_price.totals_len
      = 1;
    if (uc->parse_request.no_amount)
    {
      GNUNET_assert (! uc->template_contract.no_amount);
      *uc->compute_price.totals
        = uc->template_contract.amount;
    }
    else
    {
      GNUNET_assert (uc->template_contract.no_amount);
      *uc->compute_price.totals
        = uc->parse_request.amount;
    }
    uc->phase++;
    return;
  case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    /* handled below */
    break;
  case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    {
      const struct TALER_MERCHANT_TemplateContractPaivana *tcp
        = &uc->template_contract.details.paivana;
      json_t *choices;

      choices = json_array ();
      GNUNET_assert (NULL != choices);
      for (size_t i = 0; i < tcp->choices_len; i++)
      {
        /* Make deep copy, we're going to MODIFY it! */
        struct TALER_MERCHANT_ContractChoice choice
          = tcp->choices[i];

        choice.no_tip = uc->parse_request.no_tip;
        if (! uc->parse_request.no_tip)
        {
          if (GNUNET_YES !=
              TALER_amount_cmp_currency (&choice.amount,
                                         &uc->parse_request.tip))
            continue; /* tip does not match choice currency */
          choice.tip = uc->parse_request.tip;
          if (0 >
              TALER_amount_add (&choice.amount,
                                &choice.amount,
                                &uc->parse_request.tip))
          {
            GNUNET_break (0);
            use_reply_with_error (uc,
                                  MHD_HTTP_INTERNAL_SERVER_ERROR,
                                  TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
                                  "tip");
            return;
          }
        }
        GNUNET_assert (0 ==
                       json_array_append_new (
                         choices,
                         TALER_MERCHANT_json_from_contract_choice (&choice,
                                                                   true)));
      }
      if (0 == json_array_size (choices))
      {
        GNUNET_break_op (0);
        json_decref (choices);
        use_reply_with_error (uc,
                              MHD_HTTP_CONFLICT,
                              TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
                              "tip");
        return;
      }
      uc->compute_price.choices = choices;
    }
    /* Note: we already did the tip and pricing
       fully here, so we skip these phases. */
    uc->phase = USE_PHASE_CREATE_ORDER;
    return;
  case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    GNUNET_assert (0);
  }
  primary_currency = uc->template_contract.currency;
  if (! uc->parse_request.no_amount)
    primary_currency = uc->parse_request.amount.currency;
  if (! uc->parse_request.no_tip)
    primary_currency = uc->parse_request.tip.currency;
  if (NULL == primary_currency)
  {
    ret = compute_totals_per_currency (uc);
  }
  else
  {
    uc->compute_price.totals
      = GNUNET_new (struct TALER_Amount);
    uc->compute_price.totals_len
      = 1;
    ret = compute_inventory_total (
      uc->parse_request.inventory.items_len,
      uc->parse_request.inventory.items,
      primary_currency,
      uc->compute_price.totals);
  }
  if (GNUNET_SYSERR == ret)
  {
    use_reply_with_error (
      uc,
      MHD_HTTP_INTERNAL_SERVER_ERROR,
      TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
      "calculation of currency totals failed");
    return;
  }
  if (GNUNET_NO == ret)
  {
    use_reply_with_error (uc,
                          MHD_HTTP_CONFLICT,
                          TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
                          NULL);
    return;
  }

  uc->phase++;
}


/* ***************** USE_PHASE_CHECK_TIP **************** */


/**
 * Check that tip specified is reasonable and add to total.
 *
 * @param[in,out] uc use context
 */
static void
handle_phase_check_tip (struct UseContext *uc)
{
  struct TALER_Amount *total_amount;

  if (uc->parse_request.no_tip)
  {
    uc->phase++;
    return;
  }
  if (0 == uc->compute_price.totals_len)
  {
    if (! TMH_test_exchange_configured_for_currency (
          uc->parse_request.tip.currency))
    {
      GNUNET_break_op (0);
      use_reply_with_error (uc,
                            MHD_HTTP_CONFLICT,
                            TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
                            "Tip currency is not supported by backend");
      return;
    }
    uc->compute_price.totals
      = GNUNET_new (struct TALER_Amount);
    uc->compute_price.totals_len
      = 1;
    *uc->compute_price.totals
      = uc->parse_request.tip;
    uc->phase++;
    return;
  }
  GNUNET_assert (1 == uc->compute_price.totals_len);
  total_amount = &uc->compute_price.totals[0];
  if (GNUNET_YES !=
      TALER_amount_cmp_currency (&uc->parse_request.tip,
                                 total_amount))
  {
    GNUNET_break_op (0);
    use_reply_with_error (uc,
                          MHD_HTTP_CONFLICT,
                          TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
                          uc->parse_request.tip.currency);
    return;
  }
  if (0 >
      TALER_amount_add (total_amount,
                        total_amount,
                        &uc->parse_request.tip))
  {
    GNUNET_break (0);
    use_reply_with_error (uc,
                          MHD_HTTP_INTERNAL_SERVER_ERROR,
                          TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
                          "tip");
    return;
  }
  uc->phase++;
}


/* ***************** USE_PHASE_CHECK_TOTAL **************** */

/**
 * Check that if the client specified a total,
 * it matches our own calculation.
 *
 * @param[in,out] uc use context
 */
static void
handle_phase_check_total (struct UseContext *uc)
{
  GNUNET_assert (1 <= uc->compute_price.totals_len);
  if (! uc->parse_request.no_amount)
  {
    GNUNET_assert (1 == uc->compute_price.totals_len);
    GNUNET_assert (GNUNET_YES ==
                   TALER_amount_cmp_currency (&uc->parse_request.amount,
                                              &uc->compute_price.totals[0]));
    if (0 !=
        TALER_amount_cmp (&uc->parse_request.amount,
                          &uc->compute_price.totals[0]))
    {
      GNUNET_break_op (0);
      use_reply_with_error (uc,
                            MHD_HTTP_CONFLICT,
                            TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
                            TALER_amount2s (&uc->compute_price.totals[0]));
      return;
    }
  }
  uc->phase++;
}


/* ***************** USE_PHASE_CREATE_ORDER **************** */


/**
 * Create order request for inventory templates.
 *
 * @param[in,out] uc use context
 */
static void
create_using_templates_inventory (struct UseContext *uc)
{
  json_t *inventory_products;
  json_t *choices;

  inventory_products = json_array ();
  GNUNET_assert (NULL != inventory_products);
  for (unsigned int i = 0;
       i < uc->parse_request.inventory.items_len;
       i++)
  {
    const struct InventoryTemplateItemContext *item =
      &uc->parse_request.inventory.items[i];

    GNUNET_assert (0 ==
                   json_array_append_new (
                     inventory_products,
                     GNUNET_JSON_PACK (
                       GNUNET_JSON_pack_string ("product_id",
                                                item->product_id),
                       GNUNET_JSON_pack_string ("unit_quantity",
                                                item->unit_quantity))));
  }
  choices = json_array ();
  GNUNET_assert (NULL != choices);
  for (unsigned int i = 0;
       i < uc->compute_price.totals_len;
       i++)
  {
    GNUNET_assert (0 ==
                   json_array_append_new (
                     choices,
                     GNUNET_JSON_PACK (
                       TALER_JSON_pack_amount ("amount",
                                               &uc->compute_price.totals[i]),
                       GNUNET_JSON_pack_allow_null (
                         TALER_JSON_pack_amount ("tip",
                                                 uc->parse_request.no_tip
                                                ? NULL
                                                : &uc->parse_request.tip))
                       )));
  }

  uc->ihc.request_body
    = GNUNET_JSON_PACK (
        GNUNET_JSON_pack_allow_null (
          GNUNET_JSON_pack_string ("otp_id",
                                   uc->lookup_template.etp.otp_id)),
        GNUNET_JSON_pack_array_steal ("inventory_products",
                                      inventory_products),
        GNUNET_JSON_pack_object_steal (
          "order",
          GNUNET_JSON_PACK (
            GNUNET_JSON_pack_uint64 ("version",
                                     1),
            GNUNET_JSON_pack_array_steal ("choices",
                                          choices),
            GNUNET_JSON_pack_string ("summary",
                                     NULL == uc->parse_request.summary
                                   ? uc->template_contract.summary
                                   : uc->parse_request.summary))));
}


/**
 * Create order request for fixed-order templates.
 *
 * @param[in,out] uc use context
 */
static void
create_using_templates_fixed (struct UseContext *uc)
{
  uc->ihc.request_body
    = GNUNET_JSON_PACK (
        GNUNET_JSON_pack_allow_null (
          GNUNET_JSON_pack_string ("otp_id",
                                   uc->lookup_template.etp.otp_id)),
        GNUNET_JSON_pack_object_steal (
          "order",
          GNUNET_JSON_PACK (
            TALER_JSON_pack_amount (
              "amount",
              &uc->compute_price.totals[0]),
            GNUNET_JSON_pack_allow_null (
              TALER_JSON_pack_amount ("tip",
                                      uc->parse_request.no_tip
                                      ? NULL
                                      : &uc->parse_request.tip)),
            GNUNET_JSON_pack_string (
              "summary",
              NULL == uc->parse_request.summary
            ? uc->template_contract.summary
            : uc->parse_request.summary))));
}


/**
 * Create order request for paivana templates.
 *
 * @param[in,out] uc use context
 */
static void
create_using_templates_paivana (struct UseContext *uc)
{
  uc->ihc.request_body
    = GNUNET_JSON_PACK (
        GNUNET_JSON_pack_string (
          "session_id",
          uc->parse_request.paivana.paivana_id),
        GNUNET_JSON_pack_object_steal (
          "order",
          GNUNET_JSON_PACK (
            GNUNET_JSON_pack_uint64 ("version",
                                     1),
            GNUNET_JSON_pack_array_incref ("choices",
                                           uc->compute_price.choices),
            GNUNET_JSON_pack_string (
              "summary",
              NULL == uc->parse_request.summary
              ? uc->template_contract.summary
              : uc->parse_request.summary),
            GNUNET_JSON_pack_string ("fulfillment_url",
                                     uc->parse_request.paivana.website))));
}


static void
handle_phase_create_order (struct UseContext *uc)
{
  GNUNET_assert (NULL == uc->ihc.request_body);
  switch (uc->template_type)
  {
  case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    create_using_templates_fixed (uc);
    break;
  case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    create_using_templates_paivana (uc);
    break;
  case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    create_using_templates_inventory (uc);
    break;
  case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    GNUNET_assert (0);
  }
  uc->phase++;
}


/* ***************** Main handler **************** */

MHD_RESULT
TMH_post_using_templates_ID (
  const struct TMH_RequestHandler *rh,
  struct MHD_Connection *connection,
  struct TMH_HandlerContext *hc)
{
  struct UseContext *uc = hc->ctx;

  (void) rh;
  if (NULL == uc)
  {
    uc = GNUNET_new (struct UseContext);
    uc->hc = hc;
    hc->ctx = uc;
    hc->cc = &cleanup_use_context;
    uc->ihc.instance = hc->instance;
    uc->phase = USE_PHASE_PARSE_REQUEST;
    uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
  }

  while (1)
  {
    switch (uc->phase)
    {
    case USE_PHASE_PARSE_REQUEST:
      handle_phase_parse_request (uc);
      break;
    case USE_PHASE_LOOKUP_TEMPLATE:
      handle_phase_lookup_template (uc);
      break;
    case USE_PHASE_PARSE_TEMPLATE:
      handle_phase_template_contract (uc);
      break;
    case USE_PHASE_DB_FETCH:
      handle_phase_db_fetch (uc);
      break;
    case USE_PHASE_VERIFY:
      handle_phase_verify (uc);
      break;
    case USE_PHASE_COMPUTE_PRICE:
      handle_phase_compute_price (uc);
      break;
    case USE_PHASE_CHECK_TIP:
      handle_phase_check_tip (uc);
      break;
    case USE_PHASE_CHECK_TOTAL:
      handle_phase_check_total (uc);
      break;
    case USE_PHASE_CREATE_ORDER:
      handle_phase_create_order (uc);
      break;
    case USE_PHASE_SUBMIT_ORDER:
      return TMH_private_post_orders (
        NULL,    /* not even used */
        connection,
        &uc->ihc);
    case USE_PHASE_FINISHED_MHD_YES:
      return MHD_YES;
    case USE_PHASE_FINISHED_MHD_NO:
      return MHD_NO;
    }
  }
}
