/* eurephia_xml.c  --  Generic helper functions for XML parsing
 *
 *  GPLv2 only - Copyright (C) 2008 - 2012
 *               David Sommerseth <dazo@users.sourceforge.net>
 *
 *  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; version 2
 *  of the License.
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

/**
 * @file   eurephia_xml.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2008-12-15
 *
 * @brief  Generic XML parser functions
 *
 *
 */

#ifdef HAVE_LIBXML2
#include <stdarg.h>
#include <string.h>
#include <assert.h>

#include <libxml/tree.h>
#include <libxml/xmlstring.h>

#include <eurephia_nullsafe.h>
#include <eurephia_log.h>
#include <eurephia_xml.h>


/**
 * String replace in a xmlChar based string
 *
 * @param str xmlChar input string
 * @param s   search for this character
 * @param r   replace the character with this one
 */
void xmlReplaceChars(xmlChar *str, char s, char r) {
        if( str != NULL ) {
                xmlChar *ptr = str;

                while( *ptr != '\0' ) {
                        if( *ptr == s ) {
                                *ptr = r;
                        }
                        ptr++;
                }
        }
}


/**
 * Retrieves a given XML node attribute/property
 *
 * @param attr xmlAttr pointer from an xmlNode pointer.
 * @param key  The attribute name to search for
 *
 * @return The value of the found attribute.  If not found, NULL is returned.
 */
char *xmlGetAttrValue(xmlAttr *attr, const char *key) {
        xmlAttr *aptr;
        xmlChar *x_key = NULL;

        x_key = xmlCharStrdup(key);
        assert( x_key != NULL );

        for( aptr = attr; aptr != NULL; aptr = aptr->next ) {
                if( xmlStrcmp(aptr->name, x_key) == 0 ) {
                        // FIXME: Should find a better way to return UTF-8 data
                        free_nullsafe(NULL, x_key);
                        return (char *)(aptr->children != NULL ? aptr->children->content : NULL);
                }
        }
        free_nullsafe(NULL, x_key);
        return NULL;
}


/**
 * Loops through a xmlNode chain to look for a given tag.  The search is not recursive.
 *
 * @param node xmlNode pointer where to look
 * @param key  the name of the XML tag to find
 *
 * @return xmlNode pointer to the found xmlNode.  NULL is returned if not found.
 */
xmlNode *xmlFindNode(xmlNode *node, const char *key) {
        xmlNode *nptr = NULL;
        xmlChar *x_key = NULL;

        if( (node == NULL) || (node->children == NULL) ) {
                return NULL;
        }

        x_key = xmlCharStrdup(key);
        assert( x_key != NULL );

        for( nptr = node->children; nptr != NULL; nptr = nptr->next ) {
                if( xmlStrcmp(nptr->name, x_key) == 0 ) {
                        free_nullsafe(NULL, x_key);
                        return nptr;
                }
        }
        free_nullsafe(NULL, x_key);
        return NULL;
}


/**
 * Simple function for creating a new eurephia XML document.  On failure, this function will cause
 * an assertion error.
 *
 * @param ctx           eurephiaCTX
 * @param format        Format version of the eurephia document (int value)
 * @param eurephiaRoot  The name of the root tag of the resulting XML document
 * @param doc           xmlDoc pointer to the new document
 * @param root_n        xmlNode pointer to the root element of the document
 *
 * @return returns always 1.
 */
int eurephiaXML_CreateDoc(eurephiaCTX *ctx, int format, const char *eurephiaRoot,
                          xmlDoc **doc, xmlNode **root_n)
{
        char tmp[34];

        // Create a new XML document
        *doc = xmlNewDoc((xmlChar *)"1.0");
        assert(*doc != NULL);

        // Set the XML root to be <eurephia/>
        *root_n = xmlNewNode(NULL, (xmlChar *)"eurephia");
        assert(*root_n != NULL);

        // Add eurephia XML document format version id
        snprintf(tmp, 33, "%i%c", format, '\0');        xmlNewProp(*root_n, (xmlChar *)"format", (xmlChar *)tmp);
        xmlDocSetRootElement(*doc, *root_n);

        // Add the eurephia XML root (always inside the <eurephia/> tags)
        *root_n = xmlNewChild(*root_n, NULL, (xmlChar*)eurephiaRoot, NULL);

        return 1;
}


/**
 * Get the root node of an eurephia XML document.  This function also validates the basic structure
 * of the document and makes sure the format version of the document is valid.
 *
 * @param ctx         eurephiaCTX
 * @param doc         xmlDoc pointer to the XML document
 * @param nodeset     The expected root node to be found.
 * @param req_format  The minimum format version to be accepted
 *
 * @return Returns pointer to the given xmlNode tag.  On failure, NULL is returned.
 */
xmlNode *eurephiaXML_getRoot(eurephiaCTX *ctx, xmlDoc *doc, const char *nodeset, int req_format) {
        xmlNode *root = NULL;
        char *xmlformat_str = NULL;
        int xmlformat = 0;

        root = xmlDocGetRootElement(doc);
        if( (root == NULL) || (xmlStrcmp(root->name, (xmlChar *)"eurephia") != 0) ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not find eurephia XML root element.  "
                             "Not a valid eurephia XML document.");
                return NULL;
        }

        xmlformat_str = xmlGetAttrValue(root->properties, "format");
        xmlformat = atoi_nullsafe(xmlformat_str);
        if( xmlformat < req_format ) {
                eurephia_log(ctx, LOG_ERROR, 0, "eurephia XML document format is not supported. "
                             "The XML document uses '%s', while we need minimum '%i'", xmlformat_str, req_format);
                return NULL;
        }

        return (nodeset != NULL ? xmlFindNode(root, nodeset) : root->children);
}


/**
 * Creates a simple result message, formatted as an XML document.
 *
 * @param ctx     eurephiaCTX
 * @param type    Can be exmlRESULT or exmlERROR.  The former is used for informational messages.
 * @param info_n  xmlNode with more details about the result
 * @param fmt     stdarg format string
 *
 * @return Returns a valid eurephia ResultMsg XML document as a properly formatted result message.
 *         On failure, NULL is returned
 *
 * Skeleton for a eurephia ResultMsg XML document
 * @code
 * <eurephia format="1">
 *     <Result status="{Result|Error}">
 *          <Message>{String containing a descriptive message}</Message>
 *         [<Details>{xmlNode including children with more detailed information}</Details>]
 *     </Result>
 * </eurephia>
 * @endcode
 * The status attribute is set to "Result" on success, and "Error" in error situations
 */
 xmlDoc *eurephiaXML_ResultMsg(eurephiaCTX *ctx, exmlResultType type, xmlNode *info_n, const char *fmt, ... ) {
        va_list ap;
        xmlChar msg[2050];
        char * xmlfmt = NULL;
        xmlDoc  *msgdoc = NULL;
        xmlNode *msg_n = NULL;

        memset(&msg, 0, 2050);
        xmlfmt = strdup(fmt);
        assert( xmlfmt != NULL );

        va_start(ap, fmt);
        xmlStrVPrintf(msg, 2048, xmlfmt, ap);
        va_end(ap);
        free_nullsafe(ctx, xmlfmt);

        eurephiaXML_CreateDoc(ctx, 1, "Result", &msgdoc, &msg_n);
        assert( (msgdoc != NULL) && (msg_n != NULL) );

        switch( type ) {
        case exmlRESULT:
                xmlNewProp(msg_n, (xmlChar *) "status", (xmlChar *) "Result");
                break;

        case exmlERROR:
                xmlNewProp(msg_n, (xmlChar *) "status", (xmlChar *) "Error");
                break;

        default:
                eurephia_log(ctx, LOG_ERROR, 0, "Wrong XML result message type (%i)", type);
                return NULL;
        }
        xmlNewChild(msg_n, NULL, (xmlChar *) "Message", msg);

        if( info_n != NULL ) {
                // Create a new child tag (Details) and copy the info nodes into this tag
                xmlAddChild(xmlNewChild(msg_n, NULL, (xmlChar *) "Details", NULL),
                            xmlCopyNode(info_n, 1));
        }

        return msgdoc;
}


/**
 * Checks if the given XML document is an eurephia ResultMsg XML document
 *
 * @param ctx    eurephiaCTX
 * @param resxml XML document to validate
 *
 * @return Returns 1 if the input XML document is a ResultMsg document.  Otherwise 0
 */
unsigned int eurephiaXML_IsResultMsg(eurephiaCTX *ctx, xmlDoc *resxml) {
        xmlNode *node = NULL;

        assert( ctx != NULL );
        if( resxml == NULL ) {
                return 0;
        }
        node = eurephiaXML_getRoot(ctx, resxml, "Result", 1);
        return (node != NULL ? 1 : 0);
}

/**
 * Parses an eurephia Result XML document
 *
 * @param ctx    eurephiaCTX
 * @param resxml The result XML document, as produced by eurephiaXML_ResultMsg()
 *
 * @return Returns a pointer to an eurephiaRESULT structure containing the results.
 *         On failure NULL is returned.  This structure can be freed with free_nullsafe().
 *
 * @remark If the result XML document is freed, the information in eurephiaRESULT will be invalidated
 *         Immediately.  However, the eurephiaRESULT pointer must still be freed.
 * @see eurephiaXML_ResultMsg()
 */
eurephiaRESULT *eurephiaXML_ParseResultMsg(eurephiaCTX *ctx, xmlDoc *resxml) {
        eurephiaRESULT *res = NULL;
        xmlNode *res_n = NULL;
        char *str = NULL;

        assert( ctx != NULL );
        if( resxml == NULL ) {
                return NULL;
        }

        res_n = eurephiaXML_getRoot(ctx, resxml, "Result", 1);
        if( res_n == NULL) {
                eurephia_log(ctx, LOG_ERROR, 0, "Could not find a valid <Result> tag");
                return NULL;
        }

        res = (eurephiaRESULT *) malloc_nullsafe(ctx, sizeof(eurephiaRESULT) + 2);
        assert( res != NULL );

        str = xmlGetAttrValue(res_n->properties, "status");
        if( strcmp(str, "Error") == 0 ) {
                res->resultType = exmlERROR;
        } else if( strcmp(str, "Result") == 0 ) {
                res->resultType = exmlRESULT;
        } else {
                free_nullsafe(ctx, res);
                eurephia_log(ctx, LOG_ERROR, 0, "Invalid result status");
                return NULL;
        }

        res->message = xmlGetNodeContent(res_n, "Message");
        res->details = xmlFindNode(res_n, "Details");
        return res;
}
#endif
