/*
   +----------------------------------------------------------------------+
   | PHP Version 4                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2003 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 2.02 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available at through the world-wide-web at                           |
   | http://www.php.net/license/2_02.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.               |
   +----------------------------------------------------------------------+
   | Author: Adam Dickmeiss <adam@indexdata.dk>                           |
   +----------------------------------------------------------------------+
 */

/* $Id$ */

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

#include "php.h"
#include "php_ini.h"

#if HAVE_YAZ

#include "ext/standard/info.h"
#include "php_yaz.h"

#include <yaz/yaz-version.h>

#ifndef YAZ_VERSIONL
#error YAZ version 1.9 or later must be used.
#endif

#if YAZ_VERSIONL < 0x010900
#error YAZ version 1.9 or later must be used.
#endif

#ifdef PHP_WIN32
#include <process.h>
#endif

#include <yaz/proto.h>
#include <yaz/marcdisp.h>
#include <yaz/yaz-util.h>
#include <yaz/yaz-ccl.h>
#include <yaz/zoom.h>

#define MAX_ASSOC 100

typedef struct Yaz_AssociationInfo *Yaz_Association;

struct Yaz_AssociationInfo {
	CCL_parser ccl_parser;
	ZOOM_connection zoom_conn;
	ZOOM_resultset zoom_set;
	ZOOM_scanset zoom_scan;
	ZOOM_package zoom_package;
	char *sort_criteria;
	int persistent;
	int in_use;
	int order;
};

static Yaz_Association yaz_association_mk ()
{
	Yaz_Association p = xmalloc (sizeof(*p));

	p->zoom_conn = ZOOM_connection_create (0);
	p->zoom_set = 0;
	p->zoom_scan = 0;
    p->zoom_package = 0;
	ZOOM_connection_option_set(p->zoom_conn, "implementationName", "PHP");
	ZOOM_connection_option_set(p->zoom_conn, "async", "1");
	p->sort_criteria = 0;
	p->in_use = 0;
	p->order = 0;
	p->persistent = 0;
	p->ccl_parser = ccl_parser_create();
	p->ccl_parser->bibset = 0;
	return p;
}

static void yaz_association_destroy (Yaz_Association p)
{
	if (!p)
		return ;
	ZOOM_resultset_destroy (p->zoom_set);
	ZOOM_scanset_destroy (p->zoom_scan);
    ZOOM_package_destroy (p->zoom_package);
	ZOOM_connection_destroy (p->zoom_conn);
	xfree (p->sort_criteria);
	ccl_qual_rm(&p->ccl_parser->bibset);
	ccl_parser_destroy(p->ccl_parser);
}

#ifdef ZTS
static MUTEX_T yaz_mutex;
#endif

ZEND_DECLARE_MODULE_GLOBALS(yaz);

static Yaz_Association *shared_associations;
static int order_associations;
static int le_link;

static unsigned char third_argument_force_ref[] = {
	3, BYREF_NONE, BYREF_NONE, BYREF_FORCE };

static unsigned char second_argument_force_ref[] = {
	2, BYREF_NONE, BYREF_FORCE };

function_entry yaz_functions [] = {
	PHP_FE(yaz_connect, NULL)
	PHP_FE(yaz_close, NULL)
	PHP_FE(yaz_search, NULL)
	PHP_FE(yaz_wait, second_argument_force_ref)
	PHP_FE(yaz_errno, NULL)
	PHP_FE(yaz_error, NULL)
	PHP_FE(yaz_addinfo, NULL)
	PHP_FE(yaz_hits, NULL)
	PHP_FE(yaz_record, NULL)
	PHP_FE(yaz_syntax, NULL)
	PHP_FE(yaz_element, NULL)
	PHP_FE(yaz_range, NULL)
	PHP_FE(yaz_itemorder, NULL)
	PHP_FE(yaz_es_result, NULL)
	PHP_FE(yaz_scan, NULL)
	PHP_FE(yaz_scan_result, second_argument_force_ref)
	PHP_FE(yaz_present, NULL)
	PHP_FE(yaz_ccl_conf, NULL)
	PHP_FE(yaz_ccl_parse, third_argument_force_ref)
	PHP_FE(yaz_database, NULL)
	PHP_FE(yaz_sort, NULL)
	PHP_FE(yaz_schema, NULL)
	PHP_FE(yaz_set_option, NULL)
	PHP_FE(yaz_get_option, NULL)
	{NULL, NULL, NULL}
};

static void get_assoc (INTERNAL_FUNCTION_PARAMETERS, pval **id,
                       Yaz_Association *assocp)
{
	Yaz_Association *as = 0;
	
	*assocp = 0;
#ifdef ZTS
	tsrm_mutex_lock (yaz_mutex);
#endif
	ZEND_FETCH_RESOURCE(as, Yaz_Association *, id, -1,
						"YAZ link", le_link);
	if (as && *as && (*as)->order == YAZSG(assoc_seq) && (*as)->in_use)
	{
		*assocp = *as;
	}
	else
	{
#ifdef ZTS
		tsrm_mutex_unlock (yaz_mutex);
#endif
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid YAZ handle");
	}
}

static void release_assoc (Yaz_Association assoc)
{
#ifdef ZTS
	if (assoc)
		tsrm_mutex_unlock(yaz_mutex);
#endif
}

static const char *array_lookup_string(HashTable *ht, const char *idx)
{
	pval **pvalue;

	if (ht && zend_hash_find(ht, (char*) idx, strlen(idx)+1,
							 (void**) &pvalue) == SUCCESS)
	{
		SEPARATE_ZVAL(pvalue);
		convert_to_string(*pvalue);
		return (*pvalue)->value.str.val;
	}
	return 0;
}

static long *array_lookup_long(HashTable *ht, const char *idx)
{
	pval **pvalue;

	if (ht && zend_hash_find(ht, (char*) idx, strlen(idx)+1,
							 (void**) &pvalue) == SUCCESS)
	{
		SEPARATE_ZVAL(pvalue);
		convert_to_long(*pvalue);
		return &(*pvalue)->value.lval;
	}
	return 0;
}

static long *array_lookup_bool(HashTable *ht, const char *idx)
{
	pval **pvalue;

	if (ht && zend_hash_find(ht, (char*) idx, strlen(idx)+1,
							 (void**) &pvalue) == SUCCESS)
	{
		SEPARATE_ZVAL(pvalue);
		convert_to_boolean(*pvalue);
		return &(*pvalue)->value.lval;
	}
	return 0;
}

#if iliaa_0 /* compile warning fix */
static int send_present (Yaz_Association t);
static int send_sort_present (Yaz_Association t);
static int send_sort (Yaz_Association t);
#endif

const char *option_get (Yaz_Association as, const char *name)
{
	if (!as)
		return 0;
	return ZOOM_connection_option_get (as->zoom_conn, name);
}

int option_get_int (Yaz_Association as, const char *name, int def)
{
	const char *v;
	v = ZOOM_connection_option_get (as->zoom_conn, name);
	if (!v)
		return def;
	return atoi(v);
}

void option_set (Yaz_Association as, const char *name, const char *value)
{
	if (as && value)
		ZOOM_connection_option_set (as->zoom_conn, name, value);
}

void option_set_int (Yaz_Association as, const char *name, int v)
{
	if (as)
	{
		char s[30];

		sprintf (s, "%d", v);
		ZOOM_connection_option_set (as->zoom_conn, name, s);
	}
}

static int strcmp_null(const char *s1, const char *s2)
{
	if (s1 == 0 && s2 == 0)
		return 0;
	if (s1 == 0 || s2 == 0)
		return -1;
	return strcmp(s1, s2);
}

/* {{{ proto int yaz_connect(string zurl [, array options])
   Create target with given zurl. Returns positive id if successful. */
PHP_FUNCTION(yaz_connect)
{
	int i;
	char *cp;
	char *zurl_str;
	const char *user_str = 0, *group_str = 0, *pass_str = 0;
	const char *cookie_str = 0, *proxy_str = 0;
	const char *charset_str = 0;
    const char *client_IP = 0;
    const char *otherInfo[3];
	int persistent = 1;
	int piggyback = 1;
	pval **zurl, **user = 0;
	Yaz_Association as;

    otherInfo[0] = otherInfo[1] = otherInfo[2] = 0;
	if (ZEND_NUM_ARGS() == 1)
	{
		if (zend_get_parameters_ex (1, &zurl) == FAILURE)
			WRONG_PARAM_COUNT;
	}
	else if (ZEND_NUM_ARGS() == 2)
	{
		if (zend_get_parameters_ex (2, &zurl, &user) == FAILURE)
			WRONG_PARAM_COUNT;

		if (Z_TYPE_PP(user) == IS_ARRAY)
		{
			long *persistent_val;
			long *piggyback_val;
			HashTable *ht = Z_ARRVAL_PP(user);
			user_str = array_lookup_string(ht, "user");
			group_str = array_lookup_string(ht, "group");
			pass_str = array_lookup_string(ht, "password");
			cookie_str = array_lookup_string(ht, "cookie");
			proxy_str = array_lookup_string(ht, "proxy");
			charset_str = array_lookup_string(ht, "charset");
			persistent_val = array_lookup_bool(ht, "persistent");
			if (persistent_val)
				persistent = *persistent_val;
			piggyback_val = array_lookup_bool(ht, "piggyback");
			if (piggyback_val)
				piggyback = *piggyback_val;
            otherInfo[0] = array_lookup_string(ht, "otherInfo0");
            otherInfo[1] = array_lookup_string(ht, "otherInfo1");
            otherInfo[2] = array_lookup_string(ht, "otherInfo2");
		}
		else
		{
			convert_to_string_ex (user);
			user_str = (*user)->value.str.val;
		}
	}
	else
	{
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex (zurl);
	zurl_str = (*zurl)->value.str.val;
	for (cp = zurl_str; *cp && strchr("\t\n ", *cp); cp++)
		;
	if (!*cp)
		RETURN_LONG(0);
		
	/* see if we have it already ... */
#ifdef ZTS
	tsrm_mutex_lock (yaz_mutex);
#endif
	for (i = 0; i<YAZSG(max_links); i++)
	{
		as = shared_associations[i];
		if (persistent && as && !as->in_use &&
			!strcmp_null(option_get(as, "host"), zurl_str) &&
			!strcmp_null(option_get(as, "proxy"), proxy_str) &&
			!strcmp_null(option_get(as, "user"), user_str) &&
			!strcmp_null(option_get(as, "group"), group_str) &&
			!strcmp_null(option_get(as, "pass"), pass_str) &&
			!strcmp_null(option_get(as, "cookie"), cookie_str) &&
			!strcmp_null(option_get(as, "charset"), charset_str))
			break;
	}
	if (i == YAZSG(max_links))
	{
		/* we didn't have it (or already in use) */
		int i0 = -1;
		int min_order = 2000000000;
		/* find completely free slot or the oldest one */
		for (i = 0; i<YAZSG(max_links) && shared_associations[i]; i++)
		{
			as = shared_associations[i];
			if (persistent && !as->in_use && as->order < min_order)
			{
				min_order = as->order;
				i0 = i;
			}
		}
		if (i == YAZSG(max_links))
		{
			i = i0;
			if (i == -1)
			{
#ifdef ZTS
				tsrm_mutex_unlock (yaz_mutex);
#endif
				RETURN_LONG(0);			 /* no free slot */
			}
			else						 /* "best" free slot */
				yaz_association_destroy(shared_associations[i]);
		}
		shared_associations[i] = as = yaz_association_mk ();

        option_set (as, "proxy", proxy_str);
		option_set (as, "user", user_str);
		option_set (as, "group", group_str);
		option_set (as, "pass", pass_str);
		option_set (as, "cookie", cookie_str);
        option_set (as, "charset", charset_str);
	}
    option_set (as, "otherInfo0", otherInfo[0]);
    option_set (as, "otherInfo1", otherInfo[1]);
    option_set (as, "otherInfo2", otherInfo[2]);
    option_set (as, "clientIP", client_IP);
    option_set (as, "piggyback", piggyback ? "1" : "0");
    ZOOM_connection_connect (as->zoom_conn, zurl_str, 0);
	as->in_use = 1;
	as->persistent = persistent;
	as->order = YAZSG(assoc_seq);

#ifdef ZTS
	tsrm_mutex_unlock (yaz_mutex);
#endif
	ZEND_REGISTER_RESOURCE(return_value, &shared_associations[i], le_link);
}
/* }}} */

/* {{{ proto int yaz_close(resource id)
   Destory and close target */
PHP_FUNCTION(yaz_close)
{
	Yaz_Association p;
	pval **id;
	if (ZEND_NUM_ARGS() != 1)
		WRONG_PARAM_COUNT;
	if (zend_get_parameters_ex (1, &id) == FAILURE)
		RETURN_FALSE;
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, id, &p);
	if (!p)
		RETURN_FALSE;
	release_assoc (p);
	zend_list_delete ((*id)->value.lval);
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto int yaz_search(resource id, string type, string query)
   Specify query of type for search - returns true if successful */
PHP_FUNCTION(yaz_search)
{
	char *query_str, *type_str;
	pval **id, **type, **query;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() == 3)
	{
		if (zend_get_parameters_ex(3, &id, &type, &query) == FAILURE)
		{
			WRONG_PARAM_COUNT;
		}
	}
	else
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, id, &p);
	if (!p)
	{
		RETURN_FALSE;
	}
	convert_to_string_ex (type);
	type_str = (*type)->value.str.val;
	convert_to_string_ex (query);
	query_str = (*query)->value.str.val;

	ZOOM_resultset_destroy (p->zoom_set);
	p->zoom_set = 0;
	if (!strcmp (type_str, "rpn"))
	{
		ZOOM_query q = ZOOM_query_create ();
		ZOOM_query_prefix (q, query_str);
		if (p->sort_criteria)
			ZOOM_query_sortby (q, p->sort_criteria);
        xfree (p->sort_criteria);
        p->sort_criteria = 0;
		p->zoom_set = ZOOM_connection_search (p->zoom_conn, q);
		ZOOM_query_destroy (q);
		RETVAL_TRUE;
	}
	else
	{
		RETVAL_FALSE;
	}
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_present(resource id)
   Retrieve records */
PHP_FUNCTION(yaz_present)
{
	pval **id;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 1)
		WRONG_PARAM_COUNT;
	if (zend_get_parameters_ex(1, &id) == FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, id, &p);
	if (!p)
	{
		RETURN_FALSE;
	}
	if (p->zoom_set)
	{
		size_t start = option_get_int (p, "start", 0);
		size_t count = option_get_int (p, "count", 0);
		if (count > 0)
			ZOOM_resultset_records (p->zoom_set, 0 /* recs */, start, count);
	}
	release_assoc (p);
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto int yaz_wait([array options])
   Process events. */
PHP_FUNCTION(yaz_wait)
{
	int no = 0;
	ZOOM_connection conn_ar[MAX_ASSOC];
	int i, timeout = 15;

	if (ZEND_NUM_ARGS() == 1)
	{
		long *val = 0;
		pval **pval_options = 0;
		HashTable *options_ht = 0;
		if (zend_get_parameters_ex(1, &pval_options) == FAILURE)
		{
			WRONG_PARAM_COUNT;
		}
		if (Z_TYPE_PP(pval_options) != IS_ARRAY)
		{
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected array parameter");
			RETURN_FALSE;
		}
		options_ht = Z_ARRVAL_PP(pval_options);
		val = array_lookup_long(options_ht, "timeout");
		if (val)
			timeout = *val;
	}
#ifdef ZTS
	tsrm_mutex_lock (yaz_mutex);
#endif
	for (i = 0; i<YAZSG(max_links); i++)
	{
		Yaz_Association p = shared_associations[i];
		if (p && p->order == YAZSG(assoc_seq))
        {
            char str[20];

            sprintf (str, "%d", timeout);
            ZOOM_connection_option_set (p->zoom_conn, "timeout", str);
			conn_ar[no++] = p->zoom_conn;
        }
	}
#ifdef ZTS
	tsrm_mutex_unlock (yaz_mutex);
#endif
	if (no)
		while (ZOOM_event (no, conn_ar))
			;
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto int yaz_errno(resource id)
   Return last error number (>0 for bib-1 diagnostic, <0 for other error, 0 for no error */
PHP_FUNCTION(yaz_errno)
{
	pval **id;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &id) == FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, id, &p);
	if (!p)
	{
		RETURN_LONG(0);
	}
	RETVAL_LONG(ZOOM_connection_errcode (p->zoom_conn));
	release_assoc (p);
}
/* }}} */

/* {{{ proto string yaz_error(resource id)
   Return last error message */
PHP_FUNCTION(yaz_error)
{
	pval **id;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &id) == FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, id, &p);
	if (p)
	{
		int code = ZOOM_connection_errcode (p->zoom_conn);
		const char *msg = ZOOM_connection_errmsg (p->zoom_conn);
		if (!code)
			msg = "";
		return_value->value.str.len = strlen(msg);
		return_value->value.str.val =
			estrndup(msg, return_value->value.str.len);
		return_value->type = IS_STRING;
	}
	release_assoc (p);
}
/* }}} */

/* {{{ proto string yaz_addinfo(resource id)
   Return additional info for last error (empty string if none) */
PHP_FUNCTION(yaz_addinfo)
{
	pval **id;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &id) == FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, id, &p);
	if (p)
	{
		const char *addinfo = ZOOM_connection_addinfo (p->zoom_conn);
		return_value->value.str.len = strlen(addinfo);
		return_value->value.str.val =
			estrndup(addinfo, return_value->value.str.len);
		return_value->type = IS_STRING;
	}		 
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_hits(resource id)
   Return number of hits (result count) for last search */
PHP_FUNCTION(yaz_hits)
{
	pval **id;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &id) == FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, id, &p);
	if (p && p->zoom_set)
	{
		RETVAL_LONG(ZOOM_resultset_size (p->zoom_set));
	}
	else
	{
		RETVAL_LONG(0);
	}
	release_assoc (p);
}
/* }}} */

static Z_GenericRecord *marc_to_grs1(const char *buf, ODR o)
{
	int entry_p;
	int record_length;
	int indicator_length;
	int identifier_length;
	int base_address;
	int length_data_entry;
	int length_starting;
	int length_implementation;
	int max_elements = 256;
	Z_GenericRecord *r = odr_malloc (o, sizeof(*r));
	r->elements = odr_malloc (o, sizeof(*r->elements) * max_elements);
	r->num_elements = 0;
	
	record_length = atoi_n (buf, 5);
	if (record_length < 25)
		return 0;
	indicator_length = atoi_n (buf+10, 1);
	identifier_length = atoi_n (buf+11, 1);
	base_address = atoi_n (buf+12, 5);
	
	length_data_entry = atoi_n (buf+20, 1);
	length_starting = atoi_n (buf+21, 1);
	length_implementation = atoi_n (buf+22, 1);
	
	for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
	{
		entry_p += 3+length_data_entry+length_starting;
		if (entry_p >= record_length)
			return 0;
	}
	base_address = entry_p+1;
	for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
	{
		Z_TaggedElement *tag;
		int data_length;
		int data_offset;
		int end_offset;
		int i;
		char tag_str[4];
		int identifier_flag = 1;
		
		memcpy (tag_str, buf+entry_p, 3);
		entry_p += 3;
		tag_str[3] = '\0';
		
		if ((r->num_elements + 1) >= max_elements)
		{
			Z_TaggedElement **tmp = r->elements;
			
			/* double array space, throw away old buffer (nibble memory) */
			r->elements = odr_malloc(o, sizeof(*r->elements) *
									 (max_elements *= 2));
			memcpy(r->elements, tmp, r->num_elements * sizeof(*tmp));
		}
		tag = r->elements[r->num_elements++] = odr_malloc (o, sizeof(*tag));
		tag->tagType = odr_malloc(o, sizeof(*tag->tagType));
		*tag->tagType = 3;
		tag->tagOccurrence = 0;
		tag->metaData = 0;
		tag->appliedVariant = 0;
		tag->tagValue = odr_malloc (o, sizeof(*tag->tagValue));
		tag->tagValue->which = Z_StringOrNumeric_string;
		tag->tagValue->u.string = odr_strdup(o, tag_str);
		
		tag->content = odr_malloc(o, sizeof(*tag->content));
		tag->content->which = Z_ElementData_subtree;
		
		tag->content->u.subtree =
			odr_malloc (o, sizeof(*tag->content->u.subtree));
		tag->content->u.subtree->elements =
			odr_malloc (o, sizeof(*r->elements));
		tag->content->u.subtree->num_elements = 1;
		
		tag = tag->content->u.subtree->elements[0] =
			odr_malloc (o, sizeof(**tag->content->u.subtree->elements));
		
		tag->tagType = odr_malloc(o, sizeof(*tag->tagType));
		*tag->tagType = 3;
		tag->tagOccurrence = 0;
		tag->metaData = 0;
		tag->appliedVariant = 0;
		tag->tagValue = odr_malloc (o, sizeof(*tag->tagValue));
		tag->tagValue->which = Z_StringOrNumeric_string;
		tag->content = odr_malloc(o, sizeof(*tag->content));
		
		data_length = atoi_n (buf+entry_p, length_data_entry);
		entry_p += length_data_entry;
		data_offset = atoi_n (buf+entry_p, length_starting);
		entry_p += length_starting;
		i = data_offset + base_address;
		end_offset = i+data_length-1;

		
		if (indicator_length > 0 && indicator_length < 5)
		{
			if (buf[i + indicator_length] != ISO2709_IDFS)
				identifier_flag = 0;
		}
		else if (!memcmp (tag_str, "00", 2))
			identifier_flag = 0;

		
		if (identifier_flag && indicator_length)
		{
			/* indicator */
			tag->tagValue->u.string = odr_malloc(o, indicator_length+1);
			memcpy (tag->tagValue->u.string, buf + i, indicator_length);
			tag->tagValue->u.string[indicator_length] = '\0';
			i += indicator_length;
			
			tag->content->which = Z_ElementData_subtree;

			tag->content->u.subtree =
				odr_malloc (o, sizeof(*tag->content->u.subtree));
			tag->content->u.subtree->elements =
				odr_malloc (o, 256 * sizeof(*r->elements));
			tag->content->u.subtree->num_elements = 0;
			
			while (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS
				   && i < end_offset)
			{
				int i0;
				/* prepare tag */
				Z_TaggedElement *parent_tag = tag;
				Z_TaggedElement *tag = odr_malloc (o, sizeof(*tag));
				
				if (parent_tag->content->u.subtree->num_elements < 256)
					parent_tag->content->u.subtree->elements[
					parent_tag->content->u.subtree->num_elements++] = tag;
				
				tag->tagType = odr_malloc(o, sizeof(*tag->tagType));
				*tag->tagType = 3;
				tag->tagOccurrence = 0;
				tag->metaData = 0;
				tag->appliedVariant = 0;
				tag->tagValue = odr_malloc (o, sizeof(*tag->tagValue));
				tag->tagValue->which = Z_StringOrNumeric_string;
				
				/* sub field */
				tag->tagValue->u.string = odr_malloc (o, identifier_length);
				memcpy (tag->tagValue->u.string, buf+i+1, identifier_length-1);
				tag->tagValue->u.string[identifier_length-1] = '\0';
				i += identifier_length;
				
				/* data ... */
				tag->content = odr_malloc(o, sizeof(*tag->content));
				tag->content->which = Z_ElementData_string;
				
				i0 = i;
				while (buf[i] != ISO2709_RS && buf[i] != ISO2709_IDFS &&
					   buf[i] != ISO2709_FS && i < end_offset)
					i++;
				
				tag->content->u.string = odr_malloc (o, i - i0 + 1);
				memcpy (tag->content->u.string, buf + i0, i - i0);
				tag->content->u.string[i - i0] = '\0';
			}
		}
		else
		{
			int i0 = i;
			
			tag->tagValue->u.string = "@";
			tag->content->which = Z_ElementData_string;
			
			while (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS &&
				   i < end_offset)
				i++;
			tag->content->u.string = odr_malloc (o, i - i0 +1);
			memcpy (tag->content->u.string, buf+i0, i - i0);
			tag->content->u.string[i-i0] = '\0';
		}
	}
	return r;
}

static void retval_grs1 (zval *return_value, Z_GenericRecord *p)
{
	Z_GenericRecord *grs[20];
	int eno[20];
	int level = 0;

	array_init(return_value);
	eno[level] = 0;
	grs[level] = p;
	while (level >= 0)
	{
		zval *my_zval;
		Z_TaggedElement *e = 0;
		Z_GenericRecord *p = grs[level];
		int i;
		char tag[256];
		int taglen = 0;

		if (eno[level] >= p->num_elements)
		{
			--level;
			if (level >= 0)
				eno[level]++;
			continue;
		}
		/* eno[level]++; */

		*tag = '\0';
		for (i = 0; i<=level; i++)
		{
			int tag_type = 3;
			e = grs[i]->elements[eno[i]];

			if (e->tagType)
				tag_type = *e->tagType;

			taglen = strlen(tag);
			sprintf (tag+taglen, "(%d,", tag_type);
			taglen = strlen(tag);

			if (e->tagValue->which == Z_StringOrNumeric_string)
			{
				int len = strlen(e->tagValue->u.string);
				memcpy (tag + taglen, e->tagValue->u.string, len);
				tag[taglen+len] = '\0';
			}
			else if (e->tagValue->which == Z_StringOrNumeric_numeric)
			{
				sprintf (tag + taglen, "%d", *e->tagValue->u.numeric);
			}
			taglen = strlen(tag);
			strcpy (tag + taglen, ")");
		}
		ALLOC_ZVAL(my_zval);
		array_init(my_zval);
		INIT_PZVAL(my_zval);
		
		add_next_index_string(my_zval, tag, 1);

		switch (e->content->which)
		{
		case Z_ElementData_string:
			add_next_index_string (my_zval, e->content->u.string, 1);
			break;
		case Z_ElementData_numeric:
			add_next_index_long (my_zval, *e->content->u.numeric);
			break;
		case Z_ElementData_trueOrFalse:
			add_next_index_long (my_zval, *e->content->u.trueOrFalse);
			break;
		case Z_ElementData_subtree:
			level++;
			grs[level] = e->content->u.subtree;
			eno[level] = -1;
		}
		zend_hash_next_index_insert (return_value->value.ht,
									 (void *) &my_zval, sizeof(zval *), NULL);
		eno[level]++;
	}
}


/* {{{ proto string yaz_record(resource id, int pos, string type)
   Return record information at given result set position */
PHP_FUNCTION(yaz_record)
{
	pval **pval_id, **pval_pos, **pval_type;
	Yaz_Association p;
	int pos;
	char *type;

	if (ZEND_NUM_ARGS() != 3)
		WRONG_PARAM_COUNT;
	if (zend_get_parameters_ex(3, &pval_id, &pval_pos, &pval_type) == FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);

	convert_to_long_ex(pval_pos);
	pos = (*pval_pos)->value.lval;

	convert_to_string_ex(pval_type);
	type = (*pval_type)->value.str.val;
	if (p && p->zoom_set)
	{
		ZOOM_record r = ZOOM_resultset_record (p->zoom_set, pos-1);
		if (!strcmp(type, "string"))
			type = "render";
		if (r)
		{
			if (!strcmp (type, "array"))
			{
				Z_External *ext = (Z_External *) ZOOM_record_get (r, "ext", 0);
				oident *ent = oid_getentbyoid(ext->direct_reference);

				if (ext->which == Z_External_grs1 && ent->value == VAL_GRS1)
				{
					retval_grs1 (return_value, ext->u.grs1);
				}
				else if (ext->which == Z_External_octet)
				{
					char *buf = (char *) (ext->u.octet_aligned->buf);
					ODR odr = odr_createmem (ODR_DECODE);
					Z_GenericRecord *rec = 0;

					switch (ent->value)
					{
					case VAL_SOIF:
					case VAL_HTML:
						break;
					case VAL_TEXT_XML:
					case VAL_APPLICATION_XML:
						/* text2grs1 (&buf, &len, t->odr_in, 0, 0); */
						break;
					default:
						rec = marc_to_grs1 (buf, odr);
					}
					if (rec)
						retval_grs1 (return_value, rec);
					odr_destroy (odr);
				}
			}
            else
			{
                int rlen;
				const char *info = ZOOM_record_get (r, type, &rlen);

                return_value->value.str.len = (rlen > 0) ? rlen : 0;
                return_value->value.str.val =
                    estrndup(info, return_value->value.str.len);
                return_value->type = IS_STRING;
			}
		}
	}
	release_assoc (p);
}
/* }}} */


/* {{{ proto int yaz_syntax(resource id, string syntax)
   Set record syntax for retrieval */
PHP_FUNCTION(yaz_syntax)
{
	pval **pval_id, **pval_syntax;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 2 || 
		zend_get_parameters_ex(2, &pval_id, &pval_syntax) == FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	convert_to_string_ex (pval_syntax);
	option_set (p, "preferredRecordSyntax", (*pval_syntax)->value.str.val);
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_element(resource id, string elementsetname)
   Set Element-Set-Name for retrieval */
PHP_FUNCTION(yaz_element)
{
	pval **pval_id, **pval_element;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 2 || 
		zend_get_parameters_ex(2, &pval_id, &pval_element) == FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	convert_to_string_ex (pval_element);
	option_set (p, "elementSetName", (*pval_element)->value.str.val);
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_schema(resource id, string schema)
   Set Schema for retrieval */
PHP_FUNCTION(yaz_schema)
{
	pval **pval_id, **pval_element;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 2 || 
		zend_get_parameters_ex(2, &pval_id, &pval_element) == FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	convert_to_string_ex (pval_element);
    option_set (p, "schema", (*pval_element)->value.str.val);
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_set_option(resource id, mixed options)
   Set Option(s) for connection */
PHP_FUNCTION(yaz_set_option)
{
	pval **pval_ar, **pval_name, **pval_val, **pval_id;
	Yaz_Association p;

    if (ZEND_NUM_ARGS() == 2)
    {
        if (zend_get_parameters_ex(2, &pval_id, &pval_ar) == FAILURE)
            WRONG_PARAM_COUNT;
        if (Z_TYPE_PP(pval_ar) != IS_ARRAY)
            WRONG_PARAM_COUNT;

        get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
        if (p)
        {
            HashPosition pos;
            HashTable *ht;
            zval **ent;
            
            ht = Z_ARRVAL_PP(pval_ar);
            for(zend_hash_internal_pointer_reset_ex(ht, &pos);
                zend_hash_get_current_data_ex(ht, (void**) &ent, &pos) == SUCCESS;
                zend_hash_move_forward_ex(ht, &pos)) 
            {
                char *key;
                ulong idx;
#if PHP_API_VERSION > 20010101
                int type = zend_hash_get_current_key_ex(ht, &key, 0, 
                                                        &idx, 0, &pos);
#else
                int type = zend_hash_get_current_key_ex(ht, &key, 0, 
                                                        &idx, &pos);
#endif
                if (type != HASH_KEY_IS_STRING || Z_TYPE_PP(ent) != IS_STRING)
                    continue;
                
                option_set(p, key, (*ent)->value.str.val);
            }
            release_assoc (p);
        }
    }
    else if (ZEND_NUM_ARGS() == 3)
    {
        if (zend_get_parameters_ex(3, &pval_id, &pval_name, &pval_val)
            == FAILURE)
            WRONG_PARAM_COUNT;
        get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
        convert_to_string_ex(pval_name);
        convert_to_string_ex(pval_val);
        option_set(p, (*pval_name)->value.str.val, (*pval_val)->value.str.val);
        
        release_assoc (p);
    }
    else
    {
        WRONG_PARAM_COUNT;
    }
}
/* }}} */

/* {{{ proto string yaz_get_option(resource id, string name)
   Set Option(s) for connection */
PHP_FUNCTION(yaz_get_option)
{
	pval **pval_id, **pval_name;
	Yaz_Association p;

    if (ZEND_NUM_ARGS() != 2)
        WRONG_PARAM_COUNT;
    if (zend_get_parameters_ex(2, &pval_id, &pval_name) == FAILURE)
        WRONG_PARAM_COUNT;
    get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
    if (p)
    {
        const char *name_str, *v;
        convert_to_string_ex (pval_name);
        name_str = (*pval_name)->value.str.val;

        v = option_get(p, name_str);
        if (!v)
            v = "";
        return_value->value.str.len = strlen(v);
        return_value->value.str.val = 
            estrndup(v, return_value->value.str.len);
        return_value->type = IS_STRING;
    }
    else
    {
        RETVAL_FALSE;
    }
    release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_range(resource id, int start, int number)
   Set result set start point and number of records to request */

PHP_FUNCTION(yaz_range)
{
	pval **pval_id, **pval_start, **pval_number;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 3 || 
		zend_get_parameters_ex(3, &pval_id, &pval_start, &pval_number) ==
		FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	convert_to_long_ex (pval_start);
	convert_to_long_ex (pval_number);
	option_set_int (p, "start",  (*pval_start)->value.lval - 1);
	option_set_int (p, "count",  (*pval_number)->value.lval);
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_sort(resource id, string sortspec)
   Set result set sorting criteria */

PHP_FUNCTION(yaz_sort)
{
	pval **pval_id, **pval_criteria;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 2 || 
		zend_get_parameters_ex(2, &pval_id, &pval_criteria) ==
		FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	if (p)
	{
		convert_to_string_ex (pval_criteria);
		xfree (p->sort_criteria);
		p->sort_criteria = xstrdup ((*pval_criteria)->value.str.val);
	}
	release_assoc (p);
}
/* }}} */

const char *ill_array_lookup (void *handle, const char *name)
{
	return array_lookup_string((HashTable *) handle, name);
}


/* {{{ proto int yaz_itemorder(resource id, array package)
   Sends Item Order request */

PHP_FUNCTION(yaz_itemorder)
{
	pval **pval_id, **pval_package;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 2 || 
		zend_get_parameters_ex(2, &pval_id, &pval_package) ==
		FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	if (Z_TYPE_PP(pval_package) != IS_ARRAY)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected array parameter");
		RETURN_FALSE;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	if (p)
	{
        ZOOM_options options = ZOOM_options_create();
        
        ZOOM_options_set_callback (options, ill_array_lookup,
                                   Z_ARRVAL_PP(pval_package));
        ZOOM_package_destroy (p->zoom_package);
        p->zoom_package = ZOOM_connection_package (p->zoom_conn, options);
        ZOOM_package_send(p->zoom_package, "itemorder");
        ZOOM_options_destroy (options);
	}
	release_assoc (p);
}
/* }}} */



/* {{{ proto int yaz_scan(resource id, type, query [, flags])
   Sends Scan Request */
PHP_FUNCTION(yaz_scan)
{
	pval **pval_id, **pval_type, **pval_query, **pval_flags = 0;
	HashTable *flags_ht = 0;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() == 3)
	{	
		if (zend_get_parameters_ex(3, &pval_id, &pval_type, &pval_query) ==
			FAILURE)
		{
			WRONG_PARAM_COUNT;
		}
	}
	else if (ZEND_NUM_ARGS() == 4)
	{
		if (zend_get_parameters_ex(4, &pval_id, &pval_type, &pval_query,
								   &pval_flags) ==
			FAILURE)
		{
			WRONG_PARAM_COUNT;
		}
		if (Z_TYPE_PP(pval_flags) != IS_ARRAY)
		{
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Bad flags parameter");
			RETURN_FALSE;
		}
		flags_ht = Z_ARRVAL_PP(pval_flags);
	}
	else
	{
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex (pval_type);
	convert_to_string_ex (pval_query);

	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	ZOOM_scanset_destroy (p->zoom_scan);
	p->zoom_scan = 0;
	if (p)
	{
		option_set (p, "number", array_lookup_string (flags_ht, "number"));
		option_set (p, "position", array_lookup_string (flags_ht, "position"));
		option_set (p, "stepSize", array_lookup_string (flags_ht, "stepsize"));
		p->zoom_scan = ZOOM_connection_scan (p->zoom_conn,
											 Z_STRVAL_PP(pval_query));}
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_es_result(resource id)
   Inspects Extended Services Result */
PHP_FUNCTION(yaz_es_result)
{
	pval **pval_id;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() == 1)
	{
		if (zend_get_parameters_ex(1, &pval_id) == FAILURE)
		{
			WRONG_PARAM_COUNT;
		}
	}
	else
	{
		WRONG_PARAM_COUNT;
	}
	array_init(return_value);
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
    if (p && p->zoom_package)
    {
        const char *str = ZOOM_package_option_get(p->zoom_package,
                                                  "targetReference");
        if (str)
            add_assoc_string (return_value, "targetReference", 
                              (char*)str, 1);
    }
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_scan_result(resource id, array options)
   Inspects Scan Result */
PHP_FUNCTION(yaz_scan_result)
{
	pval **pval_id, **pval_opt = 0;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() == 2)
	{	
		if (zend_get_parameters_ex(2, &pval_id, &pval_opt) == FAILURE)
		{
			WRONG_PARAM_COUNT;
		}
	}
	else if (ZEND_NUM_ARGS() == 1)
	{
		if (zend_get_parameters_ex(1, &pval_id) == FAILURE)
		{
			WRONG_PARAM_COUNT;
		}
	}
	else
	{
		WRONG_PARAM_COUNT;
	}
	array_init(return_value);
	if (pval_opt && array_init(*pval_opt) == FAILURE)
	{
		RETURN_FALSE;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	if (p && p->zoom_scan)
	{
		int pos = 0;
		int occ, len;
		int size = ZOOM_scanset_size (p->zoom_scan);
		
		for (pos = 0; pos < size; pos++)
		{
			const char *term =
				ZOOM_scanset_term(p->zoom_scan, pos, &occ, &len);
			zval *my_zval;
			ALLOC_ZVAL(my_zval);
			array_init(my_zval);
			INIT_PZVAL(my_zval);
			
			add_next_index_string(my_zval, "term", 1);

			if (term)
				add_next_index_stringl (my_zval, (char*) term, len, 1);
			else
				add_next_index_string (my_zval, "?", 1);
			add_next_index_long (my_zval, occ);
			
			zend_hash_next_index_insert (
				return_value->value.ht, (void *) &my_zval, sizeof(zval *),
				NULL);
		}
		if (pval_opt)
		{
			const char *v;
			add_assoc_long(*pval_opt, "number", size);
			
			v = ZOOM_scanset_option_get (p->zoom_scan, "stepSize");
			if (v)
				add_assoc_long(*pval_opt, "stepsize", atoi(v));
			
			v = ZOOM_scanset_option_get (p->zoom_scan, "position");
			if (v)
				add_assoc_long(*pval_opt, "position", atoi(v));

			v = ZOOM_scanset_option_get (p->zoom_scan, "scanStatus");
			if (v)
				add_assoc_long(*pval_opt, "status", atoi(v));
		}
	}
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_ccl_conf(resource id, array package)
   Configure CCL package */

PHP_FUNCTION(yaz_ccl_conf)
{
	pval **pval_id, **pval_package;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 2 || 
		zend_get_parameters_ex(2, &pval_id, &pval_package) ==
		FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	if (Z_TYPE_PP(pval_package) != IS_ARRAY)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected array parameter");
		RETURN_FALSE;
	}
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	if (p)
	{
		HashTable *ht = Z_ARRVAL_PP(pval_package);
		HashPosition pos;
		zval **ent;
		char *key;

		ccl_qual_rm(&p->ccl_parser->bibset);
		p->ccl_parser->bibset = ccl_qual_mk();
		for(zend_hash_internal_pointer_reset_ex(ht, &pos);
			zend_hash_get_current_data_ex(ht, (void**) &ent, &pos) == SUCCESS;
			zend_hash_move_forward_ex(ht, &pos)) 
		{
			ulong idx;
#if PHP_API_VERSION > 20010101
			int type = zend_hash_get_current_key_ex(ht, &key, 0, 
													&idx, 0, &pos);
#else
			int type = zend_hash_get_current_key_ex(ht, &key, 0, 
													&idx, &pos);
#endif
			if (type != HASH_KEY_IS_STRING || Z_TYPE_PP(ent) != IS_STRING)
				continue;
			ccl_qual_fitem(p->ccl_parser->bibset, (*ent)->value.str.val, key);
		}
	}
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_ccl_parse(resource id, string query, array res)
   Parse a CCL query */

PHP_FUNCTION(yaz_ccl_parse)
{
	pval **pval_id, **pval_query, **pval_res = 0;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 3 || 
		zend_get_parameters_ex(3, &pval_id, &pval_query, &pval_res) ==
		FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	
	pval_destructor(*pval_res);
	array_init(*pval_res);
	convert_to_string_ex (pval_query);
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	if (p)
	{
		const char *query_str = (*pval_query)->value.str.val;
		struct ccl_rpn_node *rpn;
		struct ccl_token *token_list =
			ccl_parser_tokenize(p->ccl_parser, query_str);
		rpn = ccl_parser_find(p->ccl_parser, token_list);
		ccl_token_del(token_list);

		add_assoc_long(*pval_res, "errorcode", p->ccl_parser->error_code);
		if (p->ccl_parser->error_code)
		{
			add_assoc_string(*pval_res, "errorstring",
							 (char*) ccl_err_msg(p->ccl_parser->error_code),
							 1);
			add_assoc_long(*pval_res, "errorpos",
						   p->ccl_parser->error_pos - query_str);
			RETVAL_FALSE;
		}
		else
		{
			WRBUF wrbuf_pqf = wrbuf_alloc();
			ccl_pquery(wrbuf_pqf, rpn);
			add_assoc_stringl(*pval_res, "rpn", wrbuf_buf(wrbuf_pqf),
							  wrbuf_len(wrbuf_pqf),1);
			wrbuf_free(wrbuf_pqf, 1);
			RETVAL_TRUE;
		}
		ccl_rpn_delete(rpn);
	}
	else
		RETVAL_FALSE;
	release_assoc (p);
}
/* }}} */

/* {{{ proto int yaz_database (resource id, string databases)
   Specify the databases within a session */

PHP_FUNCTION(yaz_database)
{
	pval **pval_id, **pval_database;
	Yaz_Association p;
	if (ZEND_NUM_ARGS() != 2 || 
		zend_get_parameters_ex(2, &pval_id, &pval_database) ==
		FAILURE)
	{
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex (pval_database);
	get_assoc (INTERNAL_FUNCTION_PARAM_PASSTHRU, pval_id, &p);
	option_set (p, "databaseName", (*pval_database)->value.str.val);
	RETVAL_TRUE;
	release_assoc (p);
}
/* }}} */


/* {{{ php_yaz_init_globals
 */
static void php_yaz_init_globals(zend_yaz_globals *yaz_globals)
{
	yaz_globals->assoc_seq = 0;
}
/* }}} */

void yaz_close_session(Yaz_Association *as TSRMLS_DC)
{
	if (*as && (*as)->order == YAZSG(assoc_seq))
	{
		if ((*as)->persistent)
			(*as)->in_use = 0;
		else
		{
			yaz_association_destroy(*as);
			*as = 0;
		}
	}
}

static void yaz_close_link (zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
	Yaz_Association *as = (Yaz_Association *) rsrc->ptr;
	yaz_close_session (as TSRMLS_CC);
}

/* {{{ PHP_INI_BEGIN
 */
PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("yaz.max_links", "100", PHP_INI_ALL,
                      OnUpdateInt, max_links,
                      zend_yaz_globals, yaz_globals)
    STD_PHP_INI_ENTRY("yaz.log_file", NULL, PHP_INI_ALL,
                      OnUpdateString, log_file,
                      zend_yaz_globals, yaz_globals)
    PHP_INI_END()
/* }}} */
    
PHP_MINIT_FUNCTION(yaz)
{
	int i;
	nmem_init();
#ifdef ZTS
	yaz_mutex = tsrm_mutex_alloc();
#endif
	ZEND_INIT_MODULE_GLOBALS(yaz, php_yaz_init_globals, NULL);

    REGISTER_INI_ENTRIES();

    if (YAZSG(log_file))
	{
        yaz_log_init_level(LOG_ALL);
        yaz_log_init_file(YAZSG(log_file));
    }
	else
	    yaz_log_init_level (0);

	le_link = zend_register_list_destructors_ex (yaz_close_link, 0,
												"YAZ link", module_number);
	order_associations = 1;
	shared_associations = xmalloc (sizeof(*shared_associations) * MAX_ASSOC);
	for (i = 0; i<MAX_ASSOC; i++)
		shared_associations[i] = 0;
	return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(yaz)
{
	int i;

	if (shared_associations)
	{
		for (i = 0; i<MAX_ASSOC; i++)
			yaz_association_destroy (shared_associations[i]);
		xfree (shared_associations);
		shared_associations = 0;
		nmem_exit();
	}
#ifdef ZTS
	tsrm_mutex_free (yaz_mutex);
#endif

	if (yaz_log_file()) {
		fclose(yaz_log_file());
	}
	return SUCCESS;
}

PHP_MINFO_FUNCTION(yaz)
{
	php_info_print_table_start();
	php_info_print_table_row(2, "YAZ Support", "enabled");
	php_info_print_table_row(2, "YAZ Version", YAZ_VERSION);
	php_info_print_table_row(2, "ZOOM", "enabled");
	php_info_print_table_end();
}

PHP_RSHUTDOWN_FUNCTION(yaz)
{
	return SUCCESS;
}

PHP_RINIT_FUNCTION(yaz)
{
    char pidstr[20];
    sprintf (pidstr, "%ld", (long) getpid());
#ifdef ZTS
	tsrm_mutex_lock (yaz_mutex);
#endif
	YAZSG(assoc_seq) = order_associations++;
#ifdef ZTS
	tsrm_mutex_unlock (yaz_mutex);
#endif
    yaz_log_init_prefix(pidstr);
	return SUCCESS;
}

zend_module_entry yaz_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
	STANDARD_MODULE_HEADER,
#endif
	"yaz",
	yaz_functions,
	PHP_MINIT(yaz),
	PHP_MSHUTDOWN(yaz),
	PHP_RINIT(yaz),
	PHP_RSHUTDOWN(yaz),
	PHP_MINFO(yaz),
#if ZEND_MODULE_API_NO >= 20010901
	NO_VERSION_YET,
#endif
	STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_YAZ
ZEND_GET_MODULE(yaz)
#endif

#endif

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