/* -*- Mode: c; c-basic-offset: 2 -*-
 *
 * rdf_query.c - RDF Query Language/Syntax and Execution Interface
 *
 * Copyright (C) 2002-2008, David Beckett http://www.dajobe.org/
 * Copyright (C) 2002-2005, University of Bristol, UK http://www.bristol.ac.uk/
 * 
 * This package is Free Software and part of Redland http://librdf.org/
 * 
 * It is licensed under the following three licenses as alternatives:
 *   1. GNU Lesser General Public License (LGPL) V2.1 or any newer version
 *   2. GNU General Public License (GPL) V2 or any newer version
 *   3. Apache License, V2.0 or any newer version
 * 
 * You may not use this file except in compliance with at least one of
 * the above three licenses.
 * 
 * See LICENSE.html or LICENSE.txt at the top of this package for the
 * complete terms and further detail along with the license texts for
 * the licenses in COPYING.LIB, COPYING and LICENSE-2.0.txt respectively.
 * 
 * 
 */


#ifdef HAVE_CONFIG_H
#include <rdf_config.h>
#endif

#ifdef WIN32
#include <win32_rdf_config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#include <redland.h>
#include <rdf_query.h>


#ifndef STANDALONE

/* prototypes for helper functions */
static void librdf_delete_query_factories(librdf_world *world);


/**
 * librdf_init_query:
 * @world: redland world object
 *
 * INTERNAL - Initialise the query module.
 * 
 * Initialises and registers all
 * compiled query modules.  Must be called before using any of the query
 * factory functions such as librdf_get_query_factory()
 **/
int
librdf_init_query(librdf_world *world) 
{
  int rc;
  
  /* Always have rasqal implementation available */
  rc = librdf_query_rasqal_constructor(world);
  if(rc)
    return rc;

#ifdef MODULAR_LIBRDF
#else
#ifdef STORAGE_VIRTUOSO
  librdf_init_query_virtuoso(world);
#endif
#endif

  return 0;
}


/**
 * librdf_finish_query:
 * @world: redland world object
 *
 * INTERNAL - Terminate the query module.
 *
 **/
void
librdf_finish_query(librdf_world *world) 
{
  librdf_query_rasqal_destructor(world);
  librdf_delete_query_factories(world);
}



/* helper functions */

/*
 * librdf_free_query_factory - delete a query factory
 */
static void
librdf_free_query_factory(librdf_query_factory *factory)
{
  if(factory) {
    if(factory->name)
      LIBRDF_FREE(librdf_query_factory, factory->name);
    if(factory->uri)
      librdf_free_uri(factory->uri);
    LIBRDF_FREE(librdf_query_factory, factory);
  }
}


/*
 * librdf_delete_query_factories - helper function to delete all the registered query factories
 */
static void
librdf_delete_query_factories(librdf_world *world)
{
  librdf_query_factory *factory, *next;
  
  for(factory=world->query_factories; factory; factory=next) {
    next=factory->next;
    librdf_free_query_factory(factory);
  }
  world->query_factories=NULL;
}


/* class methods */

/**
 * librdf_query_register_factory:
 * @world: redland world object
 * @name: the query language name
 * @uri_string: the query language URI string (or NULL if none)
 * @factory: pointer to function to call to register the factory
 *
 * Register a query factory.
 * 
 **/
REDLAND_EXTERN_C
void
librdf_query_register_factory(librdf_world *world, const char *name,
                              const unsigned char *uri_string,
                              void (*factory) (librdf_query_factory*)) 
{
  librdf_query_factory *query;

  librdf_world_open(world);

#if defined(LIBRDF_DEBUG) && LIBRDF_DEBUG > 1
  LIBRDF_DEBUG2("Received registration for query name %s\n", name);
#endif

  for(query = world->query_factories; query; query = query->next ) {
    if(!strcmp(query->name, name)) {
      librdf_log(world,
                 0, LIBRDF_LOG_ERROR, LIBRDF_FROM_QUERY, NULL,
                 "query language %s already registered", query->name);
      return;
    }
  }

  query = LIBRDF_CALLOC(librdf_query_factory*, 1, sizeof(*query));
  if(!query)
    goto oom;

  query->name = LIBRDF_MALLOC(char*, strlen(name) + 1);
  if(!query->name)
    goto oom_tidy;
  strcpy(query->name, name);

  if(uri_string) {
    query->uri=librdf_new_uri(world, uri_string);
    if(!query->uri)
      goto oom_tidy;
  }

  query->next = world->query_factories;
  world->query_factories = query;

  /* Call the query registration function on the new object */
  (*factory)(query);

#if defined(LIBRDF_DEBUG) && LIBRDF_DEBUG > 1
  LIBRDF_DEBUG3("%s has context size %d\n", name, query->context_length);
#endif

  return;

  oom_tidy:
  librdf_free_query_factory(query);
  oom:
  LIBRDF_FATAL1(world, LIBRDF_FROM_QUERY, "Out of memory");
}


/**
 * librdf_get_query_factory:
 * @world: redland world object
 * @name: the factory name or NULL for the default factory
 * @uri: the factory URI or NULL for the default factory
 *
 * Get a query factory by name.
 * 
 * Return value: the factory object or NULL if there is no such factory
 **/
librdf_query_factory*
librdf_get_query_factory(librdf_world *world, 
                         const char *name, librdf_uri *uri) 
{
  librdf_query_factory *factory;

  librdf_world_open(world);

  /* return 1st query if no particular one wanted - why? */
  if(!name && !uri) {
    factory=world->query_factories;
    if(!factory) {
      LIBRDF_DEBUG1("No (default) query factories registered\n");
      return NULL;
    }
  } else {
    for(factory=world->query_factories; factory; factory=factory->next) {
      if(name && !strcmp(factory->name, name)) {
        break;
      }
      if(uri && factory->uri && librdf_uri_equals(factory->uri, uri)) {
        break;
      }
    }
    /* else FACTORY name not found */
    if(!factory) {
      LIBRDF_DEBUG3("No query language with name '%s' uri %s found\n", 
                    name, (uri ? (char*)librdf_uri_as_string(uri) : "NULL"));
      return NULL;
    }
  }
        
  return factory;
}

/**
 * librdf_query_languages_enumerate:
 * @world: #librdf_world
 * @counter: index into the list of query language syntaxes
 * @name: pointer to store the name of the query language syntax (or NULL)
 * @uri_string: pointer to store query language syntax URI string (or NULL)
 *
 * Get information on query language syntaxes.
 * 
 * All returned strings are shared and must be copied if needed to be
 * used dynamically.
 *
 * @deprecated: use librdf_query_language_get_description() to return
 * more information in a static structure.
 * 
 * Return value: non 0 on failure of if counter is out of range
 */
int
librdf_query_languages_enumerate(librdf_world* world,
                                 const unsigned int counter,
                                 const char **name,
                                 const unsigned char **uri_string)
{
  unsigned int i;
  librdf_query_factory *factory;

  LIBRDF_ASSERT_OBJECT_POINTER_RETURN_VALUE(world, librdf_world, 1);
  if(!name && !uri_string)
    return 1;

  librdf_world_open(world);
  
  factory = world->query_factories;
  if(!factory)
    return 1;

  for(i=0; factory && i<=counter ; i++, factory=factory->next) {
    if(i == counter) {
      if(name)
        *name=factory->name;
      if(uri_string) {
        if (factory->uri)
          *uri_string=librdf_uri_as_string(factory->uri);
        else
          *uri_string=NULL;
      }
      return 0;
    }
  }
        
  return 1;
}

/**
 * librdf_query_language_get_description:
 * @world: #librdf_world
 * @counter: index into the list of query language syntaxes
 *
 * Get a query language syntax description.
 *
 * Return value: description or NULL if counter is out of range
 */
const raptor_syntax_description*
librdf_query_language_get_description(librdf_world* world,
                                      unsigned int counter)
{
  librdf_world_open(world);

  return rasqal_world_get_query_language_description(world->rasqal_world_ptr,
                                                     counter);
}

/**
 * librdf_new_query:
 * @world: redland world object
 * @name: the name identifying the query language
 * @uri: the URI identifying the query language (or NULL)
 * @query_string: the query string
 * @base_uri: the base URI of the query string (or NULL)
 *
 * Constructor - create a new #librdf_query object.
 *
 * Return value: a new #librdf_query object or NULL on failure
 */
librdf_query*
librdf_new_query(librdf_world *world,
                 const char *name, librdf_uri *uri,
                 const unsigned char *query_string,
                 librdf_uri* base_uri)
{
  librdf_query_factory* factory;

  librdf_world_open(world);

  factory=librdf_get_query_factory(world, name, uri);
  if(!factory)
    return NULL;

  return librdf_new_query_from_factory(world, factory, name, uri, 
                                       query_string, base_uri);
}


/**
 * librdf_new_query_from_query:
 * @old_query: the existing query #librdf_query to use
 *
 * Copy constructor - create a new librdf_query object from an existing one
 *
 * Should create a new query in the same context as the existing one
 * as appropriate.
 *
 * Return value: a new #librdf_query object or NULL on failure
 */
librdf_query*
librdf_new_query_from_query(librdf_query* old_query) 
{
  librdf_query* new_query;

  LIBRDF_ASSERT_OBJECT_POINTER_RETURN_VALUE(old_query, librdf_query, NULL);

  /* FIXME: fail if clone is not supported by this query (factory) */
  if(!old_query->factory->clone) {
    LIBRDF_FATAL1(old_query->world, LIBRDF_FROM_QUERY, "clone not implemented for query factory");
    return NULL;
  }

  new_query = LIBRDF_CALLOC(librdf_query*, 1, sizeof(*new_query));
  if(!new_query)
    return NULL;
  
  new_query->usage=1;

  new_query->context = LIBRDF_CALLOC(void*, 1,
                                     old_query->factory->context_length);
  if(!new_query->context) {
    librdf_free_query(new_query);
    return NULL;
  }

  new_query->world=old_query->world;

  /* do this now so librdf_free_query won't call new factory on
   * partially copied query
   */
  new_query->factory=old_query->factory;

  /* clone is assumed to do leave the new query in the same state
   * after an init() method on an existing query - i.e ready to use
   */
  if(old_query->factory->clone(new_query, old_query)) {
    librdf_free_query(new_query);
    return NULL;
  }

  return new_query;
}


/**
 * librdf_new_query_from_factory:
 * @world: redland world object
 * @factory: the factory to use to construct the query
 * @name: query language name
 * @uri: query language URI (or NULL)
 * @query_string: the query string
 * @base_uri: base URI of the query string (or NULL)
 *
 * Constructor - create a new #librdf_query object.
 *
 * Return value: a new #librdf_query object or NULL on failure
 */
librdf_query*
librdf_new_query_from_factory(librdf_world *world,
                              librdf_query_factory* factory,
                              const char *name, librdf_uri *uri,
                              const unsigned char *query_string,
                              librdf_uri *base_uri)
{
  librdf_query* query;

  librdf_world_open(world);

  LIBRDF_ASSERT_OBJECT_POINTER_RETURN_VALUE(factory, librdf_query_factory, NULL);

  if(!factory) {
    LIBRDF_DEBUG1("No query factory given\n");
    return NULL;
  }
  
  query = LIBRDF_CALLOC(librdf_query*, 1, sizeof(*query));
  if(!query)
    return NULL;

  query->world=world;

  query->usage=1;

  query->context = LIBRDF_CALLOC(void*, 1, factory->context_length);
  if(!query->context) {
    librdf_free_query(query);
    return NULL;
  }
  
  query->factory=factory;
  
  if(factory->init(query, name, uri, query_string, base_uri)) {
    librdf_free_query(query);
    return NULL;
  }
  
  return query;
}


/**
 * librdf_free_query:
 * @query: #librdf_query object
 * 
 * Destructor - destroy a #librdf_query object.
 **/
void
librdf_free_query(librdf_query* query) 
{
  if(!query)
    return;
  
  if(--query->usage)
    return;
  
  if(query->factory)
    query->factory->terminate(query);

  if(query->context)
    LIBRDF_FREE(librdf_query_context, query->context);

  LIBRDF_FREE(librdf_query, query);
}


/* methods */


void
librdf_query_add_query_result(librdf_query *query,
                              librdf_query_results* query_results)
{
  query_results->next=query->results;
  query->results=query_results;
  /* add reference to ensure query lives as long as this runs */
  query->usage++;
}


void
librdf_query_remove_query_result(librdf_query *query,
                                 librdf_query_results* query_results)
{
  librdf_query_results *cur, *prev=NULL;
  for(cur = query->results; cur && cur != query_results; cur = cur->next)
    prev = cur;
  
  if(cur != NULL) {
    if(cur == query_results) {
      if(prev)
        prev->next = cur->next;
    }
    if(cur == query->results)
      query->results = cur->next;
  }

  /* remove reference and free if we are the last */
  librdf_free_query(query);
}


/**
 * librdf_query_execute:
 * @query: #librdf_query object
 * @model: model to operate query on
 *
 * Run the query on a model.
 * 
 * Runs the query against the (previously registered) model
 * and returns a #librdf_query_results for the result objects.
 * 
 * Return value:  #librdf_query_results or NULL on failure
 **/
librdf_query_results*
librdf_query_execute(librdf_query* query, librdf_model* model)
{
  librdf_query_results* results=NULL;
  
  LIBRDF_ASSERT_OBJECT_POINTER_RETURN_VALUE(query, librdf_query, NULL);
  LIBRDF_ASSERT_OBJECT_POINTER_RETURN_VALUE(model, librdf_model, NULL);

  if(query->factory->execute) {
    if((results=query->factory->execute(query, model)))
      librdf_query_add_query_result(query, results);
  }
  
  return results;
}


/**
 * librdf_query_get_limit:
 * @query: #librdf_query query object
 *
 * Get the query-specified limit on results.
 *
 * This is the limit given in the query on the number of results allowed.
 *
 * Return value: integer >=0 if a limit is given, otherwise <0
 **/
int
librdf_query_get_limit(librdf_query *query)
{
  if(query->factory->get_limit)
    return query->factory->get_limit(query);
  
  return -1;
}


/**
 * librdf_query_set_limit:
 * @query: #librdf_query query object
 * @limit: the limit on results, >=0 to set a limit, <0 to have no limit
 *
 * Set the query-specified limit on results.
 *
 * This is the limit given in the query on the number of results allowed.
 *
 * Return value: non-0 on failure
 **/
int
librdf_query_set_limit(librdf_query *query, int limit)
{
  if(query->factory->set_limit) {
    query->factory->set_limit(query, limit);
    return 0;
  }

  return -1;
}


/**
 * librdf_query_get_offset:
 * @query: #librdf_query query object
 *
 * Get the query-specified offset on results.
 *
 * This is the offset given in the query on the number of results allowed.
 *
 * Return value: integer >=0 if a offset is given, otherwise <0
 **/
int
librdf_query_get_offset(librdf_query *query)
{
  if(query->factory->get_offset)
    return query->factory->get_offset(query);
  
  return -1;
}


/**
 * librdf_query_set_offset:
 * @query: #librdf_query query object
 * @offset: offset for results, >=0 to set an offset, <0 to have no offset
 *
 * Set the query-specified offset on results.
 *
 * This is the offset given in the query on the number of results allowed.
 *
 * Return value: non-0 on failure
 **/
int
librdf_query_set_offset(librdf_query *query, int offset)
{
  if(query->factory->set_offset) {
    query->factory->set_offset(query, offset);
    return 0;
  }

  return -1;
}

#endif



/* TEST CODE */


#ifdef STANDALONE

/* one more prototype */
int main(int argc, char *argv[]);


#define DATA "@prefix ex: <http://example.org/> .\
ex:fido a ex:Dog ;\
        ex:label \"Fido\" .\
"
#define DATA_LANGUAGE "turtle"
#define DATA_BASE_URI "http://example.org/"
#define QUERY_STRING "SELECT ?x WHERE { ?x a ?y }"
#define QUERY_LANGUAGE "sparql"
#define VARIABLES_COUNT 1

int
main(int argc, char *argv[]) 
{
  librdf_query* query;
  librdf_query_results* results;
  librdf_model* model;
  librdf_storage* storage;
  librdf_parser* parser;
  librdf_uri *uri;
  const char *program=librdf_basename((const char*)argv[0]);
  librdf_world *world;
  size_t string_length;
  unsigned char *string;
  const char *query_string=QUERY_STRING;
  int i;
  
  world=librdf_new_world();
  librdf_world_open(world);
  
  /* list available query languages */
  fprintf(stdout, "%s: Enumerating query languages:\n", program);
  for (i = 0; 1; i++) {
    const raptor_syntax_description* desc = NULL;
    const char *name;
    const unsigned char *uri;
    
    desc = librdf_query_language_get_description(world, i);
    if (!desc)
      break;
      
    fprintf(stdout, " %s <%s>\n", desc->names[0], desc->uri_strings_count ? desc->uri_strings[0] : "");
  }

  /* create model and storage */
  if(1) {
    /* test in memory */
    storage = librdf_new_storage(world, NULL, NULL, NULL);
  } else {
    /* test on disk */
#ifdef HAVE_BDB_HASH
    storage = librdf_new_storage(world, "hashes", "test", "hash-type='bdb',dir='.',write='yes',new='yes',contexts='yes'");
#else
    storage = librdf_new_storage(world, "file", "test.rdf", NULL);
#endif
  }
  if(!storage) {
    fprintf(stderr, "%s: Failed to create new storage\n", program);
    return(1);
  }
  fprintf(stderr, "%s: Creating model\n", program);
  model=librdf_new_model(world, storage, NULL);
  if(!model) {
    fprintf(stderr, "%s: Failed to create new model\n", program);
    return(1);
  }

  /* read the example data in */
  uri=librdf_new_uri(world, (const unsigned char*)DATA_BASE_URI);
  parser=librdf_new_parser(world, DATA_LANGUAGE, NULL, NULL);
  librdf_parser_parse_string_into_model(parser, (const unsigned char*)DATA,
                                        uri, model);
  librdf_free_parser(parser);
  librdf_free_uri(uri);


  fprintf(stdout, "%s: Creating query\n", program);
  query=librdf_new_query(world, QUERY_LANGUAGE,
                         NULL, (const unsigned char*)query_string, NULL);
  if(!query) {
    fprintf(stderr, "%s: Failed to create new query\n", program);
    return(1);
  }

  /* do the query */
  if(!(results=librdf_model_query_execute(model, query))) {
    fprintf(stderr, "%s: Query of model with '%s' failed\n", 
            program, query_string);
    return 1;
  }

  /* print the results */
  while(!librdf_query_results_finished(results)) {
    const char **names=NULL;
    librdf_node* values[VARIABLES_COUNT];
    
    if(librdf_query_results_get_bindings(results, &names, values))
      break;
    
    fputs("result: [", stdout);
    if(names) {
      int i;
      int count = librdf_query_results_get_bindings_count(results);
      
      for(i = 0; i < count; i++) {
        fprintf(stdout, "%s=", names[i]);
        if(values[i]) {
          librdf_node_print(values[i], stdout);
          librdf_free_node(values[i]);
        } else
          fputs("NULL", stdout);
        if(names[i+1])
          fputs(", ", stdout);
      }
    }
    fputs("]\n", stdout);
    
    librdf_query_results_next(results);
  }
  
  fprintf(stdout, "%s: Query returned %d results\n", program, 
          librdf_query_results_get_count(results));

  librdf_free_query_results(results);


  fprintf(stdout, "%s: Executing a second time\n", program);
  if(!(results=librdf_model_query_execute(model, query))) {
    fprintf(stderr, "%s: Second query of model with '%s' failed\n", 
            program, query_string);
    return 1;
  }

  string_length=0;

  string = librdf_query_results_to_counted_string2(results, 
                                                   "xml", NULL, NULL,
                                                   NULL, &string_length);
  fprintf(stdout, "%s: Got query results string of length %d\n", program,
          (int)string_length);

  librdf_free_memory(string);
  
  librdf_free_query_results(results);


  fprintf(stdout, "%s: Freeing query\n", program);
  librdf_free_query(query);

  librdf_free_model(model);
  librdf_free_storage(storage);

  librdf_free_world(world);
  
  /* keep gcc -Wall happy */
  return(0);
}

#endif
