/*
 * Copyright (C) 2020, Red Hat Inc.
 *
 * 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 Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 * Author: Carlos Garnacho <carlosg@gnome.org>
 */

/* Deserialization to cursors for the turtle format defined at:
 *  https://www.w3.org/TR/turtle/
 *
 * And the related TRIG format defined at:
 *  http://www.w3.org/TR/trig/
 */

#include "config.h"

#include "tracker-deserializer-turtle.h"

#include <libtracker-sparql/core/tracker-sparql-grammar.h>
#include <libtracker-sparql/core/tracker-uuid.h>
#include <libtracker-sparql/tracker-private.h>

#include <strings.h>

#define BUF_SIZE 4096
#define RDF_TYPE "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"

typedef enum
{
	STATE_INITIAL,
	STATE_GRAPH,
	STATE_SUBJECT,
	STATE_PREDICATE,
	STATE_OBJECT,
	STATE_STEP,
} ParserState;

typedef struct {
	gchar *subject;
	gchar *predicate;
	ParserState state;
} StateStack;

struct _TrackerDeserializerTurtle {
	TrackerDeserializerRdf parent_instance;
	GBufferedInputStream *buffered_stream;
	GHashTable *blank_nodes;
	GArray *parser_state;
	gchar *base;
	gchar *graph;
	gchar *subject;
	gchar *predicate;
	gchar *object;
	gchar *object_lang;
	gboolean object_is_uri;
	ParserState state;
	goffset line_no;
	goffset column_no;
	gboolean parse_trig;
};

G_DEFINE_TYPE (TrackerDeserializerTurtle,
               tracker_deserializer_turtle,
               TRACKER_TYPE_DESERIALIZER_RDF)

static void advance_whitespace_and_comments (TrackerDeserializerTurtle *deserializer);

static void
tracker_deserializer_turtle_finalize (GObject *object)
{
	TrackerDeserializerTurtle *deserializer = TRACKER_DESERIALIZER_TURTLE (object);

	g_clear_object (&deserializer->buffered_stream);
	g_clear_pointer (&deserializer->blank_nodes, g_hash_table_unref);
	g_clear_pointer (&deserializer->parser_state, g_array_unref);
	g_clear_pointer (&deserializer->subject, g_free);
	g_clear_pointer (&deserializer->predicate, g_free);
	g_clear_pointer (&deserializer->object, g_free);
	g_clear_pointer (&deserializer->graph, g_free);
	g_clear_pointer (&deserializer->base, g_free);

	G_OBJECT_CLASS (tracker_deserializer_turtle_parent_class)->finalize (object);
}

static void
tracker_deserializer_turtle_constructed (GObject *object)
{
	TrackerDeserializerTurtle *deserializer_ttl = TRACKER_DESERIALIZER_TURTLE (object);
	TrackerDeserializer *deserializer = TRACKER_DESERIALIZER (object);
	GInputStream *stream;

	G_OBJECT_CLASS (tracker_deserializer_turtle_parent_class)->constructed (object);

	stream = tracker_deserializer_get_stream (deserializer);
	deserializer_ttl->buffered_stream =
		G_BUFFERED_INPUT_STREAM (g_buffered_input_stream_new (stream));
	deserializer_ttl->line_no = 1;
	deserializer_ttl->column_no = 1;

	g_object_get (object,
	              "has-graph", &deserializer_ttl->parse_trig,
	              NULL);
}

static void
push_stack (TrackerDeserializerTurtle *deserializer)
{
	StateStack state;

	state.subject = g_strdup (deserializer->subject);
	state.predicate = g_strdup (deserializer->predicate);
	state.state = deserializer->state;
	g_array_append_val (deserializer->parser_state, state);
}

static void
pop_stack (TrackerDeserializerTurtle *deserializer)
{
	StateStack *state;
	gchar *s, *p, *o;

	s = deserializer->subject;
	p = deserializer->predicate;
	o = deserializer->object;
	deserializer->subject = deserializer->predicate = deserializer->object = NULL;

	state = &g_array_index (deserializer->parser_state, StateStack, deserializer->parser_state->len - 1);
	deserializer->subject = state->subject;
	deserializer->predicate = state->predicate;
	deserializer->state = state->state;

	if (deserializer->state == STATE_OBJECT) {
		/* Restore the old subject as current object */
		deserializer->object = s;
		deserializer->object_is_uri = TRUE;
		g_clear_pointer (&deserializer->object_lang, g_free);
		s = NULL;
	} else if (deserializer->state == STATE_SUBJECT) {
		g_clear_pointer (&deserializer->subject, g_free);
		deserializer->subject = s;
		s = NULL;
	}

	g_free (s);
	g_free (p);
	g_free (o);
	g_array_remove_index (deserializer->parser_state, deserializer->parser_state->len - 1);
}

static void
calculate_num_lines_and_columns (const gchar     *start,
                                 gsize            count,
                                 goffset         *num_lines,
                                 goffset         *num_columns)
{
	*num_lines = 0;
	*num_columns = 0;

	for (size_t i = 0; i < count; i++)
	{
		if (*(start + i) == '\n') {
			*num_lines += 1;
			*num_columns = 1;
		} else {
			*num_columns += 1;
		}
	}
}

static gsize
seek_input (TrackerDeserializerTurtle *deserializer,
            gsize                      count)
{
	const gchar *buffer;
	gsize size;
	goffset num_lines;
	goffset num_columns;

	buffer = g_buffered_input_stream_peek_buffer (deserializer->buffered_stream,
	                                              &size);
	count = MIN (count, size);
	if (!count)
		return 0;

	calculate_num_lines_and_columns (buffer, count, &num_lines, &num_columns);

	deserializer->line_no += num_lines;
	if (num_lines > 0) {
		deserializer->column_no = num_columns;
	} else {
		deserializer->column_no += num_columns;
	}
	return g_input_stream_skip (G_INPUT_STREAM (deserializer->buffered_stream),
	                            count, NULL, NULL);
}

static gboolean
parse_token (TrackerDeserializerTurtle *deserializer,
             const gchar               *token)
{
	int len = strlen (token);
	const gchar *buffer;
	gsize size;

	buffer = g_buffered_input_stream_peek_buffer (deserializer->buffered_stream,
	                                              &size);
	if (size == 0)
		return FALSE;
	if (strncasecmp (buffer, token, len) != 0)
		return FALSE;
	if (!seek_input (deserializer, len))
		return FALSE;

	return TRUE;
}

static gboolean
parse_terminal (TrackerDeserializerTurtle  *deserializer,
                TrackerTerminalFunc         terminal_func,
                guint                       padding,
                gchar                     **out)
{
	const gchar *end, *buffer;
	gchar *str;
	gsize size;

	buffer = g_buffered_input_stream_peek_buffer (deserializer->buffered_stream,
	                                              &size);
	if (size == 0)
		return FALSE;

	if (!terminal_func (buffer, &buffer[size], &end))
		return FALSE;

	if (end - buffer < 2 * padding)
		return FALSE;

	str = g_strndup (&buffer[padding], end - buffer - (2 * padding));

	if (!seek_input (deserializer, end - buffer)) {
		g_free (str);
		return FALSE;
	}

	if (out)
		*out = str;
	else
		g_free (str);

	return TRUE;
}

static gchar *
generate_bnode (TrackerDeserializerTurtle *deserializer,
                const gchar               *label)
{
	gchar *bnode;

	if (!label)
		return tracker_generate_uuid ("urn:uuid");

	bnode = g_hash_table_lookup (deserializer->blank_nodes, label);

	if (!bnode) {
		bnode = tracker_generate_uuid ("urn:uuid");
		g_hash_table_insert (deserializer->blank_nodes, g_strdup (label), bnode);
	}

	return g_strdup (bnode);
}

static gchar *
expand_prefix (TrackerDeserializerTurtle  *deserializer,
               const gchar                *shortname,
               GError                    **error)
{
	TrackerNamespaceManager *namespaces;
	gchar *expanded;

	namespaces = tracker_deserializer_get_namespaces (TRACKER_DESERIALIZER (deserializer));
	expanded = tracker_namespace_manager_expand_uri (namespaces, shortname);

	if (g_strcmp0 (expanded, shortname) == 0) {
		/* Point to beginning of term */
		deserializer->column_no -= strlen(shortname);
		g_free (expanded);
		g_set_error (error,
		             TRACKER_SPARQL_ERROR,
		             TRACKER_SPARQL_ERROR_PARSE,
		             "Unknown prefix %s", shortname);
		return NULL;
	}

	return expanded;
}

static gchar *
expand_base (TrackerDeserializerTurtle *deserializer,
             gchar                     *suffix)
{
	if (deserializer->base) {
		gchar *str;

		str = g_strdup_printf ("%s%s", deserializer->base, suffix);
		g_free (suffix);
		return str;
	} else {
		return suffix;
	}
}

static void
advance_whitespace (TrackerDeserializerTurtle *deserializer)
{
	while (TRUE) {
		gsize size;
		const gchar *data;
		gchar ch;

		data = g_buffered_input_stream_peek_buffer (deserializer->buffered_stream, &size);
		if (size == 0)
			break;

		ch = data[0];
		if (!(WS))
			break;

		if (!seek_input (deserializer, 1))
			break;
	}
}

static gboolean
maybe_add_prefix (TrackerDeserializerTurtle  *deserializer,
                  const gchar                *prefix,
                  const gchar                *uri,
                  GError                    **error)
{
	TrackerNamespaceManager *namespaces;
	const gchar *existing;

	namespaces = tracker_deserializer_get_namespaces (TRACKER_DESERIALIZER (deserializer));
	existing = tracker_namespace_manager_lookup_prefix (namespaces, prefix);

	if (existing) {
		if (g_strcmp0 (existing, uri) == 0)
			return TRUE;

		g_set_error (error,
		             TRACKER_SPARQL_ERROR,
		             TRACKER_SPARQL_ERROR_PARSE,
		             "Prefix '%s' already expands to '%s'",
		             prefix, existing);
		return FALSE;
	}

	tracker_namespace_manager_add_prefix (namespaces, prefix, uri);
	return TRUE;
}

static gboolean
handle_prefix (TrackerDeserializerTurtle  *deserializer,
               GError                    **error)
{
	gchar *prefix = NULL, *uri = NULL;
	gboolean retval;

	advance_whitespace_and_comments (deserializer);
	if (!parse_terminal (deserializer, terminal_PNAME_NS, 0, &prefix))
		goto error;

	advance_whitespace_and_comments (deserializer);
	if (!parse_terminal (deserializer, terminal_IRIREF, 1, &uri))
		goto error;

	advance_whitespace_and_comments (deserializer);
	if (!parse_token (deserializer, "."))
		goto error;

	/* Remove the trailing ':' in prefix */
	prefix[strlen(prefix) - 1] = '\0';

	retval = maybe_add_prefix (deserializer, prefix, uri, error);
	g_free (prefix);
	g_free (uri);

	return retval;
error:
	g_free (prefix);
	g_free (uri);
	g_set_error (error,
	             TRACKER_SPARQL_ERROR,
	             TRACKER_SPARQL_ERROR_PARSE,
	             "Could not parse @prefix");
	return FALSE;
}

static gboolean
handle_base (TrackerDeserializerTurtle  *deserializer,
             GError                    **error)
{
	gchar *base = NULL;

	advance_whitespace_and_comments (deserializer);
	if (!parse_terminal (deserializer, terminal_IRIREF, 0, &base))
		goto error;

	advance_whitespace_and_comments (deserializer);
	if (!parse_token (deserializer, "."))
		goto error;

	g_clear_pointer (&deserializer->base, g_free);
	deserializer->base = base;
	return TRUE;
error:
	g_free (base);
	g_set_error (error,
	             TRACKER_SPARQL_ERROR,
	             TRACKER_SPARQL_ERROR_PARSE,
	             "Could not parse @base");
	return FALSE;
}

static gboolean
handle_type_cast (TrackerDeserializerTurtle  *deserializer,
                  GError                    **error)
{
	/* These actually go ignored, imposed by the ontology */
	if (parse_token (deserializer, "^^")) {
		if (parse_terminal (deserializer, terminal_IRIREF, 1, NULL) ||
		    parse_terminal (deserializer, terminal_PNAME_LN, 0, NULL) ||
		    parse_terminal (deserializer, terminal_PNAME_NS, 0, NULL))
			return TRUE;

		g_set_error (error,
		             TRACKER_SPARQL_ERROR,
		             TRACKER_SPARQL_ERROR_PARSE,
		             "Error parsing type cast");
		return FALSE;
	}

	return TRUE;
}

static void
advance_whitespace_and_comments (TrackerDeserializerTurtle *deserializer)
{
	const gchar *buffer, *str;
	gsize size;

	while (TRUE) {
		advance_whitespace (deserializer);
		buffer = g_buffered_input_stream_peek_buffer (deserializer->buffered_stream,
		                                              &size);
		if (size == 0)
			break;
		if (buffer[0] != '#')
			break;

		str = strchr (buffer, '\n');
		if (!str)
			break;

		if (!seek_input (deserializer, str + 1 - buffer))
			break;
	}
}

static gboolean
find_needle (const gchar *buffer,
             gsize        buffer_len,
             gsize        start,
             const gchar *needle)
{
	const gchar *ptr, *prev;

 retry:
	ptr = memmem (&buffer[start], buffer_len - start,
	              needle, strlen (needle));
	if (!ptr)
		return FALSE;

	/* Empty string */
	if (ptr == &buffer[start])
		return TRUE;

	prev = ptr - 1;
	g_assert (prev >= &buffer[start]);

	if (*prev == '\\') {
		start = ptr - buffer + 1;
		goto retry;
	}

	return TRUE;
}

static gboolean
maybe_expand_buffer (TrackerDeserializerTurtle  *deserializer,
                     GError                    **error)
{
	const gchar *buffer, *needle;
	gsize start, buffer_len;

	/* Expand the buffer to be able to read string terminals fully,
	 * this only applies if there is a string terminal to read right
	 * now.
	 */
	buffer = g_buffered_input_stream_peek_buffer (deserializer->buffered_stream,
	                                              &buffer_len);
	if (strncmp (buffer, "\"\"\"", 3) == 0) {
		needle = "\"\"\"";
		start = 3;
	} else if (strncmp (buffer, "'''", 3) == 0) {
		needle = "'''";
		start = 3;
	} else if (strncmp (buffer, "\"", 1) == 0) {
		needle = "\"";
		start = 1;
	} else if (strncmp (buffer, "'", 1) == 0) {
		needle = "'";
		start = 1;
	} else {
		return TRUE;
	}

	while (!find_needle (buffer, buffer_len, start, needle)) {
		gsize size, available;

		available = g_buffered_input_stream_get_available (deserializer->buffered_stream);
		size = g_buffered_input_stream_get_buffer_size (deserializer->buffered_stream);

		if (available == size) {
			size *= 2;

			/* We only allow strings up to 1GB */
			if (size > 1024 * 1024 * 1024) {
				g_set_error (error,
				             TRACKER_SPARQL_ERROR,
				             TRACKER_SPARQL_ERROR_PARSE,
				             "String too big to parse");
				return FALSE;
			}

			g_buffered_input_stream_set_buffer_size (deserializer->buffered_stream,
			                                         size);
		}

		if (g_buffered_input_stream_fill (deserializer->buffered_stream, -1, NULL, error) < 0)
			return FALSE;

		buffer = g_buffered_input_stream_peek_buffer (deserializer->buffered_stream,
		                                              &buffer_len);
	}

	return TRUE;
}


static gboolean
tracker_deserializer_turtle_iterate_next (TrackerDeserializerTurtle  *deserializer,
                                          GError                    **error)
{
	while (TRUE) {
		gchar *str, *lang;
		gsize available;

		available = g_buffered_input_stream_get_available (deserializer->buffered_stream);

		if (available < BUF_SIZE) {
			if (g_buffered_input_stream_fill (deserializer->buffered_stream,
			                                  BUF_SIZE - available,
			                                  NULL, error) < 0)
				return FALSE;
		}

		advance_whitespace_and_comments (deserializer);

		switch (deserializer->state) {
		case STATE_INITIAL:
			if (parse_token (deserializer, "@prefix")) {
				if (!handle_prefix (deserializer, error))
					return FALSE;
				break;
			} else if (parse_token (deserializer, "@base")) {
				if (!handle_base (deserializer, error))
					return FALSE;
				break;
			} else {
				if (deserializer->parse_trig)
					deserializer->state = STATE_GRAPH;
				else
					deserializer->state = STATE_SUBJECT;
			}
			break;
		case STATE_GRAPH:
			if (g_buffered_input_stream_get_available (deserializer->buffered_stream) == 0)
				return FALSE;

			g_clear_pointer (&deserializer->graph, g_free);

			if (parse_token (deserializer, "graph")) {
				advance_whitespace_and_comments (deserializer);

				if (parse_terminal (deserializer, terminal_IRIREF, 1, &str)) {
					deserializer->graph = expand_base (deserializer, str);
				} else if (parse_terminal (deserializer, terminal_PNAME_LN, 0, &str) ||
				           parse_terminal (deserializer, terminal_PNAME_NS, 0, &str)) {
					deserializer->graph = expand_prefix (deserializer, str, error);
					g_free (str);
					if (!deserializer->graph)
						return FALSE;
				} else {
					g_set_error (error,
					             TRACKER_SPARQL_ERROR,
					             TRACKER_SPARQL_ERROR_PARSE,
					             "Wrong graph token");
					return FALSE;
				}
			}

			advance_whitespace_and_comments (deserializer);

			if (!parse_token (deserializer, "{")) {
				g_set_error (error,
				             TRACKER_SPARQL_ERROR,
				             TRACKER_SPARQL_ERROR_PARSE,
				             "Expected graph block");
				return FALSE;
			}

			deserializer->state = STATE_SUBJECT;
			break;
		case STATE_SUBJECT:
			if (g_buffered_input_stream_get_available (deserializer->buffered_stream) == 0)
				return FALSE;

			g_clear_pointer (&deserializer->subject, g_free);

			if (parse_token (deserializer, "[")) {
				/* Anonymous blank node */
				push_stack (deserializer);
				deserializer->subject = generate_bnode (deserializer, NULL);
				deserializer->state = STATE_PREDICATE;
				continue;
			}

			if (parse_terminal (deserializer, terminal_IRIREF, 1, &str)) {
				deserializer->subject = expand_base (deserializer, str);
			} else if (parse_terminal (deserializer, terminal_PNAME_LN, 0, &str) ||
			           parse_terminal (deserializer, terminal_PNAME_NS, 0, &str)) {
				deserializer->subject = expand_prefix (deserializer, str, error);
				g_free (str);

				if (*error) {
					return FALSE;
				}
			} else if (parse_terminal (deserializer, terminal_BLANK_NODE_LABEL, 0, &str)) {
				deserializer->subject = generate_bnode (deserializer, str);
				g_free (str);
			} else {
				g_set_error (error,
				             TRACKER_SPARQL_ERROR,
				             TRACKER_SPARQL_ERROR_PARSE,
				             "Wrong subject token");
				return FALSE;
			}

			deserializer->state = STATE_PREDICATE;
			break;
		case STATE_PREDICATE:
			g_clear_pointer (&deserializer->predicate, g_free);

			if (parse_token (deserializer, "a")) {
				deserializer->predicate = g_strdup (RDF_TYPE);
			} else if (parse_terminal (deserializer, terminal_IRIREF, 1, &str)) {
				deserializer->predicate = expand_base (deserializer, str);
			} else if (parse_terminal (deserializer, terminal_PNAME_LN, 0, &str) ||
			           parse_terminal (deserializer, terminal_PNAME_NS, 0, &str)) {
				deserializer->predicate = expand_prefix (deserializer, str, error);
				g_free (str);

				if (*error) {
					return FALSE;
				}
			} else {
				g_set_error (error,
				             TRACKER_SPARQL_ERROR,
				             TRACKER_SPARQL_ERROR_PARSE,
				             "Wrong predicate token");
				return FALSE;
			}

			deserializer->state = STATE_OBJECT;
			break;
		case STATE_OBJECT:
			g_clear_pointer (&deserializer->object, g_free);
			g_clear_pointer (&deserializer->object_lang, g_free);
			deserializer->object_is_uri = FALSE;

			if (parse_token (deserializer, "[")) {
				/* Anonymous blank node */
				push_stack (deserializer);
				deserializer->subject = generate_bnode (deserializer, NULL);
				deserializer->state = STATE_PREDICATE;
				continue;
			}

			if (!maybe_expand_buffer (deserializer, error))
				return FALSE;

			if (parse_terminal (deserializer, terminal_IRIREF, 1, &str)) {
				deserializer->object = expand_base (deserializer, str);
				deserializer->object_is_uri = TRUE;
			} else if (parse_terminal (deserializer, terminal_PNAME_LN, 0, &str) ||
			           parse_terminal (deserializer, terminal_PNAME_NS, 0, &str)) {
				deserializer->object = expand_prefix (deserializer, str, error);
				deserializer->object_is_uri = TRUE;
				g_free (str);

				if (*error) {
					return FALSE;
				}
			} else if (parse_terminal (deserializer, terminal_BLANK_NODE_LABEL, 0, &str)) {
				deserializer->object = generate_bnode (deserializer, str);
				deserializer->object_is_uri = TRUE;
				g_free (str);
			} else if (parse_terminal (deserializer, terminal_STRING_LITERAL_LONG1, 3, &str) ||
			           parse_terminal (deserializer, terminal_STRING_LITERAL_LONG2, 3, &str)) {
				deserializer->object = g_strcompress (str);
				g_free (str);
				if (parse_terminal (deserializer, terminal_LANGTAG, 0, &lang)) {
					deserializer->object_lang = lang;
				} else if (!handle_type_cast (deserializer, error)) {
					return FALSE;
				}
			} else if (parse_terminal (deserializer, terminal_STRING_LITERAL1, 1, &str) ||
			           parse_terminal (deserializer, terminal_STRING_LITERAL2, 1, &str)) {
				deserializer->object = g_strcompress (str);
				g_free (str);
				if (parse_terminal (deserializer, terminal_LANGTAG, 0, &lang)) {
					deserializer->object_lang = lang;
				} else if (!handle_type_cast (deserializer, error)) {
					return FALSE;
				}
			} else if (parse_terminal (deserializer, terminal_DOUBLE_POSITIVE, 0, &str) ||
			           parse_terminal (deserializer, terminal_DECIMAL_POSITIVE, 0, &str) ||
			           parse_terminal (deserializer, terminal_INTEGER_POSITIVE, 0, &str) ||
			           parse_terminal (deserializer, terminal_DOUBLE_NEGATIVE, 0, &str) ||
			           parse_terminal (deserializer, terminal_DECIMAL_NEGATIVE, 0, &str) ||
			           parse_terminal (deserializer, terminal_INTEGER_NEGATIVE, 0, &str) ||
			           parse_terminal (deserializer, terminal_DOUBLE, 0, &str) ||
			           parse_terminal (deserializer, terminal_DECIMAL, 0, &str) ||
			           parse_terminal (deserializer, terminal_INTEGER, 0, &str)) {
				deserializer->object = str;
			} else if (parse_token (deserializer, "true")) {
				deserializer->object = g_strdup ("true");
			} else if (parse_token (deserializer, "false")) {
				deserializer->object = g_strdup ("false");
			} else {
				g_set_error (error,
				             TRACKER_SPARQL_ERROR,
				             TRACKER_SPARQL_ERROR_PARSE,
				             "Wrong object token");
				return FALSE;
			}

			deserializer->state = STATE_STEP;

			/* This is where next() stops, on lack of errors */
			return TRUE;
			break;
		case STATE_STEP:
			if (deserializer->parser_state->len > 0 && parse_token (deserializer, "]")) {
				pop_stack (deserializer);
				if (deserializer->state == STATE_SUBJECT) {
					deserializer->state = STATE_PREDICATE;
					continue;
				} else if (deserializer->state == STATE_OBJECT) {
					deserializer->state = STATE_STEP;
					return TRUE;
				}
			}

			if (parse_token (deserializer, ",")) {
				deserializer->state = STATE_OBJECT;
				break;
			}

			if (parse_token (deserializer, ";")) {
				advance_whitespace_and_comments (deserializer);
				deserializer->state = STATE_PREDICATE;
				/* Dot is allowed after semicolon, continue here */
			}

			if (parse_token (deserializer, ".")) {
				advance_whitespace_and_comments (deserializer);
				deserializer->state = deserializer->parse_trig ?
					STATE_SUBJECT : STATE_INITIAL;
			}

			if (deserializer->parse_trig &&
			    parse_token (deserializer, "}")) {
				advance_whitespace_and_comments (deserializer);
				deserializer->state = STATE_INITIAL;
			}

			/* If we did not advance state, this is a parsing error */
			if (deserializer->state == STATE_STEP) {
				g_set_error (error,
				             TRACKER_SPARQL_ERROR,
				             TRACKER_SPARQL_ERROR_PARSE,
				             "Expected comma, semicolon, or dot");
				return FALSE;
			}

			break;
		}
	}
}

static TrackerSparqlValueType
tracker_deserializer_turtle_get_value_type (TrackerSparqlCursor *cursor,
                                            gint                 column)
{
	TrackerDeserializerTurtle *deserializer = TRACKER_DESERIALIZER_TURTLE (cursor);

	switch (column) {
	case TRACKER_RDF_COL_SUBJECT:
		if (g_str_has_prefix (deserializer->subject, "_:"))
			return TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE;
		else
			return TRACKER_SPARQL_VALUE_TYPE_URI;
	case TRACKER_RDF_COL_PREDICATE:
		return TRACKER_SPARQL_VALUE_TYPE_URI;
	case TRACKER_RDF_COL_OBJECT:
		if (deserializer->object_is_uri)
			return TRACKER_SPARQL_VALUE_TYPE_URI;
		else
			return TRACKER_SPARQL_VALUE_TYPE_STRING;
	case TRACKER_RDF_COL_GRAPH:
		if (deserializer->parse_trig && deserializer->graph)
			return TRACKER_SPARQL_VALUE_TYPE_URI;
		else
			return TRACKER_SPARQL_VALUE_TYPE_UNBOUND;
	default:
		return TRACKER_SPARQL_VALUE_TYPE_UNBOUND;
	}
}

static const gchar *
tracker_deserializer_turtle_get_string (TrackerSparqlCursor *cursor,
                                        gint                 column,
                                        glong               *length)
{
	TrackerDeserializerTurtle *deserializer = TRACKER_DESERIALIZER_TURTLE (cursor);

	switch (column) {
	case TRACKER_RDF_COL_SUBJECT:
		return deserializer->subject;
	case TRACKER_RDF_COL_PREDICATE:
		return deserializer->predicate;
	case TRACKER_RDF_COL_OBJECT:
		return deserializer->object;
	case TRACKER_RDF_COL_GRAPH:
		return deserializer->graph;
	default:
		return NULL;
	}
}

static gboolean
tracker_deserializer_turtle_next (TrackerSparqlCursor  *cursor,
                                  GCancellable         *cancellable,
                                  GError              **error)
{
	TrackerDeserializerTurtle *deserializer = TRACKER_DESERIALIZER_TURTLE (cursor);

	return tracker_deserializer_turtle_iterate_next (deserializer, error);
}

static void
tracker_deserializer_turtle_rewind (TrackerSparqlCursor* cursor)
{
	TrackerDeserializerTurtle *deserializer = TRACKER_DESERIALIZER_TURTLE (cursor);

	g_seekable_seek (G_SEEKABLE (deserializer->buffered_stream),
	                 0, G_SEEK_SET, NULL, NULL);
	deserializer->state = STATE_INITIAL;
	deserializer->line_no = 0;
	deserializer->column_no = 0;
}

void
tracker_deserializer_turtle_close (TrackerSparqlCursor* cursor)
{
	TrackerDeserializerTurtle *deserializer = TRACKER_DESERIALIZER_TURTLE (cursor);

	g_input_stream_close (G_INPUT_STREAM (deserializer->buffered_stream), NULL, NULL);

	TRACKER_SPARQL_CURSOR_CLASS (tracker_deserializer_turtle_parent_class)->close (cursor);
}

static gboolean
tracker_deserializer_turtle_get_parser_location (TrackerDeserializer *deserializer,
                                                 goffset             *line_no,
                                                 goffset             *column_no)
{
	TrackerDeserializerTurtle *deserializer_ttl = TRACKER_DESERIALIZER_TURTLE (deserializer);

	*line_no = deserializer_ttl->line_no;
	*column_no = deserializer_ttl->column_no;
	return TRUE;
}

static void
tracker_deserializer_turtle_class_init (TrackerDeserializerTurtleClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	TrackerSparqlCursorClass *cursor_class = TRACKER_SPARQL_CURSOR_CLASS (klass);
	TrackerDeserializerClass *deserializer_class = TRACKER_DESERIALIZER_CLASS (klass);

	object_class->finalize = tracker_deserializer_turtle_finalize;
	object_class->constructed = tracker_deserializer_turtle_constructed;

	cursor_class->get_value_type = tracker_deserializer_turtle_get_value_type;
	cursor_class->get_string = tracker_deserializer_turtle_get_string;
	cursor_class->next = tracker_deserializer_turtle_next;
	cursor_class->rewind = tracker_deserializer_turtle_rewind;
	cursor_class->close = tracker_deserializer_turtle_close;

	deserializer_class->get_parser_location = tracker_deserializer_turtle_get_parser_location;
}

static void
tracker_deserializer_turtle_init (TrackerDeserializerTurtle *deserializer)
{
	deserializer->blank_nodes = g_hash_table_new_full (g_str_hash, g_str_equal,
	                                                   g_free, g_free);
	deserializer->parser_state = g_array_new (FALSE, FALSE, sizeof (StateStack));
}

TrackerSparqlCursor *
tracker_deserializer_turtle_new (GInputStream            *istream,
                                 TrackerNamespaceManager *namespaces)
{
	g_return_val_if_fail (G_IS_INPUT_STREAM (istream), NULL);

	return g_object_new (TRACKER_TYPE_DESERIALIZER_TURTLE,
	                     "stream", istream,
	                     "namespace-manager", namespaces,
	                     "has-graph", FALSE,
	                     NULL);
}

TrackerSparqlCursor *
tracker_deserializer_trig_new (GInputStream            *istream,
                               TrackerNamespaceManager *namespaces)
{
	g_return_val_if_fail (G_IS_INPUT_STREAM (istream), NULL);

	return g_object_new (TRACKER_TYPE_DESERIALIZER_TURTLE,
	                     "stream", istream,
	                     "namespace-manager", namespaces,
	                     "has-graph", TRUE,
	                     NULL);
}
