/*
     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_import.c
 * @brief handle request to import caller into address book
 * @author yids
 * @author hark
 * @author Christian Grothoff
 */
#include "gnunet-conversation-gtk.h"
#include "gnunet-conversation-gtk_contacts.h"
#include "gnunet-conversation-gtk_egos.h"
#include "gnunet-conversation-gtk_import.h"
#include "gnunet-conversation-gtk_zones.h"


/**
 * Handle to the namestore.
 */
static struct GNUNET_NAMESTORE_Handle *ns;

/**
 * Queue entry for the 'add contact' operation.
 */
static struct GNUNET_NAMESTORE_QueueEntry *add_contact_qe;

/**
 * Queue entry for the 'add phone' operation.
 */
static struct GNUNET_NAMESTORE_QueueEntry *add_phone_qe;

/**
 * Queue entry for the 'remove phone' operation.
 */
static struct GNUNET_NAMESTORE_QueueEntry *remove_phone_qe;

/**
 * Flag set to #GNUNET_YES if we are in shutdown.
 */
static int in_shutdown;

/**
 * Flag set to #GNUNET_YES if an 'add phone' operation is
 * waiting for a 'remove phone' operation to complete first.
 */
static int add_waiting;

/**
 * Our current phone label.
 */
static char *phone_label;

/**
 * Current private key for the phone entry's zone.
 */
static struct GNUNET_IDENTITY_PrivateKey zone_pkey;

/**
 * Our current phone record.
 */
static struct GNUNET_GNSRECORD_Data my_rd;

/**
 * Binary data for the current phone record (`my_rd.data`).
 */
static char *my_rd_data;

/**
 * When does our phone record expire?
 */
static struct GNUNET_TIME_Relative expiration_time;

/**
 * Is our phone record private?
 */
static int private_record;


/**
 * Continuation called to notify client about result of the
 * operation.
 *
 * @param cls NULL
 * @param ec the error code
 */
static void
add_contact_continuation (void *cls, enum GNUNET_ErrorCode ec)
{
  if (GNUNET_EC_NONE != ec)
    GCG_log (_ ("Adding contact failed: %s\n"),
             GNUNET_ErrorCode_get_hint (ec));
  add_contact_qe = NULL;
}


/**
 * Add new contact to the address book.
 *
 * @param name name to use for the contact
 * @param address public key of the contact
 */
void
GSC_add_contact (const gchar *name, const gchar *address)
{
  struct GNUNET_GNSRECORD_Data rd;
  struct GNUNET_IDENTITY_PublicKey rvalue;
  const void *value;
  struct GNUNET_IDENTITY_Ego *ego;
  size_t value_size;
  const struct GNUNET_IDENTITY_PrivateKey *zkey;
  uint32_t type;
  char cname[256];
  const char *tld;

  if (GNUNET_OK != GNUNET_DNSPARSER_check_name (address))
  {
    GCG_log (_ ("Domain name `%s' invalid\n"), address);
    return;
  }
  if (NULL != add_contact_qe)
  {
    GCG_log (_ ("Adding contact failed: %s\n"),
             _ ("previous operation still pending"));
    return;
  }

  /* if we got a .zkey address, we create a PKEY record,
     otherwise a CNAME record */
  if ((GNUNET_OK == GNUNET_GNSRECORD_zkey_to_pkey (address, &rvalue)) ||
      ((0 == strncasecmp (address, "phone.", strlen ("phone."))) &&
       (GNUNET_OK ==
        GNUNET_GNSRECORD_zkey_to_pkey (&address[strlen ("phone.")], &rvalue))))
  {
    type = GNUNET_GNSRECORD_TYPE_PKEY;
    value = &rvalue;
    value_size = GNUNET_IDENTITY_public_key_get_length (&rvalue);
  }
  else
  {
    value_size = 0;
    if (GNUNET_OK != GNUNET_DNSPARSER_builder_add_name (cname,
                                                        sizeof (cname),
                                                        &value_size,
                                                        address))
    {
      GNUNET_break (0);
      GCG_log (_ ("Failed to create CNAME record for domain name `%s'\n"),
               address);
      return;
    }
    type = GNUNET_DNSPARSER_TYPE_CNAME;
    value = cname;
  }
  rd.data = value;
  rd.data_size = value_size;
  rd.record_type = type;
  rd.flags =
    GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION | GNUNET_GNSRECORD_RF_PRIVATE;
  rd.expiration_time = GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us;
  ego = GCG_ZONES_get_selected_zone (&tld);
  zkey = GNUNET_IDENTITY_ego_get_private_key (ego);
  add_contact_qe = GNUNET_NAMESTORE_records_store (ns,
                                                   zkey,
                                                   name,
                                                   1,
                                                   &rd,
                                                   &add_contact_continuation,
                                                   NULL);
}


/**
 * Continuation called to notify client about result of the
 * add operation.  Finish 'add phone' operation.
 *
 * @param cls closure
 * @param ec the errro code
 */
static void
add_phone_continuation (void *cls, enum GNUNET_ErrorCode ec)
{
  add_phone_qe = NULL;
  if (GNUNET_EC_NONE != ec)
  {
    GCG_log (_ ("Failed to publish my PHONE record: %s\n"),
             GNUNET_ErrorCode_get_hint (ec));
    GNUNET_free (phone_label);
    phone_label = NULL;
  }
}


/**
 * Process a record that was stored in the namestore.
 *
 * @param cls closure, NULL
 * @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
add_phone_handle_existing_records (
  void *cls,
  const struct GNUNET_IDENTITY_PrivateKey *zone,
  const char *label,
  unsigned int rd_count,
  const struct GNUNET_GNSRECORD_Data *rd)
{
  struct GNUNET_GNSRECORD_Data rd_new[rd_count + 1];

  add_phone_qe = NULL;
  memcpy (rd_new, rd, sizeof (struct GNUNET_GNSRECORD_Data) * rd_count);
  rd_new[rd_count] = my_rd;
  add_phone_qe = GNUNET_NAMESTORE_records_store (ns,
                                                 zone,
                                                 label,
                                                 rd_count + 1,
                                                 rd_new,
                                                 &add_phone_continuation,
                                                 NULL);
}


/**
 * Called on error communicating with the namestore.
 */
static void
handle_add_error (void *cls)
{
  add_phone_qe = NULL;
  GCG_log (_ ("Error communicating with namestore!\n"));
  GNUNET_free (phone_label);
  phone_label = NULL;
}


/**
 * Add the data from #my_rd under the #phone_label to the namestore.
 */
static void
add_phone ()
{
  struct GNUNET_IDENTITY_Ego *ego;

  GNUNET_assert (NULL == remove_phone_qe);
  GNUNET_assert (NULL == add_phone_qe);
  add_waiting = GNUNET_NO;
  ego = GCG_EGOS_get_selected_ego ();
  zone_pkey = *GNUNET_IDENTITY_ego_get_private_key (ego);
  if (0 == strlen (phone_label))
    return;
  add_phone_qe =
    GNUNET_NAMESTORE_records_lookup (ns,
                                     &zone_pkey,
                                     phone_label,
                                     &handle_add_error,
                                     NULL,
                                     &add_phone_handle_existing_records,
                                     NULL);
}


/**
 * Add phone address to namestore.
 *
 * @param label label to use for the phone record
 * @param rd record data to publish
 */
void
GSC_add_phone (const gchar *label, const struct GNUNET_GNSRECORD_Data *rd)
{
  if (NULL != add_phone_qe)
  {
    GNUNET_NAMESTORE_cancel (add_phone_qe);
    add_phone_qe = NULL;
  }
  if (NULL != phone_label)
    GSC_remove_phone ();
  GNUNET_break (NULL == phone_label);
  GNUNET_free (my_rd_data);
  phone_label = GNUNET_strdup (label);
  my_rd = *rd;
  my_rd.expiration_time = expiration_time.rel_value_us;
  my_rd.flags |= GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
  if (GNUNET_YES == private_record)
    my_rd.flags |= GNUNET_GNSRECORD_RF_PRIVATE;

  my_rd.data = my_rd_data = GNUNET_malloc (rd->data_size);
  memcpy (my_rd_data, rd->data, rd->data_size);
  add_waiting = GNUNET_YES;
  if (NULL != remove_phone_qe)
    return; /* do not add another record until previous one is removed */
  add_phone ();
}


/**
 * Asynchronously disconnect from the namestore.
 *
 * @param cls closure (NULL)
 */
static void
async_disconnect (void *cls)
{
  if (NULL != ns)
  {
    GNUNET_NAMESTORE_disconnect (ns);
    ns = NULL;
  }
}


/**
 * Run last parts of shutdown operation (after last
 * remove has completed).
 */
static void
finish_shutdown ()
{
  GNUNET_SCHEDULER_add_now (&async_disconnect, NULL);
  GNUNET_free (my_rd_data);
  memset (&my_rd, 0, sizeof (my_rd));
  memset (&zone_pkey, 0, sizeof (zone_pkey));
}


/**
 * Continuation called to notify client about result of the
 * remove operation.  Finish remove operation and continue
 * with 'add phone' operation if one is pending.
 *
 * @param cls closure
 * @param ec the error code
 */
static void
remove_phone_continuation (void *cls, enum GNUNET_ErrorCode ec)
{
  remove_phone_qe = NULL;
  if (GNUNET_EC_NONE != ec)
    GCG_log (_ ("Failed to remove PHONE record: %s\n"),
             GNUNET_ErrorCode_get_hint (ec));
  if (GNUNET_YES == add_waiting)
    add_phone ();
  if (GNUNET_YES == in_shutdown)
    finish_shutdown ();
}


/**
 * Process records stored in the namestore.  Locates our
 * PHONE record and removes it.
 *
 * @param cls closure, NULL
 * @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
remove_phone_handle_existing_records (
  void *cls,
  const struct GNUNET_IDENTITY_PrivateKey *zone,
  const char *label,
  unsigned int rd_count,
  const struct GNUNET_GNSRECORD_Data *rd)
{
  struct GNUNET_GNSRECORD_Data rd_left[rd_count];
  unsigned int i;
  unsigned int j;

  remove_phone_qe = NULL;
  j = 0;
  for (i = 0; i < rd_count; i++)
  {
    if (GNUNET_GNSRECORD_TYPE_PHONE == rd[i].record_type)
      continue;
    rd_left[j++] = rd[i];
  }
  remove_phone_qe = GNUNET_NAMESTORE_records_store (ns,
                                                    zone,
                                                    label,
                                                    j,
                                                    rd_left,
                                                    &remove_phone_continuation,
                                                    NULL);
}


/**
 * Called on error communicating with the namestore.
 */
static void
handle_remove_error (void *cls)
{
  remove_phone_qe = NULL;
  GCG_log (_ ("Error communicating with namestore!\n"));
  if (GNUNET_YES == in_shutdown)
    finish_shutdown ();
}


/**
 * Remove previously added phone address from namestore.
 */
void
GSC_remove_phone ()
{
  if (NULL == phone_label)
    return;
  GNUNET_assert (NULL == remove_phone_qe);
  if (0 == strlen (phone_label))
    return;
  remove_phone_qe =
    GNUNET_NAMESTORE_records_lookup (ns,
                                     &zone_pkey,
                                     phone_label,
                                     &handle_remove_error,
                                     NULL,
                                     &remove_phone_handle_existing_records,
                                     NULL);
  GNUNET_free (phone_label);
  phone_label = NULL;
}


/**
 * Obtain namestore handle.
 *
 * @return handle to the namestore.
 */
struct GNUNET_NAMESTORE_Handle *
GCG_IMPORT_get_namestore ()
{
  return ns;
}


/**
 * Initialize the import subsystem.
 */
void
GCG_IMPORT_init ()
{
  const struct GNUNET_CONFIGURATION_Handle *cfg;

  cfg = GCG_get_configuration ();
  ns = GNUNET_NAMESTORE_connect (cfg);
  private_record = GNUNET_CONFIGURATION_get_value_yesno (cfg,
                                                         "CONVERSATION",
                                                         "RECORD_IS_PRIVATE");
  if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg,
                                                        "CONVERSATION",
                                                        "RECORD_EXPIRATION",
                                                        &expiration_time))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "CONVERSATION",
                               "RECORD_EXPIRATION");
  }
}


/**
 * Shutdown the import subsystem.
 */
void
GCG_IMPORT_shutdown ()
{
  if (NULL != add_contact_qe)
  {
    GNUNET_NAMESTORE_cancel (add_contact_qe);
    add_contact_qe = NULL;
  }
  if (NULL != add_phone_qe)
  {
    GNUNET_NAMESTORE_cancel (add_phone_qe);
    add_phone_qe = NULL;
  }
  add_waiting = GNUNET_NO;
  in_shutdown = GNUNET_YES;
  GSC_remove_phone ();
  if (NULL != remove_phone_qe)
    return;
  finish_shutdown ();
}


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