/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998, 1999 Alexander Larsson
 *
 * Custom Objects -- objects defined in XML rather than C.
 * Copyright (C) 1999 James Henstridge.
 *
 * Custom shape loading on demand.
 * Copyright (C) 2007 Hans Breuer.
 * 
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include <config.h>

#include "shape_info.h"
#include "custom_util.h"
#include <string.h>
#include <stdarg.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <libxml/parser.h>

/*
 * Instead of parsing the complete shape file at start-up we read only
 * the minimal info required to register the type. This should speed
 * up startup a lot and also spare some memory for shapes never used
 * at runtime.
 *
 * There are so many shapes in dia that on my computer there is a difference
 * of 16MB vs. 36MB total memory consumption with none vs. all of them loaded.
 *
 * The startup time ...
 */

typedef enum {
  READ_ON   = 0,
  READ_NAME = 1,
  READ_ICON = 2,
  READ_DONE = 3
} eState;

typedef struct _Context Context;
struct _Context {
  ShapeInfo* si;
  eState  state;
};

static void 
startElementNs (void *ctx, 
                const xmlChar *localname, 
                const xmlChar *prefix, 
                const xmlChar *URI, 
                int nb_namespaces, 
                const xmlChar **namespaces, 
                int nb_attributes, 
                int nb_defaulted, 
                const xmlChar **attributes)
{
  Context* context = (Context*)ctx;
  
  if (READ_DONE == context->state)
    /* no more to do */;
  else if (strncmp ((const char*)localname, "name", 4) == 0)
    context->state = READ_NAME;
  else if (strncmp ((const char*)localname, "icon", 4) == 0)
    context->state = READ_ICON;
  else if (context->si->name != NULL && context->si->icon != NULL)
    context->state = READ_DONE;
  else
    context->state = READ_ON;
}

static void
_characters (void *ctx,
	    const xmlChar *ch,
	    int len)
{
  Context* context = (Context*)ctx;

  if (READ_DONE == context->state)
    /* no more to do */;
  if (READ_NAME == context->state) {
    gchar *prev = context->si->name;
    if (!prev)
      context->si->name = g_strndup ((const gchar*)ch, len);
    else {
      gchar *now = g_strndup ((const gchar*)ch, len);
      context->si->name = g_strconcat (prev, now, NULL);
      g_free (prev);
      g_free (now);
    }
  } else if (READ_ICON == context->state) {
    gchar *prev = context->si->icon;
    if (!prev) 
      context->si->icon = g_strndup ((const char*)ch, len);
    else {
      gchar *now = g_strndup ((const char*)ch, len);
      context->si->icon = g_strconcat (prev, now, NULL);
      g_free (prev);
      g_free (now);
    }
  }
}
				
static void 
endElementNs (void *ctx,
	      const xmlChar *localname,
	      const xmlChar *prefix,
	      const xmlChar *URI)
{
  Context* context = (Context*)ctx;
  
  if (READ_DONE == context->state)
    return;

  if (READ_NAME == context->state)
    if (!context->si->name || context->si->name[0] == '\0')
      g_warning ("Shape (%s) missing type name", context->si->filename);
  if (READ_ICON == context->state)
    if (!context->si->icon || context->si->icon[0] == '\0')
      g_warning ("Shape (%s) missing icon name", context->si->filename);
  if (   (READ_NAME  == context->state || READ_ICON == context->state)
      && context->si->name && context->si->icon)
    context->state = READ_DONE;
  else
    context->state = READ_ON;
}

static void
_error (void *ctx,
        const char * msg, 
        ...)
{
  Context* context = (Context*)ctx;
  va_list args;

  if (READ_DONE == context->state)
    return; /* we are ready, not interested in further complains */
  va_start(args, msg);
  g_print ("Error: %s\n", context->si->filename);
  g_vprintf (msg, args);
  g_print ("\n");
  va_end(args);
}

static void
_warning (void *ctx,
          const char * msg, 
          ...)
{
  Context* context = (Context*)ctx;
  va_list args;

  if (READ_DONE == context->state)
    return; /* we are ready, not interested in further complains */
  va_start(args, msg);
  g_print ("Warning: %s\n", context->si->filename);
  g_vprintf (msg, args);
  g_print ("\n");
  va_end(args);
}

gboolean
shape_typeinfo_load (ShapeInfo* info)
{
  static xmlSAXHandler saxHandler;
  static gboolean once = FALSE;
#define BLOCKSIZE 512
  char buffer[BLOCKSIZE];
  FILE *f;
  int n;
  Context ctx = { info, READ_ON };
  
  g_assert (info->filename != NULL);
  
  if (!once) {
    LIBXML_TEST_VERSION

    memset(&saxHandler, 0, sizeof(saxHandler));
    saxHandler.initialized = XML_SAX2_MAGIC;
    saxHandler.startElementNs = startElementNs;
    saxHandler.characters = _characters;
    saxHandler.endElementNs = endElementNs;
    saxHandler.error = _error;
    saxHandler.warning = _warning;
    once = TRUE;
  }
  f = g_fopen (info->filename, "rb");
  if (!f)
    return FALSE;
  while ((n = fread (buffer, 1, BLOCKSIZE, f)) > 0) {
    int result = xmlSAXUserParseMemory (&saxHandler, &ctx, buffer, n);
    if (result != 0)
      break;
    if (ctx.state == READ_DONE)
      break;
  }
  fclose (f);
  if (ctx.state == READ_DONE) {
    gchar* tmp = info->icon;
    if (tmp) {
      info->icon = custom_get_relative_filename (info->filename, tmp);
      g_free (tmp);
    }
    return TRUE;
  } else {
    g_print ("Preloading shape file '%s' failed.\n"
             "Please ensure that <name/> and <icon/> are early in the file.\n",
             info->filename);
  }
  return FALSE;
}
