/*
    Silky - A GTK+ client for SILC.
    Copyright (C) 2003, 2004, 2005 Toni Willberg

    - API for reading/writing Silky config file

    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

    http://silky.sourceforge.net/

*/

#include <glib.h>
#include <glib/gprintf.h>

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include "ui_console.h"
#include "common.h"
#include "xmlconfig.h"
#include "xmllayout.h"

#include "common.h"
#include "log.h"

xmlDocPtr config_main;
xmlDocPtr config_servers;
xmlDocPtr config_buddies;

extern SilkyXMLAttribute xml_attributes[];
extern SilkyXMLElement xml_elements[NUM_CONFIG][NUM_CM];
extern gint xml_attr_bits[];

xmlNodePtr xml_create_node(gint config_type, gint node_cn) {
  xmlNodePtr parent = NULL, node;
  xmlDocPtr doc = xml_doc(config_type);

  debug("creating node %s", xml_elements[config_type][node_cn].name);

  if( !doc ) {
    debug("no document, aborting.");
    return NULL;
  }

  if ( (config_type == CONFIG_MAIN) && (node = xml_get_node(config_type, doc, node_cn)) ) {
    debug("working with main config and node %s already exists - ignoring", xml_elements[config_type][node_cn].name);
    return node;
  }

  if( xml_elements[config_type][node_cn].parent ) parent = xml_create_node(config_type, xml_elements[config_type][node_cn].parent);

  if( parent ) {
    debug("creating node with parent");
    return xmlNewTextChild(parent, NULL, xml_elements[config_type][node_cn].name, "");
  } else {
    debug("creating parentless node");
    if( node_cn != CM_ROOT ) {
      debug(">> WARNING: this is not desired root node!");
      return NULL;
    }
  }

  debug("creating ROOT element, behold!");
  xmlDocSetRootElement( doc, (node = xmlNewNode(NULL, xml_elements[config_type][node_cn].name)) );
  return node;
}

/* we also need to pass xmlDocPtr to this function, because we will be calling
   it also when xml_doc(config_type) would return NULL */

/* auxiliary function for getting first node from xpath in document doc */
xmlNodePtr xml_get_node(gint config_type, xmlDocPtr doc, gint node_cn) {
  xmlXPathObjectPtr result;
  xmlXPathContextPtr context;
  xmlNodePtr node;
  gint cn = node_cn;
  gchar *xpath = "";

  if( !doc ) {
    debug("doc == NULL, returning");
    return NULL;
  }

  /* sanity check */
  if( node_cn < 1 || node_cn >= NUM_CM ) {
    debug("error - node_cn %d is not within limits!", node_cn);
    return NULL;
  }

  cn = node_cn;
  while( xml_elements[config_type][cn].name ) {
    if( g_utf8_strlen(xpath, -1) )
      xpath = g_strjoin("/", xml_elements[config_type][cn].name, xpath, NULL);
    else xpath = xml_elements[config_type][cn].name;
    cn = xml_elements[config_type][cn].parent;
  }
  xpath = g_strconcat("/", xpath, NULL);

  context = xmlXPathNewContext(doc);
  if( !(result= xmlXPathEvalExpression(xpath, context)) ) {
    debug("No result found for %s", xpath);
    return NULL;
  }
  if( xmlXPathNodeSetIsEmpty(result->nodesetval) ) {
    return NULL;
  }

  xmlXPathFreeContext(context);
  node = result->nodesetval->nodeTab[0];
  xmlXPathFreeObject(result);
  return node;
}

/* this will look up Silky's internal node number by xmlNode. we check all
   parents of a given node, so duplicate node names in different places in xml
   are supported. */
gint xml_get_node_cX_by_node(gint config_type, xmlNodePtr node) {
  gint i, j, wrong = 0;
  xmlNodePtr n;

  for( i = 1; i < NUM_CM; i++ ) {
    if( !xmlStrcmp(xml_elements[config_type][i].name, node->name) ) {
      wrong = 0;
      n = node;
      j = i;
      while( (n = n->parent) && (j = xml_elements[config_type][j].parent) ) {
        if( xmlStrcmp(xml_elements[config_type][j].name, n->name) ) wrong++;
      }
      if( !wrong ) return i;
    }
  }
  return 0;
}

/* this will return 0 if config is not read succesfully */
xmlDocPtr xml_read_config(gint config_type, xmlChar *filename) {
  xmlNodePtr cur;
  xmlDocPtr doc;

  debug("Parsing config file %s", filename);
  if( !(doc = xmlParseFile(filename)) ) {
    debug("Cannot parse %s", filename);
    return NULL;
  }

  if( !(cur = xmlDocGetRootElement(doc)) || xmlStrcmp(cur->name, (const xmlChar *) xml_elements[config_type][CM_ROOT].name) ) {
    debug("Correct root element not found in %s", filename);
    xmlFreeDoc(doc);
    return NULL;
  }
  return doc;
}

/* this saves chosen xmlDoc to chosen filename. it tries to fopen()
   filename for writing to decide if we're able to write to the file */
gint xml_save_config(gint config_type, xmlChar *filename) {
  xmlNodePtr node;
  FILE *f;

  if( !(f = fopen(filename, "w")) ) {
    debug("Cannot open file %s for writing!", filename);
    debug("FIXME: create an errordialog in gui");
    return 0;
  }
  fclose(f);

  /* let's set correct version attributes for our root node */
  node = xml_get_node(config_type, xml_doc(config_type), CM_ROOT);
  xmlSetProp(node, xml_attributes[ATTRN_VERSION].name, SILKY_VERSION);
  xmlSetProp(node, xml_attributes[ATTRN_CONFIG_VERSION].name, SILKY_CONFIG_VERSION);

  xml_write_config(config_type, filename);

  return 1;
}


xmlDocPtr xml_create_config(gint config_type) {
  xmlDocPtr doc;
  xmlNodePtr node;
  gint i, a;

  if( !(doc = xmlNewDoc("1.0")) ) {
    debug("Could not create xmlDoc!");
    return NULL;
  }

  if( config_type == CONFIG_MAIN ) {
   /* this is special case, we need all nodes present */
   debug("Creating main config, creating all nodes");
   for( i=1; i<NUM_CM; i++ ) {
    if( !xml_elements[config_type][i].parent ) {
      if( i != CM_ROOT ) debug("WARNING: non-root parentless node found in xml_elements[%d][], this should NOT happen!", config_type);
      else debug("creating root node '%s', behold!", xml_elements[config_type][i].name);
      node = xmlNewNode(NULL, xml_elements[config_type][i].name);
      xmlDocSetRootElement(doc, node);
    } else {
      debug("creating node '%s' under parent '%s'", xml_elements[config_type][i].name, xml_elements[config_type][xml_elements[config_type][i].parent].name);
      node = xmlNewTextChild(xml_get_node(config_type, doc, xml_elements[config_type][i].parent), NULL, xml_elements[config_type][i].name, "");
    }

    /* set appropriate attributes, if any, and set their default values */
    for( a = 0; a < NUM_ATTRN; a++ ) {
      if( (xml_elements[config_type][i].attributes & xml_attributes[a].attribute) ) {
        debug("adding attribute '%s=\"%s\"'", xml_attributes[a].name, xml_attributes[a].default_value);
        xmlNewProp(node, xml_attributes[a].name, xml_attributes[a].default_value);
      }
    }

    debug("Node '%s': setting default value '%s'", node->name, xml_elements[config_type][i].default_value);
    xmlNodeSetContent(node, xml_elements[config_type][i].default_value);
   }
  } else {
   debug("We're not creating main config, so we just create root element for now");
   node = xmlNewNode(NULL, xml_elements[config_type][CM_ROOT].name);
   xmlDocSetRootElement(doc, node);
  }

  return doc;
}

/* checks if the node name is in xml_elements[config_type][] table */
gint xml_is_valid_node(gint config_type, xmlChar *nodename) {
  gint i;

  for( i = 0; i < NUM_CM; i++ ) {
    if( !xmlStrcmp(xml_elements[config_type][i].name, nodename) ) return 1;
  }

  return 0;
}

/* checks if the attribute name is in xml_attributes[] table */
gint xml_is_valid_attr(gint config_type, xmlNodePtr node, xmlChar *attrname) {
  gint a, valid = 0;
  gint n = xml_find_option_num_by_name(config_type, (xmlChar *)node->name);

  for( a = 0; a < NUM_ATTRN; a++ ) {
    if( (xml_elements[config_type][n].attributes & xml_attr_bits[a]) ) valid++;
  }

  if(valid) return 1;

  return 0;
}

/* this is an auxiliary function used by xml_cleanup_config() to recursively
   parse all nodes below given node (inclusively) */
void xml_aux_cleanup_node(gint config_type, xmlNodePtr node)
{
  xmlNodePtr cur;
  xmlAttrPtr attr;

  for (cur = node; cur; cur = cur->next) {
    if (cur->type == XML_ELEMENT_NODE) {  /* only test for actual xml nodes */
      if( !xml_is_valid_node(config_type, (xmlChar *)cur->name) ) {
        debug("Node '%s' is invalid, removing from xml!", cur->name);
	/* whole next big 'if' just removes the invalid node from a linked list
	   of nodes inside libxml structure, so we can free it */
        if( cur->next ) {
          if( cur->prev ) cur->prev->next = cur->next;
	  else cur->parent->children = cur->next;
        } else {
          if( cur->prev ) cur->prev->next = NULL;
	  //	  else cur->parent->children = NULL;
	  else cur = NULL;
        }
	/* all done, now let's ditch the stinker! */
        xmlFreeNode(cur);
      } else {
        /* node is valid, now let's check its attributes */
	for( attr = cur->properties; attr; attr = attr->next ) {
	  if( !xml_is_valid_attr(config_type, cur, (xmlChar *)attr->name) ) {
            debug("Attribute '%s' (from node '%s') is invalid, removing from xml!", attr->name, cur->name);
            if( attr->next ) {
              if( attr->prev ) attr->prev->next = attr->next;
	      else attr->parent->properties = attr->next;
            } else {
              if( attr->prev ) attr->prev->next = NULL;
	      else attr->parent->properties = NULL;
            }
	    /* all done, now let's ditch the stinker! */
            xmlFreeProp(attr);
	  }
	}
      }
    }

    /* recursive call to clean up children of children of children of ... :) */

    if (cur) {
      xml_aux_cleanup_node(config_type, cur->children);
    }
  }
}


/* this will remove all unknown (not defined in xml_elements[config_type][]) nodes from the
   config */
void xml_cleanup_config(gint config_type) {
  xmlNodePtr cur;
  xmlDocPtr doc = xml_doc(config_type);

  if( !doc ) {
    debug("No document!");
    return;
  }

  if( !(cur = xmlDocGetRootElement(doc)) ) {
    debug("root node could not be acquired!");
    return;
  }

  debug("Removing unknown nodes from the config...");
  xml_aux_cleanup_node(config_type, cur);
}

/* this returns content of xml node, specified by xpath from doc.
   return type is either gchar* or gint, depending on "type" attribute of
   the node, defaulting to gint */
/* if there are more than one nodes corresponding to xpath, value of first one
   is returned, rest is ignored */
gchar *xml_get_option(gint config_type, gint node_cn) {
  xmlNodePtr node;

  if( !xml_doc(config_type) ) {
    debug("XML doc == NULL!");
    return NULL;
  }

  if( !(node = xml_get_node(config_type, xml_doc(config_type), node_cn)) ) {
    debug("XML node not found: %s", xml_elements[config_type][node_cn].name);
    if( !(node = xml_create_node(config_type, node_cn)) ) {
      debug("error - node still not found, returning NULL...");
      return 0;
    }
  }

  return xmlNodeGetContent(node);
}

/* this replaces content of xml node, specified by xpath from doc with value.
   function returns 1 on success, 0 on failure. will also set "type" property */
/* if there are more than one nodes corresponding to xpath, value of first one
   is set, rest is ignored */
gint xml_set_option(gint config_type, gint node_cn, xmlChar *value) {
  xmlNodePtr node;

  if( !xml_doc(config_type) ) return 0;

  if( !(node = xml_get_node(config_type, xml_doc(config_type), node_cn)) ) {
    debug("XML node not found: %s", xml_elements[config_type][node_cn].name);
    if( !(node = xml_create_node(config_type, node_cn)) ) {
      debug("error - node still not found, returning NULL...");
      return 0;
    }
  }

  xmlNodeSetContent(node, value);
  return 1;
}

gint xml_find_option_num_by_name(gint config_type, xmlChar *name) {
  gint i;

  for( i=1; i<NUM_CM; i++ ) {
    if( !xmlStrcmp(xml_elements[config_type][i].name, name) ) return i;
  }
  return 0;
}

gint xml_aux_write_node(FILE *f, gint config_type, xmlNodePtr node, gint depth) {
  xmlNodePtr curn;
  xmlAttrPtr cura;
  gint curcn;

  for( curn = node; curn; curn = curn->next ) {
    if( curn->type == XML_ELEMENT_NODE ) {
      if( !(curcn = xml_get_node_cX_by_node(config_type, curn)) ) {
        debug("unknown node %s found, not writing it", curn->name);
	return 0;
      }
      if( !g_fprintf(f, "%s<%s", g_strnfill(depth, ' '), curn->name ) ) return 0;
      for( cura = curn->properties; cura; cura = cura->next ) {
        if( !g_fprintf(f, " %s=\"%s\"", cura->name, xmlGetProp(curn, cura->name)) ) return 0;
      }
      if( !g_fprintf(f, ">") ) return 0;
      if( xml_elements[config_type][curcn].default_value ) {
        if( !g_fprintf(f, "%s</%s>\n",
	  xmlNodeGetContent(curn), curn->name) ) return 0;
      } else {
	if( !g_fprintf(f, "\n") ) return 0;
        /* recursive call to write children */
        xml_aux_write_node(f, config_type, curn->children, depth + 1);
	if( !g_fprintf(f, "%s</%s>\n", g_strnfill(depth, ' '), curn->name ) ) return 0;
      }
    }
  }
  return 1;
}

gint xml_write_config(gint config_type, gchar *filename) {
  FILE *f;
  xmlNodePtr node;
  xmlDocPtr doc = xml_doc(config_type);

  /* some sanity checks first... */
  if( !doc ) {
    debug("error: doc == NULL");
    return 0;
  }

  if( !(node = xmlDocGetRootElement(doc)) ) {
    debug("error: could not get root element from xmlDoc!");
    return 0;
  }

  if( g_file_test(filename, G_FILE_TEST_EXISTS) ) {
    if( !g_file_test(filename, G_FILE_TEST_IS_REGULAR) ) {
      debug("config file is not a regular file: %s", filename);
      return 0;
    }
  }
  if( !(f = fopen(filename, "w")) ) {
    debug("could not open file %s for writing, aborting save", filename);
    return 0;
  }
  debug("file '%s' opened, starting to write contents", filename);

  /* write xml header first */
  if( !g_fprintf(f, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") ) return 0;

  if( !xml_aux_write_node(f, config_type, node, 0) ) return 0;

  fclose(f);
  return 1;
}

xmlDocPtr xml_doc(gint type) {
  switch (type) {
    case CONFIG_MAIN: return config_main;
    case CONFIG_SERVERS: return config_servers;
    case CONFIG_BUDDIES: return config_buddies;
    default: return NULL;
  }

  return NULL;
}


syntax highlighted by Code2HTML, v. 0.9.1