/*
     This file is part of GNUnet.
     Copyright (C) 2010-2014 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 src/conversation/gnunet-conversation-gtk_phone.c
 * @brief main logic to manage phone calls
 * @author yids
 * @author hark
 * @author Christian Grothoff
 */
#include "gnunet-conversation-gtk.h"
#include "gnunet-conversation-gtk_egos.h"
#include "gnunet-conversation-gtk_history.h"
#include "gnunet-conversation-gtk_import.h"
#include "gnunet-conversation-gtk_log.h"
#include "gnunet-conversation-gtk_phone.h"
#include "gnunet-conversation-gtk_zones.h"


/**
 * Active calls treeview columns
 */
enum ActiveCallsTreeViewColumns
{
  /**
   * A `gchar *` giving the name of the remote user.
   */
  GCG_PHONE_LS_REMOTE_USER_NAME,

  /**
   * A `struct IncomingCall *` for incoming calls, otherwise NULL.
   */
  GCG_PHONE_LS_INCOMING_CALL,

  /**
   * A `gint` uniquely identifying the call to the user.
   */
  GCG_PHONE_LS_NUMBER,

  /**
   * A `gint` for a `enum TypeOfConversation`.
   */
  GCG_PHONE_LS_TYPE,

  /**
   * A `gint` for a `enum InCallState`.
   */
  GCG_PHONE_LS_IN_CALL_STATE,

  /**
   * A `struct OutgoingCall *` for outgoing calls, otherwise NULL.
   */
  GCG_PHONE_LS_OUTGOING_CALL,

  /**
   * A `gint` for a `enum OutCallState`.
   */
  GCG_PHONE_LS_OUT_CALL_STATE,

  /**
   * A `gchararray` describing the state of the call.
   */
  GCG_PHONE_LS_STATE_NAME,

  /**
   * A `GdkPixbuf` visualizing the state of the call.
   * Not yet used.
   */
  GCG_PHONE_LS_STATE_PIXBUF

};


/**
 * Types of conversations
 */
enum TypeOfConversation
{
  /**
   * Incoming phone call.
   */
  CALL_IN,

  /**
   * Outgoing call.
   */
  CALL_OUT
};


/**
 * States for current incoming call.
 */
enum InCallState
{
  /**
   * No incoming call.
   */
  IN_STATE_NONE,

  /**
   * Our phone is ringing.
   */
  IN_STATE_RINGING,

  /**
   * The other party accepted our call and we are now connected.
   */
  IN_STATE_CONNECTED,

  /**
   * The call is currently suspended (by us).
   */
  IN_STATE_SUSPENDED_LOCAL,

  /**
   * The call is currently suspended (by remote).
   */
  IN_STATE_SUSPENDED_REMOTE,

  /**
   * The call is currently suspended (by both).
   */
  IN_STATE_SUSPENDED_BOTH
};


/**
 * Possible states of outgoing phone calls.
 */
enum OutCallState
{
  /**
   * Phone state we use if this is not an outgoing call.
   */
  OUT_STATE_NONE,

  /**
   * This phone call is currently resolving.
   */
  OUT_STATE_RESOLVING,

  /**
   * This phone call is currently ringing.
   */
  OUT_STATE_RINGING,

  /**
   * This phone call is currently active.
   */
  OUT_STATE_CONNECTED,

  /**
   * This phone call is currently suspended by local.
   */
  OUT_STATE_SUSPENDED_LOCAL,

  /**
   * This phone call is currently suspended by remote.
   */
  OUT_STATE_SUSPENDED_REMOTE,

  /**
   * This phone call is currently suspended from both sides.
   */
  OUT_STATE_SUSPENDED_BOTH

};


/**
 * List of incoming calls
 */
struct IncomingCall
{

  /**
   * Handle to hang up or activate.
   */
  struct GNUNET_CONVERSATION_Caller *caller;

  /**
   * Public key identifying the caller.
   */
  struct GNUNET_IDENTITY_PublicKey caller_id;

  /**
   * Caller ID as human-readable string.
   */
  char *caller_id_str;

  /**
   * Namestore handle for our reverse lookup (to convert
   * @e caller_id into *nice* human-readable @e caller_id_str).
   */
  struct GNUNET_NAMESTORE_QueueEntry *qe;

  /**
   * Location of this call in the list.
   */
  GtkTreeRowReference *rr;

  /**
   * Unique number of the caller.
   */
  guint caller_num;

  /**
   * State for this call.
   */
  enum InCallState state;
};


/**
 * Information we keep for an outgoing call.
 */
struct OutgoingCall
{

  /**
   * Associated conversation handle.
   */
  struct GNUNET_CONVERSATION_Call *call;

  /**
   * Name of conversation partner.
   */
  char *caller_id_str;

  /**
   * Location of this call in the list.
   */
  GtkTreeRowReference *rr;

  /**
   * Unique number of the caller.
   */
  guint caller_num;

  /**
   * State for this call.
   */
  enum OutCallState state;
};


/**
 * List of active calls
 */
static GtkListStore *active_liststore;

/**
 * List of active calls
 */
static GtkTreeView *active_treeview;

/**
 * Phone handle
 */
static struct GNUNET_CONVERSATION_Phone *phone;

/**
 * Our speaker.
 */
static struct GNUNET_SPEAKER_Handle *speaker;

/**
 * Our microphone.
 */
static struct GNUNET_MICROPHONE_Handle *mic;


/**
 * Counts the number of incoming or outgoing calls we have had so far.
 */
static guint caller_num_gen;

/**
 * Number of incoming calls that are currently ringing.
 */
static unsigned int in_ring_counter;

/**
 * Caller handle (for active incoming call, or NULL).
 */
static struct IncomingCall *active_in;

/**
 * Call handle (of the active outgoing call, or NULL).
 */
static struct OutgoingCall *active_out;


/**
 * The "add contact" (or "> contact") button.
 */
static GtkWidget *b_add_contact;

/**
 * The "accept" button.
 */
static GtkWidget *b_accept;

/**
 * The "refuse" button.
 */
static GtkWidget *b_refuse;

/**
 * The "pause" button.
 */
static GtkWidget *b_suspend;

/**
 * The "resume" button.
 */
static GtkWidget *b_resume;

/**
 * The "connect" button.
 */
static GtkWidget *b_call;

/**
 * The "disconnect" button.
 */
static GtkWidget *b_hangup;

/**
 * The "copy" button.
 */
static GtkWidget *b_copy;

/**
 * Our address entry.
 */
static GtkEntry *address_entry;


/**
 * Convert in call state to human readable string.
 *
 * @param is state to convert
 * @return human readable description
 */
static const char *
in_call_state_to_string (enum InCallState is)
{
  switch (is)
  {
  case IN_STATE_NONE:
    return _ ("none");
  case IN_STATE_RINGING:
    return _ ("ringing");
  case IN_STATE_CONNECTED:
    return _ ("connected");
  case IN_STATE_SUSPENDED_LOCAL:
    return _ ("suspended");
  case IN_STATE_SUSPENDED_REMOTE:
    return _ ("on hold");
  case IN_STATE_SUSPENDED_BOTH:
    return _ ("suspended and on hold");
  }
  return NULL;
}


/**
 * Convert out call state to human readable string.
 *
 * @param os state to convert
 * @return human readable description
 */
static const char *
out_call_state_to_string (enum OutCallState os)
{
  switch (os)
  {
  case OUT_STATE_NONE:
    return _ ("none");
  case OUT_STATE_RESOLVING:
    return _ ("resolving");
  case OUT_STATE_RINGING:
    return _ ("calling");
  case OUT_STATE_CONNECTED:
    return _ ("connected");
  case OUT_STATE_SUSPENDED_LOCAL:
    return _ ("suspended");
  case OUT_STATE_SUSPENDED_REMOTE:
    return _ ("on hold");
  case OUT_STATE_SUSPENDED_BOTH:
    return _ ("suspended and on hold");
  }
  return NULL;
}


/**
 * Free data structure associated with an incoming call.
 *
 * @param ic incoming call to free
 */
static void
destroy_in (struct IncomingCall *ic)
{
  if (NULL != ic->caller)
  {
    GNUNET_CONVERSATION_caller_hang_up (ic->caller);
    ic->caller = NULL;
  }
  GNUNET_free (ic->caller_id_str);
  if (NULL != ic->qe)
    GNUNET_NAMESTORE_cancel (ic->qe);
  if (NULL != ic->rr)
    gtk_tree_row_reference_free (ic->rr);
  GNUNET_free (ic);
}


/**
 * Free data structure associated with an outgoing call.
 *
 * @param oc outgoing call to free
 */
static void
destroy_out (struct OutgoingCall *oc)
{
  if (NULL != oc->call)
  {
    GNUNET_CONVERSATION_call_stop (oc->call);
    oc->call = NULL;
  }
  GNUNET_free (oc->caller_id_str);
  if (NULL != oc->rr)
    gtk_tree_row_reference_free (oc->rr);
  GNUNET_free (oc);
}


/**
 * Update status based on current phone state.
 */
static void
do_status ()
{
  if ((NULL == active_in) && (NULL == active_out) && (0 != in_ring_counter))
  {
    GCG_update_status_bar (_ ("The phone is ringing (%u calls waiting)"),
                           in_ring_counter);
    return;
  }
  if ((NULL == active_in) && (NULL == active_out))
  {
    if (NULL != phone)
    {
      GCG_update_status_bar ("%s", _ ("We are listening for incoming calls"));
    }
    else
    {
      if (NULL == GCG_EGOS_get_selected_ego ())
      {
        GCG_update_status_bar ("%s", _ ("No ego selected, phone is down."));
      }
      else
      {
        GCG_update_status_bar ("%s",
                               _ ("Failed to setup phone (internal error)"));
      }
    }
    return;
  }
  if (NULL != active_in)
  {
    switch (active_in->state)
    {
    case IN_STATE_NONE:
      GNUNET_break (0);
      break;
    case IN_STATE_RINGING:
      GNUNET_break (0);
      break;
    case IN_STATE_CONNECTED:
      GCG_update_status_bar (_ ("In a conversation with `%s'."),
                             active_in->caller_id_str);
      break;
    case IN_STATE_SUSPENDED_LOCAL:
      GNUNET_break (0);
      break;
    case IN_STATE_SUSPENDED_REMOTE:
      GCG_update_status_bar (_ ("On hold in a conversation with `%s'."),
                             active_in->caller_id_str);
      break;
    case IN_STATE_SUSPENDED_BOTH:
      GNUNET_break (0);
      break;
    }
    return;
  }
  if (NULL != active_out)
  {
    switch (active_out->state)
    {
    case OUT_STATE_NONE:
      GNUNET_break (0);
      break;
    case OUT_STATE_RESOLVING:
      GCG_update_status_bar (_ ("Resolving `%s'."), active_out->caller_id_str);
      break;
    case OUT_STATE_RINGING:
      GCG_update_status_bar (_ ("Calling `%s'."), active_out->caller_id_str);
      break;
    case OUT_STATE_CONNECTED:
      GCG_update_status_bar (_ ("In a conversation with `%s'."),
                             active_out->caller_id_str);
      break;
    case OUT_STATE_SUSPENDED_LOCAL:
      GNUNET_break (0);
      break;
    case OUT_STATE_SUSPENDED_REMOTE:
      GCG_update_status_bar (_ ("On hold in a conversation with `%s'."),
                             active_out->caller_id_str);
      break;
    case OUT_STATE_SUSPENDED_BOTH:
      GNUNET_break (0);
      break;
    }
    return;
  }
}


/**
 * Check if the conditions are met for the "call" button to
 * be sensitive again.  Those conditions are that we must
 * have a phone that is ready to "listen" (no error or active
 * call), and that some address text is in the address entry.
 */
static void
check_call_sensitivity ()
{
  gboolean sens;
  const char *tld;

  sens = (NULL == active_in) && (NULL == active_out);
  if (0 == strlen (gtk_entry_get_text (address_entry)))
    sens = FALSE;
  if (NULL == phone)
    sens = FALSE;
  if (NULL == GCG_ZONES_get_selected_zone (&tld))
    sens = FALSE;
  gtk_widget_set_sensitive (b_call, sens);
}


/**
 * Update sensitivity of buttons associated with the call list.
 *
 * @param sel selection that changed (belongs to #active_treeview)
 */
static void
update_call_buttons (GtkTreeSelection *sel)
{
  GtkTreeIter iter;
  GtkTreeModel *model;
  struct IncomingCall *ic;
  gint type;
  gint in_state;
  gint out_state;
  gboolean in_call;

  if (! gtk_tree_selection_get_selected (sel, &model, &iter))
  {
    /* nothing selected, disable buttons */
    gtk_widget_set_sensitive (b_add_contact, FALSE);
    gtk_widget_set_sensitive (b_accept, FALSE);
    gtk_widget_set_sensitive (b_refuse, FALSE);
    gtk_widget_set_sensitive (b_resume, FALSE);
    return;
  }
  in_call = (NULL != active_in) || (NULL != active_out);
  gtk_tree_model_get (model,
                      &iter,
                      GCG_PHONE_LS_TYPE,
                      &type,
                      GCG_PHONE_LS_OUT_CALL_STATE,
                      &out_state,
                      GCG_PHONE_LS_IN_CALL_STATE,
                      &in_state,
                      GCG_PHONE_LS_INCOMING_CALL,
                      &ic,
                      -1);
  switch ((enum TypeOfConversation) type)
  {
  case CALL_IN:
    switch ((enum InCallState) in_state)
    {
    case IN_STATE_NONE:
      GNUNET_break (0);
      break;
    case IN_STATE_RINGING:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, ! in_call);
      gtk_widget_set_sensitive (b_refuse, TRUE);
      gtk_widget_set_sensitive (b_resume, FALSE);
      break;
    case IN_STATE_CONNECTED:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, FALSE);
      gtk_widget_set_sensitive (b_refuse, (active_in != ic));
      gtk_widget_set_sensitive (b_resume, FALSE);
      break;
    case IN_STATE_SUSPENDED_LOCAL:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, FALSE);
      gtk_widget_set_sensitive (b_refuse, TRUE);
      gtk_widget_set_sensitive (b_resume, TRUE);
      break;
    case IN_STATE_SUSPENDED_REMOTE:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, FALSE);
      gtk_widget_set_sensitive (b_refuse, TRUE);
      gtk_widget_set_sensitive (b_resume, FALSE);
      break;
    case IN_STATE_SUSPENDED_BOTH:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, FALSE);
      gtk_widget_set_sensitive (b_refuse, TRUE);
      gtk_widget_set_sensitive (b_resume, TRUE);
      break;
    }
    break;
  case CALL_OUT:
    switch ((enum OutCallState) out_state)
    {
    case OUT_STATE_NONE:
      GNUNET_break (0);
      break;
    case OUT_STATE_RESOLVING:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, FALSE);
      gtk_widget_set_sensitive (b_refuse, FALSE);
      gtk_widget_set_sensitive (b_resume, FALSE);
      break;
    case OUT_STATE_RINGING:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, ! in_call);
      gtk_widget_set_sensitive (b_refuse, FALSE);
      gtk_widget_set_sensitive (b_resume, ! in_call);
      break;
    case OUT_STATE_CONNECTED:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, FALSE);
      gtk_widget_set_sensitive (b_refuse, FALSE);
      gtk_widget_set_sensitive (b_resume, FALSE);
      break;
    case OUT_STATE_SUSPENDED_LOCAL:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, FALSE);
      gtk_widget_set_sensitive (b_refuse, TRUE);
      gtk_widget_set_sensitive (b_resume, ! in_call);
      break;
    case OUT_STATE_SUSPENDED_REMOTE:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, FALSE);
      gtk_widget_set_sensitive (b_refuse, TRUE);
      gtk_widget_set_sensitive (b_resume, FALSE);
      break;
    case OUT_STATE_SUSPENDED_BOTH:
      gtk_widget_set_sensitive (b_add_contact, TRUE);
      gtk_widget_set_sensitive (b_accept, FALSE);
      gtk_widget_set_sensitive (b_refuse, TRUE);
      gtk_widget_set_sensitive (b_resume, ! in_call);
      break;
    }
    break;
  }
}


/**
 * @brief executed when selecting a different item in active call list
 *
 * @param sel selection that changed (belongs to #active_treeview)
 * @param user_data builder context (unused)
 */
void
gnunet_conversation_gtk_active_calls_treeview_selection_changed_cb (
  GtkTreeSelection *sel,
  gpointer user_data)
{
  update_call_buttons (sel);
}


/**
 * Update all aspects of the GUI (visibility and sensitivity
 * of all buttons) based on the current state.
 */
static void
update_gui ()
{
  if ((NULL != active_in) || (NULL != active_out))
  {
    /* in a call, show hangup/suspend buttons */
    gtk_widget_show (b_suspend);
    gtk_widget_show (b_hangup);
    gtk_widget_hide (b_call);
    gtk_widget_set_sensitive (b_hangup, TRUE);
    if ((NULL != active_in) ||
        ((NULL != active_out) && (OUT_STATE_RESOLVING != active_out->state) &&
         (OUT_STATE_RINGING != active_out->state)))
      gtk_widget_set_sensitive (b_suspend, TRUE);
    else
      gtk_widget_set_sensitive (b_suspend, FALSE);
    gtk_widget_set_sensitive (GTK_WIDGET (address_entry), FALSE);
  }
  else
  {
    /* not in a call, show 'call' button */
    gtk_widget_hide (b_suspend);
    gtk_widget_hide (b_hangup);
    gtk_widget_show (b_call);
    gtk_widget_set_sensitive (b_hangup, FALSE);
    gtk_widget_set_sensitive (b_suspend, FALSE);
    gtk_widget_set_sensitive (GTK_WIDGET (address_entry), TRUE);
    check_call_sensitivity ();
  }
  update_call_buttons (gtk_tree_view_get_selection (active_treeview));
  do_status ();
}


/**
 * Set state of outgoing call.
 *
 * @param oc outgoing call to change state of
 * @param state new state of the call
 */
static void
set_outgoing_call_state (struct OutgoingCall *oc, enum OutCallState state)
{
  GtkTreePath *path;
  GtkTreeIter iter;

  oc->state = state;
  path = gtk_tree_row_reference_get_path (oc->rr);
  GNUNET_assert (
    gtk_tree_model_get_iter (GTK_TREE_MODEL (active_liststore), &iter, path));
  gtk_tree_path_free (path);
  switch (state)
  {
  case OUT_STATE_NONE:
    gtk_list_store_remove (active_liststore, &iter);
    return;
  case OUT_STATE_RESOLVING:
    break;
  case OUT_STATE_RINGING:
    break;
  case OUT_STATE_CONNECTED:
    break;
  case OUT_STATE_SUSPENDED_LOCAL:
    break;
  case OUT_STATE_SUSPENDED_REMOTE:
    break;
  case OUT_STATE_SUSPENDED_BOTH:
    break;
  }
  gtk_list_store_set (active_liststore,
                      &iter,
                      GCG_PHONE_LS_OUT_CALL_STATE,
                      state,
                      GCG_PHONE_LS_STATE_NAME,
                      out_call_state_to_string (state),
                      -1);
  update_call_buttons (gtk_tree_view_get_selection (active_treeview));
}


/**
 * Set call state of a incoming call
 *
 * @param ic incoming call to change state of
 * @param state new state of the call
 */
static void
set_incoming_call_state (struct IncomingCall *ic, enum InCallState state)
{
  GtkTreePath *path;
  GtkTreeIter iter;

  ic->state = state;
  path = gtk_tree_row_reference_get_path (ic->rr);
  GNUNET_assert (
    gtk_tree_model_get_iter (GTK_TREE_MODEL (active_liststore), &iter, path));
  gtk_tree_path_free (path);
  switch (state)
  {
  case IN_STATE_NONE:
    gtk_list_store_remove (active_liststore, &iter);
    return;
  case IN_STATE_RINGING:
    break;
  case IN_STATE_CONNECTED:
    break;
  case IN_STATE_SUSPENDED_LOCAL:
    break;
  case IN_STATE_SUSPENDED_REMOTE:
    break;
  case IN_STATE_SUSPENDED_BOTH:
    break;
  }
  gtk_list_store_set (active_liststore,
                      &iter,
                      GCG_PHONE_LS_IN_CALL_STATE,
                      state,
                      GCG_PHONE_LS_STATE_NAME,
                      in_call_state_to_string (state),
                      -1);
  update_call_buttons (gtk_tree_view_get_selection (active_treeview));
}


/**
 * Process a record that was stored in the namestore.
 *
 * @param cls closure with the `struct IncomingCall *`
 * @param zone private key of the zone; NULL on disconnect
 * @param label label of the records; NULL on disconnect
 * @param rd_count number of entries in @a rd array, 0 if label was deleted
 * @param rd array of records with data to store
 */
static void
handle_reverse_lookup_result (void *cls,
                              const struct GNUNET_IDENTITY_PrivateKey *zone,
                              const char *label,
                              unsigned int rd_count,
                              const struct GNUNET_GNSRECORD_Data *rd)
{
  struct IncomingCall *ic = cls;

  ic->qe = NULL;
  GNUNET_free (ic->caller_id_str);
  ic->caller_id_str = GNUNET_strdup (label);
}


/**
 * Failed to talk to namestore.
 */
static void
handle_reverse_error (void *cls)
{
  struct IncomingCall *ic = cls;

  ic->qe = NULL;
  GCG_log (_ ("Error communicating with namestore!\n"));
}


/**
 * Function called with an event emitted by a phone.
 *
 * @param cls closure
 * @param code type of the event
 * @param caller handle for the caller
 * @param caller_id public key of the caller (in GNS)
 */
static void
phone_event_handler (void *cls,
                     enum GNUNET_CONVERSATION_PhoneEventCode code,
                     struct GNUNET_CONVERSATION_Caller *caller,
                     const struct GNUNET_IDENTITY_PublicKey *caller_id)
{
  GtkTreeIter iter;
  gboolean valid;
  struct IncomingCall *ic;
  GtkTreePath *path;
  struct GNUNET_IDENTITY_Ego *my_zone;
  const char *tld;

  switch (code)
  {
  case GNUNET_CONVERSATION_EC_PHONE_RING:
    in_ring_counter++;
    ic = GNUNET_new (struct IncomingCall);
    ic->caller = caller;
    ic->state = IN_STATE_RINGING;
    ic->caller_id = *caller_id;
    ic->caller_id_str =
      GNUNET_strdup (GNUNET_GNSRECORD_pkey_to_zkey (caller_id));

    my_zone = GCG_ZONES_get_selected_zone (&tld);
    ic->qe =
      GNUNET_NAMESTORE_zone_to_name (GCG_IMPORT_get_namestore (),
                                     GNUNET_IDENTITY_ego_get_private_key (
                                       my_zone),
                                     &ic->caller_id,
                                     &handle_reverse_error,
                                     ic,
                                     &handle_reverse_lookup_result,
                                     ic);
    ic->caller_num = caller_num_gen++;
    gtk_list_store_insert_with_values (active_liststore,
                                       &iter,
                                       -1,
                                       GCG_PHONE_LS_REMOTE_USER_NAME,
                                       ic->caller_id_str,
                                       GCG_PHONE_LS_INCOMING_CALL,
                                       ic,
                                       GCG_PHONE_LS_NUMBER,
                                       ic->caller_num,
                                       GCG_PHONE_LS_IN_CALL_STATE,
                                       ic->state,
                                       GCG_PHONE_LS_STATE_NAME,
                                       in_call_state_to_string (ic->state),
                                       GCG_PHONE_LS_TYPE,
                                       CALL_IN,
                                       -1);
    path = gtk_tree_model_get_path (GTK_TREE_MODEL (active_liststore), &iter);
    ic->rr =
      gtk_tree_row_reference_new (GTK_TREE_MODEL (active_liststore), path);
    gtk_tree_path_free (path);
    GCG_HISTORY_add (GCG_HISTORY_TYPE_INCOMING_CALL,
                     ic->caller_id_str,
                     ic->caller_num);
    break;
  case GNUNET_CONVERSATION_EC_PHONE_HUNG_UP:
    GNUNET_break (in_ring_counter > 0);
    in_ring_counter--;
    valid =
      gtk_tree_model_get_iter_first (GTK_TREE_MODEL (active_liststore), &iter);
    GNUNET_break (valid);
    while (valid)
    {
      gtk_tree_model_get (GTK_TREE_MODEL (active_liststore),
                          &iter,
                          GCG_PHONE_LS_INCOMING_CALL,
                          &ic,
                          -1);
      if ((NULL != ic) && (caller == ic->caller))
      {
        if (ic == active_in)
        {
          active_in = NULL;
          GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP,
                           ic->caller_id_str,
                           ic->caller_num);
        }
        else
        {
          if (IN_STATE_RINGING == ic->state)
            GCG_HISTORY_add (GCG_HISTORY_TYPE_INCOMING_MISSED,
                             ic->caller_id_str,
                             ic->caller_num);
          else
            GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP,
                             ic->caller_id_str,
                             ic->caller_num);
        }
        set_incoming_call_state (ic, IN_STATE_NONE);
        ic->caller = NULL;
        destroy_in (ic);
        break;
      }
      valid =
        gtk_tree_model_iter_next (GTK_TREE_MODEL (active_liststore), &iter);
    }
    GNUNET_break (valid);
    break;
  }
  update_gui ();
}


/**
 * Function called with an event emitted by a caller.
 *
 * @param cls closure with the `struct IncomingCall` of the caller
 * @param code type of the event issued by the caller
 */
static void
caller_event_handler (void *cls, enum GNUNET_CONVERSATION_CallerEventCode code)
{
  struct IncomingCall *ic = cls;
  enum InCallState is;

  if (NULL == ic)
  {
    GNUNET_break (0);
    return;
  }
  switch (code)
  {
  case GNUNET_CONVERSATION_EC_CALLER_SUSPEND:
    GCG_HISTORY_add (GCG_HISTORY_TYPE_SUSPEND_REMOTE,
                     ic->caller_id_str,
                     ic->caller_num);
    is = ic->state;
    switch (ic->state)
    {
    case IN_STATE_NONE:
      GNUNET_assert (0);
      break;
    case IN_STATE_RINGING:
      GNUNET_assert (0);
      break;
    case IN_STATE_CONNECTED:
      is = IN_STATE_SUSPENDED_REMOTE;
      break;
    case IN_STATE_SUSPENDED_LOCAL:
      is = IN_STATE_SUSPENDED_BOTH;
      break;
    case IN_STATE_SUSPENDED_REMOTE:
      GNUNET_assert (0);
      break;
    case IN_STATE_SUSPENDED_BOTH:
      GNUNET_assert (0);
      break;
    }
    set_incoming_call_state (ic, is);
    break;
  case GNUNET_CONVERSATION_EC_CALLER_RESUME:
    GCG_HISTORY_add (GCG_HISTORY_TYPE_RESUMED_REMOTE,
                     ic->caller_id_str,
                     ic->caller_num);
    is = ic->state;
    switch (ic->state)
    {
    case IN_STATE_NONE:
      GNUNET_assert (0);
      break;
    case IN_STATE_RINGING:
      GNUNET_assert (0);
      break;
    case IN_STATE_CONNECTED:
      GNUNET_assert (0);
      break;
    case IN_STATE_SUSPENDED_LOCAL:
      GNUNET_assert (0);
      break;
    case IN_STATE_SUSPENDED_REMOTE:
      is = IN_STATE_CONNECTED;
      break;
    case IN_STATE_SUSPENDED_BOTH:
      is = IN_STATE_SUSPENDED_LOCAL;
      break;
    }
    set_incoming_call_state (ic, is);
    break;
  }
  do_status ();
}


/**
 * The "accept" button was clicked.  Accept selected incoming call.
 *
 * @param button the button
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_accept_button_clicked_cb (GtkButton *button,
                                                  gpointer user_data)
{
  struct IncomingCall *ic;
  GtkTreeSelection *sel;
  GtkTreeIter iter;
  GtkTreeModel *model;

  if ((NULL != active_in) || (NULL != active_out))
  {
    /* accept button should not have been sensitive! */
    GNUNET_break (0);
    return;
  }
  sel = gtk_tree_view_get_selection (active_treeview);
  if (! gtk_tree_selection_get_selected (sel, &model, &iter))
  {
    /* accept button should not have been sensitive! */
    GNUNET_break (0);
    return;
  }
  gtk_tree_model_get (model, &iter, GCG_PHONE_LS_INCOMING_CALL, &ic, -1);
  if (NULL == ic)
  {
    /* accept button should not have been sensitive! */
    GNUNET_break (0);
    return;
  }
  active_in = ic;
  set_incoming_call_state (ic, IN_STATE_CONNECTED);
  GNUNET_CONVERSATION_caller_pick_up (ic->caller,
                                      &caller_event_handler,
                                      ic,
                                      speaker,
                                      mic);
  GCG_HISTORY_add (GCG_HISTORY_TYPE_INCOMING_ACCEPTED,
                   ic->caller_id_str,
                   ic->caller_num);
  gtk_entry_set_text (address_entry, ic->caller_id_str);
  update_gui ();
}


/**
 * The "reject" button was clicked.  Reject incoming call.
 *
 * @param button the button
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_refuse_button_clicked_cb (GtkButton *button,
                                                  gpointer user_data)
{
  struct IncomingCall *ic;
  struct OutgoingCall *oc;
  GtkTreeSelection *sel;
  GtkTreeIter iter;
  GtkTreeModel *model;

  sel = gtk_tree_view_get_selection (active_treeview);
  if (! gtk_tree_selection_get_selected (sel, &model, &iter))
  {
    /* reject button should not have been sensitive! */
    GNUNET_break (0);
    return;
  }
  gtk_tree_model_get (model,
                      &iter,
                      GCG_PHONE_LS_INCOMING_CALL,
                      &ic,
                      GCG_PHONE_LS_OUTGOING_CALL,
                      &oc,
                      -1);
  if (NULL != ic)
  {
    if (active_in == ic)
    {
      /* reject button should not have been sensitive! */
      GNUNET_break (0);
      return;
    }
    GNUNET_break (in_ring_counter > 0);
    in_ring_counter--;
    GCG_HISTORY_add (GCG_HISTORY_TYPE_INCOMING_REJECTED,
                     ic->caller_id_str,
                     ic->caller_num);
    set_incoming_call_state (ic, IN_STATE_NONE);
    destroy_in (ic);
    do_status ();
    return;
  }
  if (NULL != oc)
  {
    /* terminate suspended outgoing call */
    if (active_out == oc)
    {
      /* refuse button should not have been sensitive! */
      GNUNET_break (0);
      return;
    }
    GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP,
                     oc->caller_id_str,
                     oc->caller_num);
    set_outgoing_call_state (oc, OUT_STATE_NONE);
    destroy_out (oc);
    do_status ();
    return;
  }
  /* reject button should not have been sensitive! */
  GNUNET_break (0);
}


/**
 * The "resume" button was clicked.  Resume a call!
 *
 * @param button the button
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_resume_button_clicked_cb (GtkButton *button,
                                                  gpointer user_data)
{
  struct IncomingCall *ic;
  struct OutgoingCall *oc;
  GtkTreeSelection *sel;
  GtkTreeIter iter;
  GtkTreeModel *model;

  if ((NULL != active_in) || (NULL != active_out))
  {
    /* resume button should have been inactive */
    GNUNET_break (0);
    return;
  }

  sel = gtk_tree_view_get_selection (active_treeview);
  if (! gtk_tree_selection_get_selected (sel, &model, &iter))
  {
    /* resume button should not have been sensitive! */
    GNUNET_break (0);
    return;
  }
  gtk_tree_model_get (model,
                      &iter,
                      GCG_PHONE_LS_INCOMING_CALL,
                      &ic,
                      GCG_PHONE_LS_OUTGOING_CALL,
                      &oc,
                      -1);
  if (NULL != ic)
  {
    active_in = ic;
    set_incoming_call_state (ic, IN_STATE_CONNECTED);
    GNUNET_CONVERSATION_caller_resume (ic->caller, speaker, mic);
    GCG_HISTORY_add (GCG_HISTORY_TYPE_RESUMED_LOCAL,
                     ic->caller_id_str,
                     ic->caller_num);
    gtk_entry_set_text (address_entry, ic->caller_id_str);
    update_gui ();
    return;
  }
  if (NULL != oc)
  {
    active_out = oc;
    GNUNET_CONVERSATION_call_resume (oc->call, speaker, mic);
    set_outgoing_call_state (oc, OUT_STATE_CONNECTED);
    GCG_HISTORY_add (GCG_HISTORY_TYPE_RESUMED_LOCAL,
                     oc->caller_id_str,
                     oc->caller_num);
    gtk_entry_set_text (address_entry, active_out->caller_id_str);
    update_gui ();
    return;
  }
  /* resume button should not have been sensitive! */
  GNUNET_break (0);
}


/**
 * The "suspend" button was clicked.  Pause a call.
 *
 * @param button the button
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_suspend_button_clicked_cb (GtkButton *button,
                                                   gpointer user_data)
{
  enum OutCallState os;
  enum InCallState is;

  if ((NULL != active_in) && (NULL != active_out))
  {
    GNUNET_break (0);
    return;
  }
  if (NULL != active_out)
  {
    /* outgoing */
    GCG_HISTORY_add (GCG_HISTORY_TYPE_SUSPEND_LOCAL,
                     active_out->caller_id_str,
                     active_out->caller_num);
    switch ((os = active_out->state))
    {
    case OUT_STATE_NONE:
      GNUNET_assert (0);
      break;
    case OUT_STATE_RESOLVING:
      GNUNET_assert (0);
      break;
    case OUT_STATE_RINGING:
      GNUNET_assert (0);
      break;
    case OUT_STATE_CONNECTED:
      os = OUT_STATE_SUSPENDED_LOCAL;
      break;
    case OUT_STATE_SUSPENDED_LOCAL:
      GNUNET_assert (0);
      break;
    case OUT_STATE_SUSPENDED_REMOTE:
      os = OUT_STATE_SUSPENDED_BOTH;
      break;
    case OUT_STATE_SUSPENDED_BOTH:
      GNUNET_assert (0);
      break;
    }
    GNUNET_CONVERSATION_call_suspend (active_out->call);
    set_outgoing_call_state (active_out, os);
    active_out = NULL;
    update_gui ();
    return;
  }
  if (NULL != active_in)
  {
    /* incoming */
    GCG_HISTORY_add (GCG_HISTORY_TYPE_SUSPEND_LOCAL,
                     active_in->caller_id_str,
                     active_in->caller_num);
    GNUNET_CONVERSATION_caller_suspend (active_in->caller);
    switch ((is = active_in->state))
    {
    case IN_STATE_NONE:
      GNUNET_assert (0);
      break;
    case IN_STATE_RINGING:
      GNUNET_assert (0);
      break;
    case IN_STATE_CONNECTED:
      is = IN_STATE_SUSPENDED_LOCAL;
      break;
    case IN_STATE_SUSPENDED_LOCAL:
      GNUNET_assert (0);
      break;
    case IN_STATE_SUSPENDED_REMOTE:
      is = IN_STATE_SUSPENDED_BOTH;
      break;
    case IN_STATE_SUSPENDED_BOTH:
      GNUNET_assert (0);
      break;
    }
    set_incoming_call_state (active_in, is);
    active_in = NULL;
    update_gui ();
    return;
  }
  GNUNET_break (0);
}


/**
 * The "hangup" button was clicked.  Hang up.
 *
 * @param button the button
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_hangup_button_clicked_cb (GtkButton *button,
                                                  gpointer user_data)
{
  if ((NULL == active_in) && (NULL == active_out))
  {
    GNUNET_break (0);
    return;
  }
  if (NULL != active_out)
  {
    /* if current call is outgoing, stop it */
    GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP,
                     active_out->caller_id_str,
                     active_out->caller_num);
    set_outgoing_call_state (active_out, OUT_STATE_NONE);
    destroy_out (active_out);
    active_out = NULL;
    update_gui ();
    return;
  }
  if (NULL != active_in)
  {
    /* if selected call is incoming, hang it up */
    GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP,
                     active_in->caller_id_str,
                     active_in->caller_num);
    set_incoming_call_state (active_in, IN_STATE_NONE);
    destroy_in (active_in);
    active_in = NULL;
    update_gui ();
    return;
  }
  GNUNET_break (0);
}


/**
 * Function called with an event emitted by a call.
 *
 * @param cls our `struct OutgoingCall`
 * @param code type of the event on the call
 */
static void
call_event_handler (void *cls, enum GNUNET_CONVERSATION_CallEventCode code)
{
  struct OutgoingCall *oc = cls;
  enum OutCallState os;

  switch (code)
  {
  case GNUNET_CONVERSATION_EC_CALL_RINGING:
    GCG_HISTORY_add (GCG_HISTORY_TYPE_OUTGOING_RINGING,
                     oc->caller_id_str,
                     oc->caller_num);
    GNUNET_break (OUT_STATE_RESOLVING == oc->state);
    GNUNET_break (active_out == oc);
    set_outgoing_call_state (oc, OUT_STATE_RINGING);
    do_status ();
    break;
  case GNUNET_CONVERSATION_EC_CALL_PICKED_UP:
    GCG_HISTORY_add (GCG_HISTORY_TYPE_OUTGOING_ACCEPTED,
                     oc->caller_id_str,
                     oc->caller_num);
    GNUNET_break (OUT_STATE_RINGING == oc->state);
    set_outgoing_call_state (oc, OUT_STATE_CONNECTED);
    GNUNET_break (active_out == oc);
    gtk_widget_set_sensitive (b_suspend, TRUE);
    do_status ();
    break;
  case GNUNET_CONVERSATION_EC_CALL_GNS_FAIL:
    GCG_HISTORY_add (GCG_HISTORY_TYPE_FAILED,
                     oc->caller_id_str,
                     oc->caller_num);
    GNUNET_break (OUT_STATE_RESOLVING == oc->state);
    GNUNET_break (active_out == oc);
    set_outgoing_call_state (oc, OUT_STATE_NONE);
    oc->call = NULL;
    destroy_out (oc);
    active_out = NULL;
    update_gui ();
    break;
  case GNUNET_CONVERSATION_EC_CALL_HUNG_UP:
    GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP,
                     oc->caller_id_str,
                     oc->caller_num);
    set_outgoing_call_state (oc, OUT_STATE_NONE);
    GNUNET_break (active_out == oc);
    oc->call = NULL;
    destroy_out (oc);
    active_out = NULL;
    update_gui ();
    break;
  case GNUNET_CONVERSATION_EC_CALL_SUSPENDED:
    GCG_HISTORY_add (GCG_HISTORY_TYPE_SUSPEND_REMOTE,
                     oc->caller_id_str,
                     oc->caller_num);
    os = oc->state;
    switch (oc->state)
    {
    case OUT_STATE_NONE:
      GNUNET_assert (0);
      break;
    case OUT_STATE_RESOLVING:
      GNUNET_assert (0);
      break;
    case OUT_STATE_RINGING:
      GNUNET_assert (0);
      break;
    case OUT_STATE_CONNECTED:
      os = OUT_STATE_SUSPENDED_REMOTE;
      break;
    case OUT_STATE_SUSPENDED_LOCAL:
      os = OUT_STATE_SUSPENDED_BOTH;
      break;
    case OUT_STATE_SUSPENDED_REMOTE:
      GNUNET_assert (0);
      break;
    case OUT_STATE_SUSPENDED_BOTH:
      GNUNET_assert (0);
      break;
    }
    set_outgoing_call_state (oc, os);
    do_status ();
    break;
  case GNUNET_CONVERSATION_EC_CALL_RESUMED:
    GCG_HISTORY_add (GCG_HISTORY_TYPE_RESUMED_REMOTE,
                     oc->caller_id_str,
                     oc->caller_num);
    os = oc->state;
    switch (oc->state)
    {
    case OUT_STATE_NONE:
      GNUNET_assert (0);
      break;
    case OUT_STATE_RESOLVING:
      GNUNET_assert (0);
      break;
    case OUT_STATE_RINGING:
      GNUNET_assert (0);
      break;
    case OUT_STATE_CONNECTED:
      GNUNET_assert (0);
      break;
    case OUT_STATE_SUSPENDED_LOCAL:
      GNUNET_assert (0);
      break;
    case OUT_STATE_SUSPENDED_REMOTE:
      os = OUT_STATE_CONNECTED;
      break;
    case OUT_STATE_SUSPENDED_BOTH:
      os = OUT_STATE_SUSPENDED_LOCAL;
      break;
    }
    set_outgoing_call_state (oc, os);
    if (OUT_STATE_CONNECTED == os)
      GNUNET_break (active_out == oc);
    do_status ();
    break;
  case GNUNET_CONVERSATION_EC_CALL_ERROR:
    GCG_HISTORY_add (GCG_HISTORY_TYPE_FAILED,
                     oc->caller_id_str,
                     oc->caller_num);
    set_outgoing_call_state (oc, OUT_STATE_NONE);
    oc->call = NULL;
    if (active_out == oc)
      active_out = NULL;
    destroy_out (oc);
    update_gui ();
    break;
  }
}


/**
 * The "connect" button was clicked.  Initiate a call!
 *
 * @param button the button
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_call_button_clicked_cb (GtkButton *button,
                                                gpointer user_data)

{
  struct GNUNET_IDENTITY_Ego *caller_id;
  GtkTreePath *path;
  GtkTreeIter iter;
  struct OutgoingCall *oc;
  const char *arg;

  arg = gtk_entry_get_text (address_entry);
  caller_id = GCG_EGOS_get_selected_ego ();
  if ((NULL == caller_id) || (NULL != active_out) || (NULL != active_in))
  {
    GNUNET_break (0);
    return;
  }
  oc = GNUNET_new (struct OutgoingCall);
  oc->caller_id_str = GNUNET_strdup (arg);
  oc->state = OUT_STATE_RESOLVING;
  oc->caller_num = caller_num_gen++;
  GCG_HISTORY_add (GCG_HISTORY_TYPE_OUTGOING_CALL,
                   oc->caller_id_str,
                   oc->caller_num);
  oc->call = GNUNET_CONVERSATION_call_start (GCG_get_configuration (),
                                             caller_id,
                                             arg,
                                             speaker,
                                             mic,
                                             &call_event_handler,
                                             oc);
  if (NULL == oc->call)
  {
    GCG_HISTORY_add (GCG_HISTORY_TYPE_FAILED,
                     oc->caller_id_str,
                     oc->caller_num);
    destroy_out (oc);
    return;
  }
  active_out = oc;
  gtk_list_store_insert_with_values (active_liststore,
                                     &iter,
                                     -1,
                                     GCG_PHONE_LS_REMOTE_USER_NAME,
                                     oc->caller_id_str,
                                     GCG_PHONE_LS_INCOMING_CALL,
                                     NULL,
                                     GCG_PHONE_LS_NUMBER,
                                     oc->caller_num,
                                     GCG_PHONE_LS_IN_CALL_STATE,
                                     IN_STATE_NONE,
                                     GCG_PHONE_LS_TYPE,
                                     CALL_OUT,
                                     GCG_PHONE_LS_OUTGOING_CALL,
                                     oc,
                                     GCG_PHONE_LS_OUT_CALL_STATE,
                                     oc->state,
                                     GCG_PHONE_LS_STATE_NAME,
                                     out_call_state_to_string (oc->state),

                                     -1);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (active_liststore), &iter);
  oc->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (active_liststore), path);
  gtk_tree_path_free (path);
  update_gui ();
}


/**
 * The user has changed the address entry.  Consider activating the
 * "call" button.
 *
 * @param editable entry that was changed
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_address_entry_changed_cb (GtkEditable *editable,
                                                  gpointer user_data)
{
  check_call_sensitivity ();
}


/**
 * Update sensitivity of the "copy" button.
 */
static void
update_copy_sensitivity ()
{
  const gchar *label;
  struct GNUNET_IDENTITY_Ego *ego;

  ego = GCG_EGOS_get_selected_ego ();
  label = gtk_entry_get_text (GTK_ENTRY (
    GCG_get_main_window_object ("gnunet_conversation_gtk_ego_label_entry")));
  if ((NULL == ego) || (NULL == label) || (0 == strlen (label)))
    gtk_widget_set_sensitive (b_copy, FALSE);
  else
    gtk_widget_set_sensitive (b_copy, TRUE);
}


/**
 * Our phone record has changed, update the information
 * published in the GNU Name System.
 */
static void
update_phone_record ()
{
  struct GNUNET_GNSRECORD_Data rd;
  const gchar *label;

  if (NULL == phone)
  {
    GSC_remove_phone ();
    return;
  }
  label = gtk_entry_get_text (GTK_ENTRY (
    GCG_get_main_window_object ("gnunet_conversation_gtk_ego_label_entry")));
  if ((NULL == label) || (0 == strlen (label)))
  {
    GSC_remove_phone ();
    return;
  }
  GNUNET_CONVERSATION_phone_get_record (phone, &rd);
  GSC_add_phone (label, &rd);
}


/**
 * The user has edited the label.  Check if the new label is valid,
 * and if so commit the change to the model and update the phone
 * record.
 *
 * @param entry the entry used to edit the label
 * @param preedit new label
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_ego_label_entry_preedit_changed_cb (GtkEntry *entry,
                                                            gchar *preedit,
                                                            gpointer user_data)
{
  GtkComboBoxText *cbt;

  if (GNUNET_OK != GNUNET_DNSPARSER_check_label (preedit))
  {
    GCG_log (_ ("Invalid label `%s'\n"), preedit);
    gdk_display_beep (gdk_display_get_default ());
    return;
  }
  cbt = GTK_COMBO_BOX_TEXT (GCG_get_main_window_object (
    "gnunet_conversation_gtk_ego_label_comboboxtext"));
  gtk_combo_box_text_insert (cbt, -1, NULL, preedit);
  update_copy_sensitivity ();
  update_phone_record ();
}


/**
 * The user has changed the selected label in the combo box.  Commit
 * the change to the model and update the phone record.
 *
 * @param widget the combo box
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_ego_label_comboboxtext_changed_cb (GtkComboBox *widget,
                                                           gpointer user_data)
{
  update_copy_sensitivity ();
  update_phone_record ();
}


/**
 * @brief outgoing ego selector changed, (re)start the phone.
 *
 * @param widget the combo box that changed
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_ego_combobox_changed_cb (GtkComboBox *widget,
                                                 gpointer user_data)
{
  struct GNUNET_IDENTITY_Ego *caller_id;
  const struct GNUNET_CONFIGURATION_Handle *cfg;
  unsigned long long line;

  if (NULL != phone)
  {
    GSC_remove_phone ();
    GNUNET_CONVERSATION_phone_destroy (phone);
    phone = NULL;
  }
  caller_id = GCG_EGOS_get_selected_ego ();
  if (NULL == caller_id)
  {
    do_status ();
    check_call_sensitivity ();
    return;
  }
  cfg = GCG_get_configuration ();
  if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (cfg,
                                                          "CONVERSATION",
                                                          "LINE",
                                                          &line))
  {
    GCG_log (_ ("No phone line specified in configuration!\n"));
    do_status ();
    check_call_sensitivity ();
    return;
  }
  GCG_log (_ ("Initializing phone on line %llu\n"), line);
  phone = GNUNET_CONVERSATION_phone_create (cfg,
                                            caller_id,
                                            &phone_event_handler,
                                            NULL);
  if (NULL == phone)
  {
    check_call_sensitivity ();
    do_status ();
    return;
  }
  update_phone_record ();
  update_copy_sensitivity ();
  check_call_sensitivity ();
  do_status ();
}


/**
 * Initialize phone subsystem.
 */
void
GCG_PHONE_init ()
{
  const struct GNUNET_CONFIGURATION_Handle *cfg;

  cfg = GCG_get_configuration ();
  speaker = GNUNET_SPEAKER_create_from_hardware (cfg);
  mic = GNUNET_MICROPHONE_create_from_hardware (cfg);
  b_add_contact = GTK_WIDGET (
    GCG_get_main_window_object ("gnunet_conversation_gtk_add_contact_button"));
  b_accept = GTK_WIDGET (
    GCG_get_main_window_object ("gnunet_conversation_gtk_accept_button"));
  b_refuse = GTK_WIDGET (
    GCG_get_main_window_object ("gnunet_conversation_gtk_refuse_button"));
  b_suspend = GTK_WIDGET (
    GCG_get_main_window_object ("gnunet_conversation_gtk_suspend_button"));
  b_resume = GTK_WIDGET (
    GCG_get_main_window_object ("gnunet_conversation_gtk_resume_button"));
  b_call = GTK_WIDGET (
    GCG_get_main_window_object ("gnunet_conversation_gtk_call_button"));
  b_hangup = GTK_WIDGET (
    GCG_get_main_window_object ("gnunet_conversation_gtk_hangup_button"));
  b_copy = GTK_WIDGET (
    GCG_get_main_window_object ("gnunet_conversation_gtk_ego_copy_button"));
  address_entry = GTK_ENTRY (
    GCG_get_main_window_object ("gnunet_conversation_gtk_address_entry"));
  active_liststore = GTK_LIST_STORE (GCG_get_main_window_object (
    "gnunet_conversation_gtk_active_calls_liststore"));
  active_treeview = GTK_TREE_VIEW (GCG_get_main_window_object (
    "gnunet_conversation_gtk_active_calls_treeview"));
  do_status ();
}


/**
 * Shutdown phone subsystem.
 */
void
GCG_PHONE_shutdown ()
{
  if (NULL != active_in)
  {
    destroy_in (active_in);
    active_in = NULL;
  }
  if (NULL != active_out)
  {
    destroy_out (active_out);
    active_out = NULL;
  }
  if (NULL != phone)
  {
    GSC_remove_phone ();
    GNUNET_CONVERSATION_phone_destroy (phone);
    phone = NULL;
  }
  gtk_list_store_clear (active_liststore);
  GNUNET_SPEAKER_destroy (speaker);
  speaker = NULL;
  GNUNET_MICROPHONE_destroy (mic);
  mic = NULL;
}

/* end of gnunet-conversation-gtk_phone.c */
