/*
   +----------------------------------------------------------------------+
   | PHP HTML Embedded Scripting Language Version 3.0                     |
   +----------------------------------------------------------------------+
   | Copyright (c) 1999 PHP Development Team (See Credits file)           |
   +----------------------------------------------------------------------+
   | This program is free software; you can redistribute it and/or modify |
   | it under the terms of one of the following licenses:                 |
   |                                                                      |
   |  A) 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.                                               |
   |                                                                      |
   |  B) the PHP License as published by the PHP Development Team and     |
   |     included in the distribution in the file: LICENSE                |
   |                                                                      |
   | 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 both licenses referred to here.   |
   | If you did not, or have any questions about PHP licensing, please    |
   | contact core@php.net.                                                |
   +----------------------------------------------------------------------+
   | Authors: Sascha Schumann <sascha@schumann.cx>                        |
   +----------------------------------------------------------------------+
 */

/* $Id: dba.c,v 1.1.1.3 2001/01/25 04:59:12 wsanchez Exp $ */

#include "php.h"

#if HAVE_DBA

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

#include "php_gdbm.h"
#include "php_ndbm.h"
#include "php_dbm.h"
#include "php_cdb.h"
#include "php_db2.h"
#include "php_db3.h"

function_entry dba_functions[] = {
	PHP_FE(dba_open, NULL)
	PHP_FE(dba_popen, NULL)
	PHP_FE(dba_close, NULL)
	PHP_FE(dba_delete, NULL)
	PHP_FE(dba_exists, NULL)
	PHP_FE(dba_fetch, NULL)
	PHP_FE(dba_insert, NULL)
	PHP_FE(dba_replace, NULL)
	PHP_FE(dba_firstkey, NULL)
	PHP_FE(dba_nextkey, NULL)
	PHP_FE(dba_optimize, NULL)
	PHP_FE(dba_sync, NULL)
	{NULL,NULL,NULL}
};

static PHP_MINIT_FUNCTION(dba);
static PHP_MSHUTDOWN_FUNCTION(dba);
static PHP_MINFO_FUNCTION(dba);

zend_module_entry dba_module_entry = {
	"dba", dba_functions, 
	PHP_MINIT(dba), 
	PHP_MSHUTDOWN(dba), 
	NULL, NULL,
	PHP_MINFO(dba),
	STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_DBA
ZEND_GET_MODULE(dba)
#endif

typedef struct dba_handler {
	char *name;
	int (*open)(dba_info *);
	void (*close)(dba_info *);
	char* (*fetch)(dba_info *, char *, int, int *);
	int (*update)(dba_info *, char *, int, char *, int, int);
	int (*exists)(dba_info *, char *, int);
	int (*delete)(dba_info *, char *, int);
	char* (*firstkey)(dba_info *, int *);
	char* (*nextkey)(dba_info *, int *);
	int (*optimize)(dba_info *);
	int (*sync)(dba_info *);
} dba_handler;

/* {{{ macromania */

#define DBA_ID_PARS 											\
	pval **id; 													\
	dba_info *info = NULL; 										\
	int type, ac = ZEND_NUM_ARGS()

/* these are used to get the standard arguments */

#define DBA_GET1 												\
	if(ac != 1 || zend_get_parameters_ex(ac, &id) != SUCCESS) { 		\
		WRONG_PARAM_COUNT; 										\
	}

#define DBA_GET2 												\
	pval **key; 												\
	if(ac != 2 || zend_get_parameters_ex(ac, &key, &id) != SUCCESS) { 	\
		WRONG_PARAM_COUNT; 										\
	} 															\
	convert_to_string_ex(key)

#define DBA_IF_NOT_CORRECT_TYPE(link_id) 						\
	info = zend_list_find(link_id, &type); 						\
	if(!info || (type != GLOBAL(le_db) && type != GLOBAL(le_pdb)))
	
#define DBA_ID_GET 												\
	convert_to_long_ex(id); 									\
	DBA_IF_NOT_CORRECT_TYPE(Z_LVAL_PP(id)) { 				\
		php_error(E_WARNING, "Unable to find DBA identifier %d", \
				Z_LVAL_PP(id)); 							\
		RETURN_FALSE; 											\
	}
	
#define DBA_ID_GET1 DBA_ID_PARS; DBA_GET1; DBA_ID_GET
#define DBA_ID_GET2 DBA_ID_PARS; DBA_GET2; DBA_ID_GET

/* a DBA handler must have specific routines */

#define DBA_HND(x) \
{\
	#x, dba_open_##x, dba_close_##x, dba_fetch_##x, dba_update_##x, \
	dba_exists_##x, dba_delete_##x, dba_firstkey_##x, dba_nextkey_##x, \
	dba_optimize_##x, dba_sync_##x \
},

/* check whether the user has write access */
#define DBA_WRITE_CHECK \
	if(info->mode != DBA_WRITER && info->mode != DBA_TRUNC && info->mode != DBA_CREAT) { \
		php_error(E_WARNING, "you cannot perform a modification to a database without proper access"); \
		RETURN_FALSE; \
	}

#define GLOBAL(a) a

/* }}} */

/* {{{ globals */

static dba_handler handler[] = {
#if DBA_GDBM
	DBA_HND(gdbm)
#endif
#if DBA_DBM
	DBA_HND(dbm)
#endif
#if DBA_NDBM
	DBA_HND(ndbm)
#endif
#if DBA_CDB
	DBA_HND(cdb)
#endif
#if DBA_DB2
	DBA_HND(db2)
#endif
#if DBA_DB3
	DBA_HND(db3)
#endif
	{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};

static int le_db;
static int le_pdb;
static HashTable ht_keys;
/* }}} */

/* {{{ helper routines */
	/* {{{ dba_close */

static void dba_close(dba_info *info)
{
	if(info->hnd) info->hnd->close(info);
	if(info->path) free(info->path);
	free(info);
}
/* }}} */

static void dba_close_rsrc(zend_rsrc_list_entry *rsrc)
{
	dba_info *info = (dba_info *)rsrc->ptr;
	dba_close(info);
}

static PHP_MINIT_FUNCTION(dba)
{
	zend_hash_init(&ht_keys, 0, NULL, NULL, 1);
	GLOBAL(le_db) = zend_register_list_destructors_ex(dba_close_rsrc, NULL, "dba", module_number);
	GLOBAL(le_pdb) = zend_register_list_destructors_ex(NULL, dba_close_rsrc, "dba persistent", module_number);
	return SUCCESS;
}

static PHP_MSHUTDOWN_FUNCTION(dba)
{
	zend_hash_destroy(&ht_keys);
	return SUCCESS;
}

#include "ext/standard/php_smart_str.h"

static PHP_MINFO_FUNCTION(dba)
{
	dba_handler *hptr;
	smart_str handlers = {0};

	for(hptr = handler; hptr->name; hptr++) {
		smart_str_appends(&handlers, hptr->name);
		smart_str_appendc(&handlers, ' ');
 	}

	php_info_print_table_start();
 	php_info_print_table_row(2, "DBA support", "enabled");
	if (handlers.c) {
		smart_str_0(&handlers);
		php_info_print_table_row(2, "Supported handlers", handlers.c);
		smart_str_free(&handlers);
	} else {
		php_info_print_table_row(2, "Supported handlers", "none");
	}
	php_info_print_table_end();
}
                                

static void php_dba_update(INTERNAL_FUNCTION_PARAMETERS, int mode)
{
	DBA_ID_PARS;
	pval **val, **key;

	if(ac != 3 || zend_get_parameters_ex(ac, &key, &val, &id) != SUCCESS) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(key);
	convert_to_string_ex(val);
	DBA_ID_GET;

	DBA_WRITE_CHECK;
	
	if(info->hnd->update(info, VALLEN(key), VALLEN(val), mode) == SUCCESS)
		RETURN_TRUE;
	RETURN_FALSE;
}

#define FREENOW if(args) efree(args); if(key) efree(key)

static void php_dba_open(INTERNAL_FUNCTION_PARAMETERS, int persistent)
{
	pval ***args = (pval ***) NULL;
	int ac = ZEND_NUM_ARGS();
	dba_mode_t modenr;
	dba_info *info;
	dba_handler *hptr;
	char *key = NULL;
	int keylen = 0;
	int listid;
	int i;
	
	if(ac < 3) {
		WRONG_PARAM_COUNT;
	}
	
	/* we pass additional args to the respective handler */
	args = emalloc(ac * sizeof(pval *));
	if(zend_get_parameters_array_ex(ac, args) != SUCCESS) {
		FREENOW;
		WRONG_PARAM_COUNT;
	}
		
	/* we only take string arguments */
	for(i = 0; i < ac; i++) {
		convert_to_string_ex(args[i]);
		keylen += Z_STRLEN_PP(args[i]);
	}

	if(persistent) {
		/* calculate hash */
		key = emalloc(keylen);
		keylen = 0;
		
		for(i = 0; i < ac; i++) {
			memcpy(key+keylen,Z_STRVAL_PP(args[i]),Z_STRLEN_PP(args[i]));
			keylen += Z_STRLEN_PP(args[i]);
		}
		
		if(zend_hash_find(&ht_keys, key, keylen, (void **) &info) == SUCCESS) {
			FREENOW;
			RETURN_LONG(zend_list_insert(info, GLOBAL(le_pdb)));
		}
	}
	
	for(hptr = handler; hptr->name &&
			strcasecmp(hptr->name, (*args[2])->value.str.val); hptr++);

	if(!hptr->name) {
		php_error(E_WARNING, "no such handler: %s", (*args[2])->value.str.val);
		FREENOW;
		RETURN_FALSE;
	}

	switch((*args[1])->value.str.val[0]) {
		case 'c': 
			modenr = DBA_CREAT; 
			break;
		case 'w': 
			modenr = DBA_WRITER; 
			break;
		case 'r': 
			modenr = DBA_READER; 
			break;
		case 'n':
			modenr = DBA_TRUNC;
			break;
		default:
			php_error(E_WARNING,"illegal DBA mode: %s",(*args[1])->value.str.val);
			FREENOW;
			RETURN_FALSE;
	}
			
	info = malloc(sizeof(*info));
	memset(info, 0, sizeof(info));
	info->path = strdup((*args[0])->value.str.val);
	info->mode = modenr;
	info->argc = ac - 3;
	info->argv = args + 3;
	info->hnd = NULL;

	if(hptr->open(info) != SUCCESS) {
		dba_close(info);
		php_error(E_WARNING, "driver initialization failed");
		FREENOW;
		RETURN_FALSE;
	}
	info->hnd = hptr;
	info->argc = 0;
	info->argv = NULL;

	listid = zend_list_insert(info, persistent?GLOBAL(le_pdb):GLOBAL(le_db));
	if(persistent) {
		zend_hash_update(&ht_keys, key, keylen, info, sizeof(*info), NULL);
	}
	
	FREENOW;
	RETURN_LONG(listid);
}
#undef FREENOW
/* }}} */
/* }}} */

/* {{{ proto int dba_popen(string path, string mode, string handlername [, string ...])
   Opens path using the specified handler in mode persistently */
PHP_FUNCTION(dba_popen)
{
	php_dba_open(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */

/* {{{ proto int dba_open(string path, string mode, string handlername [, string ...])
   Opens path using the specified handler in mode*/
PHP_FUNCTION(dba_open)
{
	php_dba_open(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */

/* {{{ proto void dba_close(int handle)
   Closes database */
PHP_FUNCTION(dba_close)
{
	DBA_ID_GET1;	
	
	zend_list_delete(Z_LVAL_PP(id));
}
/* }}} */

/* {{{ proto bool dba_exists(string key, int handle)
   Checks, if the specified key exists */
PHP_FUNCTION(dba_exists)
{
	DBA_ID_GET2;

	if(info->hnd->exists(info, VALLEN(key)) == SUCCESS) {
		RETURN_TRUE;
	}
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto string dba_fetch(string key, int handle)
   Fetches the data associated with key */
PHP_FUNCTION(dba_fetch)
{
	char *val;
	int len = 0;
	DBA_ID_GET2;

	if((val = info->hnd->fetch(info, VALLEN(key), &len)) != NULL) {
		RETURN_STRINGL(val, len, 0);
	} 
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto string dba_firstkey(int handle)
   Resets the internal key pointer and returns the first key */
PHP_FUNCTION(dba_firstkey)
{
	char *fkey;
	int len;
	DBA_ID_GET1;

	fkey = info->hnd->firstkey(info, &len);
	if(fkey)
		RETURN_STRINGL(fkey, len, 0);
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto string dba_nextkey(int handle)
   Returns the next key */
PHP_FUNCTION(dba_nextkey)
{
	char *nkey;
	int len;
	DBA_ID_GET1;

	nkey = info->hnd->nextkey(info, &len);
	if(nkey)
		RETURN_STRINGL(nkey, len, 0);
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto bool dba_delete(string key, int handle)
   Deletes the entry associated with key */
PHP_FUNCTION(dba_delete)
{
	DBA_ID_GET2;
	
	DBA_WRITE_CHECK;
	
	if(info->hnd->delete(info, VALLEN(key)) == SUCCESS)
		RETURN_TRUE;
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto bool dba_insert(string key, string value, int handle)
   Inserts value as key, returns false, if key exists already */
PHP_FUNCTION(dba_insert)
{
	php_dba_update(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */

/* {{{ proto bool dba_replace(string key, string value, int handle)
   Inserts value as key, replaces key, if key exists already */
PHP_FUNCTION(dba_replace)
{
	php_dba_update(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */

/* {{{ proto bool dba_optimize(int handle)
   Optimizes (e.g. clean up, vacuum) database */
PHP_FUNCTION(dba_optimize)
{
	DBA_ID_GET1;
	
	DBA_WRITE_CHECK;
	if(info->hnd->optimize(info) == SUCCESS) {
		RETURN_TRUE;
	}
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto bool dba_sync(int handle)
   Synchronizes database */
PHP_FUNCTION(dba_sync)
{
	DBA_ID_GET1;
	
	if(info->hnd->sync(info) == SUCCESS) {
		RETURN_TRUE;
	}
	RETURN_FALSE;
}
/* }}} */

#endif
