/*
 * Copyright 2008-2011 Novell, 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.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, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/* type driven marshalling */
#include <glib.h>
#include <stdio.h>

#include "config.h"
#include "dbind-any.h"

#undef DEBUG

/*  Align a value upward to a boundary, expressed as a number of bytes.
 *  E.g. align to an 8-byte boundary with argument of 8.
 *
 *   (this + boundary - 1)
 *          &
 *    ~(boundary - 1)
 */
#define ALIGN_VALUE(this, boundary) \
  ((((gulong) (this)) + (((gulong) (boundary)) - 1)) & (~(((gulong) (boundary)) - 1)))

#define ALIGN_ADDRESS(this, boundary) \
  ((gpointer) ALIGN_VALUE (this, boundary))

#define PTR_PLUS(ptr, offset) \
  ((gpointer) (((guchar *) (ptr)) + (offset)))

#define DBIND_POD_CASES   \
  DBUS_TYPE_BYTE:         \
  case DBUS_TYPE_INT16:   \
  case DBUS_TYPE_UINT16:  \
  case DBUS_TYPE_INT32:   \
  case DBUS_TYPE_UINT32:  \
  case DBUS_TYPE_BOOLEAN: \
  case DBUS_TYPE_INT64:   \
  case DBUS_TYPE_UINT64:  \
  case DBUS_TYPE_DOUBLE

/*---------------------------------------------------------------------------*/

static void
warn_braces ()
{
  fprintf (stderr, "Error: dbus flags structures & dicts with braces rather than "
                   " an explicit type member of 'struct'\n");
}

/*---------------------------------------------------------------------------*/

static unsigned int
dbind_find_c_alignment_r (const char **type)
{
  unsigned int retval = 1;

  char t = **type;
  (*type)++;

#ifdef DEBUG
  fprintf (stderr, "\tfind align for %c (0x%x)\n", t, t);
#endif

  switch (t)
    {
    case DBUS_TYPE_BYTE:
      return ALIGNOF_CHAR;
    case DBUS_TYPE_BOOLEAN:
      return ALIGNOF_DBUS_BOOL_T;
    case DBUS_TYPE_INT16:
    case DBUS_TYPE_UINT16:
      return ALIGNOF_DBUS_INT16_T;
    case DBUS_TYPE_INT32:
    case DBUS_TYPE_UINT32:
      return ALIGNOF_DBUS_INT32_T;
    case DBUS_TYPE_INT64:
    case DBUS_TYPE_UINT64:
      return ALIGNOF_DBUS_INT64_T;
    case DBUS_TYPE_DOUBLE:
      return ALIGNOF_DOUBLE;
    /* ptr types */
    case DBUS_TYPE_STRING:
    case DBUS_TYPE_OBJECT_PATH:
    case DBUS_TYPE_SIGNATURE:
    case DBUS_TYPE_ARRAY:
      return ALIGNOF_DBIND_POINTER;
    case DBUS_STRUCT_BEGIN_CHAR:
      /* TODO: I think this would break with a nested struct */
#if ALIGNOF_DBIND_STRUCT > 1
      retval = MAX (retval, ALIGNOF_DBIND_STRUCT);
#endif
      while (**type != DBUS_STRUCT_END_CHAR)
        {
          int elem_align = dbind_find_c_alignment_r (type);
          retval = MAX (retval, elem_align);
        }
      (*type)++;
      return retval;
    case DBUS_DICT_ENTRY_BEGIN_CHAR:
#if ALIGNOF_DBIND_STRUCT > 1
      retval = MAX (retval, ALIGNOF_DBIND_STRUCT);
#endif
      while (**type != DBUS_DICT_ENTRY_END_CHAR)
        {
          int elem_align = dbind_find_c_alignment_r (type);
          retval = MAX (retval, elem_align);
        }
      (*type)++;
      return retval;
    case DBUS_TYPE_STRUCT:
    case DBUS_TYPE_DICT_ENTRY:
      warn_braces ();
      return ALIGNOF_DBIND_POINTER;
    case '\0':
      g_assert_not_reached ();
      break;
    default:
      return 1;
    }
}

/*---------------------------------------------------------------------------*/

/* gather immediate allocation information for this type */
static size_t
dbind_gather_alloc_info_r (const char **type)
{
  char t = **type;
  (*type)++;
  if (t == DBUS_TYPE_ARRAY)
    {
      switch (**type)
        {
        case DBUS_STRUCT_BEGIN_CHAR:
          while (**type != DBUS_STRUCT_END_CHAR && **type != '\0')
            (*type)++;
          if (**type != '\0')
            (*type)++;
          break;
        case DBUS_DICT_ENTRY_BEGIN_CHAR:
          while (**type != DBUS_DICT_ENTRY_END_CHAR && **type != '\0')
            (*type)++;
          if (**type != '\0')
            (*type)++;
          break;
        case '\0':
          break;
        default:
          (*type)++;
          break;
        }
    }

  switch (t)
    {
    case DBUS_TYPE_BYTE:
      return sizeof (char);
    case DBUS_TYPE_BOOLEAN:
      return sizeof (dbus_bool_t);
    case DBUS_TYPE_INT16:
    case DBUS_TYPE_UINT16:
      return sizeof (dbus_int16_t);
    case DBUS_TYPE_INT32:
    case DBUS_TYPE_UINT32:
      return sizeof (dbus_int32_t);
    case DBUS_TYPE_INT64:
    case DBUS_TYPE_UINT64:
      return sizeof (dbus_int64_t);
    case DBUS_TYPE_DOUBLE:
      return sizeof (double);
    /* ptr types */
    case DBUS_TYPE_STRING:
    case DBUS_TYPE_OBJECT_PATH:
    case DBUS_TYPE_SIGNATURE:
    case DBUS_TYPE_ARRAY:
      return sizeof (void *);
    case DBUS_STRUCT_BEGIN_CHAR:
      {
        int sum = 0, stralign;

        stralign = dbind_find_c_alignment (*type - 1);

        while (**type != DBUS_STRUCT_END_CHAR)
          {
            sum = ALIGN_VALUE (sum, dbind_find_c_alignment (*type));
            sum += dbind_gather_alloc_info_r (type);
          }
        sum = ALIGN_VALUE (sum, stralign);

        g_assert (**type == DBUS_STRUCT_END_CHAR);
        (*type)++;

        return sum;
      }
    case DBUS_DICT_ENTRY_BEGIN_CHAR:
      {
        int sum = 0, stralign;

        stralign = dbind_find_c_alignment (*type - 1);

        while (**type != DBUS_DICT_ENTRY_END_CHAR)
          {
            sum = ALIGN_VALUE (sum, dbind_find_c_alignment (*type));
            sum += dbind_gather_alloc_info_r (type);
          }
        sum = ALIGN_VALUE (sum, stralign);

        g_assert (**type == DBUS_DICT_ENTRY_END_CHAR);
        (*type)++;

        return sum;
      }
    case DBUS_TYPE_STRUCT:
    case DBUS_TYPE_DICT_ENTRY:
      warn_braces ();
    default:
      return 0;
    }
}

static size_t
dbind_gather_alloc_info (const char *type)
{
  return dbind_gather_alloc_info_r (&type);
}

/*---------------------------------------------------------------------------*/

static void
dbind_any_free_r (const char **type, void **data)
{
#ifdef DEBUG
  fprintf (stderr, "any free '%c' to %p\n", **type, *data);
#endif

  switch (**type)
    {
    case DBIND_POD_CASES:
      *data = ((guchar *) *data) + dbind_gather_alloc_info (*type);
      (*type)++;
      break;
    case DBUS_TYPE_STRING:
    case DBUS_TYPE_OBJECT_PATH:
    case DBUS_TYPE_SIGNATURE:
#ifdef DEBUG
      fprintf (stderr, "string free %p\n", **(void ***) data);
#endif
      g_free (**(void ***) data);
      *data = ((guchar *) *data) + dbind_gather_alloc_info (*type);
      (*type)++;
      break;
    case DBUS_TYPE_ARRAY:
      {
        int i;
        GArray *vals = **(void ***) data;
        size_t elem_size, elem_align;
        const char *saved_child_type;

        (*type)++;
        saved_child_type = *type;

        elem_size = dbind_gather_alloc_info (*type);
        elem_align = dbind_find_c_alignment_r (type);

        for (i = 0; i < vals->len; i++)
          {
            void *ptr = vals->data + elem_size * i;
            *type = saved_child_type; /* rewind type info */
            ptr = ALIGN_ADDRESS (ptr, elem_align);
            dbind_any_free_r (type, &ptr);
          }
        g_array_free (vals, TRUE);
        break;
      }
    case DBUS_STRUCT_BEGIN_CHAR:
      {
        gconstpointer data0 = *data;
        int offset = 0, stralign;

        stralign = dbind_find_c_alignment (*type);
        (*type)++;

        offset = 0;
        while (**type != DBUS_STRUCT_END_CHAR)
          {
            const char *subt = *type;
            offset = ALIGN_VALUE (offset, dbind_find_c_alignment (*type));
            *data = PTR_PLUS (data0, offset);
            dbind_any_free_r (type, data);
            offset += dbind_gather_alloc_info (subt);
          }

        offset = ALIGN_VALUE (offset, stralign);
        *data = PTR_PLUS (data0, offset);

        g_assert (**type == DBUS_STRUCT_END_CHAR);
        (*type)++;

        break;
      }
    case DBUS_DICT_ENTRY_BEGIN_CHAR:
      {
        gconstpointer data0 = *data;
        int offset = 0, stralign;

        stralign = dbind_find_c_alignment (*type);
        (*type)++;

        offset = 0;
        while (**type != DBUS_DICT_ENTRY_END_CHAR)
          {
            const char *subt = *type;
            offset = ALIGN_VALUE (offset, dbind_find_c_alignment (*type));
            *data = PTR_PLUS (data0, offset);
            dbind_any_free_r (type, data);
            offset += dbind_gather_alloc_info (subt);
          }

        offset = ALIGN_VALUE (offset, stralign);
        *data = PTR_PLUS (data0, offset);

        g_assert (**type == DBUS_DICT_ENTRY_END_CHAR);
        (*type)++;

        break;
      }
    case DBUS_TYPE_STRUCT:
    case DBUS_TYPE_DICT_ENTRY:
      warn_braces ();
      break;
    }
}

/*---------------------------------------------------------------------------*/

void
dbind_any_marshal (DBusMessageIter *iter,
                   const char **type,
                   void **data)
{
  size_t len;

#ifdef DEBUG
  fprintf (stderr, "any marshal '%c' to %p\n", **type, *data);
#endif

  switch (**type)
    {
    case DBIND_POD_CASES:
    case DBUS_TYPE_STRING:
    case DBUS_TYPE_OBJECT_PATH:
    case DBUS_TYPE_SIGNATURE:
      len = dbind_gather_alloc_info (*type);
      dbus_message_iter_append_basic (iter, **type, *data);
      *data = ((guchar *) *data) + len;
      (*type)++;
      break;
    case DBUS_TYPE_ARRAY:
      {
        int i;
        GArray *vals = **(void ***) data;
        size_t elem_size, elem_align;
        DBusMessageIter sub;
        const char *saved_child_type;
        char *child_type_string;

        (*type)++;
        saved_child_type = *type;

        elem_size = dbind_gather_alloc_info (*type);
        elem_align = dbind_find_c_alignment_r (type);

        /* wow this part of the API sucks too ... */
        child_type_string = g_strndup (saved_child_type, *type - saved_child_type);
        /* fprintf (stderr, "array child type '%s'\n", child_type_string); */
        dbus_message_iter_open_container (iter, DBUS_TYPE_ARRAY,
                                          child_type_string, &sub);
        for (i = 0; i < vals->len; i++)
          {
            void *ptr = vals->data + elem_size * i;
            *type = saved_child_type; /* rewind type info */
            ptr = ALIGN_ADDRESS (ptr, elem_align);
            dbind_any_marshal (&sub, type, &ptr);
          }

        dbus_message_iter_close_container (iter, &sub);
        g_free (child_type_string);
        break;
      }
    case DBUS_STRUCT_BEGIN_CHAR:
      {
        gconstpointer data0 = *data;
        int offset = 0, stralign;
        DBusMessageIter sub;

        stralign = dbind_find_c_alignment (*type);

        (*type)++;

        dbus_message_iter_open_container (iter, DBUS_TYPE_STRUCT, NULL, &sub);

        offset = 0;
        while (**type != DBUS_STRUCT_END_CHAR)
          {
            const char *subt = *type;
            offset = ALIGN_VALUE (offset, dbind_find_c_alignment (*type));
            *data = PTR_PLUS (data0, offset);
            dbind_any_marshal (&sub, type, data);
            offset += dbind_gather_alloc_info (subt);
          }

        offset = ALIGN_VALUE (offset, stralign);
        *data = PTR_PLUS (data0, offset);

        dbus_message_iter_close_container (iter, &sub);

        g_assert (**type == DBUS_STRUCT_END_CHAR);
        (*type)++;

        break;
      }
    case DBUS_DICT_ENTRY_BEGIN_CHAR:
      {
        gconstpointer data0 = *data;
        int offset = 0, stralign;
        DBusMessageIter sub;

        stralign = dbind_find_c_alignment (*type);

        (*type)++;

        dbus_message_iter_open_container (iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub);

        offset = 0;
        while (**type != DBUS_DICT_ENTRY_END_CHAR)
          {
            const char *subt = *type;
            offset = ALIGN_VALUE (offset, dbind_find_c_alignment (*type));
            *data = PTR_PLUS (data0, offset);
            dbind_any_marshal (&sub, type, data);
            offset += dbind_gather_alloc_info (subt);
          }

        offset = ALIGN_VALUE (offset, stralign);
        *data = PTR_PLUS (data0, offset);

        dbus_message_iter_close_container (iter, &sub);

        g_assert (**type == DBUS_DICT_ENTRY_END_CHAR);
        (*type)++;

        break;
      }
    case DBUS_TYPE_STRUCT:
    case DBUS_TYPE_DICT_ENTRY:
      warn_braces ();
      break;
    }
}

/*---------------------------------------------------------------------------*/

void
dbind_any_marshal_va (DBusMessageIter *iter,
                      const char **arg_types,
                      va_list args)
{
  const char *p = *arg_types;

  /* Guard against null arg types
     Fix for - http://bugs.freedesktop.org/show_bug.cgi?id=23027
   */
  if (p == NULL)
    p = "";

  {
    /* special case base-types since we need to walk the stack worse-luck */
    for (; *p != '\0' && *p != '=';)
      {
        int intarg;
        void *ptrarg;
        double doublearg;
        dbus_int64_t int64arg;
        void *arg = NULL;

        switch (*p)
          {
          case DBUS_TYPE_BYTE:
          case DBUS_TYPE_BOOLEAN:
          case DBUS_TYPE_INT16:
          case DBUS_TYPE_UINT16:
          case DBUS_TYPE_INT32:
          case DBUS_TYPE_UINT32:
            intarg = va_arg (args, int);
            arg = &intarg;
            break;
          case DBUS_TYPE_INT64:
          case DBUS_TYPE_UINT64:
            int64arg = va_arg (args, dbus_int64_t);
            arg = &int64arg;
            break;
          case DBUS_TYPE_DOUBLE:
            doublearg = va_arg (args, double);
            arg = &doublearg;
            break;
          /* ptr types */
          case DBUS_TYPE_STRING:
          case DBUS_TYPE_OBJECT_PATH:
          case DBUS_TYPE_SIGNATURE:
          case DBUS_TYPE_ARRAY:
          case DBUS_TYPE_DICT_ENTRY:
            ptrarg = va_arg (args, void *);
            arg = &ptrarg;
            break;
          case DBUS_STRUCT_BEGIN_CHAR:
            ptrarg = va_arg (args, void *);
            arg = ptrarg;
            break;
          case DBUS_DICT_ENTRY_BEGIN_CHAR:
            ptrarg = va_arg (args, void *);
            arg = ptrarg;
            break;

          case DBUS_TYPE_VARIANT:
            fprintf (stderr, "No variant support yet - very toolkit specific\n");
            ptrarg = va_arg (args, void *);
            arg = &ptrarg;
            break;
          default:
            fprintf (stderr, "Unknown / invalid arg type %c\n", *p);
            break;
          }
        if (arg != NULL)
          dbind_any_marshal (iter, &p, &arg);
      }
    if (*arg_types)
      *arg_types = p;
  }
}

/*---------------------------------------------------------------------------*/

void
dbind_any_demarshal (DBusMessageIter *iter,
                     const char **type,
                     void **data)
{
  size_t len;

#ifdef DEBUG
  fprintf (stderr, "any demarshal '%c' to %p\n", **type, *data);
#endif

  switch (**type)
    {
    case DBIND_POD_CASES:
      len = dbind_gather_alloc_info (*type);
      dbus_message_iter_get_basic (iter, *data);
      *data = ((guchar *) *data) + len;
      (*type)++;
      break;
    case DBUS_TYPE_STRING:
    case DBUS_TYPE_OBJECT_PATH:
    case DBUS_TYPE_SIGNATURE:
      len = dbind_gather_alloc_info (*type);
      dbus_message_iter_get_basic (iter, *data);
#ifdef DEBUG
      fprintf (stderr, "dup string '%s' (%p)\n", **(void ***) data, **(void ***) data);
#endif
      **(void ***) data = g_strdup (**(void ***) data);
      *data = ((guchar *) *data) + len;
      (*type)++;
      break;
    case DBUS_TYPE_ARRAY:
      {
        GArray *vals;
        DBusMessageIter child;
        size_t elem_size, elem_align;
        const char *stored_child_type;
        int i;

        (*type)++;
        stored_child_type = *type;

        elem_size = dbind_gather_alloc_info (*type);
        elem_align = dbind_find_c_alignment_r (type);
        vals = g_array_new (FALSE, FALSE, elem_size);
        (**(void ***) data) = vals;
        *data = ((guchar *) *data) + sizeof (void *);

        i = 0;
        dbus_message_iter_recurse (iter, &child);
        while (dbus_message_iter_get_arg_type (&child) != DBUS_TYPE_INVALID)
          {
            void *ptr;
            const char *subt = stored_child_type;
            g_array_set_size (vals, i + 1);
            ptr = vals->data + elem_size * i;
            ptr = ALIGN_ADDRESS (ptr, elem_align);
            dbind_any_demarshal (&child, &subt, &ptr);
            i++;
          };
        break;
      }
    case DBUS_STRUCT_BEGIN_CHAR:
      {
        gconstpointer data0 = *data;
        int offset = 0, stralign;
        DBusMessageIter child;

        stralign = dbind_find_c_alignment (*type);

        (*type)++;

        dbus_message_iter_recurse (iter, &child);

        while (**type != DBUS_STRUCT_END_CHAR)
          {
            const char *subt = *type;
            offset = ALIGN_VALUE (offset, dbind_find_c_alignment (*type));
            *data = PTR_PLUS (data0, offset);
            dbind_any_demarshal (&child, type, data);
            offset += dbind_gather_alloc_info (subt);
          }

        offset = ALIGN_VALUE (offset, stralign);
        *data = PTR_PLUS (data0, offset);

        g_assert (**type == DBUS_STRUCT_END_CHAR);
        (*type)++;

        break;
      }
    case DBUS_DICT_ENTRY_BEGIN_CHAR:
      {
        gconstpointer data0 = *data;
        int offset = 0, stralign;
        DBusMessageIter child;

        stralign = dbind_find_c_alignment (*type);

        (*type)++;

        dbus_message_iter_recurse (iter, &child);

        while (**type != DBUS_DICT_ENTRY_END_CHAR)
          {
            const char *subt = *type;
            offset = ALIGN_VALUE (offset, dbind_find_c_alignment (*type));
            *data = PTR_PLUS (data0, offset);
            dbind_any_demarshal (&child, type, data);
            offset += dbind_gather_alloc_info (subt);
          }

        offset = ALIGN_VALUE (offset, stralign);
        *data = PTR_PLUS (data0, offset);

        g_assert (**type == DBUS_DICT_ENTRY_END_CHAR);
        (*type)++;

        break;
      case DBUS_TYPE_VARIANT:
        /* skip; unimplemented for now */
        (*type)++;
        break;
      }
    case DBUS_TYPE_STRUCT:
    case DBUS_TYPE_DICT_ENTRY:
      warn_braces ();
      break;
    }
  dbus_message_iter_next (iter);
}

static const char *
pass_complex_arg (const char *p, char begin, char end)
{
  int level = 1;

  p++;
  while (*p && level > 0)
    {
      if (*p == begin)
        level++;
      else if (*p == end)
        level--;
      p++;
    }
  if (*p == end)
    p++;
  return p;
}

static const char *
pass_arg (const char *p)
{
  switch (*p)
    {
    case '(':
      return pass_complex_arg (p, '(', ')');
    case '{':
      return pass_complex_arg (p, '{', '}');
    case 'a':
      return pass_arg (p + 1);
    default:
      return p + 1;
    }
}

/*---------------------------------------------------------------------------*/

void
dbind_any_demarshal_va (DBusMessageIter *iter,
                        const char **arg_types,
                        va_list args)
{
  const char *p = *arg_types;

  /* Just consume the in args without doing anything to them */
  for (; *p != '\0' && *p != '=';)
    {
      switch (*p)
        {
        case DBUS_TYPE_BYTE:
        case DBUS_TYPE_BOOLEAN:
        case DBUS_TYPE_INT16:
        case DBUS_TYPE_UINT16:
        case DBUS_TYPE_INT32:
        case DBUS_TYPE_UINT32:
          va_arg (args, int);
          break;
        case DBUS_TYPE_INT64:
        case DBUS_TYPE_UINT64:
          va_arg (args, dbus_int64_t);
          break;
        case DBUS_TYPE_DOUBLE:
          va_arg (args, double);
          break;
        /* ptr types */
        case DBUS_TYPE_STRING:
        case DBUS_TYPE_OBJECT_PATH:
        case DBUS_TYPE_SIGNATURE:
        case DBUS_TYPE_ARRAY:
        case DBUS_TYPE_DICT_ENTRY:
          va_arg (args, void *);
          break;
        case DBUS_STRUCT_BEGIN_CHAR:
          va_arg (args, void *);
          break;
        case DBUS_DICT_ENTRY_BEGIN_CHAR:
          va_arg (args, void *);
          break;

        case DBUS_TYPE_VARIANT:
          fprintf (stderr, "No variant support yet - very toolkit specific\n");
          va_arg (args, void *);
          break;
        default:
          fprintf (stderr, "Unknown / invalid arg type %c\n", *p);
          break;
        }
      p = pass_arg (p);
    }

  if (p[0] == '=' && p[1] == '>')
    p += 2;

  for (; *p != '\0';)
    {
      void *arg = va_arg (args, void *);
      dbind_any_demarshal (iter, &p, &arg);
    }
}

/*---------------------------------------------------------------------------*/

/* nice deep free ... */
void
dbind_any_free (const char *type,
                void *ptr)
{
  dbind_any_free_r (&type, &ptr);
}

/* should this be the default normalization ? */
void
dbind_any_free_ptr (const char *type, void *ptr)
{
  dbind_any_free (type, &ptr);
}

/*---------------------------------------------------------------------------*/

unsigned int
dbind_find_c_alignment (const char *type)
{
  return dbind_find_c_alignment_r (&type);
}

/*END------------------------------------------------------------------------*/
