/*
     This file is part of GNUnet.
     Copyright (C) 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 src/setup/gnunet-setup-exit-services.c
 * @brief code for the dialog to configure EXIT records
 * @author Christian Grothoff
 */
#include "gnunet-setup.h"
#include <gnunet/gnunet_util_lib.h>
#include <gdk/gdkkeysyms.h>


/**
 * Columns in the hosted service model.
 */
enum ServiceModelColumns
{
  /**
   * A gchararray
   */
  SERVICE_MC_SERVICE_NAME = 0,

  /**
   * A gboolean
   */
  SERVICE_MC_ISUDP = 1,

  /**
   * A guint
   */
  SERVICE_MC_VISIBLE_PORT = 2,

  /**
   * Destination adddress, a gchararray
   */
  SERVICE_MC_DESTINATION_ADDRESS = 3

};


/**
 * Check if the section represents a hosted service and then update
 * the GtkListStore accordingly.
 *
 * @param cls the list store to modify
 * @param section name of the section
 */
static void
add_name_entry_to_list_store (void *cls, const char *section)
{
  GtkListStore *ls = cls;
  GtkTreeIter iter;
  char *sld;
  char *destination;
  char *redirect;
  char *cpy;
  gboolean udp;
  guint local_port;

  if (NULL == section)
  {
    gtk_list_store_insert_with_values (ls,
                                       &iter,
                                       G_MAXINT,
                                       SERVICE_MC_SERVICE_NAME,
                                       "",
                                       SERVICE_MC_ISUDP,
                                       FALSE,
                                       SERVICE_MC_VISIBLE_PORT,
                                       (guint) 80,
                                       SERVICE_MC_DESTINATION_ADDRESS,
                                       "169.254.86.1:8080",
                                       -1);
    return;
  }

  if ((8 > strlen (section)) ||
      (0 != strcmp (".gnunet.", section + ((strlen (section) - 8)))))
    return;
  sld = GNUNET_strdup (section);
  sld[strlen (section) - 8] = '\0';
  udp = FALSE;
  do
  {
    if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg,
                                                            section,
                                                            (udp)
                                                              ? "UDP_REDIRECTS"
                                                              : "TCP_REDIRECTS",
                                                            &cpy))
    {
      for (redirect = strtok (cpy, " "); NULL != redirect;
           redirect = strtok (NULL, " "))
      {
        if (NULL == (destination = strstr (redirect, ":")))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                      _ ("Option `%s' is not formatted correctly!\n"),
                      redirect);
          continue;
        }
        destination[0] = '\0';
        destination++;

        local_port = atoi (redirect);
        if (! ((local_port > 0) && (local_port < 65536)))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                      _ ("`%s' is not a valid port number!\n"),
                      redirect);
          continue;
        }
        gtk_list_store_insert_with_values (ls,
                                           &iter,
                                           0,
                                           SERVICE_MC_SERVICE_NAME,
                                           sld,
                                           SERVICE_MC_ISUDP,
                                           udp,
                                           SERVICE_MC_VISIBLE_PORT,
                                           local_port,
                                           SERVICE_MC_DESTINATION_ADDRESS,
                                           destination,
                                           -1);
      }
      GNUNET_free (cpy);
    }
    udp = ! udp;
  } while (udp);
  GNUNET_free (sld);
}


/**
 * Initialize the GtkListModel with the hosted service specification.
 *
 * @param cls NULL
 * @param section section with the value (NULL)
 * @param option option name (NULL)
 * @param value value as a string (NULL)
 * @param widget widget to initialize (the GtkTreeView)
 * @param cfg configuration handle
 * @return #GNUNET_OK on success, #GNUNET_SYSERR if there was a problem
 */
int
load_hosted_service_configuration (
  const void *cls,
  const char *section,
  const char *option,
  const char *value,
  GObject *widget,
  const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  GtkTreeView *tv;
  GtkListStore *ls;

  tv = GTK_TREE_VIEW (widget);
  if (NULL == tv)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  ls = GTK_LIST_STORE (gtk_tree_view_get_model (tv));
  GNUNET_CONFIGURATION_iterate_sections (cfg,
                                         &add_name_entry_to_list_store,
                                         ls);
  /* finally, add empty entry */
  add_name_entry_to_list_store (ls, NULL);
  return GNUNET_OK;
}


/**
 * Records we use to build DNS information lists.
 */
struct HostedServiceInfo
{
  /**
   * We keep these in a singly-linked list.
   */
  struct HostedServiceInfo *next;

  /**
   * Name of the section in the configuration (must end in ".gnunet.").
   */
  char *section;

  /**
   * String describing all TCP redirects.
   */
  char *tcpred;

  /**
   * String describing all UDP redirects.
   */
  char *udpred;
};


/**
 * Function called for each section in the configuration.
 * Gather existing ttl, section names and altnames.
 *
 * @param cls 'struct HostedServiceInfo**' to create
 * @param section name of a section in the configuration
 */
static void
collect_hosted_service_sections (void *cls, const char *section)
{
  struct HostedServiceInfo **base = cls;
  struct HostedServiceInfo *pos;

  if ((8 > strlen (section)) ||
      (0 != strcmp (".gnunet.", section + ((strlen (section) - 8)))))
    return;
  pos = GNUNET_new (struct HostedServiceInfo);
  pos->section = GNUNET_strdup (section);
  pos->tcpred = GNUNET_strdup ("");
  pos->udpred = GNUNET_strdup ("");
  pos->next = *base;
  *base = pos;
}


/**
 * Function called for each section in the configuration.
 * Removes those ending in '.gnunet.'.
 *
 * @param cls unused
 * @param section name of a section in the configuration
 */
static void
remove_hosted_service_sections (void *cls, const char *section)
{
  if ((8 > strlen (section)) ||
      (0 != strcmp (".gnunet.", section + ((strlen (section) - 8)))))
    return;
  GNUNET_CONFIGURATION_remove_section (cfg, section);
}


/**
 * Given the list store and the data in it, update the
 * configuration file accordingly.
 *
 * @param tm model to use
 */
static void
update_hosted_service_configuration (GtkTreeModel *tm)
{
  GtkTreeIter iter;
  gchar *name;
  guint srcport;
  gchar *targetaddress;
  gboolean is_udp;
  char *tmp;
  struct HostedServiceInfo *head;
  struct HostedServiceInfo *pos;

  head = NULL;
  GNUNET_CONFIGURATION_iterate_sections (cfg,
                                         &collect_hosted_service_sections,
                                         &head);
  if (gtk_tree_model_get_iter_first (tm, &iter))
    do
    {
      gtk_tree_model_get (tm,
                          &iter,
                          SERVICE_MC_SERVICE_NAME,
                          &name,
                          SERVICE_MC_ISUDP,
                          &is_udp,
                          SERVICE_MC_VISIBLE_PORT,
                          &srcport,
                          SERVICE_MC_DESTINATION_ADDRESS,
                          &targetaddress,
                          -1);
      if (0 != strlen (name))
      {
        pos = head;
        GNUNET_asprintf (&tmp, "%s.gnunet.", name);
        while ((NULL != pos) && (0 != strcasecmp (tmp, pos->section)))
          pos = pos->next;
        if (NULL == pos)
        {
          pos = GNUNET_new (struct HostedServiceInfo);
          pos->section = tmp;
          pos->tcpred = GNUNET_strdup ("");
          pos->udpred = GNUNET_strdup ("");
          pos->next = head;
          head = pos;
        }
        else
        {
          GNUNET_free (tmp);
        }
        GNUNET_asprintf (&tmp,
                         "%u:%s %s",
                         srcport,
                         targetaddress,
                         (is_udp) ? pos->udpred : pos->tcpred);
        if (is_udp)
        {
          GNUNET_free (pos->udpred);
          pos->udpred = tmp;
        }
        else
        {
          GNUNET_free (pos->tcpred);
          pos->tcpred = tmp;
        }
      }
      g_free (name);
      g_free (targetaddress);
    } while (TRUE == gtk_tree_model_iter_next (tm, &iter));
  GNUNET_CONFIGURATION_iterate_sections (cfg,
                                         &remove_hosted_service_sections,
                                         NULL);
  while (NULL != head)
  {
    pos = head;
    head = pos->next;
    if (strlen (pos->udpred) > 0)
      GNUNET_CONFIGURATION_set_value_string (cfg,
                                             pos->section,
                                             "UDP_REDIRECTS",
                                             pos->udpred);
    if (strlen (pos->tcpred) > 0)
      GNUNET_CONFIGURATION_set_value_string (cfg,
                                             pos->section,
                                             "TCP_REDIRECTS",
                                             pos->tcpred);
    GNUNET_free (pos->tcpred);
    GNUNET_free (pos->udpred);
    GNUNET_free (pos->section);
    GNUNET_free (pos);
  }
}


/**
 * The user has edited the DNS name of a service we're offering.
 * Update the GtkTreeModel (at the given path) and update the
 * respective service entry in the configuration file.  Finally,
 * if the edited path is for a "fresh" entry, create another empty
 * one at the bottom.  If the hostname was set to empty, remove
 * the entire entry from the configuration and the model.
 *
 * @param renderer GtkCellRendererText that changed
 * @param path GtkTreePath identifying where in the Model the change is
 * @param new_text the new text that was stored in the line
 * @param user_data NULL
 */
static void
save_hosted_service_name (GtkCellRendererText *renderer,
                          gchar *path,
                          gchar *new_text,
                          gpointer user_data)
{
  GtkTreeModel *tm;
  GtkListStore *ls;
  GtkTreeIter iter;
  gchar *old;

  tm = GTK_TREE_MODEL (
    GNUNET_SETUP_get_object ("GNUNET_setup_hosted_service_liststore"));
  if (NULL == tm)
  {
    GNUNET_break (0);
    return;
  }
  ls = GTK_LIST_STORE (tm);
  if (NULL == ls)
  {
    GNUNET_break (0);
    return;
  }
  if (TRUE != gtk_tree_model_get_iter_from_string (tm, &iter, path))
  {
    GNUNET_break (0);
    return;
  }
  gtk_tree_model_get (tm, &iter, SERVICE_MC_SERVICE_NAME, &old, -1);
  if ((0 != strlen (old)) && (0 == strlen (new_text)))
  {
    /* deletion */
    gtk_list_store_remove (ls, &iter);
    g_free (old);
    /* update configuration */
    update_hosted_service_configuration (tm);
    return;
  }
  /* update model */
  gtk_list_store_set (ls, &iter, SERVICE_MC_SERVICE_NAME, new_text, -1);
  /* update configuration */
  update_hosted_service_configuration (tm);
  if ((0 == strlen (old)) && (0 != strlen (new_text)))
  {
    /* need another empty entry at the end for future expansion */
    add_name_entry_to_list_store (GTK_LIST_STORE (tm), NULL);
  }
  g_free (old);
}


/**
 * Initialize the GtkListModel with the VPN's DNS service specification.
 *
 * @param cls NULL
 * @param section section with the value (NULL)
 * @param option option name (NULL)
 * @param widget widget to initialize (the GtkTreeView)
 * @param cfg configuration handle
 * @return #GNUNET_OK on success, #GNUNET_SYSERR if there was a problem
 */
int
hosted_service_name_install_edited_handler (
  const void *cls,
  const char *section,
  const char *option,
  GObject *widget,
  struct GNUNET_CONFIGURATION_Handle *cfg)
{
  static int once;
  GtkCellRendererText *rt;

  rt = GTK_CELL_RENDERER_TEXT (widget);
  if (NULL == rt)
    return GNUNET_SYSERR;
  if (0 != once++)
    return GNUNET_OK;
  g_signal_connect (rt, "edited", G_CALLBACK (&save_hosted_service_name), NULL);
  return GNUNET_OK;
}


/**
 * The user has edited the DNS name of a service we're offering.
 * Update the GtkTreeModel (at the given path) and update the
 * respective service entry in the configuration file.  Finally,
 * if the edited path is for a "fresh" entry, create another empty
 * one at the bottom.  If the hostname was set to empty, remove
 * the entire entry from the configuration and the model.
 *
 * @param renderer GtkCellRendererToggle that changed
 * @param path GtkTreePath identifying where in the Model the change is
 * @param user_data NULL
 */
static void
save_hosted_service_is_udp (GtkCellRendererToggle *renderer,
                            gchar *path,
                            gpointer user_data)
{
  GtkTreeModel *tm;
  GtkListStore *ls;
  GtkTreeIter iter;
  gboolean is_udp;

  tm = GTK_TREE_MODEL (
    GNUNET_SETUP_get_object ("GNUNET_setup_hosted_service_liststore"));
  if (NULL == tm)
  {
    GNUNET_break (0);
    return;
  }
  ls = GTK_LIST_STORE (tm);
  if (NULL == ls)
  {
    GNUNET_break (0);
    return;
  }
  if (TRUE != gtk_tree_model_get_iter_from_string (tm, &iter, path))
  {
    GNUNET_break (0);
    return;
  }
  /* update model */
  gtk_tree_model_get (tm, &iter, SERVICE_MC_ISUDP, &is_udp, -1);
  gtk_list_store_set (ls, &iter, SERVICE_MC_ISUDP, ! is_udp, -1);
  /* update configuration */
  update_hosted_service_configuration (tm);
}


/**
 * Initialize the GtkListModel with the VPN's DNS service specification.
 *
 * @param cls NULL
 * @param section section with the value (NULL)
 * @param option option name (NULL)
 * @param widget widget to initialize (the GtkTreeView)
 * @param cfg configuration handle
 * @return #GNUNET_OK on success, #GNUNET_SYSERR if there was a problem
 */
int
hosted_service_is_udp_install_toggled_handler (
  const void *cls,
  const char *section,
  const char *option,
  GObject *widget,
  struct GNUNET_CONFIGURATION_Handle *cfg)
{
  static int once;
  GtkCellRendererToggle *rt;

  rt = GTK_CELL_RENDERER_TOGGLE (widget);
  if (NULL == rt)
    return GNUNET_SYSERR;
  if (0 != once++)
    return GNUNET_OK;
  g_signal_connect (rt,
                    "toggled",
                    G_CALLBACK (&save_hosted_service_is_udp),
                    NULL);
  return GNUNET_OK;
}


/**
 * The user has edited the DNS name of a service we're offering.
 * Update the GtkTreeModel (at the given path) and update the
 * respective service entry in the configuration file.  Finally,
 * if the edited path is for a "fresh" entry, create another empty
 * one at the bottom.  If the hostname was set to empty, remove
 * the entire entry from the configuration and the model.
 *
 * @param renderer GtkCellRendererText that changed
 * @param path GtkTreePath identifying where in the Model the change is
 * @param new_text the new text that was stored in the line
 * @param user_data NULL
 */
static void
save_hosted_service_visible_port (GtkCellRendererText *renderer,
                                  gchar *path,
                                  gchar *new_text,
                                  gpointer user_data)
{
  GtkTreeModel *tm;
  GtkListStore *ls;
  GtkTreeIter iter;
  int port;

  if ((1 != sscanf (new_text, "%d", &port)) || (port < 1) ||
      (port > UINT16_MAX))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Illegal value `%s' for port\n"),
                new_text);
    return;
  }
  tm = GTK_TREE_MODEL (
    GNUNET_SETUP_get_object ("GNUNET_setup_hosted_service_liststore"));
  if (NULL == tm)
  {
    GNUNET_break (0);
    return;
  }
  ls = GTK_LIST_STORE (tm);
  if (NULL == ls)
  {
    GNUNET_break (0);
    return;
  }
  if (TRUE != gtk_tree_model_get_iter_from_string (tm, &iter, path))
  {
    GNUNET_break (0);
    return;
  }
  /* update model */
  gtk_list_store_set (ls, &iter, SERVICE_MC_VISIBLE_PORT, port, -1);
  /* update configuration */
  update_hosted_service_configuration (tm);
}


/**
 * Initialize the GtkListModel with the VPN's DNS service specification.
 *
 * @param cls NULL
 * @param section section with the value (NULL)
 * @param option option name (NULL)
 * @param widget widget to initialize (the GtkTreeView)
 * @param cfg configuration handle
 * @return #GNUNET_OK on success, #GNUNET_SYSERR if there was a problem
 */
int
hosted_service_visible_port_install_edited_handler (
  const void *cls,
  const char *section,
  const char *option,
  GObject *widget,
  struct GNUNET_CONFIGURATION_Handle *cfg)
{
  static int once;
  GtkCellRendererText *rt;

  rt = GTK_CELL_RENDERER_TEXT (widget);
  if (NULL == rt)
    return GNUNET_SYSERR;
  if (0 != once++)
    return GNUNET_OK;
  g_signal_connect (rt,
                    "edited",
                    G_CALLBACK (&save_hosted_service_visible_port),
                    NULL);
  return GNUNET_OK;
}


/**
 * The user has edited the DNS name of a service we're offering.
 * Update the GtkTreeModel (at the given path) and update the
 * respective service entry in the configuration file.  Finally,
 * if the edited path is for a "fresh" entry, create another empty
 * one at the bottom.  If the hostname was set to empty, remove
 * the entire entry from the configuration and the model.
 *
 * @param renderer GtkCellRendererText that changed
 * @param path GtkTreePath identifying where in the Model the change is
 * @param new_text the new text that was stored in the line
 * @param user_data NULL
 */
static void
save_hosted_service_destination (GtkCellRendererText *renderer,
                                 gchar *path,
                                 gchar *new_text,
                                 gpointer user_data)
{
  GtkTreeModel *tm;
  GtkListStore *ls;
  GtkTreeIter iter;
  struct sockaddr_in v4;
  struct sockaddr_in6 v6;

  if (('[' != new_text[0]) &&
      (GNUNET_OK !=
       GNUNET_STRINGS_to_address_ipv4 (new_text, strlen (new_text), &v4)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Illegal IPv4 destination address `%s'\n"),
                new_text);
    return;
  }
  if (('[' == new_text[0]) &&
      (GNUNET_OK !=
       GNUNET_STRINGS_to_address_ipv6 (new_text, strlen (new_text), &v6)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Illegal IPv6 destination address `%s'\n"),
                new_text);
    return;
  }
  tm = GTK_TREE_MODEL (
    GNUNET_SETUP_get_object ("GNUNET_setup_hosted_service_liststore"));
  if (NULL == tm)
  {
    GNUNET_break (0);
    return;
  }
  ls = GTK_LIST_STORE (tm);
  if (NULL == ls)
  {
    GNUNET_break (0);
    return;
  }
  if (TRUE != gtk_tree_model_get_iter_from_string (tm, &iter, path))
  {
    GNUNET_break (0);
    return;
  }
  /* update model */
  gtk_list_store_set (ls, &iter, SERVICE_MC_DESTINATION_ADDRESS, new_text, -1);
  /* update configuration */
  update_hosted_service_configuration (tm);
}


/**
 * Initialize the GtkListModel with the VPN's DNS service specification.
 *
 * @param cls NULL
 * @param section section with the value (NULL)
 * @param option option name (NULL)
 * @param widget widget to initialize (the GtkTreeView)
 * @param cfg configuration handle
 * @return #GNUNET_OK on success, #GNUNET_SYSERR if there was a problem
 */
int
hosted_service_destination_install_edited_handler (
  const void *cls,
  const char *section,
  const char *option,
  GObject *widget,
  struct GNUNET_CONFIGURATION_Handle *cfg)
{
  static int once;
  GtkCellRendererText *rt;

  rt = GTK_CELL_RENDERER_TEXT (widget);
  if (NULL == rt)
    return GNUNET_SYSERR;
  if (0 != once++)
    return GNUNET_OK;
  g_signal_connect (rt,
                    "edited",
                    G_CALLBACK (&save_hosted_service_destination),
                    NULL);
  return GNUNET_OK;
}


/* end of gnunet-setup-dns-exit.c */
