/*
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.0 of the PHP license,       |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_0.txt.                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Authors: Christophe Vu-Brugier <cvubrugier@yahoo.fr>                 |
   +----------------------------------------------------------------------+
*/

/* $ Id: $ */ 

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <locale>

#include "php_clucene.h"

#define PHP_CLUCENE_MODULE_VERSION "0.0.9"

using namespace lucene::analysis;
using namespace lucene::document;
using namespace lucene::queryParser;
using namespace lucene::search;
using namespace lucene::util;

#if HAVE_CLUCENE

/* Other analyzers should be added in the future */
enum {STANDARD_ANALYZER};

static zend_object_handlers index_searcher_object_handlers;
static zend_object_handlers hits_object_handlers;

zend_class_entry *index_searcher_class_entry;
zend_class_entry *hits_class_entry;

/* {{{ clucene_module_entry */
zend_module_entry clucene_module_entry = {
	STANDARD_MODULE_HEADER,
	"clucene",
	NULL,
	PHP_MINIT(clucene),     /* Replace with NULL if there is nothing to do at php startup */ 
	NULL,                   /* PHP_MSHUTDOWN(clucene), */
	NULL,                   /* PHP_RINIT(clucene), */
	NULL,                   /* PHP_RSHUTDOWN(clucene), */
	PHP_MINFO(clucene),
	PHP_CLUCENE_MODULE_VERSION, 
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

/* {{{ index_searcher_object_dtor */
static void index_searcher_object_dtor(void *object, zend_object_handle handle TSRMLS_DC)
{
	index_searcher_object *intern = (index_searcher_object*) object;
	
	zend_hash_destroy(intern->std.properties);
	FREE_HASHTABLE(intern->std.properties);
	
	/* Free other members */
	if (intern->searcher != NULL) {
		intern->searcher->close();
		delete intern->searcher;
	}
	if (intern->field != NULL) {
		delete [] intern->field;
	}
	efree(object);
} /* }}} */

/* {{{ index_searcher_object_new */
static zend_object_value index_searcher_object_new(zend_class_entry *class_type TSRMLS_DC)
{
	zend_object_value retval;
	index_searcher_object *intern;
	zval *tmp;
	
	intern = (index_searcher_object*) emalloc(sizeof(index_searcher_object));
	memset(intern, 0, sizeof(index_searcher_object));
	intern->searcher = NULL;
	intern->field    = NULL;
	intern->std.ce   = class_type;

	ALLOC_HASHTABLE(intern->std.properties);
	zend_hash_init(intern->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0);
	zend_hash_copy(intern->std.properties,
				   &class_type->default_properties,
				   (copy_ctor_func_t) zval_add_ref,
				   (void *) &tmp, sizeof(zval *));
	retval.handle = zend_objects_store_put(intern,
										   index_searcher_object_dtor,
										   NULL,
										   NULL TSRMLS_CC);
	retval.handlers = &index_searcher_object_handlers;
	return retval;
} /* }}} */

/* {{{ hits_object_dtor */
static void hits_object_dtor(void *object, zend_object_handle handle TSRMLS_DC)
{
	hits_object *intern = (hits_object*) object;
	
	zend_hash_destroy(intern->std.properties);
	FREE_HASHTABLE(intern->std.properties);
	
	/* Free other members */
	if (intern->hits != NULL) {
		delete intern->hits;
	}

	efree(object);
} /* }}} */

/* {{{  hits_object_new */
static zend_object_value hits_object_new(zend_class_entry *class_type TSRMLS_DC)
{
	zend_object_value retval;
	hits_object *intern;
	zval *tmp;
	
	intern = (hits_object*) emalloc(sizeof(hits_object));
	memset(intern, 0, sizeof(hits_object));
	intern->hits   = NULL;
	intern->std.ce = class_type;

	ALLOC_HASHTABLE(intern->std.properties);
	zend_hash_init(intern->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0);
	zend_hash_copy(intern->std.properties,
				   &class_type->default_properties,
				   (copy_ctor_func_t) zval_add_ref,
				   (void *) &tmp, sizeof(zval *));
	retval.handle = zend_objects_store_put(intern,
										   hits_object_dtor,
										   NULL,
										   NULL TSRMLS_CC);
	retval.handlers = &hits_object_handlers;
	return retval;

} /* }}} */

/* {{{  hits_object_create */
static zend_object_value hits_object_create(zend_class_entry *class_type, Hits *hits TSRMLS_DC)
{
	zend_object_value retval;
	hits_object *intern;
	zval *tmp;
	
	intern = (hits_object*) emalloc(sizeof(hits_object));
	memset(intern, 0, sizeof(hits_object));
	intern->hits   = hits;
	intern->std.ce = class_type;

	ALLOC_HASHTABLE(intern->std.properties);
	zend_hash_init(intern->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0);
	zend_hash_copy(intern->std.properties,
				   &class_type->default_properties,
				   (copy_ctor_func_t) zval_add_ref,
				   (void *) &tmp, sizeof(zval *));
	retval.handle = zend_objects_store_put(intern,
										   hits_object_dtor,
										   NULL,
										   NULL TSRMLS_CC);
	retval.handlers = &hits_object_handlers;
	return retval;
} /* }}} */

#ifdef COMPILE_DL_CLUCENE
extern "C" {
ZEND_GET_MODULE(clucene)
} /* extern "C" */
#endif

/* Forward declarations for IndexSearcher methods */
PHP_METHOD(IndexSearcher, __construct);
PHP_METHOD(IndexSearcher, search);
PHP_METHOD(IndexSearcher, close);

static zend_function_entry index_searcher_functions[] = {
	PHP_ME(IndexSearcher, __construct,    NULL, ZEND_ACC_PUBLIC)
	PHP_ME(IndexSearcher, search,         NULL, ZEND_ACC_PUBLIC)
	PHP_ME(IndexSearcher, close,          NULL, ZEND_ACC_PUBLIC)
	{NULL, NULL, NULL}
};

/* Forward declarations for Hits methods */
PHP_METHOD(Hits, __construct);
PHP_METHOD(Hits, length);
PHP_METHOD(Hits, get);
PHP_METHOD(Hits, id);
PHP_METHOD(Hits, score);

/* The creation of a Hits object is not allowed */
static zend_function_entry hits_functions[] = {
	PHP_ME(Hits, __construct, NULL, ZEND_ACC_PRIVATE)
	PHP_ME(Hits, length,      NULL, ZEND_ACC_PUBLIC)
	PHP_ME(Hits, get,         NULL, ZEND_ACC_PUBLIC)
	PHP_ME(Hits, id,          NULL, ZEND_ACC_PUBLIC)
	PHP_ME(Hits, score,       NULL, ZEND_ACC_PUBLIC)
	{NULL, NULL, NULL}
};

/* {{{ proto void IndexSearcher::__construct(string path)
   Constructs a new IndexSearcher instance. */
PHP_METHOD(IndexSearcher, __construct)
{
	index_searcher_object *intern;
	char *path;
	long path_len;
	char *field;
	long field_len;

	php_set_error_handling(EH_THROW, zend_exception_get_default()
						   TSRMLS_CC);
	
	/* TODO: Add the analyzer as a 3rd parameter when several
	 *       analyzers will be available.
	 */
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &path,
							  &path_len, &field, &field_len) == FAILURE) {
		php_set_error_handling(EH_NORMAL, NULL TSRMLS_CC);
		return;
	}

	intern = (index_searcher_object*) zend_object_store_get_object(getThis() TSRMLS_CC);

	php_set_error_handling(EH_NORMAL, NULL TSRMLS_CC);

	try {
		intern->searcher = new IndexSearcher(path);
	} catch (CLuceneError& error) {
		zend_throw_exception(zend_exception_get_default(), error.what(), 0 TSRMLS_CC);
		return;
	}
	intern->field = Misc::_charToWide(field);
} /* }}} */

/* {{{ proto object IndexSearcher::search(string query)
   Return documents matching query */
PHP_METHOD(IndexSearcher, search)
{
	index_searcher_object *intern;
	char        *query_string;
	wchar_t     *tmp;
	long        len;
	QueryParser *parser;
	Query       *query;
	Hits        *hits;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &query_string, &len) == FAILURE) {
		return;
	}
	intern = (index_searcher_object*) zend_object_store_get_object(getThis() TSRMLS_CC);
	if (intern->searcher == NULL) {
		zend_throw_exception(zend_exception_get_default(), "The IndexSearcher has been closed", 0 TSRMLS_CC);
		return;
	}

	parser = new QueryParser(intern->field, &analyzer);
	tmp = Misc::_charToWide(query_string);
	query = parser->parse(tmp);

	hits = intern->searcher->search(query);

	delete [] tmp;
	delete query;
	delete parser;

	return_value->type = IS_OBJECT;
	return_value->value.obj = hits_object_create(hits_class_entry, hits);
}
/* }}} */

/* {{{ proto void IndexSearcher::close() 
   Frees resources associated with the searcher */
PHP_METHOD(IndexSearcher, close)
{
	index_searcher_object *intern;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
		return;
	}

	intern = (index_searcher_object*)
		zend_object_store_get_object(getThis() TSRMLS_CC);
	
	if (intern->searcher != NULL)
	{
		intern->searcher->close();
		delete intern->searcher;
		intern->searcher = NULL;
	}
	delete [] intern->field;
	intern->field = NULL;
}
/* }}} */

/* {{{ proto void Hits::__construct()
   Constructs a new Hits instance. */
PHP_METHOD(Hits, __construct)
{
	hits_object *intern;

	php_set_error_handling(EH_THROW, zend_exception_get_default()
						   TSRMLS_CC);
	
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
		php_set_error_handling(EH_NORMAL, NULL TSRMLS_CC);
		return;
	}

	intern = (hits_object*) zend_object_store_get_object(getThis() TSRMLS_CC);

	php_set_error_handling(EH_NORMAL, NULL TSRMLS_CC);

} /* }}} */

/* {{{ proto int Hits::length()
   Returns the total number of hits available in this set. */
PHP_METHOD(Hits, length)
{
	hits_object *intern;
	long hits_length;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
		php_set_error_handling(EH_NORMAL, NULL TSRMLS_CC);
		return;
	}

	intern = (hits_object*)
		zend_object_store_get_object(getThis() TSRMLS_CC);

	if (intern->hits != NULL) {
		hits_length = (long) intern->hits->length();
	} else {
		/* Should not occur */
		php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Object was not fully initialized");
		return;
		/* hits_length = 0; */
	}

	RETURN_LONG(hits_length);
} /* }}} */

/* {{{ proto string|NULL Hits::get(int n, string name)
   Returns the string value of the name field for the nth document in
   this set or NULL. */
PHP_METHOD(Hits, get)
{
	hits_object *intern = NULL;
	long n;
	long max;
	char *name  = NULL;
	char *tmp   = NULL;
	char *value = NULL;
	wchar_t *wname = NULL;
	long len;
	size_t sz;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ls", &n, &name, &len) == FAILURE) {
		php_set_error_handling(EH_NORMAL, NULL TSRMLS_CC);
		return;
	}
	intern = (hits_object*) zend_object_store_get_object(getThis() TSRMLS_CC);

	if (intern->hits != NULL) {
		max = (long) intern->hits->length();
		if ((n < 0) || (n >= max)) {
			zend_throw_exception_ex(zend_exception_get_default(), 0 TSRMLS_CC, "Seek position %ld is out of range", n);
			return;
		}
		try {
			Document& doc = intern->hits->doc(n);
			wname = Misc::_charToWide(name);
			if (doc.get(wname) != NULL) {
				tmp = Misc::_wideToChar(doc.get(wname));
			} else {
				tmp = NULL;
			}
			delete [] wname;
		} catch (CLuceneError& error) {
			zend_throw_exception(zend_exception_get_default(), error.what(), 0 TSRMLS_CC);
			return;
		}
	} else {
		php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Object was not fully initialized");
		return;
	}

	if (tmp != NULL) {
		value = estrdup(tmp);
		delete [] tmp;
		RETURN_STRING(value, 0);
	} else {
		RETURN_NULL();
	}
} /* }}} */

/* {{{ proto int Hits::id(int n)
   Returns the id for the nth document in this set. */
PHP_METHOD(Hits, id)
{
	hits_object *intern;
	long n;
	long max;
	long id;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &n) == FAILURE) {
		php_set_error_handling(EH_NORMAL, NULL TSRMLS_CC);
		return;
	}
	intern = (hits_object*)
		zend_object_store_get_object(getThis() TSRMLS_CC);

	if (intern->hits != NULL) {
		max = (long) intern->hits->length();
		if ((n < 0) || (n >= max)) {
			zend_throw_exception_ex(zend_exception_get_default(), 0 TSRMLS_CC, "Seek position %ld is out of range", n);
			return;
		}
		try {
			id = (long) (intern->hits->id(n));
		} catch (CLuceneError& error) {
			zend_throw_exception(zend_exception_get_default(), error.what(), 0 TSRMLS_CC);
			return;
		}
	} else {
		php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Object was not fully initialized");
		return;
	}
	RETURN_LONG(id);
} /* }}} */

/* {{{ proto float Hits::score(int n)
   Returns the score for the nth document in this set. */
PHP_METHOD(Hits, score)
{
	hits_object *intern;
	long n;
	long max;
	double score;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &n) == FAILURE) {
		php_set_error_handling(EH_NORMAL, NULL TSRMLS_CC);
		return;
	}
	intern = (hits_object*)
		zend_object_store_get_object(getThis() TSRMLS_CC);

	if (intern->hits != NULL) {
		max = (long) intern->hits->length();
		if ((n < 0) || (n >= max)) {
			zend_throw_exception_ex(zend_exception_get_default(), 0 TSRMLS_CC, "Seek position %ld is out of range", n);
			return;
		}
		try {
			score = (double) (intern->hits->score(n));
		} catch (CLuceneError& error) {
			zend_throw_exception(zend_exception_get_default(), error.what(), 0 TSRMLS_CC);
			return;
		}
	} else {
		php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Object was not fully initialized");
		return;
	}
	RETURN_DOUBLE(score);
} /* }}} */

/* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(clucene)
{
	zend_class_entry index_searcher_ce;
	zend_class_entry hits_ce;
	
	/* IndexSearcher class */
	INIT_CLASS_ENTRY(index_searcher_ce, "IndexSearcher", index_searcher_functions);
	index_searcher_class_entry = zend_register_internal_class(&index_searcher_ce TSRMLS_CC);
	index_searcher_class_entry->create_object = index_searcher_object_new;
	memcpy(&index_searcher_object_handlers, zend_get_std_object_handlers(),
		   sizeof(zend_object_handlers));
	index_searcher_class_entry->ce_flags |= ZEND_ACC_FINAL_CLASS;

	/* Hits class */
	INIT_CLASS_ENTRY(hits_ce, "Hits", hits_functions);
	hits_class_entry = zend_register_internal_class(&hits_ce TSRMLS_CC);
	hits_class_entry->create_object = hits_object_new;
	memcpy(&hits_object_handlers, zend_get_std_object_handlers(),
		   sizeof(zend_object_handlers));
	hits_class_entry->ce_flags |= ZEND_ACC_FINAL_CLASS;

	REGISTER_LONG_CONSTANT("STANDARD_ANALYZER", STANDARD_ANALYZER, CONST_CS | CONST_PERSISTENT);	

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(clucene)
{

	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_RINIT_FUNCTION */
PHP_RINIT_FUNCTION(clucene)
{
	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_RSHUTDOWN_FUNCTION */
PHP_RSHUTDOWN_FUNCTION(clucene)
{
	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(clucene)
{
	php_info_print_table_start();
	{
		php_info_print_table_row(2, "CLucene support", "enabled");
		php_info_print_table_row(2, "Extension version", PHP_CLUCENE_MODULE_VERSION);
	}
	php_info_print_table_end();
}
/* }}} */

#endif /* HAVE_CLUCENE */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
