/*
  This file is part of Taler.
  Copyright (C) 2009, 2010, 2011, 2016 GNUnet e.V.
  Copyright (C) 2018 Taler Systems SA

  Taler is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as
  published by the Free Software Foundation; either version 3,
  or (at your option) any later version.

  Taler is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public
  License along with Taler; see the file COPYING.  If not,
  write to the Free Software Foundation, Inc., 51 Franklin
  Street, Fifth Floor, Boston, MA 02110-1301, USA.  */

/**
 * @file twister_api.c
 * @brief api to control twister proxy
 * @author Christian Grothoff
 * @author Marcello Stanisci
 */
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
#include "taler_twister_service.h"
#include "twister.h"

#define LOG(kind,...) \
  GNUNET_log_from (kind, "twister-api",__VA_ARGS__)


/**
 * Opaque handle for asynchronous operation.
 */
struct TALER_TWISTER_Operation
{

  /**
   * Pointer to next operation.
   */
  struct TALER_TWISTER_Operation *next;

  /**
   * Pointer to previous operation.
   */
  struct TALER_TWISTER_Operation *prev;

  /**
   * Pointer to main handle (= connection to the twister).
   */
  struct TALER_TWISTER_Handle *h;

  /**
   * Callback for this operation.
   */
  GNUNET_SCHEDULER_TaskCallback cb;

  /**
   * Closure to pass the callback above.
   */
  void *cb_cls;
};


/**
 * Handle for talking with the Twister service.
 */
struct TALER_TWISTER_Handle
{
  /**
   * Configuration to use.
   */
  const struct GNUNET_CONFIGURATION_Handle *cfg;

  /**
   * Message queue (if available).
   */
  struct GNUNET_MQ_Handle *mq;

  /**
   * First pending operation.
   */
  struct TALER_TWISTER_Operation *op_head;

  /**
   * Last pending operation.
   */
  struct TALER_TWISTER_Operation *op_tail;
};


/**
 * Generic error handler, called with the appropriate
 * error code and the same closure specified at the creation of
 * the message queue.
 * Not every message queue implementation supports an error handler.
 *
 * @param cls closure with the `struct TALER_TWISTER_Handle *`
 * @param error error code
 */
static void
mq_error_handler (void *cls,
                  enum GNUNET_MQ_Error error)
{
  struct TALER_TWISTER_Handle *h = cls;

  GNUNET_MQ_destroy (h->mq);
  h->mq = NULL;
  /* FIXME: maybe give test case nicer way to shut down... */
  GNUNET_assert (0);
}


/**
 * Type of a function to call when we receive a message
 * from the service.
 *
 * @param cls closure
 * @param client_msg message received
 */
static void
handle_acknowledgement (void *cls,
                        const struct GNUNET_MessageHeader *ack)
{
  struct TALER_TWISTER_Handle *h = cls;
  struct TALER_TWISTER_Operation *op;

  op = h->op_head;
  GNUNET_assert (NULL != op); /* twister very wrong, fail test */
  GNUNET_CONTAINER_DLL_remove (h->op_head,
                               h->op_tail,
                               op);
  if (NULL != op->cb)
    op->cb (op->cb_cls);
  GNUNET_free (op);
}


/**
 * Connect to the twister service.
 *
 * @param cfg the configuration to use
 * @return handle to use in #TALER_TWISTER_disconnect to disconnect
 */
struct TALER_TWISTER_Handle *
TALER_TWISTER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  struct TALER_TWISTER_Handle *h;

  h = GNUNET_new (struct TALER_TWISTER_Handle);
  h->cfg = cfg;
  {
    struct GNUNET_MQ_MessageHandler handlers[] = {
      GNUNET_MQ_hd_fixed_size
        (acknowledgement,
        TWISTER_MESSAGE_TYPE_ACKNOWLEDGEMENT,
        struct GNUNET_MessageHeader,
        h),
      GNUNET_MQ_handler_end ()
    };

    LOG (GNUNET_ERROR_TYPE_DEBUG,
         "Connecting to twister service.\n");
    h->mq = GNUNET_CLIENT_connect (h->cfg,
                                   "twister",
                                   handlers,
                                   &mq_error_handler,
                                   h);
  }
  if (NULL == h->mq)
  {
    LOG (GNUNET_ERROR_TYPE_ERROR,
         "Could not connect to twister service\n");
    GNUNET_free (h);
    return NULL;
  }
  return h;
}


/**
 * Disconnect from twister service.
 *
 * @param h handle to destroy
 */
void
TALER_TWISTER_disconnect (struct TALER_TWISTER_Handle *h)
{
  struct TALER_TWISTER_Operation *op;

  while (NULL != (op = h->op_head))
  {
    GNUNET_CONTAINER_DLL_remove (h->op_head,
                                 h->op_tail,
                                 op);
    GNUNET_free (op);
  }
  if (NULL != h->mq)
  {
    GNUNET_MQ_destroy (h->mq);
    h->mq = NULL;
  }
  GNUNET_free (h);
}


/**
 * Abort operation.  Twister behavior may then include the
 * changes requested by the operation, or not!  Must be called
 * before the operation callback was invoked.
 *
 * @param op operation to cancel,
 *        operation's callback will not be called
 */
void
TALER_TWISTER_cancel (struct TALER_TWISTER_Operation *op)
{
  /* Just don't call the callback anymore */
  op->cb = NULL;
}


/**
 * Randomly truncate the request.
 *
 * @param h twister instance to control
 * @param cb function to call once twister is ready; typically
 *        a acknowledge function.
 * @param cb_cls closure for @a cb
 * @return operation handle (to possibly abort)
 */
struct TALER_TWISTER_Operation *
TALER_TWISTER_malform_upload (struct TALER_TWISTER_Handle *h,
                              GNUNET_SCHEDULER_TaskCallback cb,
                              void *cb_cls)
{
  struct TALER_TWISTER_Operation *op;
  struct GNUNET_MQ_Envelope *env;
  struct TWISTER_Malform *src;

  op = GNUNET_new (struct TALER_TWISTER_Operation);
  op->h = h;
  op->cb = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head,
                                    h->op_tail,
                                    op);
  /* Prepare *env*elope. */
  env = GNUNET_MQ_msg
          (src, TWISTER_MESSAGE_TYPE_MALFORM_UPLOAD);
  /* Send message. */
  GNUNET_MQ_send (h->mq, env);
  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Batching a (upload) body malformation\n");
  return op;
}


/**
 * Randomly truncate the response.
 *
 * @param h twister instance to control
 * @param cb function to call once twister has processed this
 *           request.
 * @param cb_cls closure for @a cb
 * @return operation handle (to possibly abort)
 */
struct TALER_TWISTER_Operation *
TALER_TWISTER_malform (struct TALER_TWISTER_Handle *h,
                       GNUNET_SCHEDULER_TaskCallback cb,
                       void *cb_cls)
{
  struct TALER_TWISTER_Operation *op;
  struct GNUNET_MQ_Envelope *env;
  struct TWISTER_Malform *src;

  op = GNUNET_new (struct TALER_TWISTER_Operation);
  op->h = h;
  op->cb = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head,
                                    h->op_tail,
                                    op);
  /* Prepare *env*elope. */
  env = GNUNET_MQ_msg
          (src, TWISTER_MESSAGE_TYPE_MALFORM);
  /* Send message. */
  GNUNET_MQ_send (h->mq, env);
  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Batching a body malformation\n");
  return op;
}


/**
 * Instruct the twister to flip a character into
 * the string JSON field that belongs to the object
 * being returned to the HTTP client.
 *
 * @param h twister instance to control
 * @param path object-like notation to point the string
 *        object where we seek a character to flip.
 * @param cb function to call once twister has processed this
 *        request
 * @param cb_cls closure for @a cb
 * @return operation handle (to possibly abort)
 */
struct TALER_TWISTER_Operation *
TALER_TWISTER_flip_download (struct TALER_TWISTER_Handle *h,
                             const char *path,
                             GNUNET_SCHEDULER_TaskCallback cb,
                             void *cb_cls)
{
  struct TALER_TWISTER_Operation *op;
  struct GNUNET_MQ_Envelope *env;
  struct TWISTER_FlipPath *src; // FIXME 'src' right name?
  uint16_t stralloc;

  stralloc = strlen (path) + 1;
  if (stralloc + sizeof (struct TWISTER_FlipPath) > UINT16_MAX)
  {
    GNUNET_break (0);
    return NULL;
  }
  op = GNUNET_new (struct TALER_TWISTER_Operation);
  op->h = h;
  op->cb = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head,
                                    h->op_tail,
                                    op);
  env = GNUNET_MQ_msg_extra (src,
                             stralloc,
                             TWISTER_MESSAGE_TYPE_FLIP_PATH_DL);
  GNUNET_assert
    (stralloc == GNUNET_STRINGS_buffer_fill ((char *) &src[1],
                                             stralloc,
                                             1,
                                             path));
  GNUNET_MQ_send (h->mq, env);
  return op;
}


/**
 * Instruct the twister to flip a character into
 * the string JSON field that belongs to the object
 * being uploaded to the proxied service.
 *
 * @param h twister instance to control
 * @param path object-like notation to point the string
 *        object where we seek a character to flip.
 * @param cb function to call once twister has batched this
 *        request
 * @param cb_cls closure for @a cb
 * @return operation handle (to possibly abort)
 */
struct TALER_TWISTER_Operation *
TALER_TWISTER_flip_upload (struct TALER_TWISTER_Handle *h,
                           const char *path,
                           GNUNET_SCHEDULER_TaskCallback cb,
                           void *cb_cls)
{
  struct TALER_TWISTER_Operation *op;
  struct GNUNET_MQ_Envelope *env;
  struct TWISTER_FlipPath *src; // FIXME 'src' right name?
  uint16_t stralloc = strlen (path) + 1;

  if (stralloc + sizeof (struct TWISTER_FlipPath) > UINT16_MAX)
  {
    GNUNET_break (0);
    return NULL;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Will UL-flip: %s\n",
              path);

  op = GNUNET_new (struct TALER_TWISTER_Operation);
  op->h = h;
  op->cb = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head,
                                    h->op_tail,
                                    op);

  env = GNUNET_MQ_msg_extra (src,
                             stralloc,
                             TWISTER_MESSAGE_TYPE_FLIP_PATH_UL);
  GNUNET_assert
    (stralloc == GNUNET_STRINGS_buffer_fill ((char *) &src[1],
                                             stralloc,
                                             1,
                                             path));
  GNUNET_MQ_send (h->mq, env);
  return op;
}


/**
 * Delete the object pointed to by @a path.  Note, this
 * object belongs to the JSON response object.
 *
 * @param h twister instance to control
 * @param path object-like notation to point the object to be
          deleted.  E.g., the path "f1.f2.0" will delete the object
          {"f1": {"f2": [{"to be": "deleted"}]}}.
 * @param cb function to call once twister is ready
 * @param cb_cls closure for @a cb
 * @return operation handle (to possibly abort)
 */
struct TALER_TWISTER_Operation *
TALER_TWISTER_delete_path (struct TALER_TWISTER_Handle *h,
                           const char *path,
                           GNUNET_SCHEDULER_TaskCallback cb,
                           void *cb_cls)
{
  struct TALER_TWISTER_Operation *op;
  struct GNUNET_MQ_Envelope *env;
  struct TWISTER_DeletePath *src; // FIXME 'src' right name?
  uint16_t stralloc;

  stralloc = strlen (path) + 1;
  if (stralloc + sizeof (struct TWISTER_DeletePath) > UINT16_MAX)
  {
    GNUNET_break (0);
    return NULL;
  }
  op = GNUNET_new (struct TALER_TWISTER_Operation);
  op->h = h;
  op->cb = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head,
                                    h->op_tail,
                                    op);
  env = GNUNET_MQ_msg_extra (src,
                             stralloc,
                             TWISTER_MESSAGE_TYPE_DELETE_PATH);
  GNUNET_assert
    (stralloc == GNUNET_STRINGS_buffer_fill ((char *) &src[1],
                                             stralloc,
                                             1,
                                             path));
  GNUNET_MQ_send (h->mq, env);
  return op;
}


/**
 * Change the response field pointed by @a modify_path with
 * @a modify_value.
 *
 * @param h twister instance to control
 * @param path object-like notation path to the object to modify
 * @param value value to use for @a modify_path
 * @param cb callback to call once twister gets this instruction.
 * @param cb_cls closure for @a cb_callback
 *
 * @return operation handle.
 */
struct TALER_TWISTER_Operation *
TALER_TWISTER_modify_path_dl (struct TALER_TWISTER_Handle *h,
                              const char *path,
                              const char *value,
                              GNUNET_SCHEDULER_TaskCallback cb,
                              void *cb_cls)
{
  struct TALER_TWISTER_Operation *op;
  struct GNUNET_MQ_Envelope *env;
  struct TWISTER_ModifyPath *src;
  uint16_t stralloc;

  stralloc = strlen (path) + strlen (value) + 2;
  if (sizeof (*src) + stralloc > UINT16_MAX)
  {
    GNUNET_break (0);
    return NULL;
  }
  op = GNUNET_new (struct TALER_TWISTER_Operation);
  op->h = h;
  op->cb = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head,
                                    h->op_tail,
                                    op);
  env = GNUNET_MQ_msg_extra (src,
                             stralloc,
                             TWISTER_MESSAGE_TYPE_MODIFY_PATH_DL);

  GNUNET_assert (stralloc ==
                 GNUNET_STRINGS_buffer_fill ((char *) &src[1],
                                             stralloc,
                                             2,
                                             path,
                                             value));
  GNUNET_MQ_send (h->mq, env);
  return op;
}


/**
 * Change the JSON field pointed by @a path to the new @a value.
 * It only applies to upload objects.
 *
 * @param h twister instance to control
 * @param path object-like notation path to the object to modify
 * @param value value to use for @a modify_path
 * @param cb callback to call once twister gets this instruction.
 * @param cb_cls closure for @a cb_callback
 *
 * @return operation handle.
 */
struct TALER_TWISTER_Operation *
TALER_TWISTER_modify_path_ul (struct TALER_TWISTER_Handle *h,
                              const char *path,
                              const char *value,
                              GNUNET_SCHEDULER_TaskCallback cb,
                              void *cb_cls)
{
  struct TALER_TWISTER_Operation *op;
  struct GNUNET_MQ_Envelope *env;
  struct TWISTER_ModifyPath *src;
  uint16_t stralloc;

  stralloc = strlen (path) + strlen (value) + 2;
  if (sizeof (*src) + stralloc > UINT16_MAX)
  {
    GNUNET_break (0);
    return NULL;
  }
  op = GNUNET_new (struct TALER_TWISTER_Operation);
  op->h = h;
  op->cb = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head,
                                    h->op_tail,
                                    op);
  env = GNUNET_MQ_msg_extra (src,
                             stralloc,
                             TWISTER_MESSAGE_TYPE_MODIFY_PATH_UL);
  GNUNET_assert (stralloc ==
                 GNUNET_STRINGS_buffer_fill ((char *) &src[1],
                                             stralloc,
                                             2,
                                             path,
                                             value));
  GNUNET_MQ_send (h->mq, env);
  return op;
}


/**
 * Change the HTTP response header of @a header to @a value.
 *
 * @param h twister instance to control
 * @param header the HTTP response header to modify
 * @param value value to use for @a header
 * @param cb callback to call once twister gets this instruction.
 * @param cb_cls closure for @a cb_callback
 *
 * @return operation handle.
 */
struct TALER_TWISTER_Operation *
TALER_TWISTER_modify_header_dl (struct TALER_TWISTER_Handle *h,
                                const char *header,
                                const char *value,
                                GNUNET_SCHEDULER_TaskCallback cb,
                                void *cb_cls)
{
  struct TALER_TWISTER_Operation *op;
  struct GNUNET_MQ_Envelope *env;
  struct TWISTER_ModifyPath *src;
  uint16_t stralloc;

  if ( (0 == strcasecmp (header,
                         MHD_HTTP_HEADER_CONNECTION)) &&
       (0 == strcasecmp (value,
                         "Keep-Alive")) )
  {
    GNUNET_break (0);
    return NULL;
  }
  if (0 == strcasecmp (header,
                       MHD_HTTP_HEADER_CONTENT_LENGTH))
  {
    GNUNET_break (0);
    return NULL;
  }
  stralloc = strlen (header) + strlen (value) + 2;
  if (sizeof (*src) + stralloc > UINT16_MAX)
  {
    GNUNET_break (0);
    return NULL;
  }
  op = GNUNET_new (struct TALER_TWISTER_Operation);
  op->h = h;
  op->cb = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head,
                                    h->op_tail,
                                    op);
  env = GNUNET_MQ_msg_extra (src,
                             stralloc,
                             TWISTER_MESSAGE_TYPE_MODIFY_HEADER_DL);

  GNUNET_assert (stralloc ==
                 GNUNET_STRINGS_buffer_fill ((char *) &src[1],
                                             stralloc,
                                             2,
                                             header,
                                             value));
  GNUNET_MQ_send (h->mq, env);
  return op;
}


/**
 * Change the next response code to @a new_rc.
 *
 * @param h twister instance to control
 * @param new_rc response code to return from the next response
 * @param cb function to call once twister is ready
 * @param cb_cls closure for @a cb
 * @return operation handle (to possibly abort)
 */
struct TALER_TWISTER_Operation *
TALER_TWISTER_change_response_code (struct TALER_TWISTER_Handle *h,
                                    unsigned int new_rc,
                                    GNUNET_SCHEDULER_TaskCallback cb,
                                    void *cb_cls)
{
  struct TALER_TWISTER_Operation *op;
  struct GNUNET_MQ_Envelope *env;
  struct TWISTER_SetResponseCode *src;

  op = GNUNET_new (struct TALER_TWISTER_Operation);
  op->h = h;
  op->cb = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head,
                                    h->op_tail,
                                    op);
  /* Prepare *env*elope. */
  env = GNUNET_MQ_msg
          (src, TWISTER_MESSAGE_TYPE_SET_RESPONSE_CODE);
  /* Put data into the envelope. */
  src->response_code = htonl ((uint32_t) new_rc);
  /* Send message. */
  GNUNET_MQ_send (h->mq, env);
  return op;
}


/* end of twister_api.c */
