/*
 * rdfserver.c
 * Routines for managing the rdf server.
 *
 * Copyright (C) Leni Mayo 2002
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License, version 2, as
 *	published by the Free Software Foundation.
 *	
 *	This program 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 General Public License for more details.
 *	
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdlib.h>
#include <string.h>     /* for strchr */
#include <time.h>       /* for gmtime */
#include <sys/types.h>  /* for stat() */
#include <sys/stat.h>   /* for stat() */
#include <unistd.h>     /* for stat() */
#include <ctype.h>
#include "log.h"
#include "serverconfig.h"
#include "rdfserver.h"

static int newStorageAndModel(const char *pszFilePrefix, StorageCode code, librdf_storage **ppStorage, librdf_model **ppModel);
static int isWhiteOrSemicolon(char ch);

static char * pszRdfDataDirectory = NULL;
static GSList * pListRdfUriSeq = NULL;

RdfServer * rdfServerNew()
{
	RdfServer *pRdfServer;
	int i;
	int ret;

	pRdfServer = (RdfServer *) malloc(sizeof(RdfServer));

	ret = pRdfServer != NULL;

	if (ret && !pWorld)
	{
		pWorld = librdf_new_world();

		librdf_world_open(pWorld);
	}

	ret = pWorld != NULL;

	if (ret)
	{
		ret = rdfConstInitialise();
	}

	if (ret)
	{
		for (i = 0; i < eRdfModelCodeMax; i++)
		{
			pRdfServer->table[i].pModel   = NULL;
			pRdfServer->table[i].pStorage = NULL;
		}

		pRdfServer->table[eRdfModelRecentBoot].pszFilePrefix  = "data-boot";
		pRdfServer->table[eRdfModelDefinitions].pszFilePrefix = "data-defs";

		/*
		 * leni TODO: we can probably take a peek at the mtimes of the uri sequence here
		 * and if stale, parse 'em.  Would save time later...
		 */
		ret = ret && serviceCatalogInitialise(&(pRdfServer->table[eRdfModelRecentBoot]));
		ret = ret && serviceCatalogInitialise(&(pRdfServer->table[eRdfModelDefinitions]));
	}

	if (ret)
	{
		pRdfServer->pszBaseUri = configAsString(eConfigKeyBaseUri);

		pRdfServer->pBaseUri = librdf_new_uri(pWorld, pRdfServer->pszBaseUri);

		ret = pRdfServer->pBaseUri != NULL;
	}

	if (ret)
	{
		pRdfServer->pParser = librdf_new_parser(pWorld, configAsString(eConfigKeyRdfParser), NULL, NULL);

		ret = pRdfServer->pParser != NULL;

		if (!ret)
		{
			printLog(eLogLevelDefault, "Error: Unable to initialise RDF parser: %s.\n",
				configAsString(eConfigKeyRdfParser));
		}
	}

	pRdfServer->pszErrorMessage = NULL;

	pRdfServer->state = eRdfServerStateInitial;

	return ret ? pRdfServer : NULL;
}

void rdfServerDelete(RdfServer *pRdfServer)
{
	int i;

	if (pRdfServer)
	{
		for (i = 0; i < eRdfModelCodeMax; i++)
		{
			if (pRdfServer->table[i].pModel != NULL)
				librdf_free_model(pRdfServer->table[i].pModel);
			
			if (pRdfServer->table[i].pStorage != NULL)
				librdf_free_storage(pRdfServer->table[i].pStorage);
		}

		if (pRdfServer->pParser)
		{
			librdf_free_parser(pRdfServer->pParser);
		}

		if (pRdfServer->pBaseUri)
		{
			librdf_free_uri(pRdfServer->pBaseUri);
		}

		if (pRdfServer->pszErrorMessage)
		{
			librdf_free_uri(pRdfServer->pBaseUri);
		}

		free(pRdfServer);
	}

	librdf_free_world(pWorld);

	pWorld = NULL; /* just in case we ever come back this way again */
}

/*
 * The algorithm for creating long-term storage is as follows:
 * 1. create the long-term model from long-term storage on disk
 * 2. if that fails, try truncating the storage on disk and creating the model from that
 * 3. if that fails, try creating the model from purely memory-based storage
 */
int serviceCatalogInitialise(ServiceCatalog *pServiceCatalog)
{
	librdf_model *pModel = NULL;
	librdf_storage *pStorage = NULL;
	const char *pszFilePrefix = pServiceCatalog->pszFilePrefix;
	const char *pszDirectory = rdfServerGetDataDirectory();
	int ret = TRUE;

	assert(pWorld && pServiceCatalog && pServiceCatalog->pszFilePrefix);

	if (!configAsBool(eConfigKeyRdfDbIgnore))
	{
		ret = newStorageAndModel(pszFilePrefix, eStorageCodeDiskOpenRW, &pStorage, &pModel); 
	}

	if (!ret && !configAsBool(eConfigKeyRdfDbIgnore))
	{
		printLog(eLogLevelDefault, "Unable to read database in %s ... ", pszDirectory);

		ret = newStorageAndModel(pszFilePrefix, eStorageCodeDiskCreate, &pStorage, &pModel); 

		printLog(eLogLevelDefault, " %s.\n", (ret ? "created" : "continuing"));
	}

	if (!ret || configAsBool(eConfigKeyRdfDbIgnore))
	{
		if (!configAsBool(eConfigKeyRdfDbIgnore))
		{
			printLog(eLogLevelDefault, "Unable to create database in %s ... continuing.\n", pszDirectory);
		}

		ret = newStorageAndModel(NULL, eStorageCodeMemory, &pStorage, &pModel); 
	}

	pServiceCatalog->pModel   = pModel;
	pServiceCatalog->pStorage = pStorage;

	return pModel != NULL && pStorage != NULL;
}

ServiceCatalog * serviceCatalogFromUri(RdfServer *pRdfServer, const char *pszUri, const char *pszSource)
{
	librdf_uri *pUri = NULL;
	librdf_model *pModel = NULL;
	librdf_storage *pStorage = NULL;
	ServiceCatalog *pServiceCatalog = NULL;

	assert(pRdfServer && pszUri);

	pUri = librdf_new_uri(pWorld, pszUri);

	pStorage = librdf_new_storage(pWorld, "hashes", "data", "hash-type='memory'");

	if (pStorage != NULL)
	{
		int failed;

		pModel = librdf_new_model(pWorld, pStorage, NULL);

		failed = librdf_parser_parse_into_model(pRdfServer->pParser, pUri, pRdfServer->pBaseUri, pModel);

		if (failed)
		{
			librdf_free_model(pModel);
			librdf_free_storage(pStorage);

			pModel = NULL;
			pStorage = NULL;
		}
#ifdef TAKENOUT
		else
		{
			printf("Parsed %s into model:\n", pszUri);

			librdf_model_print(pModel, stdout);
		}
#endif
	}

	if (pStorage && pModel)
	{
		char *pszMtime = mtimeFromUri(pszSource);
		char *pszU = strdup(pszSource);
		ServiceCatalogMetaData *pMetaData;

		pServiceCatalog = serviceCatalogNew(pModel, pStorage);

		if (pszMtime)
		{
			pMetaData = serviceCatalogMetaDataNew(eSerelMtime, pszMtime);

			if (pMetaData)
			{
				pServiceCatalog->pList = g_slist_append(pServiceCatalog->pList, pMetaData);
			}
		}

		if (pszU)
		{
			pMetaData = serviceCatalogMetaDataNew(eSerelSourceUri, pszU);

			if (pMetaData)
			{
				pServiceCatalog->pList = g_slist_append(pServiceCatalog->pList, pMetaData);
			}
		}
	}

	librdf_free_uri(pUri);

	return pServiceCatalog;
}

ServiceCatalog * serviceCatalogNew(librdf_model *pModel, librdf_storage *pStorage)
{
	ServiceCatalog *pReturn;

	assert(pModel && pStorage);

	pReturn = malloc(sizeof(ServiceCatalog));

	if (pReturn)
	{
		pReturn->pModel = pModel;
		pReturn->pStorage = pStorage;
		pReturn->pList = NULL;
	}

	return pReturn;
}

void serviceCatalogDelete(ServiceCatalog *pServiceCatalog)
{
	GSList *pElement;
	assert(pServiceCatalog && pServiceCatalog->pModel);

	librdf_free_model(pServiceCatalog->pModel);
	librdf_free_storage(pServiceCatalog->pStorage);

	for (pElement = pServiceCatalog->pList; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement->data);

		serviceCatalogMetaDataDelete(pElement->data);
	}

	free(pServiceCatalog);
}

/*
 * mtimeFromUri()
 *
 * mtime is really an implementation detail of the comparison function
 * that answers the question: "has the uri changed?".
 * An alternative way of implementing this might be a file-checksum like md5sum.
 * While slower, that sort of approach might be less fragile, and it'd work for
 * non file: scheme uris.  Still, sysadmins understand mtime, and anyway, it's
 * be easy enough to change.
 * leni - Wed Apr 10 07:24:06 EST 2002
 *
 * Returns:
 *	- NULL for non "file:" scheme uris, otherwise
 *	- A new string containing the mtime of the "file:" uri in ISO 8601 format.
 *	  eg. 2002-03-08 15:45:17
 *	  If this time looks odd to you, try running the gnu date command:
 *	  "date --iso-8601='seconds'"
 */
char * mtimeFromUri(const char *pszUri)
{
	const char *pszScheme="file:";
	char *pszReturn = NULL;

	assert(pszUri);

	if (strncmp(pszScheme, pszUri, strlen(pszScheme)) == 0)
	{
		struct tm *pTm;
		struct stat stat_buf;
		char buf[100];
		char *p = strchr(pszUri, (int) ':') + 1;

		stat(p, &stat_buf);

		pTm = gmtime(&(stat_buf.st_mtime));

		strftime(buf, sizeof(buf), "%G-%m-%d %T UTC", pTm);

		pszReturn = strdup(buf);
	}

	/* printf("mtimeFromUri(%s) returns %s\n", pszUri, pszReturn ? pszReturn : "NULL"); */

	return pszReturn;
}

ServiceCatalogMetaData * serviceCatalogMetaDataNew(RdfProperty code, char *pszValue)
{
	ServiceCatalogMetaData *pReturn = malloc(sizeof(ServiceCatalogMetaData));

	assert(pszValue);

	if (pReturn)
	{
		pReturn->code = code;
		pReturn->pszValue = pszValue;
	}

	return pReturn;
}

void serviceCatalogMetaDataDelete(ServiceCatalogMetaData *p)
{
	assert(p && p->pszValue);

	free(p->pszValue);
	free(p);
}

const char * serviceCatalogMetaDataValue(ServiceCatalog *pServiceCatalog, RdfProperty code)
{
	char *pszReturn = NULL;
	GSList *pElement;
	ServiceCatalogMetaData *pMetaData;

	if (pServiceCatalog)
	{
		for (pElement = pServiceCatalog->pList; pElement != NULL; pElement = pElement->next)
		{
			pMetaData = (ServiceCatalogMetaData *) pElement->data;

			if (pMetaData && pMetaData->code == code)
			{
				pszReturn = pMetaData->pszValue;
				break;
			}
		}
	}

	return pszReturn;
}

GSList *newListFromLists(GSList *pList1, GSList *pList2)
{
	GSList *pReturn = NULL;

	if (pList1)
		pReturn = g_slist_concat(pReturn, g_slist_copy(pList1));

	if (pList2)
		pReturn = g_slist_concat(pReturn, g_slist_copy(pList2));

	return pReturn;
}

int parseUriIntoModel(RdfServer *pRdfServer, librdf_model *pModel, const char *pszUri, const char *pszBaseUri)
{
	librdf_uri *pUri = NULL;
	librdf_uri *pBaseUri = pRdfServer->pBaseUri;
	int failed = TRUE;

	assert(pRdfServer && pModel && pszUri);

	if (pszBaseUri)
	{
		pBaseUri = librdf_new_uri(pWorld, pszBaseUri);
	}

	pUri = librdf_new_uri(pWorld, pszUri);

	if (pUri && pBaseUri)
	{
		failed = librdf_parser_parse_into_model(pRdfServer->pParser, pUri, pBaseUri, pModel);

		librdf_free_uri(pUri);
	}

	if (pszBaseUri && pBaseUri)
	{
		librdf_free_uri(pBaseUri);
	}

	return !failed;
}

/*
 * Note that librdf_new_storage() actually just sets flags inside librdf,
 * without going to the filesystem.  The real work happens when you go to
 * create the model.  By then of course, it's too late to adjust the choice
 * of storage.  Bad design decision in librdf IMO (one of the few).
 *      "premature optimisation is the root of all evil!"
 * This is why storage and model are created together.
 * leni - Sat Apr 20 15:07:50 EST 2002
 */
int newStorageAndModel(const char *pszFilePrefix, StorageCode code,
			librdf_storage **ppStorage, librdf_model **ppModel)
{
	char bufOptions[200];
	const char *pszFormat = configAsString(eConfigKeyRdfDbFormat);
	const char *pszDir    = rdfServerGetDataDirectory();
	librdf_storage *pStorage = NULL;
	librdf_model *pModel = NULL;

	assert(pWorld && ppStorage && ppModel);

	switch(code)
	{
	case eStorageCodeDiskCreate:
		assert(pszFilePrefix != NULL);
		sprintf(bufOptions, "mode='0600',hash-type='%s',dir='%s',write='%s',new='%s'", pszFormat, pszDir, "yes", "yes");
		break;
	case eStorageCodeDiskOpenR:
		assert(pszFilePrefix != NULL);
		sprintf(bufOptions, "mode='0600',hash-type='%s',dir='%s',write='%s',new='%s'", pszFormat, pszDir, "no", "no");
		break;
	case eStorageCodeDiskOpenRW:
		assert(pszFilePrefix != NULL);
		sprintf(bufOptions, "mode='0600',hash-type='%s',dir='%s',write='%s',new='%s'", pszFormat, pszDir, "yes", "no");
		break;
	case eStorageCodeMemory:
		assert(pszFilePrefix == NULL);
		sprintf(bufOptions, "hash-type='%s'", "memory");
		break;
	default:
		assert(NOTHERE);
		break;
	}

	if (!pszFilePrefix)
	{
		pszFilePrefix = "dud"; /* librdf needs something here, even tho it's 'memory' storage */
	}

	pStorage = librdf_new_storage(pWorld, "hashes", (char *) pszFilePrefix, bufOptions);
	pModel   = librdf_new_model(pWorld, pStorage, NULL);

	if (pStorage && pModel)
	{
		*ppStorage = pStorage;
		*ppModel = pModel;
	}

	return pModel && pStorage;
}

int truncateStorage(const char *pszFilePrefix)
{
	librdf_storage *pStorage;
	librdf_model *pModel;
	int ret = TRUE;

	if (!configAsBool(eConfigKeyRdfDbIgnore))
	{
		ret = newStorageAndModel(pszFilePrefix, eStorageCodeDiskCreate, &pStorage, &pModel);

		if (ret)
		{
			librdf_free_model(pModel);
			librdf_free_storage(pStorage);
		}
	}

	return ret;
}

void rdfServerSetDataDirectory(const char *pszDirectory)
{
	char *pszNew;
	assert(pszRdfDataDirectory == NULL);

	pszNew = newAbsoluteFromRelativePath(pszDirectory ? pszDirectory : "");

	pszRdfDataDirectory = pszNew ? pszNew : strdup(pszDirectory);

	/* printf("rdfServerSetDataDirectory(%s) sets pszRdfDataDirectory to %s\n", pszDirectory, pszRdfDataDirectory); */
}

char * rdfServerGetDataDirectory()
{
	return pszRdfDataDirectory;
}

/*
 * rdfServerSetUriSeq()
 *	Break up a semi-colon delimited string containing mixed URI's and filenames of the form:
 *	"/etc/serel/default.rdf;file:/etc/serel/local.rdf;http://www.example.com/service-definitions.rdf"
 *	into a GSList of URI's.  eg:
 *		- item 1: "file:/etc/serel/default.rdf"
 *		- item 2: "file:/etc/serel/local.rdf"
 *		- item 3: "http://www.example.com/service-definitions.rdf"
 */

void rdfServerSetUriSeq(const char *pszUriSeq)
{
	char *pszOld, *pszNew;
	char *pszUriSeqCopy = strdup(pszUriSeq); /* gets permuted */
	GSList *pList = splitStringIntoList(pszUriSeqCopy, isWhiteOrSemicolon);
	GSList *pElement;

	assert(pListRdfUriSeq == NULL); /* easier than testing and freeing */

	for (pElement = pList; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement->data);
		pszOld = (char *) pElement->data;
		pszNew = newAbsoluteFromRelativePath(pszOld);

		if (pszNew)
		{
			pListRdfUriSeq = g_slist_append(pListRdfUriSeq, newUriStringEnsuringScheme(pszNew));
			free(pszNew);
		}
		else
		{
			pListRdfUriSeq = g_slist_append(pListRdfUriSeq, newUriStringEnsuringScheme(pszOld));
		}
	}

	free(pszUriSeqCopy);
	g_slist_free(pList);

#ifdef TAKENOUT
	{
		char *psz = newStringFromStringsInList(pListRdfUriSeq);
		printf("rdfServerSetUriSeq() generated: %s\n", psz ? psz : "NULL");
		free(psz);
	}
#endif
}

GSList * rdfServerGetUriSeq()
{
	return pListRdfUriSeq;
}

int isWhiteOrSemicolon(char ch)
{
	return isspace(ch) || ch == ';';
}

