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

     GNUnet 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 of the License,
     or (at your option) any later version.

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */

/**
 * @file nse/nse_api.c
 * @brief api to get information from the network size estimation service
 * @author Nathan Evans
 */
#include "platform.h"
#include "gnunet_constants.h"
#include "gnunet_arm_service.h"
#include "gnunet_protocols.h"
#include "gnunet_util_lib.h"
#include "gnunet_nse_service.h"
#include "nse.h"

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

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

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

  /**
   * Task doing exponential back-off trying to reconnect.
   */
  struct GNUNET_SCHEDULER_Task *reconnect_task;

  /**
   * Time for next connect retry.
   */
  struct GNUNET_TIME_Relative reconnect_delay;

  /**
   * Callback function to call when message is received.
   */
  GNUNET_NSE_Callback recv_cb;

  /**
   * Closure to pass to @e recv_cb callback.
   */
  void *recv_cb_cls;
};


/**
 * Try again to connect to network size estimation service.
 *
 * @param cls closure with the `struct GNUNET_NSE_Handle *`
 */
static void
reconnect (void *cls);


/**
 * 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 GNUNET_NSE_Handle *`
 * @param error error code
 */
static void
mq_error_handler (void *cls, enum GNUNET_MQ_Error error)
{
  struct GNUNET_NSE_Handle *h = cls;

  (void) error;
  GNUNET_MQ_destroy (h->mq);
  h->mq = NULL;
  h->reconnect_task =
    GNUNET_SCHEDULER_add_delayed (h->reconnect_delay, &reconnect, h);
  h->reconnect_delay = GNUNET_TIME_STD_BACKOFF (h->reconnect_delay);
}


/**
 * 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_estimate (void *cls, const struct GNUNET_NSE_ClientMessage *client_msg)
{
  struct GNUNET_NSE_Handle *h = cls;

  h->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
  h->recv_cb (h->recv_cb_cls,
              GNUNET_TIME_absolute_ntoh (client_msg->timestamp),
              GNUNET_ntoh_double (client_msg->size_estimate),
              GNUNET_ntoh_double (client_msg->std_deviation));
}


/**
 * Try again to connect to network size estimation service.
 *
 * @param cls the `struct GNUNET_NSE_Handle *`
 */
static void
reconnect (void *cls)
{
  struct GNUNET_NSE_Handle *h = cls;
  struct GNUNET_MQ_MessageHandler handlers[] =
  { GNUNET_MQ_hd_fixed_size (estimate,
                             GNUNET_MESSAGE_TYPE_NSE_ESTIMATE,
                             struct GNUNET_NSE_ClientMessage,
                             h),
    GNUNET_MQ_handler_end () };
  struct GNUNET_MessageHeader *msg;
  struct GNUNET_MQ_Envelope *env;

  h->reconnect_task = NULL;
  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Connecting to network size estimation service.\n");
  GNUNET_assert (NULL == h->mq);
  h->mq = GNUNET_CLIENT_connect (h->cfg, "nse", handlers, &mq_error_handler, h);
  if (NULL == h->mq)
    return;
  env = GNUNET_MQ_msg (msg, GNUNET_MESSAGE_TYPE_NSE_START);
  GNUNET_MQ_send (h->mq, env);
}


/**
 * Connect to the network size estimation service.
 *
 * @param cfg the configuration to use
 * @param func function to call with network size estimate
 * @param func_cls closure to pass to @a func
 * @return handle to use
 */
struct GNUNET_NSE_Handle *
GNUNET_NSE_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
                    GNUNET_NSE_Callback func,
                    void *func_cls)
{
  struct GNUNET_NSE_Handle *h;

  GNUNET_assert (NULL != func);
  h = GNUNET_new (struct GNUNET_NSE_Handle);
  h->cfg = cfg;
  h->recv_cb = func;
  h->recv_cb_cls = func_cls;
  h->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
  reconnect (h);
  if (NULL == h->mq)
  {
    GNUNET_free (h);
    return NULL;
  }
  return h;
}


/**
 * Disconnect from network size estimation service
 *
 * @param h handle to destroy
 */
void
GNUNET_NSE_disconnect (struct GNUNET_NSE_Handle *h)
{
  if (NULL != h->reconnect_task)
  {
    GNUNET_SCHEDULER_cancel (h->reconnect_task);
    h->reconnect_task = NULL;
  }
  if (NULL != h->mq)
  {
    GNUNET_MQ_destroy (h->mq);
    h->mq = NULL;
  }
  GNUNET_free (h);
}


/* end of nse_api.c */
