/*
 * Copyright (c) 2005-2006 Tom N Harris.
 *
 *     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; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by Tom N Harris <telliamed@whoopdedo.org>
 *
 */

#include "RdfGraph.h"
#include "globals.h"

#include <iterator>

using namespace SimpleRDF;
using namespace std;


RdfTriple::RdfTriple(char const * __s, char const * __p, char const * __o)
	: pred(__p), subj(__s), obj(__o)
{
}
RdfTriple::RdfTriple(char const * __s, char const * __p, char const * __o, char const * __l)
	: pred(__p), subj(__s), obj(__o), lang(__l)
{
}
RdfTriple::RdfTriple(char const * __s, char const * __p, char const * __o, char const * __l, char const * __t)
	: pred(__p), subj(__s), obj(__o), lang(__l), type(__t)
{
}

unsigned long RdfGraph::LibraryCounter = 0;

RdfGraph::RdfGraph()
{
	if (LibraryCounter++ == 0)
		raptor_init();
}

RdfGraph::~RdfGraph()
{
	EraseAllStatements();
	if (--LibraryCounter == 0)
		raptor_finish();
}

void
RdfGraph::error_callback(void* data, raptor_locator* locator, const char* message)
{
	RdfGraph* _This = reinterpret_cast<RdfGraph*>(data);
	_This->ErrorMsg = message;
	char tmp[40];
	sprintf(tmp, "line %d, column %d", 
	        raptor_locator_line(locator), 
	        raptor_locator_column(locator));
	_This->ErrorLoc = tmp;
	/*
	int len = raptor_format_locator(NULL, 0, locator);
	if (len > 0)
	{
		char* tmp = new char[len+1];
		raptor_format_locator(tmp, len, locator);
		_This->ErrorLoc = tmp;
		delete[] tmp;
	}
	else
		_This->ErrorLoc.clear();
	*/

	raptor_parse_abort(_This->m_parser);
}

void
RdfGraph::warning_callback(void*, raptor_locator*, const char*)
{
}

static char* 
statement_part(const void* term, raptor_identifier_type type, raptor_uri* literal_type, const unsigned char* lang)
{
	char* ret_string = NULL;
	switch (type)
	{
	  case RAPTOR_IDENTIFIER_TYPE_RESOURCE:
	  case RAPTOR_IDENTIFIER_TYPE_PREDICATE:
	  {
		size_t len;
		unsigned char const * uri_string = 
				raptor_uri_as_counted_string(reinterpret_cast<raptor_uri*>(const_cast<void*>(term)), &len);
		ret_string = new char[len+1];
		memcpy(ret_string, uri_string, len+1);
		break;
	  }
	  case RAPTOR_IDENTIFIER_TYPE_ANONYMOUS:
	  {
		char const * name_string = reinterpret_cast<char const*>(term);
		ret_string = new char[strlen(name_string)+3];
		ret_string[0] = '_';
		ret_string[1] = ':';
		strcpy(ret_string+2, name_string);
		break;
	  }
	  case RAPTOR_IDENTIFIER_TYPE_ORDINAL:
		ret_string = new char[sizeof_rdf_rdf_namespace_uri+12];
		sprintf(ret_string, "%s_%d", rdf_rdf_namespace_uri, *reinterpret_cast<int const*>(term));
		break;
	  case RAPTOR_IDENTIFIER_TYPE_LITERAL:
	  case RAPTOR_IDENTIFIER_TYPE_XML_LITERAL:
	  {
		char const * name_string = reinterpret_cast<char const*>(term);
		ret_string = new char[strlen(name_string)+1];
		strcpy(ret_string, name_string);
		break;
	  }
	case RAPTOR_IDENTIFIER_TYPE_UNKNOWN:
		return NULL;
	}
	return ret_string;
}

static char* 
statement_lang(raptor_identifier_type type, const unsigned char* lang)
{
	if (type != RAPTOR_IDENTIFIER_TYPE_LITERAL && type != RAPTOR_IDENTIFIER_TYPE_XML_LITERAL)
		return NULL;
	if (lang == NULL)
		return NULL;
	char* ret_string = new char[strlen(reinterpret_cast<char const*>(lang))+1];
	strcpy(ret_string, reinterpret_cast<char const*>(lang));
	return ret_string;
}

static char* 
statement_type(raptor_identifier_type type, raptor_uri* datatype)
{
	char* ret_string;
	switch (type)
	{
	  case RAPTOR_IDENTIFIER_TYPE_XML_LITERAL:
		ret_string = new char[sizeof_rdf_rdf_xmlliteral_datatype+1];
		strcpy(ret_string, rdf_rdf_xmlliteral_datatype);
		return ret_string;
	  case RAPTOR_IDENTIFIER_TYPE_LITERAL:
	  {
		if (datatype == NULL)
			return NULL;
		size_t len;
		unsigned char const * uri_string = 
				raptor_uri_as_counted_string(datatype, &len);
		ret_string = new char[len+1];
		memcpy(ret_string, uri_string, len+1);
		return ret_string;
	  }
	  default:
		break;
	};
	return NULL;
}

void 
RdfGraph::statement_callback(void * _This, const raptor_statement * stmt)
{
	char * subj = statement_part(stmt->subject, stmt->subject_type, NULL, NULL);
	char * pred = statement_part(stmt->predicate, stmt->predicate_type, NULL, NULL);
	char * obj =  statement_part(stmt->object, stmt->object_type, stmt->object_literal_datatype, stmt->object_literal_language);
	char * lang = statement_lang(stmt->object_type, stmt->object_literal_language);
	char * type = statement_type(stmt->object_type, stmt->object_literal_datatype);
	reinterpret_cast<RdfGraph*>(_This)->AddStatement(stmt, subj, pred, obj, lang?:"", type?:"");
	delete[] type;
	delete[] lang;
	delete[] obj;
	delete[] pred;
	delete[] subj;
}

RdfTriple* 
RdfGraph::AddStatement(raptor_statement const * stmt, char const * subj, char const * pred, char const * obj, char const * lang, char const * type)
{
	PredList::iterator existing_pred = m_predicates.find(string(pred));
	if (existing_pred != m_predicates.end())
	{
		TripleList::iterator existing_triple = existing_pred->second.begin();
		TripleList::iterator last_triple = existing_pred->second.end();
		for (; existing_triple != last_triple; ++existing_triple)
		{
			if ((*existing_triple)->subj == subj
			 && (*existing_triple)->obj == obj
			 && (*existing_triple)->lang == lang
			 && (*existing_triple)->type == type)
			{
				return *existing_triple;
			}
		}
	}

	RdfTriple* triple = new RdfTriple(subj, pred, obj, lang, type);

	m_triples.push_back(triple);
	if (existing_pred == m_predicates.end())
	{
		existing_pred = m_predicates.insert(make_pair(triple->pred,TripleList())).first;
	}
	existing_pred->second.push_back(triple);

	if (triple->pred == rdf_rdf_predicate_type)
	{
		m_types[triple->obj].push_back(triple);
		
		if (triple->obj == rdf_rdf_seq_type 
		 || triple->obj == rdf_rdf_alt_type 
		 || triple->obj == rdf_rdf_bag_type)
		{
			PredList::iterator existing_seq = m_sequences.find(triple->subj);
			if (existing_seq == m_sequences.end())
				m_sequences.insert(make_pair(triple->subj,TripleList()));
		}
	}
	else
	{
		if (stmt->predicate_type == RAPTOR_IDENTIFIER_TYPE_ORDINAL)
		{
			PredList::iterator existing_seq = m_sequences.find(triple->subj);
			// What if the rdf:type hasn't been encountered, or is a mis-match?
			// Ignore it then. Will likely never happen.
			if (existing_seq != m_sequences.end())
			{
				existing_seq->second.push_back(triple);
			}
		}
	}


	return triple;
}

void 
RdfGraph::EraseAllStatements(void)
{
	m_sequences.clear();
	m_types.clear();
	m_predicates.clear();
	TripleList::iterator t = m_triples.begin();
	for (; t != m_triples.end(); ++t)
		delete *t;
	m_triples.clear();
}


RdfGraph::TripleList 
RdfGraph::FindByType(char const * type) const
{
	PredList::const_iterator typelist = m_types.find(string(type));
	if (typelist == m_types.end())
		return TripleList();
	return TripleList(typelist->second);
}

RdfGraph::TripleList 
RdfGraph::FindByPredicate(char const * pred) const
{
	PredList::const_iterator predlist = m_predicates.find(string(pred));
	if (predlist == m_predicates.end())
		return TripleList();
	return TripleList(predlist->second);
}

RdfGraph::TripleList 
RdfGraph::FindBySubject(char const * subj) const
{
	TripleList retlist;
	TripleList::const_iterator t = m_triples.begin();
	TripleList::const_iterator end = m_triples.end();
	insert_iterator<TripleList> out = inserter(retlist, retlist.end());

	for (; t != end; ++t, ++out)
	{
		if ((*t)->subj == subj)
			*out = *t;
	}

	return retlist;
}

RdfGraph::TripleList 
RdfGraph::FindBySubjectPredicate(char const * subj, char const * pred) const
{
	PredList::const_iterator predlist = m_predicates.find(string(pred));
	if (predlist == m_predicates.end())
		return TripleList();
	TripleList const& srclist = predlist->second;
	TripleList retlist;
	TripleList::const_iterator t = srclist.begin();
	insert_iterator<TripleList> out = inserter(retlist, retlist.end());

	for (; t != srclist.end(); ++t, ++out)
	{
		if ((*t)->subj == subj)
			*out = *t;
	}

	return retlist;
}

RdfTriple* 
RdfGraph::FindOneBySubjectPredicate(char const * subj, char const * pred) const
{
	PredList::const_iterator predlist = m_predicates.find(string(pred));
	if (predlist == m_predicates.end())
		return NULL;
	TripleList const& srclist = predlist->second;
	TripleList::const_iterator t = srclist.begin();

	for (; t != srclist.end(); ++t)
	{
		if ((*t)->subj == subj)
			return *t;
	}

	return NULL;
}

RdfTriple* 
RdfGraph::FindOneBySubjectPredicateLang(char const * subj, char const * pred, char const * lang) const
{
	PredList::const_iterator predlist = m_predicates.find(string(pred));
	if (predlist == m_predicates.end())
		return NULL;
	TripleList const& srclist = predlist->second;
	TripleList::const_iterator t = srclist.begin();

	RdfTriple* first = NULL;
	RdfTriple* loose = NULL;
	char looselang[4];
	{
		char const* c = lang;
		int i = 0;
		while (*c != '\0' && *c != '-' && i < 4)
			looselang[i++] = *c++;
		looselang[i] = '\0';
	}

	for (; t != srclist.end(); ++t)
	{
		if ((*t)->subj == subj)
		{
			if (first == NULL)
				first = *t;
			if ((*t)->lang == lang || (*t)->lang == looselang)
				return *t;
			if (loose == NULL && (*t)->lang.find('-') != string::npos)
			{
				if (0 == (*t)->lang.compare(0, (*t)->lang.find('-'), looselang))
				{
					loose = *t;
					// Don't return right away in case a specific lang comes later.
				}
			}
		}
	}
	return (loose != NULL) ? loose : first;
}

RdfGraph::TripleList 
RdfGraph::GetSequence(char const * subj) const
{
	PredList::const_iterator seqlist = m_sequences.find(string(subj));
	if (seqlist == m_sequences.end())
		return TripleList();
	return TripleList(seqlist->second);
}

bool 
RdfGraph::LoadFromFile(char const * fname)
{
	unsigned char * suri = raptor_uri_filename_to_uri_string(fname);
	if (!suri)
	{
		return false;
	}
	raptor_uri * uri = raptor_new_uri(suri);
	if (!uri)
	{
		raptor_free_memory(suri);
		return false;
	}

	m_parser = raptor_new_parser_for_content(NULL, NULL, NULL, 0, suri);
	if (!m_parser)
	{
		raptor_free_uri(uri);
		raptor_free_memory(suri);
		return false;
	}

	raptor_set_fatal_error_handler(m_parser, this, error_callback);
	raptor_set_error_handler(m_parser, this, error_callback);
	raptor_set_warning_handler(m_parser, NULL, warning_callback);
	raptor_set_statement_handler(m_parser, this, statement_callback);

	int retval = raptor_parse_file(m_parser, uri, NULL);

	raptor_free_parser(m_parser);
	raptor_free_uri(uri);
	raptor_free_memory(suri);

	return retval == 0;
}

bool 
RdfGraph::LoadFromUri(char const * suri)
{
	raptor_uri * uri = raptor_new_uri(reinterpret_cast<unsigned char const*>(suri));
	if (!uri)
	{
		return false;
	}

	m_parser = raptor_new_parser_for_content(NULL, NULL, NULL, 0, reinterpret_cast<unsigned char const*>(suri));
	if (!m_parser)
	{
		raptor_free_uri(uri);
		return false;
	}

	raptor_set_fatal_error_handler(m_parser, this, error_callback);
	raptor_set_error_handler(m_parser, this, error_callback);
	raptor_set_warning_handler(m_parser, NULL, warning_callback);
	raptor_set_statement_handler(m_parser, this, statement_callback);

	int retval = raptor_parse_uri(m_parser, uri, NULL);

	raptor_free_parser(m_parser);
	raptor_free_uri(uri);

	return retval == 0;
}

