/*
 * Copyright (C) 2007,2008 Nicolas Joseph
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 3 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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 "gmarkup-dom.h"

#include <ctype.h>

#include <glib/gfileutils.h>
#include <glib/gmarkup.h>
#include <glib/gmem.h>
#include <glib/gmessages.h>
#include <glib/gstrfuncs.h>

enum
{
  PROP_0,
  PROP_CONTENTS
};

typedef struct _GMarkupDomContext GMarkupDomContext;

struct _GMarkupDomContext
{
  gint level;
  GMarkupDomNode* root;
  GMarkupDomNode* last_root;
  GMarkupDomNode* last_node;
  GMarkupDomNode* current;
};

G_DEFINE_TYPE (GMarkupDomNode, g_markup_dom_node, G_TYPE_OBJECT)

static void g_markup_dom_node_get_property (GObject *object, guint property_id,
                                            GValue *value, GParamSpec *pspec)
{
  GMarkupDomNode* self = G_MARKUP_DOM_NODE (object);

  switch (property_id)
  {
    case PROP_CONTENTS:
      g_value_set_string (value,  g_markup_dom_node_get_contents (self));
    break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void g_markup_dom_node_class_init (GMarkupDomNodeClass* klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GParamSpec *pspec = NULL;

  g_return_if_fail (klass != NULL);

  pspec = g_param_spec_boolean ("contents",
                                "Contents",
                                "Contents",
                                FALSE,
                                G_PARAM_CONSTRUCT | G_PARAM_READABLE);

  g_object_class_install_property (gobject_class,
                                   PROP_CONTENTS,
                                   pspec);
  gobject_class->get_property = g_markup_dom_node_get_property;
}

static void g_markup_dom_node_init (GMarkupDomNode* self)
{
  g_return_if_fail (self != NULL);
}

static int str_isspace (const gchar *s)
{
  if (!s) return 0;
  for (; *s && isspace (*s); s++);
  return !*s;
}

static void xml_start_element (GMarkupParseContext *context,
                               const gchar *element_name,
                               const gchar **attribute_names,
                               const gchar **attribute_values,
                               gpointer user_data,
                               GError **error)
{
  gint i;
  GMarkupDomContext *dom_context = user_data;
  GMarkupDomNode *node = NULL;

  g_return_if_fail (dom_context != NULL);

  node = g_malloc (sizeof (*node));
  node->name = NULL;
  node->content = NULL;
  node->level = 0;
  node->attributs = NULL;
  node->parent = NULL;
  node->child = NULL;
  node->next = NULL;
  node->prev = NULL;

  dom_context->level++;
  dom_context->current = node;
  if (dom_context->root == NULL)
  {
    dom_context->root = node;
  }

  node->name = g_strdup (element_name);
  node->level = dom_context->level;

  /* previous - next */
  if (dom_context->last_node != NULL
      && dom_context->last_node->level == node->level)
  {
    node->prev = dom_context->last_node;
    dom_context->last_node->next = node;
  }

  /* Copy attributs */
  for (i = 0; attribute_names[i] != NULL; i++)
  {
  }
  node->attributs = g_malloc (sizeof (*node->attributs) * (i + 1));
  for (i = 0; attribute_names[i] != NULL; i++)
  {
    node->attributs[i] = g_malloc (sizeof (**node->attributs));
    node->attributs[i]->name = g_strdup (attribute_names[i]);
    node->attributs[i]->value = g_strdup (attribute_values[i]);
  }
  node->attributs[i] = g_malloc (sizeof (**node->attributs));
  node->attributs[i]->name = NULL;
  node->attributs[i]->value = NULL;

  /* parent - child */
  node->parent = dom_context->last_root;
  if (dom_context->last_root && dom_context->last_root->child == NULL)
  {
    dom_context->last_root->child = node;
  }
  dom_context->last_root = node;

  /* Unused parameters */
  (void)context;
  (void)error;
}

static void xml_end_element (GMarkupParseContext *context,
                             const gchar *element_name, gpointer user_data,
                             GError **error)
{
  GMarkupDomContext *dom_context = user_data;

  g_return_if_fail (dom_context != NULL);
  g_return_if_fail (dom_context->current != NULL);

  dom_context->level--;
  dom_context->last_root = dom_context->current->parent;
  dom_context->last_node = dom_context->current;
  dom_context->current = dom_context->current->parent;

  /* Unused parameters */
  (void)context;
  (void)element_name;
  (void)error;
}

static void xml_text (GMarkupParseContext *context, const gchar *text,
                      gsize text_len, gpointer user_data, GError **error)
{
  GMarkupDomContext *dom_context = user_data;

  g_return_if_fail (dom_context != NULL);
  g_return_if_fail (dom_context->current != NULL);

  if (dom_context->current->content == NULL && !str_isspace (text))
  {
    dom_context->current->content = g_strdup (text);
  }

  /* Unused parameters */
  (void)context;
  (void)text_len;
  (void)error;
}

/**
 * g_markup_dom_node_new:
 * @file_name: name of a file to parse contents from.
 * @error: return location for a #GError, or %NULL.
 *
 * Create a dom tree of @filename content.
 *
 * Return value: a new #GMarkupDomNode.
 **/
GMarkupDomNode *g_markup_dom_node_new (const gchar *filename, GError **error)
{
  GMarkupParseContext *markup_parse_context = NULL;
  GMarkupDomContext context = {0, NULL, NULL, NULL, NULL};

  g_return_val_if_fail (filename != NULL, context.root);

  {
    GMarkupParser markup_parser;

    markup_parser.start_element = xml_start_element;
    markup_parser.end_element = xml_end_element;
    markup_parser.text = xml_text;
    markup_parser.passthrough = NULL;
    markup_parser.error = NULL;
    markup_parse_context = g_markup_parse_context_new (&markup_parser, 0,
                                                       &context, NULL);
  }
  {
    gchar *text = NULL;
    gsize length = -1;

    g_file_get_contents (filename, &text, &length, error);
    if (text != NULL)
    {
      g_markup_parse_context_parse (markup_parse_context, text, length, error);
      g_free (text), text = NULL;
    }
    g_free (markup_parse_context), markup_parse_context = NULL;
  }
  return context.root;
}

/**
 * g_markup_dom_node_free:
 * @root: a #GMarkupDomNode.
 *
 * Frees a #GMarkupDomNode.
 **/
void g_markup_dom_node_free (GMarkupDomNode *root)
{
  if (root != NULL)
  {
    gint i;

    g_free (root->name), root->name = NULL;
    g_free (root->content), root->content = NULL;
    for (i = 0; root->attributs[i]->name; i++)
    {
      g_free (root->attributs[i]->name), root->attributs[i]->name = NULL;
      g_free (root->attributs[i]->value), root->attributs[i]->value = NULL;
      g_free (root->attributs[i]), root->attributs[i] = NULL;
    }
    g_free (root->attributs), root->attributs = NULL;
    root->parent = NULL;
    root->prev = NULL;
    g_markup_dom_node_free (root->child), root->child = NULL;
    g_markup_dom_node_free (root->next), root->next = NULL;
    g_free (root), root = NULL;
  }
}

/**
 * g_markup_dom_node_get_contents:
 * @root: a #GMarkupDomNode.
 *
 * Create XML file contents from a #GMarkupDomNode.
 **/
gchar *g_markup_dom_node_get_contents (GMarkupDomNode *root)
{
#define INDENT(s, niv)                 \
do                                     \
{                                      \
  gint i;                              \
                                       \
  for (i = 0; i < (niv) - 1; i++)      \
  {                                    \
    (s) = g_string_append ((s), "\t"); \
  }                                    \
} while (0)                            \

  gchar *str;
  GString *s = NULL;
  gchar *tmp = NULL;

  s = g_string_new ("");
  if (root != NULL)
  {
    INDENT (s, root->level);
    g_string_append_printf (s, "<%s", root->name);
    {
      gint i = 0;

      while (root->attributs[i]->name != NULL)
      {
        g_string_append_printf (s, " %s=\"%s\"", root->attributs[i]->name,
                                                 root->attributs[i]->value);
        i++;
      }
    }
    s = g_string_append (s, ">\n");
    if (root->content != NULL)
    {
      INDENT (s, root->level + 1);
      g_string_append_printf (s, "%s\n", root->content);
    }
    tmp = g_markup_dom_node_get_contents (root->child);
    g_string_append_printf (s, tmp);
    g_free (tmp), tmp = NULL;
    INDENT (s, root->level);
    g_string_append_printf (s, "</%s>\n", root->name);
    tmp = g_markup_dom_node_get_contents (root->next);
    g_string_append_printf (s, tmp);
    g_free (tmp), tmp = NULL;
  }
#undef INDENT
  str = s->str;
  g_string_free (s, FALSE), s = NULL;
  return str;
}

/**
 * g_markup_dom_node_get_element_by_name
 * @root: a #GMarkupDomNode.
 * @name: search tag name.
 *
 * Return value: a #GMarkupDomNode.
 *
 * Return #GMarkupDomNode with name #name.
 **/
GMarkupDomNode *g_markup_dom_node_get_element_by_name (GMarkupDomNode *root,
                                                       const gchar *name)
{
  GMarkupDomNode *node = NULL;

  g_return_val_if_fail (name != NULL, node);

  if (root != NULL)
  {
    if (strcmp (root->name, name) == 0)
    {
      node = root;
    }
    else
    {
      node = g_markup_dom_node_get_element_by_name (root->child, name);
      if (node == NULL)
      {
        node = g_markup_dom_node_get_element_by_name (root->next, name);
      }
    }
  }
  return node;
}

/**
 * g_markup_dom_node_get_element_by_path
 * @root: a #GMarkupDomNode.
 * @path: search tag path.
 *
 * Return value : a #GMarkupDomNode.
 *
 * Return #GMarkupDomNode with path #path.
 **/
GMarkupDomNode *g_markup_dom_node_get_element_by_path (GMarkupDomNode *root,
                                                       const gchar *path)
{
  gint i;
  gchar **names = NULL;

  g_return_val_if_fail (root != NULL, root);
  g_return_val_if_fail (path != NULL, root);

  names = g_strsplit (path, "/", -1);
  for (i = 0; names[i] != NULL; i++)
  {
    GMarkupDomNode *tmp = NULL;

    tmp = g_markup_dom_node_get_element_by_name (root, names[i]);
    if (tmp == NULL)
    {
      root = NULL;
      break;
    }
    else
    {
      root = tmp;
    }
  }
  g_strfreev (names), names = NULL;
  return root;
}

