/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */

/* GIO - GLib Input, Output and Streaming Library
 *
 * Copyright (C) 2008 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"
#include <glib.h>
#include "glibintl.h"

#include <stdio.h>
#include <string.h>

#include "gthreadedresolver.h"
#include "gnetworkingprivate.h"

#include "gcancellable.h"
#include "gtask.h"
#include "gsocketaddress.h"


G_DEFINE_TYPE (GThreadedResolver, g_threaded_resolver, G_TYPE_RESOLVER)

static void
g_threaded_resolver_init (GThreadedResolver *gtr)
{
}

static void
do_lookup_by_name (GTask         *task,
                   gpointer       task_data,
                   GCancellable  *cancellable,
                   GError       **error)
{
  const char *hostname = task_data;
  struct addrinfo *res = NULL;
  GList *addresses;
  gint retval;

  retval = getaddrinfo (hostname, NULL, &_g_resolver_addrinfo_hints, &res);
  addresses = _g_resolver_addresses_from_addrinfo (hostname, res, retval, error);
  if (res)
    freeaddrinfo (res);

  g_task_set_result_gpointer (task, addresses,
                              (GDestroyNotify)g_resolver_free_addresses);
}

static GList *
lookup_by_name (GResolver     *resolver,
                const gchar   *hostname,
                GCancellable  *cancellable,
                GError       **error)
{
  GTask *task;
  GList *addresses;

  task = g_task_new_sync (resolver, g_strdup (hostname), g_free, cancellable);
  g_task_run_sync (task, do_lookup_by_name);
  addresses = g_task_propagate_result_gpointer (task, error);

  g_object_unref (task);
  return addresses;
}

static void
lookup_by_name_async (GResolver           *resolver,
                      const gchar         *hostname,
                      GCancellable        *cancellable,
                      GAsyncReadyCallback  callback,
                      gpointer             user_data)
{
  GTask *task;

  task = g_task_new (resolver, g_strdup (hostname), g_free,
                     G_PRIORITY_DEFAULT, cancellable,
                     callback, user_data);
  g_task_run_async (task, do_lookup_by_name);
  g_object_unref (task);
}

static GList *
lookup_by_name_finish (GResolver     *resolver,
                       GAsyncResult  *result,
                       GError       **error)
{
  g_return_val_if_fail (g_task_verify (result, resolver));

  return g_task_propagate_result_gpointer (G_TASK (result), error);
}


static void
do_lookup_by_address (GTask     *task,
                      gpointer   task_data,
                      GError   **error)
{
  GInetAddress *address = task_data;
  struct sockaddr_storage sockaddr;
  gsize sockaddr_size;
  gchar namebuf[NI_MAXHOST], *name;
  gint retval;

  _g_resolver_address_to_sockaddr (address, &sockaddr, &sockaddr_size);
  retval = getnameinfo ((struct sockaddr *)&sockaddr, sockaddr_size,
                        namebuf, sizeof (namebuf), NULL, 0, NI_NAMEREQD);
  name = _g_resolver_name_from_nameinfo (address, namebuf, retval, error);
  g_task_set_result_gpointer (task, name, g_free);
}

static gchar *
lookup_by_address (GResolver        *resolver,
                   GInetAddress     *address,
                   GCancellable     *cancellable,
                   GError          **error)
{
  GTask *task;
  gchar *name;

  task = g_task_new_sync (resolver, g_object_ref (address), g_object_unref, cancellable);
  g_task_run_sync (task, do_lookup_by_address);
  name = g_task_propagate_result_gpointer (task, error);

  g_object_unref (task);
  return name;
}

static void
lookup_by_address_async (GResolver           *resolver,
                         GInetAddress        *address,
                         GCancellable        *cancellable,
                         GAsyncReadyCallback  callback,
                         gpointer             user_data)
{
  GTask *task;

  task = g_task_new (resolver, g_object_ref (address), g_object_unref,
                     G_PRIORITY_DEFAULT, cancellable,
                     callback, user_data);
  g_task_run_async (task, do_lookup_by_address);
  g_object_unref (task);
}

static gchar *
lookup_by_address_finish (GResolver     *resolver,
                          GAsyncResult  *result,
                          GError       **error)
{
  g_return_val_if_fail (g_task_verify (result, resolver));

  return g_task_propagate_result_gpointer (G_TASK (result), error);
}


static void
do_lookup_service (GTask     *task,
                   gpointer   task_data,
                   GError   **error)
{
  const char *rrname = task_data;
  GList *targets;
#if defined(G_OS_UNIX)
  gint len, herr;
  guchar answer[1024];
#elif defined(G_OS_WIN32)
  DNS_STATUS status;
  DNS_RECORD *results;
#endif

#if defined(G_OS_UNIX)
  len = res_query (rrname, C_IN, T_SRV, answer, sizeof (answer));
  herr = h_errno;
  targets = _g_resolver_targets_from_res_query (rrname, answer, len, herr, error);
#elif defined(G_OS_WIN32)
  status = DnsQuery_A (rrname, DNS_TYPE_SRV,
                       DNS_QUERY_STANDARD, NULL, &results, NULL);
  targets = _g_resolver_targets_from_DnsQuery (rrname, status, results, error);
  DnsRecordListFree (results, DnsFreeRecordList);
#endif

  g_task_set_result_gpointer (task, targets,
                              (GDestroyNotify)g_resolver_free_targets);
}

static GList *
lookup_service (GResolver        *resolver,
                const gchar      *rrname,
		GCancellable     *cancellable,
                GError          **error)
{
  GTask *task;
  GList *targets;

  task = g_task_new_sync (resolver, g_strdup (rrname), g_free, cancellable);
  g_task_run_sync (task, do_lookup_service);
  targets = g_task_propagate_result_gpointer (task, error);

  g_object_unref (task);
  return targets;
}

static void
lookup_service_async (GResolver           *resolver,
                      const char          *rrname,
		      GCancellable        *cancellable,
                      GAsyncReadyCallback  callback,
		      gpointer             user_data)
{
  GTask *task;

  task = g_task_new (resolver, g_strdup (rrname), g_free,
                     G_PRIORITY_DEFAULT, cancellable,
                     callback, user_data);
  g_task_run_async (task, do_lookup_service);
  g_object_unref (task);
}

static GList *
lookup_service_finish (GResolver     *resolver,
                       GAsyncResult  *result,
		       GError       **error)
{
  g_return_val_if_fail (g_task_verify (result, resolver));

  return g_task_propagate_result_gpointer (G_TASK (result), error);
}


static void
g_threaded_resolver_class_init (GThreadedResolverClass *threaded_class)
{
  GResolverClass *resolver_class = G_RESOLVER_CLASS (threaded_class);
  GObjectClass *object_class = G_OBJECT_CLASS (threaded_class);

  resolver_class->lookup_by_name           = lookup_by_name;
  resolver_class->lookup_by_name_async     = lookup_by_name_async;
  resolver_class->lookup_by_name_finish    = lookup_by_name_finish;
  resolver_class->lookup_by_address        = lookup_by_address;
  resolver_class->lookup_by_address_async  = lookup_by_address_async;
  resolver_class->lookup_by_address_finish = lookup_by_address_finish;
  resolver_class->lookup_service           = lookup_service;
  resolver_class->lookup_service_async     = lookup_service_async;
  resolver_class->lookup_service_finish    = lookup_service_finish;
}
