/**
 * Main run function.
 *
 * @param cls NULL
 * @param args arguments passed to GNUNET_PROGRAM_run
 * @param cfgfile the path to configuration file
 * @param cfg the configuration file handle
 */
static void
run (void *cls, char *const *args, const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *config)
{
  host = GNUNET_TESTBED_host_create (NULL, NULL, 0);
  GNUNET_assert (NULL != host);
  if (GNUNET_YES != GNUNET_TESTBED_is_host_habitable (host, config))
  {
    GNUNET_TESTBED_host_destroy (host);
    host = NULL;
    (void) PRINTF ("%s",
                   "Unable to run the test as this system is not configured "
                   "to use password less SSH logins to localhost.\n"
                   "Marking test as successful\n");
    result = SUCCESS;
    return;
  }
  cfg = GNUNET_CONFIGURATION_dup (config);
  cp = GNUNET_TESTBED_controller_start ("127.0.0.1", host, cfg, status_cb,
                                        NULL);
  abort_task =
      GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
                                    (GNUNET_TIME_UNIT_MINUTES, 5), &do_abort,
                                    NULL);
}
/**
 * Main function that will be run by the scheduler.
 *
 * @param cls closure
 * @param args remaining command-line arguments
 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
 * @param config configuration
 */
static void
run (void *cls, char *const *args, const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *config)
{
  unsigned int nhost;

  if (NULL == args[0])
  {
    fprintf (stderr, _("No hosts-file specified on command line\n"));
    return;
  }
  if (0 == num_peers)
  {
    result = GNUNET_OK;
    return;
  }
  num_hosts = GNUNET_TESTBED_hosts_load_from_file (args[0], &hosts);
  if (0 == num_hosts)
  {
    fprintf (stderr, _("No hosts loaded. Need at least one host\n"));
    return;
  }
  for (nhost = 0; nhost < num_hosts; nhost++)
  {
    if (GNUNET_YES != GNUNET_TESTBED_is_host_habitable (hosts[nhost], config))
    {
      fprintf (stderr, _("Host %s cannot start testbed\n"),
	       GNUNET_TESTBED_host_get_hostname_ (hosts[nhost]));
      break;
    }
  }
  if (num_hosts != nhost)
  {
    fprintf (stderr, _("Exiting\n"));
    shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
    return;
  }
  cfg = GNUNET_CONFIGURATION_dup (config);
  mc_proc = 
      GNUNET_TESTBED_controller_start (GNUNET_TESTBED_host_get_hostname_ 
                                       (hosts[0]),
                                       hosts[0],
                                       cfg,
                                       status_cb,
                                       NULL);
  abort_task =
      GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
                                    (GNUNET_TIME_UNIT_SECONDS, 5), &do_abort,
                                    NULL);
}
/**
 * Callbacks of this type are called by GNUNET_TESTBED_is_host_habitable to
 * inform whether the given host is habitable or not. The Handle returned by
 * GNUNET_TESTBED_is_host_habitable() is invalid after this callback is called
 *
 * @param cls NULL
 * @param host the host whose status is being reported; will be NULL if the host
 *          given to GNUNET_TESTBED_is_host_habitable() is NULL
 * @param status GNUNET_YES if it is habitable; GNUNET_NO if not
 */
static void
host_habitable_cb (void *cls, const struct GNUNET_TESTBED_Host *_host,
                   int status)
{
  hc_handle = NULL;
  if (GNUNET_NO == status)
  {
    (void) PRINTF ("%s",
                   "Unable to run the test as this system is not configured "
                   "to use password less SSH logins to localhost.\n"
                   "Skipping test\n");
    GNUNET_SCHEDULER_cancel (abort_task);
    abort_task = NULL;
    (void) GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
    result = SKIP;
    return;
  }
  cp = GNUNET_TESTBED_controller_start ("127.0.0.1", host, status_cb,
                                        NULL);
}
/**
 * Message handler for #GNUNET_MESSAGE_TYPE_TESTBED_LCONTROLLERS message
 *
 * @param cls identification of the client
 * @param msg the actual message
 */
void
handle_link_controllers (void *cls,
                         const struct GNUNET_TESTBED_ControllerLinkRequest *msg)
{
  struct GNUNET_SERVICE_Client *client = cls;
  struct LCFContext *lcf;
  struct Route *route;
  struct Route *new_route;
  uint64_t op_id;
  uint32_t delegated_host_id;
  uint32_t slave_host_id;

  if (NULL == GST_context)
  {
    GNUNET_break (0);
    GNUNET_SERVICE_client_drop (client);
    return;
  }
  delegated_host_id = ntohl (msg->delegated_host_id);
  if (delegated_host_id == GST_context->host_id)
  {
    GNUNET_break (0);
    LOG (GNUNET_ERROR_TYPE_WARNING,
         "Trying to link ourselves\n");
    GNUNET_SERVICE_client_drop (client);
    return;
  }
  if ((delegated_host_id >= GST_host_list_size) ||
      (NULL == GST_host_list[delegated_host_id]))
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
         "Delegated host %u not registered with us\n",
         delegated_host_id);
    GNUNET_SERVICE_client_drop (client);
    return;
  }
  slave_host_id = ntohl (msg->slave_host_id);
  if ((slave_host_id >= GST_host_list_size) ||
      (NULL == GST_host_list[slave_host_id]))
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
         "Slave host %u not registered with us\n",
         slave_host_id);
    GNUNET_SERVICE_client_drop (client);
    return;
  }
  if (slave_host_id == delegated_host_id)
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
         "Slave and delegated host are same\n");
    GNUNET_SERVICE_client_drop (client);
    return;
  }
  op_id = GNUNET_ntohll (msg->operation_id);
  if (slave_host_id == GST_context->host_id)    /* Link from us */
  {
    struct Slave *slave;
    struct LinkControllersContext *lcc;

    if (1 != msg->is_subordinate)
    {
      struct Neighbour *n;
      struct NeighbourConnectCtxt *ncc;

      if ((delegated_host_id < neighbour_list_size) &&
        (NULL != neighbour_list[delegated_host_id]))
      {
        GNUNET_break (0);
        GNUNET_SERVICE_client_drop (client);
        return;
      }
      LOG_DEBUG ("Received request to establish a link to host %u\n",
                 delegated_host_id);
      n = GST_create_neighbour (GST_host_list[delegated_host_id]);
      ncc = GNUNET_new (struct NeighbourConnectCtxt);
      ncc->n = n;
      ncc->op_id = op_id;
      ncc->client = client;
      ncc->nh = GST_neighbour_get_connection (n,
                                              &neighbour_connect_cb,
                                              ncc);
      ncc->timeout_task
        = GNUNET_SCHEDULER_add_delayed (GST_timeout,
                                        &timeout_neighbour_connect,
                                        ncc);
      GNUNET_CONTAINER_DLL_insert_tail (ncc_head,
                                        ncc_tail,
                                        ncc);
      GNUNET_SERVICE_client_continue (client);
      return;
    }
    if ( (delegated_host_id < GST_slave_list_size) &&
         (NULL != GST_slave_list[delegated_host_id]) )
    {
      GNUNET_break (0);
      GNUNET_SERVICE_client_drop (client);
      return;
    }
    LOG_DEBUG ("Received request to start and establish a link to host %u\n",
               delegated_host_id);
    slave = GNUNET_new (struct Slave);
    slave->host_id = delegated_host_id;
    slave->reghost_map = GNUNET_CONTAINER_multihashmap_create (100,
                                                               GNUNET_NO);
    slave_list_add (slave);
    lcc = GNUNET_new (struct LinkControllersContext);
    lcc->operation_id = op_id;
    lcc->client = client;
    slave->lcc = lcc;
    slave->controller_proc
      = GNUNET_TESTBED_controller_start (GST_context->master_ip,
                                         GST_host_list[slave->host_id],
                                         &slave_status_cb,
                                         slave);
    new_route = GNUNET_new (struct Route);
    new_route->dest = delegated_host_id;
    new_route->thru = GST_context->host_id;
    route_list_add (new_route);
    return;
  }

  /* Route the request */
  if (slave_host_id >= route_list_size)
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
         "No route towards slave host");
    GNUNET_SERVICE_client_drop (client);
    return;
  }
  lcf = GNUNET_new (struct LCFContext);
  lcf->delegated_host_id = delegated_host_id;
  lcf->slave_host_id = slave_host_id;
  route = GST_find_dest_route (slave_host_id);
  GNUNET_assert (NULL != route);        /* because we add routes carefully */
  GNUNET_assert (route->dest < GST_slave_list_size);
  GNUNET_assert (NULL != GST_slave_list[route->dest]);
  lcf->is_subordinate = msg->is_subordinate;
  lcf->state = INIT;
  lcf->operation_id = op_id;
  lcf->gateway = GST_slave_list[route->dest];
  lcf->client = client;
  if (NULL == lcf_head)
  {
    GNUNET_assert (NULL == lcf_proc_task_id);
    GNUNET_CONTAINER_DLL_insert_tail (lcf_head,
                                      lcf_tail,
                                      lcf);
    lcf_proc_task_id = GNUNET_SCHEDULER_add_now (&lcf_proc_task,
                                                 lcf);
  }
  else
  {
    GNUNET_CONTAINER_DLL_insert_tail (lcf_head,
                                      lcf_tail,
                                      lcf);
  }
  /* FIXME: Adding a new route should happen after the controllers are linked
   * successfully */
  if (1 != msg->is_subordinate)
  {
    GNUNET_SERVICE_client_continue (client);
    return;
  }
  if ( (delegated_host_id < route_list_size) &&
       (NULL != route_list[delegated_host_id]) )
  {
    GNUNET_break_op (0);        /* Are you trying to link delegated host twice
                                 * with is subordinate flag set to GNUNET_YES? */
    GNUNET_SERVICE_client_drop (client);
    return;
  }
  new_route = GNUNET_new (struct Route);
  new_route->dest = delegated_host_id;
  new_route->thru = route->dest;
  route_list_add (new_route);
  GNUNET_SERVICE_client_continue (client);
}