/***************************************************************************
 * CVSID: $Id$
 *
 * hf-util.c : utilities
 *
 * Copyright (C) 2006 Jean-Yves Lefort <jylefort@FreeBSD.org>
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 **************************************************************************/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <glib.h>

#include "../hald_runner.h"
#include "../osspec.h"
#include "../util.h"
#include "../device_info.h"

#include "hf-util.h"

typedef struct
{
  gboolean done;
} AsyncInfo;

typedef struct
{
  int exit_type;
  int return_code;
} RunnerInfo;

gboolean hf_is_waiting = FALSE;

static void
hf_async_init (AsyncInfo *info)
{
  g_return_if_fail(info != NULL);

  info->done = FALSE;
}

static void
hf_async_wait (AsyncInfo *info)
{
  g_return_if_fail(info != NULL);
  g_return_if_fail(hf_is_waiting == FALSE);

  while (! info->done)
    {
      hf_is_waiting = TRUE;
      g_main_context_iteration(NULL, TRUE);
      hf_is_waiting = FALSE;
    }

  info->done = FALSE;		/* reset in case they want to call us again */
}

HalDevice *
hf_device_new (HalDevice *parent)
{
  HalDevice *device;

  device = hal_device_new();

  hal_device_property_set_string(device, "info.parent", parent ? parent->udi : HF_COMPUTER);

  return device;
}

static void
hf_device_callout_done_cb (HalDevice *device, gpointer data1, gpointer data2)
{
  AsyncInfo *async = data1;

  async->done = TRUE;
}

gboolean
hf_device_preprobe (HalDevice *device)
{
  AsyncInfo async;
  gboolean ignore;

  g_return_val_if_fail(HAL_IS_DEVICE(device), FALSE);

  /* add to temporary device store */
  hal_device_store_add(hald_get_tdl(), device);

  /* process preprobe fdi files */
  di_search_and_merge(device, DEVICE_INFO_TYPE_PREPROBE);

  /* run preprobe callouts */
  hf_async_init(&async);
  hal_util_callout_device_preprobe(device, hf_device_callout_done_cb, &async, NULL);
  hf_async_wait(&async);

  ignore = hal_device_property_get_bool(device, "info.ignore");
  if (ignore)
    {
      hal_device_property_remove(device, "info.category");
      hal_device_property_remove(device, "info.capabilities");
      hal_device_property_set_string(device, "info.udi", "/org/freedesktop/Hal/devices/ignored-device");
      hal_device_property_set_string(device, "info.product", "Ignored Device");

      /* move from temporary to global device store */
      hal_device_store_remove(hald_get_tdl(), device);
      hal_device_store_add(hald_get_gdl(), device);
    }

  return ! ignore;
}

void
hf_device_add (HalDevice *device)
{
  AsyncInfo async;

  g_return_if_fail(HAL_IS_DEVICE(device));

  /* process information and policy fdi files */
  di_search_and_merge(device, DEVICE_INFO_TYPE_INFORMATION);
  di_search_and_merge(device, DEVICE_INFO_TYPE_POLICY);

  /* run add callouts */
  hf_async_init(&async);
  hal_util_callout_device_add(device, hf_device_callout_done_cb, &async, NULL);
  hf_async_wait(&async);

  /* move from temporary to global device store */
  hal_device_store_remove(hald_get_tdl(), device);
  hal_device_store_add(hald_get_gdl(), device);
}

gboolean
hf_device_preprobe_and_add (HalDevice *device)
{
  g_return_val_if_fail(HAL_IS_DEVICE(device), FALSE);

  if (hf_device_preprobe(device))
    {
      hf_device_add(device);
      return TRUE;
    }
  else
    return FALSE;
}

void
hf_device_remove (HalDevice *device)
{
  AsyncInfo async;

  g_return_if_fail(HAL_IS_DEVICE(device));

  hf_async_init(&async);
  hal_util_callout_device_remove(device, hf_device_callout_done_cb, &async, NULL);
  hf_async_wait(&async);

  hal_device_store_remove(hald_get_gdl(), device);
  g_object_unref(device);
}

void
hf_device_remove_children (HalDevice *device)
{
  GSList *children;

  g_return_if_fail(HAL_IS_DEVICE(device));

  children = hf_device_store_get_children(hald_get_gdl(), device);
  children = g_slist_reverse(children);	/* remove leaves first */
  g_slist_foreach(children, (GFunc) hf_device_remove, NULL);
  g_slist_free(children);
}

/* remove the device and all its children */
void
hf_device_remove_tree (HalDevice *device)
{
  g_return_if_fail(HAL_IS_DEVICE(device));

  hf_device_remove_children(device);
  hf_device_remove(device);
}

void
hf_device_set_udi (HalDevice *device, const char *format, ...)
{
  va_list args;
  char *udi;

  g_return_if_fail(HAL_IS_DEVICE(device));
  g_return_if_fail(format != NULL);

  va_start(args, format);
  udi = g_strdup_vprintf(format, args);
  va_end(args);

  hf_device_set_full_udi(device, "/org/freedesktop/Hal/devices/%s", udi);
  g_free(udi);
}

void
hf_device_set_full_udi (HalDevice *device, const char *format, ...)
{
  va_list args;
  char *requested_udi;
  char actual_udi[256];

  g_return_if_fail(HAL_IS_DEVICE(device));
  g_return_if_fail(format != NULL);

  va_start(args, format);
  requested_udi = g_strdup_vprintf(format, args);
  va_end(args);

  hal_util_compute_udi(hald_get_gdl(), actual_udi, sizeof(actual_udi), "%s", requested_udi);
  g_free(requested_udi);

  hal_device_set_udi(device, actual_udi);
  hal_device_property_set_string(device, "info.udi", actual_udi);
}

void
hf_device_property_set_string_printf (HalDevice *device,
				      const char *key,
				      const char *format,
				      ...)
{
  char *value = NULL;

  g_return_if_fail(HAL_IS_DEVICE(device));
  g_return_if_fail(key != NULL);

  if (format)
    {
      va_list args;

      va_start(args, format);
      value = g_strdup_vprintf(format, args);
      va_end(args);
    }

  hal_device_property_set_string(device, key, value);
  g_free(value);
}

void
hf_device_set_input (HalDevice *device,
		     const char *class,
		     const char *devname)
{
  g_return_if_fail(HAL_IS_DEVICE(device));

  hal_device_add_capability(device, "input");

  if (class)
    {
      char *category;

      category = g_strdup_printf("input.%s", class);
      hal_device_property_set_string(device, "info.category", category);
      hal_device_add_capability(device, category);
      g_free(category);
    }
  else
    hal_device_property_set_string(device, "info.category", "input");

  if (devname)
    hf_device_property_set_string_printf(device, "input.device", "/dev/%s", devname);
  else
    hal_device_property_set_string(device, "input.device", NULL);
}

gboolean
hal_properties_equal (HalProperty *a, HalProperty *b)
{
  g_return_val_if_fail(a != NULL, FALSE);
  g_return_val_if_fail(b != NULL, FALSE);

  if (hal_property_get_type(a) != hal_property_get_type(b))
    return FALSE;

  switch (hal_property_get_type(a))
    {
    case HAL_PROPERTY_TYPE_INT32:
      return hal_property_get_int(a) == hal_property_get_int(b);

    case HAL_PROPERTY_TYPE_UINT64:
      return hal_property_get_uint64(a) == hal_property_get_uint64(b);

    case HAL_PROPERTY_TYPE_DOUBLE:
      return hal_property_get_double(a) > hal_property_get_double(b)
	|| hal_property_get_double(a) < hal_property_get_double(b);

    case HAL_PROPERTY_TYPE_BOOLEAN:
      return hal_property_get_bool(a) == hal_property_get_bool(b);

    case HAL_PROPERTY_TYPE_STRING:
      {
	const char *str_a = hal_property_get_string(a);
	const char *str_b = hal_property_get_string(b);

	return str_a == str_b	/* both NULL */
	  || (str_a && str_b && ! strcmp(str_a, str_b));
      }

    default:
      g_return_val_if_reached(FALSE);
      break;
    }
}

/* takes ownership of the passed properties */
HalDevice *
hf_device_store_match (HalDeviceStore *store, ...)
{
  va_list args;
  HalProperty *property_a;
  GSList *properties = NULL;
  HalDevice *device = NULL;
  GSList *a;

  g_return_val_if_fail(HAL_IS_DEVICE_STORE(store), NULL);

  va_start(args, store);
  while ((property_a = va_arg(args, HalProperty *)))
    properties = g_slist_prepend(properties, property_a);
  va_end(args);

  HF_LIST_FOREACH(a, store->devices)
    {
      GSList *b;

      device = a->data;

      HF_LIST_FOREACH(b, properties)
	{
	  HalProperty *property_b;

	  property_a = b->data;
	  property_b = hal_device_property_find(device, hal_property_get_key(property_a));

	  if (! property_b || ! hal_properties_equal(property_a, property_b))
	    {
	      device = NULL;
	      break;
	    }
	}

      if (device)
	break;
    }

  g_slist_foreach(properties, (GFunc) hal_property_free, NULL);
  g_slist_free(properties);

  return device;
}

HalDevice *
hf_device_store_get_parent (HalDeviceStore *store, HalDevice *device)
{
  const char *parent_udi;

  g_return_val_if_fail(HAL_IS_DEVICE_STORE(store), NULL);
  g_return_val_if_fail(HAL_IS_DEVICE(device), NULL);

  parent_udi = hal_device_property_get_string(device, "info.parent");
  if (parent_udi)
    return hal_device_store_find(store, parent_udi);
  else
    return NULL;
}

GSList *
hf_device_store_get_children (HalDeviceStore *store, HalDevice *device)
{
  GSList *children;
  GSList *tmp;
  GSList *l;

  g_return_val_if_fail(HAL_IS_DEVICE_STORE(store), NULL);
  g_return_val_if_fail(HAL_IS_DEVICE(device), NULL);

  children = hal_device_store_match_multiple_key_value_string(store, "info.parent", device->udi);

  /* recurse down the tree */
  tmp = g_slist_copy(children);
  HF_LIST_FOREACH(l, tmp)
    children = g_slist_concat(children, hf_device_store_get_children(store, l->data));
  g_slist_free(tmp);

  return children;
}

gboolean
hf_has_sysctl (const char *format, ...)
{
  va_list args;
  char *name;
  size_t value_len;
  gboolean status;

  g_return_val_if_fail(format != NULL, FALSE);

  va_start(args, format);
  name = g_strdup_vprintf(format, args);
  va_end(args);

  status = sysctlbyname(name, NULL, &value_len, NULL, 0) == 0;

  g_free(name);
  return status;
}

gboolean
hf_get_int_sysctl (int *value, GError **err, const char *format, ...)
{
  va_list args;
  char *name;
  size_t value_len = sizeof(int);
  gboolean status;

  g_return_val_if_fail(value != NULL, FALSE);
  g_return_val_if_fail(format != NULL, FALSE);

  va_start(args, format);
  name = g_strdup_vprintf(format, args);
  va_end(args);

  status = sysctlbyname(name, value, &value_len, NULL, 0) == 0;
  if (! status)
    g_set_error(err, 0, 0, "%s", g_strerror(errno));

  g_free(name);
  return status;
}

char *
hf_get_string_sysctl (GError **err, const char *format, ...)
{
  va_list args;
  char *name;
  size_t value_len;
  char *str = NULL;

  g_return_val_if_fail(format != NULL, FALSE);

  va_start(args, format);
  name = g_strdup_vprintf(format, args);
  va_end(args);

  if (sysctlbyname(name, NULL, &value_len, NULL, 0) == 0)
    {
      str = g_new(char, value_len + 1);
      if (sysctlbyname(name, str, &value_len, NULL, 0) == 0)
	str[value_len] = 0;
      else
	{
	  g_free(str);
	  str = NULL;
	}
    }

  if (! str)
    g_set_error(err, 0, 0, "%s", g_strerror(errno));

  g_free(name);
  return str;
}

char *
hf_run (GError **err, const char *format, ...)
{
  va_list args;
  char *command;
  int exit_status;
  char *output = NULL;

  g_return_val_if_fail(format != NULL, NULL);

  va_start(args, format);
  command = g_strdup_vprintf(format, args);
  va_end(args);

  if (g_spawn_command_line_sync(command, &output, NULL, &exit_status, err))
    {
      if (exit_status != 0)
	{
	  g_set_error(err, 0, 0, "command returned status %i", exit_status);
	  g_free(output);
	  output = NULL;
	}
    }

  g_free(command);
  return output;
}

static void
hf_runner_terminated_cb (HalDevice *device,
			 guint32 exit_type,
			 int return_code,
			 char **error,
			 gpointer data1,
			 gpointer data2)
{
  AsyncInfo *async = data1;
  RunnerInfo *info = data2;

  info->exit_type = exit_type;
  info->return_code = return_code;

  async->done = TRUE;
}

int
hf_runner_run_sync (HalDevice *device,
                    int timeout,
                    const char *command_line, ...)
{
  AsyncInfo async;
  RunnerInfo info;
  GPtrArray *extra_env;
  const char *variable;
  va_list args;

  g_return_val_if_fail(HAL_IS_DEVICE(device), 0);
  g_return_val_if_fail(command_line != NULL, 0);

  if (timeout <= 0)
    timeout = HAL_HELPER_TIMEOUT;

  extra_env = g_ptr_array_new();

  va_start(args, command_line);
  while ((variable = va_arg(args, const char *)))
    {
      const char *value;

      value = va_arg(args, const char *);
      g_assert(value != NULL);

      g_ptr_array_add(extra_env, g_strdup_printf("%s=%s", variable, value));
    }
  va_end(args);

  g_ptr_array_add(extra_env, NULL);

  hf_async_init(&async);
  hald_runner_run(device, command_line, (char **) extra_env->pdata, timeout, hf_runner_terminated_cb, &async, &info);
  hf_async_wait(&async);

  g_ptr_array_foreach(extra_env, (GFunc) g_free, NULL);
  g_ptr_array_free(extra_env, TRUE);

  return info.exit_type == HALD_RUN_SUCCESS ? info.return_code : -1;
}

int
hf_strv_find (char **strv, const char *elem)
{
  int i;

  g_return_val_if_fail(strv != NULL, -1);
  g_return_val_if_fail(elem != NULL, -1);

  for (i = 0; strv[i]; i++)
    if (! strcmp(strv[i], elem))
      return i;

  return -1;
}
