/**
 * This is part of an XML patch library.
 *
 * Copyright (C) 2010 Nokia Corporation.
 *
 * Contact: Jari Urpalainen <jari.urpalainen@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <stdio.h>
#include <string.h>
#include <check.h>

#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>

#include <glib.h>

#include <libxml/tree.h>

#include "xml_diff.h"
#include "xml_patch.h"

static struct state_s {
    const char *pcszDocFrom,
	       *pcszDocTo,
	       *pcszOutput,
	       *pcszDirectory;
    xmlDocPtr docFrom, docTo, docDiff;
    int fPrint, fShort;
} state[1];

/** patching test, don't care about request namespaces */
static int
patch (xmlDocPtr doc, xmlNodePtr node)
{
    int rc = 0;

    if (!strcmp ((char*)node->name, "add"))
	rc = xml_patch_add (doc, node);
    else if (!strcmp ((char*)node->name, "replace"))
	rc = xml_patch_replace (doc, node);
    else if (!strcmp ((char*)node->name, "remove"))
	rc = xml_patch_remove (doc, node);
    else
	rc = -1;
    return rc;
}

static void
open_files (struct state_s *state, const char *sz1, const char *sz2)
{
    state->docFrom = xmlParseFile (state->pcszDocFrom = sz1);
    state->docTo = xmlParseFile (state->pcszDocTo = sz2);
}

static void
setup (void)
{
    state->docDiff = xmlNewDoc ((const xmlChar*)"1.0");

    state->docDiff->children = xmlNewDocNode (state->docDiff,
					      NULL,
					      (const xmlChar*)"changes",
					      NULL);

    xmlSetNs (state->docDiff->children,
	      xmlNewNs (state->docDiff->children,
			(const xmlChar*)"urn:xml-changes",
			(const xmlChar*)"x"));
}

static void
teardown (void)
{
    xmlFreeDoc (state->docFrom);
    xmlFreeDoc (state->docTo);
    xmlFreeDoc (state->docDiff);

    xmlCleanupParser ();
}

static int
patch_test (struct state_s *state)
{
    xmlNodePtr node = xmlDocGetRootElement (state->docDiff);
    int rc = -1;

    for (node = node ? node->children : NULL;
	 node; node = node->next) {

	if (node->type == XML_ELEMENT_NODE) {
	    rc = patch (state->docFrom, node);

	    if (rc) {
		fprintf (stderr, "Patch could not be applied with the produced diff file !\n");
		break;
	    }
	}
    }
    if (!rc) {
	rc = xml_exec_diff (state->docFrom, state->docTo, FALSE, state->fShort,
			    state->docDiff ? state->docDiff->children : NULL);

	if (rc) {
	    fprintf (stderr, "Additional patching test FAILED !\n");
	}
	else {
	    fprintf (stdout, "Additional patching test succeeded\n");
	}
    }
    return rc;
}

static void
diff_test (struct state_s *state,
	   const char *fn,
	   const char *sz1,
	   const char *sz2)
{
    printf ("*** starting %s\n", fn);

    open_files (state, sz1, sz2);

    int rc = xml_exec_diff (state->docFrom, state->docTo, state->fPrint, state->fShort,
			    state->docDiff ? state->docDiff->children : NULL);

    fail_if (rc <= 0, "diff_generation failed");

    if (rc > 0) {
	xmlDocDump (stdout, state->docDiff);

	fail_if (patch_test (state) != 0, "patch test failed");
    }
    printf ("*** %s ended\n\n", fn);
}

START_TEST (test_12)
{
    diff_test (state, __FUNCTION__, "base1.xml", "base2.xml");
}
END_TEST

START_TEST (test_13)
{
    diff_test (state, __FUNCTION__, "base1.xml", "base3.xml");
}
END_TEST

START_TEST (test_14)
{
    diff_test (state, __FUNCTION__, "base1.xml", "base4.xml");
}
END_TEST

START_TEST (test_15)
{
    diff_test (state, __FUNCTION__, "base1.xml", "base5.xml");
}
END_TEST

START_TEST (test_21)
{
    diff_test (state, __FUNCTION__, "base2.xml", "base1.xml");
}
END_TEST

START_TEST (test_31)
{
    diff_test (state, __FUNCTION__, "base3.xml", "base1.xml");
}
END_TEST

START_TEST (test_41)
{
    diff_test (state, __FUNCTION__, "base4.xml", "base1.xml");
}
END_TEST

START_TEST (test_51)
{
    diff_test (state, __FUNCTION__, "base5.xml", "base1.xml");
}
END_TEST

START_TEST (test_from_to)
{
    diff_test (state, __FUNCTION__, "from.xml", "to.xml");
}
END_TEST

START_TEST (test_to_from)
{
    diff_test (state, __FUNCTION__, "to.xml", "from.xml");
}
END_TEST

START_TEST (test_n01)
{
    diff_test (state, __FUNCTION__, "basen.xml", "basen1.xml");
}
END_TEST

START_TEST (test_n10)
{
    diff_test (state, __FUNCTION__, "basen1.xml", "basen.xml");
}
END_TEST

START_TEST (test_n02)
{
    diff_test (state, __FUNCTION__, "basen.xml", "basen2.xml");
}
END_TEST

START_TEST (test_n20)
{
    diff_test (state, __FUNCTION__, "basen2.xml", "basen.xml");
}
END_TEST

static struct {
    const char *name;
    void (*fn) (int foo);
} arr_tests[] = {
    {"test_1_to_2", test_12},
    {"test_1_to_3", test_13},
    {"test_1_to_4", test_14},
    {"test_1_to_5", test_15},
    {"test_2_to_1", test_21},
    {"test_3_to_1", test_31},
    {"test_4_to_1", test_41},
    {"test_5_to_1", test_51},
    {"test_from_to", test_from_to},
    {"test_to_from", test_to_from},
    {"test_n_0_to_1", test_n01},
    {"test_n_0_to_2", test_n02},
    {"test_n_2_to_0", test_n10},
    {"test_n_1_to_0", test_n20},
};

static void
add_test (TCase *tc, int i)
{
    _tcase_add_test (tc, arr_tests[i].fn, arr_tests[i].name, 0, 0, 1);
}

static void
diff_tests (Suite *s, int dump, int argc, char *argv[])
{
    int i;

    TCase *tc = tcase_create ("diff-tests");
    suite_add_tcase (s, tc);

    if (dump) {
	printf ("Available tests:\n");
	for (i = 0; i < sizeof (arr_tests) / sizeof (arr_tests[0]); i++) {
	    printf ("   %s\n", arr_tests[i].name);
	}
    }
    else if (argc == 0) {
	for (i = 0; i < sizeof (arr_tests) / sizeof (arr_tests[0]); i++) {
	    add_test (tc, i);
	}
    }
    else {
	for (i = 0; i < argc; i++) {
	    int j;

	    for (j = 0; j < sizeof (arr_tests) / sizeof (arr_tests[0]); j++) {
		if (!strcmp (argv[i], arr_tests[j].name))  {
		    add_test (tc, j);
		}
	    }
	}
    }
    tcase_add_checked_fixture (tc, setup, teardown);

    tcase_set_timeout (tc, 60);
}

static Suite *
make_diff_suite (int dump, int argc, char *argv[])
{
    Suite *s = suite_create ("xml_diff tests");

    diff_tests (s, dump, argc, argv);
    return s;
}

int
main (int argc, char *argv[])
{
    int ret, opt_index, dump = FALSE, debug = FALSE;
    Suite *diff;
    SRunner *sr;

    static struct option const opt_tbl[] =
    {
	{"directory",	required_argument, NULL, 'd'},
	{"dump",	no_argument, NULL, 'p'},
	{"no_ws",	no_argument, NULL, 'w'},
	{"short",	no_argument, NULL, 's'},
	{"debug",	no_argument, NULL, 'g'},
	{"help",	no_argument, NULL, 'h'},
	{NULL, 0, NULL, 0}
    };

    state->fPrint = TRUE;
    state->pcszDirectory = "../diff/tests";

    while (ret = getopt_long (argc, argv, "shpgwd:", opt_tbl, &opt_index), ret != -1)
    switch (ret) {
    case 'p':
	dump = TRUE;
	break;

    case 'd':
	state->pcszDirectory = optarg;
	break;

    case 'w':
	state->fShort = XML_DIFF_NO_WHITESPACE;
	break;

    case 's':
	state->fShort = XML_DIFF_SIZE_OPTIMIZE;
	break;

    case 'g':
	debug = TRUE;
	break;

    case 'h':
    default:
	printf ("Usage: test-diff [options] [test_name1 name2...]\n"
		"  test_name == list of selected tests\n"
		"    options:\n"
		"       -p (--dump) dump available test names, no actual runs\n"
		"       -d (--directory) where all test files exist (%s)\n"
		"       -g (--debug) no forking, making debugging easier\n", state->pcszDirectory
	       );
	exit (0);
    }
    if (chdir (state->pcszDirectory))
	fprintf (stderr, "chdir() error: %s\n", strerror (errno));

    diff = make_diff_suite (dump, argc - optind, &argv[optind]);

    sr = srunner_create (diff);

    srunner_set_xml (sr, "result.xml");

    if (debug)
	srunner_set_fork_status(sr, CK_NOFORK);

    srunner_run_all (sr, CK_NORMAL);
    ret = srunner_ntests_failed (sr);

    srunner_free (sr);
    return ret;
}


