/* Sord, a lightweight RDF model library.
 * Copyright 2010-2011 David Robillard <d@drobilla.net>
 *
 * Sord 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 3 of the License, or
 * (at your option) any later version.
 *
 * Sord 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 details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#define _XOPEN_SOURCE 500

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sord/sord.h"

static const int DIGITS  = 3;
static const int MAX_NUM = 999;

typedef struct { SordTuple query; int expected_num_results; } QueryTest;

static SordID
uri(Sord sord, int num)
{
	if (num == 0)
		return 0;

	char         uri[]   = "eg:000";
	const size_t uri_len = 3 + DIGITS;
	char*        uri_num = uri + 3;  // First `0'
	snprintf(uri_num, DIGITS + 1, "%0*d", DIGITS, num);
	return sord_get_uri_counted(sord, true, uri, uri_len);
}

void
generate(Sord sord, size_t n_tuples, size_t n_objects_per)
{
	fprintf(stderr, "Generating %zu (S P *) tuples with %zu objects each\n",
			n_tuples, n_objects_per);

	for (size_t i = 0; i < n_tuples; ++i) {
		int num = (i * n_objects_per) + 1;

		SordID ids[2 + n_objects_per];
		for (size_t j = 0; j < 2 + n_objects_per; ++j) {
			ids[j] = uri(sord, num++);
		}

		for (size_t j = 0; j < n_objects_per; ++j) {
			SordTuple tup = { ids[0], ids[1], ids[2 + j] };
			sord_add(sord, tup);
		}
	}

	// Add some literals
	SordTuple tup;
	tup[0] = uri(sord, 98);
	tup[1] = uri(sord, 4);
	tup[2] = sord_get_literal(sord, true, 0, "hello", NULL);
	tup[3] = 0;
	sord_add(sord, tup);
	tup[2] = sord_get_literal(sord, true, 0, "hi", NULL);
	sord_add(sord, tup);

	tup[0] = uri(sord, 14);
	tup[2] = sord_get_literal(sord, true, 0, "bonjour", "fr");
	sord_add(sord, tup);
	tup[2] = sord_get_literal(sord, true, 0, "salut", "fr");
	sord_add(sord, tup);

	// Attempt to add some duplicates
	sord_add(sord, tup);
	sord_add(sord, tup);

	// Add a blank node subject
	tup[0] = sord_get_blank(sord, true, "ablank");
	sord_add(sord, tup);

	tup[1] = uri(sord, 6);
	tup[2] = uri(sord, 7);
	sord_add(sord, tup);
}

/** Trivial function to return EXIT_FAILURE (useful as a breakpoint) */
int
test_fail()
{
	return EXIT_FAILURE;
}

#define TUP_FMT "(%6s %6s %6s)"
#define TUP_FMT_ARGS(t) \
	(sord_node_load(sord, (t)[0]) \
		? sord_node_get_string(sord_node_load(sord, (t)[0])) : "*"), \
	(sord_node_load(sord, (t)[1]) \
		? sord_node_get_string(sord_node_load(sord, (t)[1])) : "*"), \
	(sord_node_load(sord, (t)[2]) \
		? sord_node_get_string(sord_node_load(sord, (t)[2])) : "*")

int
test_read(Sord sord, const size_t n_tuples, const int n_objects_per)
{
	int ret = EXIT_SUCCESS;

	SordTuple id;

	SordIter iter = sord_begin(sord);
	if (sord_iter_get_sord(iter) != sord) {
		fprintf(stderr, "Fail: Iterator has incorrect sord pointer\n");
		return test_fail();
	}

	for (; !sord_iter_end(iter); sord_iter_next(iter))
		sord_iter_get(iter, id);

	// Attempt to increment past end
	if (!sord_iter_next(iter)) {
		fprintf(stderr, "Fail: Successfully incremented past end\n");
		return test_fail();
	}

	sord_iter_free(iter);

#define NUM_PATTERNS 9

	QueryTest patterns[NUM_PATTERNS] = {
		{ { 0, 0, 0 }, (n_tuples * n_objects_per) + 6 },
		{ { uri(sord, 9), uri(sord, 9), uri(sord, 9) }, 0 },
		{ { uri(sord, 1), uri(sord, 2), uri(sord, 4) }, 1 },
		{ { uri(sord, 3), uri(sord, 4), uri(sord, 0) }, 2 },
		{ { uri(sord, 0), uri(sord, 2), uri(sord, 4) }, 1 },
		{ { uri(sord, 0), uri(sord, 0), uri(sord, 4) }, 1 },
		{ { uri(sord, 1), uri(sord, 0), uri(sord, 0) }, 2 },
		{ { uri(sord, 1), uri(sord, 0), uri(sord, 4) }, 1 },
		{ { uri(sord, 0), uri(sord, 2), uri(sord, 0) }, 2 } };

	for (unsigned i = 0; i < NUM_PATTERNS; ++i) {
		QueryTest test = patterns[i];
		SordTuple pat = { test.query[0], test.query[1], test.query[2], 0 };
		fprintf(stderr, "Query " TUP_FMT "... ", TUP_FMT_ARGS(pat));

		iter = sord_find(sord, pat);
		int num_results = 0;
		for (; !sord_iter_end(iter); sord_iter_next(iter)) {
			sord_iter_get(iter, id);
			++num_results;
			if (!sord_tuple_match(pat, id)) {
				sord_iter_free(iter);
				fprintf(stderr, "Fail: Query result " TUP_FMT " does not match pattern\n",
				        TUP_FMT_ARGS(id));
				return test_fail();
			}
		}
		sord_iter_free(iter);
		if (num_results != test.expected_num_results) {
			fprintf(stderr, "Fail: Expected %d results, got %d\n",
					test.expected_num_results, num_results);
			return test_fail();
		}
		fprintf(stderr, "OK (%u matches)\n", test.expected_num_results);
	}

	// Query blank node subject
	SordTuple pat = { sord_get_blank(sord, true, "ablank"), 0, 0 };
	if (!pat[0]) {
		fprintf(stderr, "Blank node subject lost\n");
		return test_fail();
	}
	fprintf(stderr, "Query " TUP_FMT "... ", TUP_FMT_ARGS(pat));
	iter = sord_find(sord, pat);
	int num_results = 0;
	for (; !sord_iter_end(iter); sord_iter_next(iter)) {
		sord_iter_get(iter, id);
		++num_results;
		if (!sord_tuple_match(pat, id)) {
			sord_iter_free(iter);
			fprintf(stderr, "Fail: Query result " TUP_FMT " does not match pattern\n",
			        TUP_FMT_ARGS(id));
			return test_fail();
		}
	}
	fprintf(stderr, "OK\n");
	sord_iter_free(iter);
	if (num_results != 2) {
		fprintf(stderr, "Blank node subject query failed\n");
		return test_fail();
	}

	// Test nested queries
	fprintf(stderr, "Nested Queries... ");
	pat[0] = pat[1] = pat[2] = 0;
	SordID last_subject = 0;
	iter = sord_find(sord, pat);
	for (; !sord_iter_end(iter); sord_iter_next(iter)) {
		sord_iter_get(iter, id);
		if (id[0] == last_subject)
			continue;

		SordTuple subpat = { id[0], 0, 0 };
		SordIter subiter = sord_find(sord, subpat);
		int num_sub_results = 0;
		for (; !sord_iter_end(subiter); sord_iter_next(subiter)) {
			SordTuple subid;
			sord_iter_get(subiter, subid);
			if (!sord_tuple_match(subpat, subid)) {
				sord_iter_free(iter);
				sord_iter_free(subiter);
				fprintf(stderr, "Fail: Nested query result does not match pattern\n");
				return test_fail();
			}
			++num_sub_results;
		}
		sord_iter_free(subiter);
		if (num_sub_results != n_objects_per) {
			fprintf(stderr, "Fail: Nested query failed (got %d results, expected %d)\n",
					num_sub_results, n_objects_per);
			return test_fail();
		}
		last_subject = id[0];
	}
	fprintf(stderr, "OK\n\n");
	sord_iter_free(iter);

	return ret;
}

int
test_write(Sord sord, const size_t n_tuples, const int n_objects_per)
{
	int ret = EXIT_SUCCESS;

	fprintf(stderr, "Removing Statements... ");
	
	// Remove statements
	SordIter iter;
	for (iter = sord_begin(sord); !sord_iter_end(iter);) {
		sord_remove_iter(sord, iter);
	}
	sord_iter_free(iter);

	const int num_tuples = sord_num_tuples(sord);
	if (num_tuples != 0) {
		fprintf(stderr, "Fail: All tuples removed but %d tuples remain\n", num_tuples);
		return test_fail();
	}

	fprintf(stderr, "OK\n\n");
	
	return ret;
}

int
main(int argc, char** argv)
{
	static const size_t n_tuples      = 300;
	static const int    n_objects_per = 2;

	sord_free(NULL); // Shouldn't crash

	// Create with default options
	Sord sord = sord_new();
	sord_set_option(sord, "http://unknown", "something", SORD_LITERAL, NULL, NULL);
	sord_open(sord);
	generate(sord, n_tuples, n_objects_per);

	if (test_read(sord, n_tuples, n_objects_per)) {
		sord_free(sord);
		return EXIT_FAILURE;
	}

	// Check interning merges equivalent values
	SordID uri_id   = sord_get_uri(sord, true, "http://example.org");
	SordID blank_id = sord_get_uri(sord, true, "testblank");
	SordID lit_id   = sord_get_literal(sord, true, uri_id, "hello", NULL);
	//sord_clear_cache(write);
	SordID uri_id2   = sord_get_uri(sord, false, "http://example.org");
	SordID blank_id2 = sord_get_uri(sord, false, "testblank");
	SordID lit_id2   = sord_get_literal(sord, false, uri_id, "hello", NULL);
	if (uri_id2 != uri_id) {
		fprintf(stderr, "Fail: URI interning failed (duplicates)\n");
		goto fail;
	} else if (blank_id2 != blank_id) {
		fprintf(stderr, "Fail: Blank node interning failed (duplicates)\n");
		goto fail;
	} else if (lit_id2 != lit_id) {
		fprintf(stderr, "Fail: Literal interning failed (duplicates)\n");
		goto fail;
	}

	// Check interning doesn't clash non-equivalent values
	SordID uri_id3   = sord_get_uri(sord, false, "http://example.orgX");
	SordID blank_id3 = sord_get_uri(sord, false, "testblankX");
	SordID lit_id3   = sord_get_literal(sord, false, uri_id, "helloX", NULL);
	if (uri_id3 == uri_id) {
		fprintf(stderr, "Fail: URI interning failed (clash)\n");
		goto fail;
	} else if (blank_id3 == blank_id) {
		fprintf(stderr, "Fail: Blank node interning failed (clash)\n");
		goto fail;
	} else if (lit_id3 == lit_id) {
		fprintf(stderr, "Fail: Literal interning failed (clash)\n");
		goto fail;
	}

	sord_free(sord);

	// Test each pattern type with each index
	static const char* const index_names[6] = {
		"spo", "sop", "ops", "osp", "pso", "pos"
	};

	char*        option     = strdup("http://drobilla.net/ns/sord#index-xxx");
	const size_t option_len = strlen(option);
	for (int i = 0; i < 6; ++i) {
		strncpy(option + option_len - 3, index_names[i], 3);
		sord = sord_new();
		sord_set_option(sord, option, "true", SORD_LITERAL, NULL, NULL);
		printf("Testing Index `%s'\n", index_names[i]);
		sord_open(sord);
		generate(sord, n_tuples, n_objects_per);
		if (test_read(sord, n_tuples, n_objects_per))
			goto fail;
		sord_free(sord);
	}
	free(option);

	sord = sord_new();
	sord_open(sord);
	if (test_write(sord, n_tuples, n_objects_per))
	  goto fail;

	sord_free(sord);

	return EXIT_SUCCESS;

fail:
	sord_free(sord);
	return EXIT_FAILURE;
}