/**
 * 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)
{
  unsigned int cnt;

  cfg = GNUNET_CONFIGURATION_dup (config);
  host = GNUNET_TESTBED_host_create ("localhost", NULL, cfg, 0);
  GNUNET_assert (NULL != host);
  GNUNET_assert (0 != GNUNET_TESTBED_host_get_id_ (host));
  GNUNET_TESTBED_host_destroy (host);
  host = GNUNET_TESTBED_host_create (NULL, NULL, cfg, 0);
  GNUNET_assert (NULL != host);
  GNUNET_assert (0 == GNUNET_TESTBED_host_get_id_ (host));
  GNUNET_assert (host == GNUNET_TESTBED_host_lookup_by_id_ (0));
  hosts = NULL;
  num_hosts = GNUNET_TESTBED_hosts_load_from_file ("sample_hosts.txt", cfg, &hosts);
  GNUNET_assert (7 == num_hosts);
  GNUNET_assert (NULL != hosts);
  for (cnt = 0; cnt < num_hosts; cnt++)
  {
    if (cnt < 3)
    {
      GNUNET_assert (0 == strcmp ("totakura",
                                  GNUNET_TESTBED_host_get_username_
                                  (hosts[cnt])));
      GNUNET_assert (NULL != GNUNET_TESTBED_host_get_hostname (hosts[cnt]));
      GNUNET_assert (22 == GNUNET_TESTBED_host_get_ssh_port_ (hosts[cnt]));
    }
    if (3 == cnt)
    {
      GNUNET_assert (0 == strcmp ("totakura",
                                  GNUNET_TESTBED_host_get_username_
                                  (hosts[cnt])));
      GNUNET_assert (NULL != GNUNET_TESTBED_host_get_hostname (hosts[cnt]));
      GNUNET_assert (2022 == GNUNET_TESTBED_host_get_ssh_port_ (hosts[cnt]));
    }
    if (4 == cnt)
    {
      GNUNET_assert (0 == strcmp ("totakura",
                                  GNUNET_TESTBED_host_get_username_
                                  (hosts[cnt])));
      GNUNET_assert (0 == strcmp ("asgard.realm",
                                  GNUNET_TESTBED_host_get_hostname
                                  (hosts[cnt])));
      GNUNET_assert (22 == GNUNET_TESTBED_host_get_ssh_port_ (hosts[cnt]));
    }
    if (5 == cnt)
    {
      GNUNET_assert (NULL == GNUNET_TESTBED_host_get_username_ (hosts[cnt]));
      GNUNET_assert (0 == strcmp ("rivendal",
                                  GNUNET_TESTBED_host_get_hostname
                                  (hosts[cnt])));
      GNUNET_assert (22 == GNUNET_TESTBED_host_get_ssh_port_ (hosts[cnt]));
    }
    if (6 == cnt)
    {
      GNUNET_assert (NULL == GNUNET_TESTBED_host_get_username_ (hosts[cnt]));
      GNUNET_assert (0 == strcmp ("rohan",
                                  GNUNET_TESTBED_host_get_hostname
                                  (hosts[cnt])));
      GNUNET_assert (561 == GNUNET_TESTBED_host_get_ssh_port_ (hosts[cnt]));
    }
  }
  status = GNUNET_YES;
  shutdown_id =
      GNUNET_SCHEDULER_add_delayed (TIME_REL_SECS (0), &do_shutdown, NULL);
}
/**
 * Signature of the event handler function called by the
 * respective event controller.
 *
 * @param cls closure
 * @param event information about the event
 */
static void
controller_cb (void *cls, const struct GNUNET_TESTBED_EventInformation *event)
{
    switch (event->type)
    {
    case GNUNET_TESTBED_ET_OPERATION_FINISHED:
        if ((NULL != event->op_cls) ||
                (NULL != event->details.operation_finished.emsg))
        {
            GNUNET_break (0);
            abort_test ();
            return;
        }
        switch (result)
        {
        case PEERS_STOPPED:
            if (NULL != event->details.operation_finished.generic)
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            if (event->op == peer1.operation)
            {
                GNUNET_TESTBED_operation_done (peer1.operation);
                peer1.operation = NULL;
                peer1.peer = NULL;
            }
            else if (event->op == peer2.operation)
            {
                GNUNET_TESTBED_operation_done (peer2.operation);
                peer2.operation = NULL;
                peer2.peer = NULL;
            }
            else if (event->op == peer3.operation)
            {
                GNUNET_TESTBED_operation_done (peer3.operation);
                peer3.operation = NULL;
                peer3.peer = NULL;
            }
            else
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            if ((NULL == peer1.peer) && (NULL == peer2.peer) && (NULL == peer3.peer))
            {
                result = SUCCESS;
                GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
            }
            break;
        case PEER1_STARTED:
            if ((NULL != event->details.operation_finished.generic) ||
                    (NULL == common_operation))
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            GNUNET_TESTBED_operation_done (common_operation);
            common_operation = NULL;
            result = CONTROLLER2_UP;
            peer2.operation =
                GNUNET_TESTBED_peer_create (controller1, neighbour1, cfg,
                                            &peer_create_cb, NULL);
            if (NULL == peer2.operation)
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            break;
        case PEER2_STARTED:
            if ((NULL != event->details.operation_finished.generic) ||
                    (NULL == common_operation))
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            GNUNET_TESTBED_operation_done (common_operation);
            common_operation = NULL;
            result = CONTROLLER3_UP;
            peer3.operation =
                GNUNET_TESTBED_peer_create (controller1, neighbour2, cfg,
                                            &peer_create_cb, NULL);
            if (NULL == peer3.operation)
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            break;
        default:
            GNUNET_break (0);
            abort_test ();
            return;
        }
        break;
    case GNUNET_TESTBED_ET_PEER_START:
        switch (result)
        {
        case PEER1_CREATED:
            if (event->details.peer_start.host != host)
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            peer1.is_running = GNUNET_YES;
            GNUNET_TESTBED_operation_done (peer1.operation);
            peer1.operation = NULL;
            result = PEER1_STARTED;
            common_operation =
                GNUNET_TESTBED_controller_link (NULL, controller1, neighbour1, NULL,
                                                GNUNET_YES);
            break;
        case PEER2_CREATED:
            if (event->details.peer_start.host != neighbour1)
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            peer2.is_running = GNUNET_YES;
            GNUNET_TESTBED_operation_done (peer2.operation);
            peer2.operation = NULL;
            result = PEER2_STARTED;
            if (NULL != common_operation)
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            common_operation =
                GNUNET_TESTBED_controller_link (NULL, controller1, neighbour2, NULL,
                                                GNUNET_YES);
            if (NULL == common_operation)
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            break;
        case PEER3_CREATED:
            if (event->details.peer_start.host != neighbour2)
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            peer3.is_running = GNUNET_YES;
            GNUNET_TESTBED_operation_done (peer3.operation);
            peer3.operation = NULL;
            result = PEER3_STARTED;
            common_operation =
                GNUNET_TESTBED_overlay_connect (NULL, &op_comp_cb, NULL, peer2.peer,
                                                peer1.peer);
            break;
        default:
            GNUNET_break (0);
            abort_test ();
            return;
        }
        break;
    case GNUNET_TESTBED_ET_PEER_STOP:
        if (PEERS_CONNECTED_2 != result)
        {
            GNUNET_break (0);
            abort_test ();
            return;
        }
        if (event->details.peer_stop.peer == peer1.peer)
        {
            peer1.is_running = GNUNET_NO;
            GNUNET_TESTBED_operation_done (peer1.operation);
        }
        else if (event->details.peer_stop.peer == peer2.peer)
        {
            peer2.is_running = GNUNET_NO;
            GNUNET_TESTBED_operation_done (peer2.operation);
        }
        else if (event->details.peer_stop.peer == peer3.peer)
        {
            peer3.is_running = GNUNET_NO;
            GNUNET_TESTBED_operation_done (peer3.operation);
        }
        else
        {
            GNUNET_break (0);
            abort_test ();
            return;
        }
        if ((GNUNET_NO == peer1.is_running) && (GNUNET_NO == peer2.is_running) &&
                (GNUNET_NO == peer3.is_running))
        {
            result = PEERS_STOPPED;
            peer1.operation = GNUNET_TESTBED_peer_destroy (peer1.peer);
            peer2.operation = GNUNET_TESTBED_peer_destroy (peer2.peer);
            peer3.operation = GNUNET_TESTBED_peer_destroy (peer3.peer);
        }
        break;
    case GNUNET_TESTBED_ET_CONNECT:
        if ((NULL != peer1.operation) || (NULL != peer2.operation) ||
                (NULL != peer3.operation) || (NULL == common_operation))
        {
            GNUNET_break (0);
            abort_test ();
            return;
        }
        switch (result)
        {
        case PEER3_STARTED:
            if ((event->details.peer_connect.peer1 != peer2.peer) ||
                    (event->details.peer_connect.peer2 != peer1.peer))
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            GNUNET_TESTBED_operation_done (common_operation);
            common_operation = NULL;
            result = PEERS_1_2_CONNECTED;
            LOG (GNUNET_ERROR_TYPE_DEBUG, "Peers connected\n");
            common_operation =
                GNUNET_TESTBED_overlay_connect (NULL, &op_comp_cb, NULL, peer2.peer,
                                                peer3.peer);
            break;
        case PEERS_1_2_CONNECTED:
            if ((event->details.peer_connect.peer1 != peer2.peer) ||
                    (event->details.peer_connect.peer2 != peer3.peer))
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            GNUNET_TESTBED_operation_done (common_operation);
            common_operation = NULL;
            result = PEERS_2_3_CONNECTED;
            delayed_connect_task =
                GNUNET_SCHEDULER_add_delayed (TIME_REL_SECS (3), &do_delayed_connect,
                                              NULL);
            break;
        case PEERS_2_3_CONNECTED:
            if ((event->details.peer_connect.peer1 != peer1.peer) ||
                    (event->details.peer_connect.peer2 != peer2.peer))
            {
                GNUNET_break (0);
                abort_test ();
                return;
            }
            GNUNET_TESTBED_operation_done (common_operation);
            common_operation = NULL;
            result = PEERS_CONNECTED_2;
            LOG (GNUNET_ERROR_TYPE_DEBUG, "Peers connected again\n");
            peer1.operation = GNUNET_TESTBED_peer_stop (NULL, peer1.peer, NULL, NULL);
            peer2.operation = GNUNET_TESTBED_peer_stop (NULL, peer2.peer, NULL, NULL);
            peer3.operation = GNUNET_TESTBED_peer_stop (NULL, peer3.peer, NULL, NULL);
            break;
        default:
            GNUNET_break (0);
            abort_test ();
            return;
        }
        break;
    default:
        GNUNET_break (0);
        abort_test ();
        return;
    }
}