#include <stdio.h>

#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#ifndef _MSC_VER
# include <strings.h>
# include <unistd.h>	/* read */
#else
# define strncasecmp strnicmp
# include <stdlib.h>
# include <io.h>
#endif
#include <assert.h>
#include <sys/stat.h>	/* fstat */
#include <fcntl.h>	/* open */

#include "xml.h"

static const char *_static_root = "/";
static const char *_static_element = "*";
static unsigned int _fcount = 0;
static char **_filenames = 0;
static char *_element = 0;
static char *_value = 0;
static char *_root = 0;
static char *_print = 0;
static char *_attribute = 0;
static int print_filenames = 0;

static void free_and_exit(int i);

#define USE_BUFFER		0
#define NODE_NAME_LEN		256
#define STRING_LEN		2048

#define SHOW_NOVAL(opt) \
{ \
    printf("option '%s' requires a value\n\n", (opt)); \
    free_and_exit(-1); \
}

void
show_help ()
{
    printf("usage: xmlgrep [options] [file ...]\n\n");
    printf("Options:\n");
    printf("\t-h\t\tshow this help message\n");
    printf("\t-a <string>\tprint this attribute as the output\n");
    printf("\t-e <id>\t\tshow sections that contain this element\n");
    printf("\t-p <id>\t\tprint this element as the output\n");
    printf("\t-r <path>\tspecify the XML search root\n");
    printf("\t-v <string>\tfilter sections that contain this vale\n\n");
    printf(" To print the contents of the 'type' element of the XML section ");
    printf("that begins\n at '/printer/output' use the following command:\n\n");
    printf("\txmlgrep -r /printer/output -p type sample.xml\n\n");
    printf(" To filter 'output' elements under '/printer' that have attribute");
    printf(" 'n' set to '1'\n use the following command:\n\n");
    printf("\txmlgrep -r /printer -p output -a n -v 1 sample.xml\n\n");
    printf(" To filter out sections that contain the 'driver' element with ");
    printf("'generic' as\n it's value use the following command:");
    printf("\n\n\txmlgrep -r /printer/output -e driver -v generic sample.xml");
    printf("\n\n");
    free_and_exit(0);
}

void
free_and_exit(int ret)
{
    if (_root && _root != _static_root) free(_root);
    if (_element && _element != _static_element) free(_element);
    if (_value) free(_value);
    if (_print) free(_print);
    if (_attribute) free(_attribute);
    if (_filenames)
    {
        unsigned int i;
        for (i=0; i < _fcount; i++)
        {
            if (_filenames[i])
            {
                if (print_filenames) printf("%s\n", _filenames[i]);
                free(_filenames[i]);
            }
        }
        free(_filenames);
    }
 
    exit(ret);
}

int
parse_option(char **args, int n, int max)
{
    char *opt, *arg = 0;
    unsigned int alen = 0;
    unsigned int olen;

    opt = args[n];
    if (strncmp(opt, "--", 2) == 0)
        opt++;

    if ((arg = strchr(opt, '=')) != NULL)
    {
        *arg++ = 0;
    }
    else if (++n < max)
    {
        arg = args[n];
#if 0
        if (arg && arg[0] == '-')
            arg = 0;
#endif
    }

    olen = strlen(opt);
    if (strncmp(opt, "-help", olen) == 0)
    {
        show_help();
    }
    else if (strncmp(opt, "-root", olen) == 0)
    {
        if (arg == 0) SHOW_NOVAL(opt);
        alen = strlen(arg)+1;
        if (_root) free(_root);
        _root = malloc(alen);
        memcpy(_root, arg, alen);
        return 2;
    }
    else if (strncmp(opt, "-element", olen) == 0)
    {
        if (arg == 0) SHOW_NOVAL(opt);
        alen = strlen(arg)+1;
        if (_element) free(_element);
        _element = malloc(alen);
        memcpy(_element, arg, alen);
        return 2;
    }
    else if (strncmp(opt, "-value", olen) == 0)
    {
        if (arg == 0) SHOW_NOVAL(opt);
        alen = strlen(arg)+1;
        if (_value) free(_value);
        _value = malloc(alen);
        memcpy(_value, arg, alen);
        return 2;
    }
    else if (strncmp(opt, "-print", olen) == 0)
    {
        if (arg == 0) SHOW_NOVAL(opt);
        alen = strlen(arg)+1;
        if (_print) free(_print);
        _print = malloc(alen);
        memcpy(_print, arg, alen);
        return 2;
    }
    else if (strncmp(opt, "-attribute", olen) == 0)
    {
        if (arg == 0) SHOW_NOVAL(opt);
        alen = strlen(arg)+1;
        if (_attribute) free(_attribute);
        _attribute = malloc(alen);
        memcpy(_attribute, arg, alen);
        return 2;
    }
    else if (strncmp(opt, "-list-filenames", olen) == 0)
    { /* undocumented test argument */
        print_filenames = 1;
        return 1;
    }
    else if (opt[0] == '-')
    {
        printf("Unknown option %s\n", opt);
        free_and_exit(-1);
    }
    else
    {
        int pos = _fcount++;
        if (_filenames == 0)
        {
            _filenames = (char **)malloc(sizeof(char*));
        }
        else
        {
            char **ptr = (char **)realloc(_filenames, _fcount*sizeof(char*));
            if (ptr == 0)
            {
                printf("Out of memory.\n\n");
                free_and_exit(-1);
            }
           _filenames = ptr;
        }

        alen = strlen(opt)+1;
        _filenames[pos] = malloc(alen);
        memcpy(_filenames[pos], opt, alen);
    }

    return 1;
}

void walk_the_tree(size_t num, void *xid, char *tree)
{
    unsigned int i, no_elements;

    if (!tree)					/* last node from the tree */
    {
        void *xmid = xmlMarkId(xid);
        if (xmid && _print)
        {
            no_elements = xmlNodeGetNum(xid, _print);
            for (i=0; i<no_elements; i++)
            {
                if (xmlNodeGetPos(xid, xmid, _print, i) != 0)
                {
                    char *value;

                    value = xmlGetString(xmid);
                    if (_value && _attribute && value)
                    {
#if 1
                       char *a = xmlAttributeGetString(xmid, _attribute);
                       if (a && !strcmp(a, _value))
#else
                       if (!xmlAttributeCompareString(xmid, _attribute, _value))
#endif
                       {
                          printf("%s: <%s %s=\"%s\">%s</%s>\n",
                                 _filenames[num], _print, _attribute, _value,
                                                  value, _print);
                       }
                       if (value) free(value);
                    }
                    else
                    {
                       printf("%s: <%s>%s</%s>\n",
                              _filenames[num], _print, value, _print);
                    }
                }
            }
            free(xmid);
        }
        else if (xmid && _value)
        {
            no_elements = xmlNodeGetNum(xmid, _element);
            for (i=0; i<no_elements; i++)
            {
                if (xmlNodeGetPos(xid, xmid, _element, i) != 0)
                {
                    char nodename[NODE_NAME_LEN];

                    xmlNodeCopyName(xmid, (char *)&nodename, NODE_NAME_LEN);
                    if (xmlCompareString(xmid, _value) == 0)
                    {
                        printf("%s: <%s>%s</%s>\n",
                               _filenames[num], nodename, _value, nodename);
                    }
                }
            }
            free(xmid);
        }
        else if (xmid && _element)
        {
            char parentname[NODE_NAME_LEN];

            xmlNodeCopyName(xid, (char *)&parentname, NODE_NAME_LEN);

            no_elements = xmlNodeGetNum(xmid, _element);
            for (i=0; i<no_elements; i++)
            {
                if (xmlNodeGetPos(xid, xmid, _element, i) != 0)
                {
                    char nodename[NODE_NAME_LEN];

                    xmlNodeCopyName(xmid, (char *)&nodename, NODE_NAME_LEN);
                    if (!strncasecmp((char*)&nodename, _element, NODE_NAME_LEN))
                    {
                        char value[NODE_NAME_LEN];
                        xmlCopyString(xmid, (char *)&value, NODE_NAME_LEN);
                        printf("%s: <%s> <%s>%s</%s> </%s>\n",
                                _filenames[num], parentname, nodename, value,
                                                 nodename, parentname);
                    }
                }
            }
        }
        else printf("Error executing xmlMarkId\n");
    }
    else if (xid)			 /* walk the rest of the tree */
    {
        char *elem, *next;
        void *xmid;

        elem = tree;
        if (*elem == '/') elem++;

        next = strchr(elem, '/');
        
        xmid = xmlMarkId(xid);
        if (xmid)
        {
            if (next)
            {
               *next++ = 0;
            }

            no_elements = xmlNodeGetNum(xid, elem);
            for (i=0; i<no_elements; i++)
            {
                if (xmlNodeGetPos(xid, xmid, elem, i) != 0)
                    walk_the_tree(num, xmid, next);
            }

            if (next)
            {
               *--next = '/';
            }

            free(xmid);
        }
        else printf("Error executing xmlMarkId\n");
    }
}

void grep_file(unsigned num)
{
    void *xid;

    xid = xmlOpen(_filenames[num]);
    if (xid)
    {
       void *xrid = xmlMarkId(xid);
       int r = 0;

       walk_the_tree(num, xrid, _root);

       r = xmlErrorGetNo(xrid, 0);
       if (r)
       {
            size_t n = xmlErrorGetLineNo(xrid, 0);
            size_t c = xmlErrorGetColumnNo(xrid, 0);
            const char *s = xmlErrorGetString(xrid, 1); /* clear the error */
            printf("%s: at line %u, column %u: '%s'\n",_filenames[num], n,c, s);
       }

       free(xrid);
    }
    else
    {
        fprintf(stderr, "Error reading file '%s'\n", _filenames[num]);
    }

    xmlClose(xid);
}


void grep_file_buffer(unsigned num)
{
    struct stat st;
    void *xid, *buf;
    int fd, res;

    fd = open(_filenames[num], O_RDONLY);
    if (fd == -1)
    {
        printf("read error opening file '%s'\n", _filenames[num]);
        return;
    }
    
    fstat(fd, &st);
    buf = malloc(st.st_size);
    if (!buf)
    {
        printf("unable to allocate enough memory for reading.\n");
        return;
    }

    res = read(fd, buf, st.st_size);
    if (res == -1)
    {
        printf("unable to read from file '%s'.\n", _filenames[num]);
        return;
    }
    close(fd);

    xid = xmlInitBuffer(buf, st.st_size);
    if (xid)
    {
       void *xrid = xmlMarkId(xid);
       int r = 0;

       walk_the_tree(num, xrid, _root);

       r = xmlErrorGetNo(xrid, 0);
       if (r)
       {
            size_t n = xmlErrorGetLineNo(xrid, 0);
            size_t c = xmlErrorGetColumnNo(xrid, 0);
            const char *s = xmlErrorGetString(xrid, 1); /* clear the error */
            printf("%s: at line %u, column %u: '%s'\n",_filenames[num], n,c, s);
       }

       free(xrid);
    }
    else
    {
        fprintf(stderr, "Error reading file '%s'\n", _filenames[num]);
    }

    xmlClose(xid);
    free(buf);
}

int
main (int argc, char **argv)
{
    unsigned int u;
    int i;

    if (argc == 1)
        show_help();

    for (i=1; i<argc;)
    {
        int ret = parse_option(argv, i, argc);
        i += ret;
    }

    if (_root == 0) _root = (char *)_static_root;
    if (_element == 0) _element = (char *)_static_element;

    for (u=0; u<_fcount; u++)
#if USE_BUFFER
        grep_file_buffer(u);
#else
        grep_file(u);
#endif

    free_and_exit(0);

    return 0;
}
