/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * GIO - GLib Input, Output and Streaming Library
 *
 * Copyright 2010 Collabora, Ltd.
 *
 * 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.1 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, see
 * <http://www.gnu.org/licenses/>.
 *
 * Author: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
 */

#include "config.h"

#include <proxy.h>
#include <stdlib.h>
#include <string.h>

#include "glibproxyresolver.h"

#include <glib.h>
#include <glib/gi18n-lib.h>

struct _GLibproxyResolver {
  GObject parent_instance;
  pxProxyFactory *factory;
};

static void g_libproxy_resolver_iface_init (GProxyResolverInterface *iface);

#ifdef GLIBPROXY_MODULE
static void
g_libproxy_resolver_class_finalize (GLibproxyResolverClass *klass)
{
}

G_DEFINE_DYNAMIC_TYPE_EXTENDED (GLibproxyResolver,
                                g_libproxy_resolver,
                                G_TYPE_OBJECT, 0,
                                G_IMPLEMENT_INTERFACE_DYNAMIC (G_TYPE_PROXY_RESOLVER,
                                                               g_libproxy_resolver_iface_init))
#else
G_DEFINE_TYPE_EXTENDED (GLibproxyResolver,
                        g_libproxy_resolver,
                        G_TYPE_OBJECT, 0,
                        G_IMPLEMENT_INTERFACE (G_TYPE_PROXY_RESOLVER,
                                               g_libproxy_resolver_iface_init))
#endif

static void
g_libproxy_resolver_finalize (GObject *object)
{
  GLibproxyResolver *resolver = G_LIBPROXY_RESOLVER (object);
  
  if (resolver->factory)
    {
      px_proxy_factory_free (resolver->factory);
      resolver->factory = NULL;
    }

  /* must chain up */
  G_OBJECT_CLASS (g_libproxy_resolver_parent_class)->finalize (object);
}

static void
g_libproxy_resolver_init (GLibproxyResolver *resolver)
{
  resolver->factory = px_proxy_factory_new ();
}

static gboolean
g_libproxy_resolver_is_supported (GProxyResolver *object)
{
  GLibproxyResolver *resolver = G_LIBPROXY_RESOLVER (object);
  return resolver->factory != NULL;
}

static gchar **
copy_proxies (gchar **proxies)
{
  gchar **copy;
  int len = 0;
  int i, j;

  for (i = 0; proxies[i]; i++)
    {
      if (!strncmp ("socks://", proxies[i], 8))
        len += 3;
      else
        len++;
    }

  copy = g_new (gchar *, len + 1);
  for (i = j = 0; proxies[i]; i++, j++)
    {
      if (!strncmp ("socks://", proxies[i], 8))
        {
          copy[j++] = g_strdup_printf ("socks5://%s", proxies[i] + 8);
          copy[j++] = g_strdup_printf ("socks4a://%s", proxies[i] + 8);
          copy[j] = g_strdup_printf ("socks4://%s", proxies[i] + 8);
        }
      else
        {
          copy[j] = g_strdup (proxies[i]);
        }
    }
  copy[j] = NULL;

  return copy;
}

/* FIXME: this function should be removed and replaced by a call to
 * px_proxy_factory_free_proxies() once libproxy 0.4.16 is released.
 * Sadly libproxy does not have any version check macros so it will
 * have to be a hard dep.
 */
static void
free_libproxy_proxies (gchar **proxies)
{
  int i;

  for (i = 0; proxies[i]; i++)
    free (proxies[i]);
  free (proxies);
}

static void
get_libproxy_proxies (GTask        *task,
                      gpointer      source_object,
                      gpointer      task_data,
                      GCancellable *cancellable)
{
  GLibproxyResolver *resolver = source_object;
  const gchar *uri = task_data;
  GError *error = NULL;
  gchar **proxies;

  if (g_task_return_error_if_cancelled (task))
    return;

  proxies = px_proxy_factory_get_proxies (resolver->factory, uri);
  if (proxies)
    {
      /* We always copy to be able to translate "socks" entry into
       * three entries ("socks5", "socks4a", "socks4").
       */
      g_task_return_pointer (task, copy_proxies (proxies), (GDestroyNotify) g_strfreev);
      free_libproxy_proxies (proxies);
    }
  else
    {
      g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
                           _("Proxy resolver internal error."));
      g_task_return_error (task, error);
    }
}

static gchar **
g_libproxy_resolver_lookup (GProxyResolver  *iresolver,
                            const gchar     *uri,
                            GCancellable    *cancellable,
                            GError         **error)
{
  GLibproxyResolver *resolver = G_LIBPROXY_RESOLVER (iresolver);
  GTask *task;
  gchar **proxies;

  task = g_task_new (resolver, cancellable, NULL, NULL);
  g_task_set_name (task, "[glib-networking] g_libproxy_resolver_lookup");
  g_task_set_task_data (task, g_strdup (uri), g_free);
  g_task_set_return_on_cancel (task, TRUE);

  g_task_run_in_thread_sync (task, get_libproxy_proxies);
  proxies = g_task_propagate_pointer (task, error);
  g_object_unref (task);

  return proxies;
}

static void
g_libproxy_resolver_lookup_async (GProxyResolver      *resolver,
                                  const gchar         *uri,
                                  GCancellable        *cancellable,
                                  GAsyncReadyCallback  callback,
                                  gpointer             user_data)
{
  GTask *task;

  task = g_task_new (resolver, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_libproxy_resolver_lookup_async);
  g_task_set_name (task, "[glib-networking] g_libproxy_resolver_lookup_async");
  g_task_set_task_data (task, g_strdup (uri), g_free);
  g_task_set_return_on_cancel (task, TRUE);
  g_task_run_in_thread (task, get_libproxy_proxies);
  g_object_unref (task);
}

static gchar **
g_libproxy_resolver_lookup_finish (GProxyResolver     *resolver,
                                   GAsyncResult       *result,
                                   GError            **error)
{
  g_return_val_if_fail (g_task_is_valid (result, resolver), NULL);
  g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == g_libproxy_resolver_lookup_async, NULL);

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

static void
g_libproxy_resolver_class_init (GLibproxyResolverClass *resolver_class)
{
  GObjectClass *object_class;
  
  object_class = G_OBJECT_CLASS (resolver_class);
  object_class->finalize = g_libproxy_resolver_finalize;
}

static void
g_libproxy_resolver_iface_init (GProxyResolverInterface *iface)
{
  iface->is_supported = g_libproxy_resolver_is_supported;
  iface->lookup = g_libproxy_resolver_lookup;
  iface->lookup_async = g_libproxy_resolver_lookup_async;
  iface->lookup_finish = g_libproxy_resolver_lookup_finish;
}

#ifdef GLIBPROXY_MODULE
void
g_libproxy_resolver_register (GIOModule *module)
{
  g_libproxy_resolver_register_type (G_TYPE_MODULE (module));
  if (!module)
    g_io_extension_point_register (G_PROXY_RESOLVER_EXTENSION_POINT_NAME);
  g_io_extension_point_implement (G_PROXY_RESOLVER_EXTENSION_POINT_NAME,
                                  g_libproxy_resolver_get_type(),
                                  "libproxy",
                                  0);
}
#endif
