/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998 Alexander Larsson
 *
 * 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 <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#include <glib.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xmlmemory.h>
#include "dia_xml_libxml.h"
#include <string.h>

#include "intl.h"

#include "sheet.h"
#include "message.h"
#include "object.h"
#include "dia_dirs.h"
#include "charconv.h"

static GSList *sheets = NULL;

Sheet *
new_sheet(char *name, utfchar *description)
{
  Sheet *sheet;

  sheet = g_new(Sheet, 1);

#ifdef UNICODE_WORK_IN_PROGRESS
  sheet->name = g_strdup(name);
  sheet->description = g_strdup(description);
#else
  sheet->name = charconv_utf8_to_local8(name);
  sheet->description = charconv_utf8_to_local8(description);
#endif

  sheet->objects = NULL;
  return sheet;
}

void
sheet_prepend_sheet_obj(Sheet *sheet, SheetObject *obj)
{
  ObjectType *type;

  type = object_get_type(obj->object_type);
  if (type == NULL) {
    message_warning("Object '%s' needed in sheet '%s' was not found.\n"
		    "It will not be available for use.",
		    obj->object_type, sheet->name);
  } else {
    sheet->objects = g_slist_prepend( sheet->objects, (gpointer) obj);
  }
}

void
sheet_append_sheet_obj(Sheet *sheet, SheetObject *obj)
{
  ObjectType *type;

  type = object_get_type(obj->object_type);
  if (type == NULL) {
    message_warning("Object '%s' needed in sheet '%s' was not found.\n"
		    "It will not be availible for use.",
		    obj->object_type, sheet->name);
  } else {
    sheet->objects = g_slist_append( sheet->objects, (gpointer) obj);
  }
}

void
register_sheet(Sheet *sheet)
{
  sheets = g_slist_append(sheets, (gpointer) sheet);
}

GSList *
get_sheets_list(void)
{
  return sheets;
}

/* Sheet file management */

static void load_sheets_from_dir(const gchar *directory);
static void load_register_sheet(const gchar *directory,const gchar *filename);

void load_all_sheets(void) {
  char *sheet_path;
  char *home_dir;

  home_dir = dia_config_filename("sheets");
  if (home_dir) {
    load_sheets_from_dir(home_dir);
    g_free(home_dir);
  }

  sheet_path = getenv("DIA_SHEET_PATH");
  if (sheet_path) {
    char **dirs = g_strsplit(sheet_path,G_SEARCHPATH_SEPARATOR_S,0);
    int i;

    for (i=0; dirs[i] != NULL; i++) 
      load_sheets_from_dir(dirs[i]);
    g_strfreev(dirs);
  } else {
    char *thedir = dia_get_data_directory("sheets");
    load_sheets_from_dir(thedir);
    g_free(thedir);
  }
}

static void 
load_sheets_from_dir(const gchar *directory)
{
  DIR *dp;
  struct dirent *dirp;
  struct stat statbuf;
  gchar *p;

  dp = opendir(directory);
  if (!dp) return;

  while ( (dirp = readdir(dp)) ) {
    gchar *filename = g_strconcat(directory,G_DIR_SEPARATOR_S,
				  dirp->d_name,NULL);

    if (!strcmp(dirp->d_name, ".") || !strcmp(dirp->d_name, "..")) {
      g_free(filename);
      continue;
    }
    /* filter out non-files */
    if (stat(filename, &statbuf) < 0) {
      g_free(filename);
      continue;
    }
    if (!S_ISREG(statbuf.st_mode)) {
      g_free(filename);
      continue;
    }

    /* take only .sheet files */
    p = filename + strlen(filename) - 6 /* strlen(".sheet") */;
    if (0!=strncmp(p,".sheet",6)) {
      g_free(filename);
      continue;
    }

    load_register_sheet(directory,filename);
    g_free(filename);
				
  }

  closedir(dp);
}

static void 
load_register_sheet(const gchar *dirname, const gchar *filename)
{
  xmlDocPtr doc;
  xmlNsPtr ns;
  xmlNodePtr node, contents,subnode,root;
  char *tmp;
  gchar *name = NULL, *description = NULL;
  int name_score = -1;
  int descr_score = -1;
  Sheet *sheet = NULL;
  GSList *sheetp;
  gboolean set_line_break = FALSE;

  /* the XML fun begins here. */

  doc = xmlDoParseFile(filename);
  if (!doc) return;
  root = doc->xmlRootNode;
  while (root && (root->type != XML_ELEMENT_NODE)) root=root->next;
  if (!root) return;
  if (xmlIsBlankNode(root)) return;

  if (!(ns = xmlSearchNsByHref(doc,root,
	   "http://www.lysator.liu.se/~alla/dia/dia-sheet-ns"))) {
    g_warning("could not find sheet namespace");
    xmlFreeDoc(doc); 
    return;
  }
    if ((root->ns != ns) || (strcmp(root->name,"sheet"))) {
    g_warning("root element was %s -- expecting sheet", 
              doc->xmlRootNode->name);
    xmlFreeDoc(doc);
    return;
  }

  contents = NULL;
  for (node = root->xmlChildrenNode; node != NULL; node = node->next) {
    if (xmlIsBlankNode(node)) continue;
    if (node->type != XML_ELEMENT_NODE)
      continue;

    if (node->ns == ns && !strcmp(node->name, "name")) {
      gint score;
      
      /* compare the xml:lang property on this element to see if we get a
       * better language match.  LibXML seems to throw away attribute
       * namespaces, so we use "lang" instead of "xml:lang" */
      tmp = xmlGetProp(node, "xml:lang");
      if (!tmp) tmp = xmlGetProp(node, "lang");
      score = intl_score_locale(tmp);
      if (tmp) xmlFree(tmp);

      if (name_score < 0 || score < name_score) {
        name_score = score;
        if (name) xmlFree(name);
        name = xmlNodeGetContent(node);
      }      
    } else if (node->ns == ns && !strcmp(node->name, "description")) {
      gint score;

      /* compare the xml:lang property on this element to see if we get a
       * better language match.  LibXML seems to throw away attribute
       * namespaces, so we use "lang" instead of "xml:lang" */
      tmp = xmlGetProp(node, "xml:lang");
      if (!tmp) tmp = xmlGetProp(node, "lang");
      score = intl_score_locale(tmp);
      if (tmp) xmlFree(tmp);

      if (descr_score < 0 || score < descr_score) {
        descr_score = score;
        if (description) xmlFree(description);
        description = xmlNodeGetContent(node);
      }
      
    } else if (node->ns == ns && !strcmp(node->name, "contents")) {
      contents = node;
    }
  }
  
  if (!contents) {
    g_warning("no contents in sheet %s.", filename);
    xmlFreeDoc(doc);
    if (name) xmlFree(name);
    if (description) xmlFree(description);
    return;
  }

  sheetp = get_sheets_list();
  while (sheetp) {
    if (sheetp->data && !strcmp(((Sheet *)(sheetp->data))->name,name)) {
      sheet = sheetp->data;
      break;
    }
    sheetp = sheetp->next;
  }
  
  if (!sheet) 
    sheet = new_sheet(name,description);
   
  xmlFree(name);
  xmlFree(description);

  for (node = contents->xmlChildrenNode ; node != NULL; node = node->next) {
    SheetObject *sheet_obj;
    ObjectType *otype;
    gchar *iconname = NULL;

    int subdesc_score = -1;
    gchar *objdesc = NULL;

    gint intdata = 0;
    gchar *chardata = NULL;

    if (xmlIsBlankNode(node)) continue;

    if (node->type != XML_ELEMENT_NODE) 
      continue;
    if (node->ns != ns) continue;
    if (!strcmp(node->name,"object")) {
      /* nothing */
    } else if (!strcmp(node->name,"shape")) {
      g_message("%s: you should use object tags rather than shape tags now",
		filename);
    } else if (!strcmp(node->name,"br")) {
      /* Line break tag. */
      set_line_break = TRUE;
      continue;
    } else
      continue; /* unknown tag */
    
    tmp = xmlGetProp(node,"intdata");
    if (tmp) { 
      char *p;
      intdata = (gint)strtol(tmp,&p,0);
      if (*p != 0) intdata = 0;
      xmlFree(tmp);
    }
    chardata = xmlGetProp(node,"chardata");
    /* TODO.... */
    if (chardata) xmlFree(chardata);
    
    for (subnode = node->xmlChildrenNode; 
         subnode != NULL ; 
         subnode = subnode->next) {
      if (xmlIsBlankNode(subnode)) continue;

      if (subnode->ns == ns && !strcmp(subnode->name, "description")) {
	gint score;

	/* compare the xml:lang property on this element to see if we get a
	 * better language match.  LibXML seems to throw away attribute
	 * namespaces, so we use "lang" instead of "xml:lang" */
	  
	tmp = xmlGetProp(subnode, "xml:lang");
	if (!tmp) tmp = xmlGetProp(subnode, "lang");
	score = intl_score_locale(tmp);
	if (tmp) xmlFree(tmp);

	if (subdesc_score < 0 || score < subdesc_score) {
	  subdesc_score = score;
	  if (objdesc) xmlFree(objdesc);
	  objdesc = xmlNodeGetContent(subnode);
	}
	  
      } else if (subnode->ns == ns && !strcmp(subnode->name,"icon")) {
	tmp = xmlNodeGetContent(subnode);
	iconname = g_strconcat(dirname,G_DIR_SEPARATOR_S,tmp,NULL);
	if (tmp) xmlFree(tmp);
      }
    }

    tmp = xmlGetProp(node,"name");

    sheet_obj = g_new(SheetObject,1);
    sheet_obj->object_type = g_strdup(tmp);
#ifdef UNICODE_WORK_IN_PROGRESS   
    sheet_obj->description = g_strdup(objdesc);
#else
    sheet_obj->description = charconv_utf8_to_local8(objdesc);
#endif
    xmlFree(objdesc); objdesc = NULL;

    sheet_obj->pixmap = NULL;
    sheet_obj->user_data = (void *)intdata; /* XXX modify user_data type ? */
    sheet_obj->pixmap_file = iconname; 
    sheet_obj->line_break = set_line_break;
    set_line_break = FALSE;

    if ((otype = object_get_type(tmp)) == NULL) {
      if (sheet_obj->description) g_free(sheet_obj->description);
      g_free(sheet_obj->pixmap_file);
      g_free(sheet_obj->object_type);
      g_free(sheet_obj);
      if (tmp) xmlFree(tmp);
      continue; 
    }	  

    /* set defaults */
    if (sheet_obj->pixmap_file == NULL) {
      sheet_obj->pixmap = otype->pixmap;
      sheet_obj->pixmap_file = otype->pixmap_file;
    }
    if (sheet_obj->user_data == NULL)
      sheet_obj->user_data = otype->default_user_data;
    if (tmp) xmlFree(tmp);
      
    /* we don't need to fix up the icon and descriptions for simple objects,
       since they don't have their own description, and their icon is 
       already automatically handled. */
    sheet_append_sheet_obj(sheet,sheet_obj);
  }
  
  if (sheet) register_sheet(sheet);
  xmlFreeDoc(doc);
}
