/* xmlfs -- a translator for accessing XML documents

   Copyright (C) 2002, 2005 HurdFR.
   Written by  Marc de Saint Sauveur <marc@hurdfr.org>
           and Manuel Menal          <mmenal@hurdfr.org>
   
   xmlfs 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, or (at
   your option) any later version.

   xmlfs 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA. 
*/

#include "xmlfs.h"
#include "fsutils.h"

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

#include <libxml/xpath.h>

error_t
xml_dump_node (xmlNodePtr node, char **text, size_t *size)
{
  static xmlBufferPtr buffer = NULL;
  error_t err;

  if (node == NULL && text == NULL)
    {
      xmlBufferFree (buffer);
      return 0;
    }

  if (node->type == XML_ATTRIBUTE_NODE)
      node = node->children;
  
  assert (node);
  buffer = xmlBufferCreate();
  if (!buffer)
    return -1;
  
  err = xmlNodeDump (buffer, xmlfs->doc, node, 0, 1);
  if (!err)
    return -1;
  
  *text = (char *) xmlBufferContent (buffer); 
  *size = strlen (*text);
  return 0;
}

int
xml_node_compare (const void *a, const void *b)
{
  int err;
  xmlNodePtr x = *((xmlNodePtr *) a);
  xmlNodePtr y = *((xmlNodePtr *) b);

 if (x == NULL || y == NULL || x == y)
    return 0;

 err = xmlStrcmp (x->name, y->name);

 /* Make it stable. */
 /* FIXME: And if we have two nodes with the same name of the same line ? */
 if (!err)
   err = x->line - y->line;

 return err;
}


xmlNodeSetPtr
xml_find_nodeset (const char *path)
{
  static xmlXPathObjectPtr xp = NULL;
  static xmlXPathContextPtr xpc = NULL;

  /* If path is null, free and return. */
  if (path == NULL)
    {
      if (xp != NULL)
	  xmlXPathFreeObject (xp);
      if (xpc != NULL)
	xmlXPathFreeContext (xpc);
      return NULL;
    }

  xpc = xmlXPathNewContext (xmlfs->doc);
  xp = xmlXPathEval ((xmlChar *) path, xpc);

  assert (xp);

  return xp->nodesetval;
}

xmlNodePtr
xml_find_node (const char *path)
{
  DEBUG ("NOTICE: %s (path: %s)\n", __PRETTY_FUNCTION__, path);

  xmlNodeSetPtr set = xml_find_nodeset (path);
  
  /* This is not a buggy case, or the assert in
     xml_find_nodeset would have been triggered. */
  if (!set)
    return NULL;

  DEBUG ("NOTICE: %s returning with %d nodes found.\n", 
	 __PRETTY_FUNCTION__, xmlXPathNodeSetGetLength (set));

  assert (xmlXPathNodeSetGetLength (set) == 1);

  return set->nodeTab[0];
}

xmlNodeSetPtr
xml_find_children (const char *xpath)
{
  char *expr = NULL;
  xmlNodeSetPtr set;

  asprintf (&expr, "%s/node()", xpath);
  
  set = xml_find_nodeset (expr);
  
  free (expr);

  return set;
}

xmlNodeSetPtr 
xml_find_children_sorted (const char *xpath)
{
  xmlNodeSetPtr set = xml_find_children (xpath);
  
  /* Sort the nodes so we can make their names unique easily. */
  qsort (set->nodeTab, xmlXPathNodeSetGetLength (set),
	 sizeof (xmlNodePtr), xml_node_compare);
  
  return set;
}

static void
make_unique_name (xmlNodePtr node, struct node *dir, int count, int index, char **name, char **pathname)
{
  /* Create a unique name AND pathname. */
  char *prefixed_name = NULL, *node_name = NULL;
  char *sindex = NULL;
  
  switch (node->type)
    {
    case XML_TEXT_NODE:
      asprintf (&node_name, "text()"); 
      asprintf (&prefixed_name, "%s%s", ".", node->name);
      break;
    default:
      /* XML_ELEMENT_NODE and all unhandled cases. */
      node_name = (char *) xmlStrdup(node->name); 
      prefixed_name = (char *) xmlStrdup ((xmlChar *) node_name);
      break;
    }
  
  if (index == -1)
    *name = strdup (prefixed_name);
  else
    asprintf (name, "%s%d", prefixed_name, index);
  
  if (count == -1)
    sindex = strdup ("");
  else
    asprintf (&sindex, "[%d]", count+1);
    
  free (prefixed_name);
  
  asprintf (pathname, "%s/%s%s", dir->nn->pathname,
	    node_name, sindex);
}

/* Find children nodes and populate DIR with them. */
/* XXX: Should be split. */
error_t
fill_dir_with_nodes (struct node *dir)
{
  xmlNodeSetPtr set;
  error_t err;

  set = xml_find_children_sorted (dir->nn->pathname);

  DEBUG ("INFO: %s has %d children.\n", dir->nn->pathname, xmlXPathNodeSetGetLength (set));

  /* Keep track of the position in a serie. Used for pathname. */
  int count = -1;
  /* Keep track of the index to add to the node name. Differs from count
     because it does not consider blank nodes and such. */
  int index = -1;

  for (int i = 0; i < xmlXPathNodeSetGetLength (set); i++)
    {
      /* Index of cur. */
      int cur_index;
      int cur_count;
      /* Real name (with index, first char according to type added). */
      char *name = NULL, *pathname = NULL;
      struct node *p = NULL;
      xmlNodePtr cur = set->nodeTab[i];
      
      DEBUG ("INFO: adding entry of name %s and type %d\n", cur->name, cur->type);
      
      if (count != -1)
	count++;
      if (index != -1)
	index++;

      cur_index = index;
      cur_count = count;

      /* Compute index so that name is unique in DIR. */
      if (i != xmlXPathNodeSetGetLength (set)-1)
	{
	  int diff = xmlStrcmp (cur->name, set->nodeTab[i+1]->name);
	  if (!diff && count == -1)
	    {
	      DEBUG ("INFO: setting count = 0 (starting a serie)\n");
	      index = count = cur_index = cur_count = 0;
	    }
	  else if (diff && count != -1)
	    {
	      DEBUG ("INFO: setting count = -1 (stopping a serie)\n");
	      count = index = -1;
	    }
	}
     
      /* Skip empty text nodes. */
      if (xmlIsBlankNode (cur))
	{
	  /* Finally we're not going to add it... */
	  if (index != 0) index--;
	  DEBUG ("INFO: entry is blank\n");
	  continue;
	}

      DEBUG ("INFO: count = %d, index = %d\n", cur_count, cur_index);
      
      /* Create unique name and pathnames. */
      make_unique_name (cur, dir, cur_count, cur_index, &name, &pathname);
      
      /* <node /> are the only nodes we do not want
	 to make a directory. Otherwise, make it a subdir
	 so that we can add its text node children and so on. */
      if (cur->children != NULL)
	{
	  err = fs_make_subdir (&p, dir, name);
	  if (err)
	    return err;

	  DEBUG ("NOTICE: %s: created node S_IFDIR: %s in %s\n", 
		 __PRETTY_FUNCTION__, name, dir->nn->pathname);
	}
      else
	{
	  err = fs_make_node (&p, dir, name, S_IFREG|0444);
	  if (err)
	    return err;

	  DEBUG ("NOTICE: %s: created node S_IFREG: %s in %s\n", 
		 __PRETTY_FUNCTION__, name, dir->nn->pathname);
	}
      
      p->nn->pathname = pathname;

      DEBUG ("NOTICE: %s: %s has pathname %s\n", 
	     __PRETTY_FUNCTION__, name, p->nn->pathname);
      free (name);
    }

  xml_find_nodeset (NULL);
  
  return 0;
}

/* We do not use XPath for that since attributes
   are unique, so we do need to sort them. */
error_t
fill_dir_with_attrs (struct node *dir)
{
  xmlNodePtr dir_node = xml_find_node (dir->nn->pathname);
  xmlAttrPtr res;
  error_t err;

  if (dir_node->type == XML_ELEMENT_NODE)
    res = dir_node->properties;
  else
    res = NULL;
  
  for (xmlAttrPtr p = res; p; p = p->next)
    {
      struct node *n;
      char *name = NULL, *pathname = NULL;
     
      asprintf (&name, "@%s", p->name);
      asprintf (&pathname, "%s/@%s", dir->nn->pathname, p->name);
      
      err = fs_make_node (&n, dir, name, S_IFREG|0444);
      if (err)
	return err;
      
      n->nn->pathname = pathname;
    }
  
  return 0;
}

/* Populate DIR. */
error_t
fill_dirnode (struct node *dir)
{
  error_t err;
  assert (dir);

  DEBUG ("NOTICE: %s (DIR (%p, name: %s, pathname: %s)\n", __PRETTY_FUNCTION__, 
	 dir, dir->nn->name, dir->nn->pathname);

  err = fill_dir_with_nodes (dir);
  if (err)
    return err;
  err = fill_dir_with_attrs (dir);
  if (err)
    return err;
  
  dir->nn->filled = true;

  return 0;
}
