/*
      This file is part of GNUnet
      Copyright (C) 2008--2013 GNUnet e.V.

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

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

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

/**
 * @file testbed/testbed_api_services.c
 * @brief convenience functions for accessing services
 * @author Christian Grothoff
 */
#include "platform.h"
#include "testbed_api.h"
#include "testbed_api_peers.h"
#include "testbed_api_operations.h"


/**
 * States for Service connect operations
 */
enum State
{
  /**
   * Initial state
   */
  INIT,

  /**
   * The configuration request has been sent
   */
  CFG_REQUEST_QUEUED,

  /**
   * connected to service
   */
  SERVICE_CONNECTED
};


/**
 * Data accessed during service connections
 */
struct ServiceConnectData
{
  /**
   * helper function callback to establish the connection
   */
  GNUNET_TESTBED_ConnectAdapter ca;

  /**
   * helper function callback to close the connection
   */
  GNUNET_TESTBED_DisconnectAdapter da;

  /**
   * Closure to the above callbacks
   */
  void *cada_cls;

  /**
   * Service name
   */
  char *service_name;

  /**
   * Closure for operation event
   */
  void *op_cls;

  /**
   * The operation which created this structure
   */
  struct GNUNET_TESTBED_Operation *operation;

  /**
   * The operation context from GNUNET_TESTBED_forward_operation_msg_()
   */
  struct OperationContext *opc;

  /**
   * The peer handle
   */
  struct GNUNET_TESTBED_Peer *peer;

  /**
   * The acquired configuration of the peer
   */
  struct GNUNET_CONFIGURATION_Handle *cfg;

  /**
   * The op_result pointer from ConnectAdapter
   */
  void *op_result;

  /**
   * The operation completion callback
   */
  GNUNET_TESTBED_ServiceConnectCompletionCallback cb;

  /**
   * The closure for operation completion callback
   */
  void *cb_cls;

  /**
   * State information
   */
  enum State state;

};


/**
 * Type of a function to call when we receive a message
 * from the service.
 *
 * @param cls ServiceConnectData
 * @param msg message received, NULL on timeout or fatal error
 */
static void
configuration_receiver (void *cls, const struct GNUNET_MessageHeader *msg)
{
  struct ServiceConnectData *data = cls;
  struct GNUNET_TESTBED_Controller *c;
  const char *emsg;
  struct GNUNET_TESTBED_EventInformation info;
  uint16_t mtype;

  c = data->peer->controller;
  mtype = ntohs (msg->type);
  emsg = NULL;
  info.type = GNUNET_TESTBED_ET_OPERATION_FINISHED;
  info.op = data->operation;
  info.op_cls = data->op_cls;
  if (GNUNET_MESSAGE_TYPE_TESTBED_OPERATION_FAIL_EVENT == mtype)
  {
    emsg =
        GNUNET_TESTBED_parse_error_string_ ((const struct
                                             GNUNET_TESTBED_OperationFailureEventMessage
                                             *) msg);
    if (NULL == emsg)
      emsg = "Unknown error";
    info.details.operation_finished.emsg = emsg;
    info.details.operation_finished.generic = NULL;
    goto call_cb;
  }
  data->cfg = GNUNET_TESTBED_extract_config_ (msg);
  GNUNET_assert (NULL == data->op_result);
  data->op_result = data->ca (data->cada_cls, data->cfg);
  info.details.operation_finished.emsg = NULL;
  info.details.operation_finished.generic = data->op_result;
  data->state = SERVICE_CONNECTED;

call_cb:
  if ((0 != (GNUNET_TESTBED_ET_OPERATION_FINISHED & c->event_mask)) &&
      (NULL != c->cc))
    c->cc (c->cc_cls, &info);
  if (NULL != data->cb)
    data->cb (data->cb_cls, data->operation, data->op_result, emsg);
}


/**
 * Function called when a service connect operation is ready
 *
 * @param cls the closure from GNUNET_TESTBED_operation_create_()
 */
static void
opstart_service_connect (void *cls)
{
  struct ServiceConnectData *data = cls;
  struct GNUNET_TESTBED_PeerGetConfigurationMessage *msg;
  struct GNUNET_TESTBED_Controller *c;
  uint64_t op_id;

  GNUNET_assert (NULL != data);
  GNUNET_assert (NULL != data->peer);
  c = data->peer->controller;
  op_id = GNUNET_TESTBED_get_next_op_id (c);
  msg =
      GNUNET_TESTBED_generate_peergetconfig_msg_ (data->peer->unique_id, op_id);
  data->opc =
      GNUNET_TESTBED_forward_operation_msg_ (c, op_id, &msg->header,
                                             &configuration_receiver, data);
  GNUNET_free (msg);
  data->state = CFG_REQUEST_QUEUED;
}


/**
 * Callback which will be called when service connect type operation is
 * released
 *
 * @param cls the closure from GNUNET_TESTBED_operation_create_()
 */
static void
oprelease_service_connect (void *cls)
{
  struct ServiceConnectData *data = cls;

  switch (data->state)
  {
  case INIT:
    break;
  case CFG_REQUEST_QUEUED:
    GNUNET_assert (NULL != data->opc);
    GNUNET_TESTBED_forward_operation_msg_cancel_ (data->opc);
    break;
  case SERVICE_CONNECTED:
    GNUNET_assert (NULL != data->cfg);
    GNUNET_CONFIGURATION_destroy (data->cfg);
    if (NULL != data->da)
      data->da (data->cada_cls, data->op_result);
    break;
  }
  GNUNET_free (data);
}


/**
 * Connect to a service offered by the given peer.  Will ensure that
 * the request is queued to not overwhelm our ability to create and
 * maintain connections with other systems.  The actual service
 * handle is then returned via the 'op_result' member in the event
 * callback.  The 'ca' callback is used to create the connection
 * when the time is right; the 'da' callback will be used to
 * destroy the connection (upon 'GNUNET_TESTBED_operation_done').
 * 'GNUNET_TESTBED_operation_done' can be used to abort this
 * operation until the event callback has been called.
 *
 * @param op_cls closure to pass in operation event
 * @param peer peer that runs the service
 * @param service_name name of the service to connect to
 * @param cb the callback to call when this operation finishes
 * @param cb_cls closure for the above callback
 * @param ca helper function to establish the connection
 * @param da helper function to close the connection
 * @param cada_cls closure for ca and da
 * @return handle for the operation
 */
struct GNUNET_TESTBED_Operation *
GNUNET_TESTBED_service_connect (void *op_cls, struct GNUNET_TESTBED_Peer *peer,
                                const char *service_name,
                                GNUNET_TESTBED_ServiceConnectCompletionCallback
                                cb, void *cb_cls,
                                GNUNET_TESTBED_ConnectAdapter ca,
                                GNUNET_TESTBED_DisconnectAdapter da,
                                void *cada_cls)
{
  struct ServiceConnectData *data;

  data = GNUNET_new (struct ServiceConnectData);
  data->ca = ca;
  data->da = da;
  data->cada_cls = cada_cls;
  data->op_cls = op_cls;
  data->peer = peer;
  data->state = INIT;
  data->cb = cb;
  data->cb_cls = cb_cls;
  data->operation =
      GNUNET_TESTBED_operation_create_ (data, &opstart_service_connect,
                                        &oprelease_service_connect);
  GNUNET_TESTBED_operation_queue_insert_ (peer->
                                          controller->opq_parallel_service_connections,
                                          data->operation);
  GNUNET_TESTBED_operation_queue_insert_ (peer->
                                          controller->opq_parallel_operations,
                                          data->operation);
  GNUNET_TESTBED_operation_begin_wait_ (data->operation);
  return data->operation;
}

/* end of testbed_api_services.c */
