/*===========================================================================*/
/*
 *  Copyright (C) 2000 Steve Huston
 *  Copyright (C) 2001 Petr Baudis <pasky@ji.cz>
 *
 *  Version 0.52 - *ALPHA VERSION*
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of 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.
 *
 *  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 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.
 */
/*===========================================================================*/

/*
 *		Please note, that I don't know much about licensing and legalities.  So
 *		far as I know, what I'm doing is fine; but if someone cares to point out
 *		my shortsightedness, feel free :P
 *
 *		This is a minor rewrite of the original megahal.c and megahal.h files
 *		to compile them in as a module for Eggdrop.  Guarantees: None,
 *		Warranties: none, etc.  Use at your own risk, if it makes your hdd
 *		become possessed, I don't care, yadda yadda yadda.  I'm not big on
 *		comments, but I'll try to note where changes were made.  In order to
 *		keep this as simple as possible, I'm leaving all previous comments and
 *		remarks in the code.
 *
 *		        Steve Huston (huston@elvis.rowan.edu)
 *
 *	So do I.
 * 
 *	As this is my first eggdrop module, I just used megahal.mod as a base
 *	for making it - i re-used and modified core functions, and take some
 *	inspiration from do_megahal(), however most of the work is mine.
 *
 *		Petr Baudis <pasky@ji.cz>
 */

/*===========================================================================*/

/*
 * TODO (non-priority order)
 * 	- no ret->attr checks for owner?
 * 	- better access control system, a lot better
 * 	- some TCL interface? anyone wants?
 * 	- code cleanup
 * 	- rest FIXME fixing
 * 	- rest TODO have to be done
 * 	- rest XXX cleanup
 * 	- modularization of db interface
 * 	- expmem
 * 	- multiple databases for various channels
 * 	- same with languages
 * 	- more intelligent -- delete
 * 	- web interface?
 * 	- fix help
 * 	- test it extensively
 * 	- make botnet query interface available
 * 	- make database reloading available
 */
 
#define MAKING_ASSOC
#define MODULE_NAME "assoc"
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <malloc.h>
#include <string.h>
#include <signal.h>
#include <math.h>
#include <time.h>
#include <ctype.h>
#include <sys/types.h>
#include "../module.h"
#include "db.h"

#undef global

#define lookup_db(key)		db_lookup(db, key, &vol) /* XXX potential source of your confusion! */
#define assign_db(key, val,\
                  attr, vol,\
		  owner)	db_assign(db, key, val, attr, vol, owner)
#define unassign_db(key, idx)	db_unassign(db, key, idx)
#define load_db(file)		db=db_load_(file)
#define save_db(file)		db_save(db, file)
#define forget_db()             db_forget(db)
 


static Function *global = NULL;

static int db_expmem()
{
  int size = 0;

  Context;
  return size;
}

/*
 * Next, a hacked attempt at strdup(), since I can't use malloc() anywhere.
 */

char *mystrdup(const char *s) {
	char *mytmp = nmalloc(strlen(s)+1);
	if (mytmp==NULL) return NULL;
	else strcpy(mytmp, s);

	return mytmp;
}

#include "dbi.c"

static	db_t *db=NULL;

	/* Aaaa, main stuff! */
static void do_db(int idx, char *prefix, char *text, char *nick, char *handle)
{
	char stuff[4096]; /* FIXME FIXME FIXME - we checks this in another places, but... */
	char *in=mystrdup(text), *action=NULL, *data=NULL, *ccnick=NULL, rep[256], *val, *owner=NULL;
	int vol, fvol=255;
	hash_t **ret;

        Context;
	
	if (nick[0])
		sprintf(rep, "NOTICE %s :", nick);
	else
		sprintf(rep, "");
       
	Context;
	
	/* Is there anything to parse? */
	if (!text[0]) {
		sprintf(stuff, DB_LANG_NOKEY, rep);
		dprintf(idx, "%s", stuff);
		return;
	}

	Context;

	if (!handle)
		putlog(LOG_MISC, "*** CRYYYYY *** I have null handle! That's evil! Nick %s, prefix %s, text %s, idx %d\n",
		    nick, prefix, text, idx);
	else {
		Context;
		if (!strcmp(handle, "*")) handle=NULL; /* they are evil, trying to fool us with so dummy shits */
	}

	Context;

	/* Load entire key, even with spaces */
	action=in;
	while (1)
		if (action=strchr(action, ' ')) {
			switch (action[1]) {
				case '=':
				case '+':
				case '-':
				case '>':
				case '|':
				case '@':
				case '!': *action=0; action++; goto key_end;
			}
			action++;
		} else
			break;
key_end:
	Context;

	if (action) {
		if (data=strchr(action, ' ')) {
			*data=0; data++;
		}
	}

	Context;

	/* strip leading and trailing spaces */
	while (in[0]==' ') memmove(in, in+1, strlen(in));
	while (in[strlen(in)-1]==' ') in[strlen(in)-1]=0; /* TODO do this more effectively */

	Context;
	
	/* Take [%d] at the end as force-volume */
	if (in[strlen(in)-1]==']') {
		char *idx;
		if (idx=strrchr(in, '[')) {
			idx++;
			fvol=atoi(idx);
			if (fvol<0) fvol=255;
			idx[-1]=0;
			/* and cleanup trailing spaces once more */
			while (in[strlen(in)-1]==' ') in[strlen(in)-1]=0; /* TODO do this more effectively */
		}
	}
		
	Context;

#define	BLAH	DB_LANG_ERRPREFIX
#define dprintf_(x, y...) /*fprintf(stderr, ">>%d: %d / %s || %s / %s / %s\n", __LINE__, x, y, in, action, data);*/ dprintf(x, y);
	
	if (action)
		switch (action[0]) {
			case '=': if (!data) {
					  sprintf(stuff, DB_LANG_ESETNODATA, rep, BLAH);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  return;
				  }
				  {
					  assign_db(in, data, 0, fvol, handle);
					  sprintf(stuff, DB_LANG_SETSUCC, rep, in);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  return;
				  }
				  break;
			case '+': if (!data) {
					  sprintf(stuff, DB_LANG_EADDNODATA, rep, BLAH);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  return;
				  }
				  if (!(ret=lookup_db(in))) {
					  assign_db(in, data, 0, fvol, handle);
					  sprintf(stuff, DB_LANG_SETSUCC, rep, in);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  return;
				  }
				  if (fvol-255 && fvol>=vol) fvol=vol-1;
				  vol=fvol-255?fvol:0;
				  if (ret[vol]->attr&HASH_ATTR_RDONLY) {
					  sprintf(stuff, DB_LANG_ENOPERM, rep, BLAH, in, vol);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  nfree(ret);
					  return;
				  } else {
					  char *wrk=nmalloc(strlen(ret[vol]->val)+strlen(data)+3);
					  strcpy(wrk, ret[vol]->val);
					  strcat(wrk, ", ");
					  strcat(wrk, data);
					  if (strlen(wrk)>1024) wrk[1024]=0;
					  if (!ret[vol]->owner) owner=handle; else owner=mystrdup(ret[vol]->owner);
					  assign_db(in, wrk, ret[vol]->attr, vol, owner);
					  nfree(wrk);
					  sprintf(stuff, DB_LANG_ADDSUCC, rep, in, vol);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  nfree(ret);
					  return;
				  }
				  break;
			case '-': if (!data) {
					  sprintf(stuff, DB_LANG_ESUBNODATA, rep, BLAH);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  return;
				  }
				  if (!(ret=lookup_db(in))) {
					  sprintf(stuff, DB_LANG_ENOENT, rep, BLAH, in);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  return;
				  }
				  if (fvol-255 && fvol>=vol) fvol=vol-1;
				  vol=fvol-255?fvol:0;
				  if (ret[vol]->attr&HASH_ATTR_RDONLY) {
					  sprintf(stuff, DB_LANG_ENOPERM, rep, BLAH, in, vol);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  nfree(ret);
					  return;
				  } else {
					  char *rem, *wrk=mystrdup(ret[vol]->val), *dd, o=0;
					  dd=nmalloc(strlen(data)+3); sprintf(dd, ", %s", data);
					  if (!(rem=strstr(wrk, dd)) && !(o++, rem=strstr(wrk, data))) {
						  nfree(wrk);
						  nfree(dd);
						  sprintf(stuff, DB_LANG_ENOSUBINSTR,
							  rep, BLAH, data, in, vol);
						  dprintf_(idx, "%s", stuff);
						  nfree(in);
						  nfree(ret);
						  return;
					  } else {
						  memmove(rem, rem+strlen(o?data:dd), strlen(rem+strlen(o?data:dd))+1);
					  	  if (!ret[vol]->owner) owner=handle; else owner=mystrdup(ret[vol]->owner);
						  assign_db(in, wrk, ret[vol]->attr, vol, owner);
						  nfree(wrk);
						  nfree(dd);
					          sprintf(stuff, DB_LANG_SUBSUCC, rep, in, vol);
					          dprintf_(idx, "%s", stuff);
					          nfree(in);
						  nfree(ret);
						  return;
					  }
				  }
				  break;
			case '!': if (!(ret=lookup_db(in))) {
					  sprintf(stuff, DB_LANG_ENOENT, rep, BLAH, in);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  return;
				  }
				  if (fvol-255 && fvol>=vol) fvol=vol-1;
				  vol=fvol-255?fvol:0;
				  if (ret[vol]->attr&HASH_ATTR_RDONLY) {
					  sprintf(stuff, DB_LANG_ENOPERM, rep, BLAH, in, vol);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  nfree(ret);
					  return;
				  } else {
					  unassign_db(in, vol);
					  sprintf(stuff, DB_LANG_DELSUCC, rep, in, vol);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  nfree(ret);
					  return;
				  }
				  break;
			case 0:   break;
			case '>': ccnick=data;
				  break;
			case '|': if (!(ret=lookup_db(in))) {
					  sprintf(stuff, DB_LANG_ENOENT, rep, BLAH, in);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  return;
				  } else {
				  	  if (fvol-255 && fvol>=vol) fvol=vol-1;
				  	  vol=fvol-255?fvol:0;
					  Context;
					  if (data) {
					  	  if (!ret[vol]->owner)
							  owner=handle?mystrdup(handle):NULL;
						  else
							  owner=mystrdup(ret[vol]->owner);
						  
						  if ((!handle && (!ret[vol]->owner || (ret[vol]->owner && strcmp(owner, "*"))))
						       || strncmp(handle, owner, 9)) {
							  sprintf(stuff, DB_LANG_ENOATTRPERM,
									 rep, BLAH, ret[vol]->owner, in, vol);
							  dprintf_(idx, "%s", stuff);
							  nfree(in);
							  nfree(owner);
							  nfree(ret);
							  return;
						  }
						  if (!owner) owner=NULL;
						  
						  val=mystrdup(ret[vol]->val);
						  while(data && data[0]) {
							  long wanted;
							  while (data[0]==' ') data++;
							  if (!data[0]) break;
							  if (wanted=atoi(data)) {
								  if (wanted<0)
									  ret[vol]->attr&=!wanted;
								  else
									  ret[vol]->attr|=wanted;
							  } else {
								  /* TODO make this more intelligent */
								  if (!strncmp(data, "readonly", 8))
									  ret[vol]->attr|=HASH_ATTR_RDONLY; else
								  if (!strncmp(data, "-readonly", 9))
									  ret[vol]->attr&=!HASH_ATTR_RDONLY; else
								  if (!strncmp(data, "secret", 6))
									  ret[vol]->attr|=HASH_ATTR_SECRET; else
								  if (!strncmp(data, "-secret", 7))
									  ret[vol]->attr&=!HASH_ATTR_SECRET; else
								  if (!strncmp(data, "none", 4))
									  ret[vol]->attr=0; else
								  { sprintf(stuff, DB_LANG_ENOATTRENT, rep, BLAH, data);
								    dprintf_(idx, "%s", stuff); }/* FIXME abusable to lag bot */
							  }
							  data=strchr(data, ' ');
							  if (data) { *data=0; data++; }
						  }
						  assign_db(in, val, ret[vol]->attr, vol, owner);
						  nfree(val); if (owner) nfree(owner);
					  }
					  sprintf(stuff, "%s%s[%d] |= ", data?rep:prefix, in, vol);
					  if (ret[vol]->attr & HASH_ATTR_RDONLY) strcat(stuff, "readonly ");
					  if (ret[vol]->attr & HASH_ATTR_SECRET) strcat(stuff, "secret ");
					  if (!ret[vol]->attr) strcat(stuff, "none"); else stuff[strlen(stuff)-1]=0;
					  strcat(stuff, "\n");
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  nfree(ret);
					  return;
				  }
				  break;
			case '@': if (!(ret=lookup_db(in))) {
					  sprintf(stuff, DB_LANG_ENOENT, rep, BLAH, in);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  return;
				  } else {
				  	  if (fvol-255 && fvol>=vol) fvol=vol-1;
				  	  vol=fvol-255?fvol:0;
					  Context;
					  if (data) {
						  if (!ret[vol]->owner) owner=handle; else owner=ret[vol]->owner;
						  if ((!handle && (!ret[vol]->owner || (ret[vol]->owner && strcmp(owner, "*"))))
						      || strncmp(handle, owner, 9)) { /* <-^- XXX fool the non-chosens! */
							  sprintf(stuff, DB_LANG_ENOGIVEPERM,
									 rep, BLAH, ret[vol]->owner, in, vol);
							  dprintf_(idx, "%s", stuff);
							  nfree(in);
							  nfree(ret);
							  return;
						  }
						  if (action[1]!='!') {
							  sprintf(stuff, DB_LANG_GIVECONFIRM, rep);
							  dprintf_(idx, "%s", stuff);
							  nfree(in);
							  nfree(ret);
							  return;
						  }
						  owner=data;
						  if (!strcmp(owner, "*"))
							  owner=NULL;
						  else {
							  struct userrec *urec=get_user_by_handle(userlist, owner);
							  if (!urec) {
								  sprintf(stuff, DB_LANG_ENOGIVEENT, rep, BLAH, owner);
								  dprintf_(idx, "%s", stuff);
								  nfree(in);
								  nfree(ret);
								  return;
							  }
						  }
						  val=mystrdup(ret[vol]->val);
						  assign_db(in, val, ret[vol]->attr, vol, owner);
						  nfree(val);
					  }
					  sprintf(stuff, "%s%s[%d] @= %s\n", prefix, in, vol, data?owner:ret[vol]->owner);
					  dprintf_(idx, "%s", stuff);
					  nfree(in);
					  nfree(ret);
					  return;
				  }
				  break;
		}

        Context;
	ret=lookup_db(in);
	if (!ret) {
		sprintf(stuff, DB_LANG_ENOENT, rep, BLAH, in);
		dprintf_(idx, "%s", stuff);
		nfree(in);
		return;
	} else {
		int f, voll=vol, p=0;
		if (fvol-255 && fvol>=vol) fvol=vol-1;
		vol=fvol-255?fvol:0;
		for (f=(fvol-255?vol:0); f<(fvol-255?vol+1:voll); f++) {
			if (ret[f]->attr&HASH_ATTR_SECRET && strchr(prefix, '#')) {
									/* XXX it is lovely channel-dest detection, isn't it? ;-) */
				sprintf(stuff, DB_LANG_ENOPERM, rep, BLAH, in, f);
				dprintf_(idx, "%s", stuff);
			} else {
				p++;
				sprintf(stuff, "%s%s[%d] = %s\n", prefix, in, f, ret[f]->val);
				dprintf_(idx, "%s", stuff);
				if (ccnick) {
					sprintf(stuff, "NOTICE %s :%s[%d] = %s\n", ccnick, in, f, ret[f]->val);
										/* FIXME centralised NOTICE/PRIVMSG setting */
					dprintf_(idx, "%s", stuff);
				}
			}
		}
		if (p && ccnick) {
			sprintf(stuff, DB_LANG_FORWARD, rep, ccnick);
			dprintf_(idx, "%s", stuff);
		}
		nfree(in);
		nfree(ret);
		return;
	}
}

static int pub_db(char *nick, char *host, char *hand,
		     char *channel, char *text)
{
	char prefix[50];
	struct chanset_t *chan = findchan(channel);

	Context;
	if (chan != NULL) {
		sprintf(prefix, "PRIVMSG %s :", channel);
		do_db(DP_HELP, prefix, text, nick, hand);
	}
	return 0;
}

static int pub2_db(char *nick, char *host, char *hand,
		     char *channel, char *text)
{
	char prefix[50];
	struct chanset_t *chan = findchan(channel);

	Context;
	if (chan != NULL) {
		sprintf(prefix, "NOTICE %s :", nick);
		do_db(DP_HELP, prefix, text, nick, hand);
	}
	return 0;
}

static int msg_save_db(char *nick, char *host, struct userrec *u, char *text)
{
	Context;
	putlog(LOG_MISC, "*", "Database saved by %s", nick);
	save_db("db.dat");
	return 0;
}

static int msg_db(char *nick, char *host, struct userrec *u, char *text)
{
	char prefix[50];

	Context;
	sprintf(prefix, "NOTICE %s :", nick);
	do_db(DP_SERVER, prefix, text, nick, u?u->handle:NULL);
	return 0;
}


static int dcc_db(struct userrec *u, int idx, char *par)
{
	Context;
	do_db(idx, "", par, "", u->handle);
	return 0;
}

static int dcc_save_db(struct userrec *u, int idx, char *par)
{
	Context;
	putlog(LOG_MISC, "*", "Database saved by %s", u->handle);
	save_db("db.dat");
	return 0;
}

static void hook_save_db() {
	Context;
	putlog(LOG_MISC, "*", "Saving database...");
	save_db("db.dat");
	return;
}

#if 0
/* FIXME this is silly, do not use it yet TODO fix this */
static void hook_reload_db() {
	Context;
	putlog(LOG_MISC, "*", "Reloading database...");
	save_db("db.dat.old");
	forget_db();
	load_db("db.dat");
	return;
}
#endif

/* a report on the module status */
static void db_report(int idx, int details)
{
  Context;
  if (details)
    dprintf(idx, "    db.so - Simple associations database for Eggdrop by pasky, v0.52\n");
}

/*static tcl_cmds mytcls[] =
{
  {"getdesc", tcl_getdesc},
  {"getowner", tcl_getowner},
  {"getattr", tcl_getowner},
  {"setdesc", tcl_setdesc},
  {"setattr", tcl_setdesc},
  {"setowner", tcl_setdesc},
  {NULL, NULL},
};*/

static cmd_t db_dcc[] =
{
  {"??", "", dcc_db, NULL},
  {"???", "", dcc_db, NULL},
  {"dbsave", "n", dcc_save_db, NULL},
  {NULL, NULL, NULL, NULL},
};

static cmd_t db_msg[] =
{
  {"??", "", msg_db, NULL},
  {"???", "", msg_db, NULL},
  {"dbsave", "n", msg_save_db, NULL},
  {NULL, NULL, NULL, NULL},
};

static cmd_t db_pub[] =
{
  {"??", "", pub_db, NULL},
  {"???", "", pub2_db, NULL},
  {NULL, NULL, NULL, NULL},
};

static int server_db_setup(char *mod)
{
	p_tcl_bind_list H_temp;
	if ((H_temp = find_bind_table("msg")))
		add_builtins(H_temp, db_msg);
	return 0;
}

static int irc_db_setup(char *mod)
{
	p_tcl_bind_list H_temp;
	if ((H_temp = find_bind_table("pub")))
		add_builtins(H_temp, db_pub);
	return 0;
}

static cmd_t db_load[] =
{
	{"server", "", server_db_setup, 0},
	{"irc", "", irc_db_setup, 0},
    {NULL, NULL, NULL, NULL},
};

static char *db_close()
{
	p_tcl_bind_list H_temp;
	del_hook(HOOK_HOURLY, (Function) hook_save_db);
	del_hook(HOOK_USERFILE, (Function) hook_save_db);
//	del_hook(HOOK_REHASH, (Function) hook_reload_db);
	save_db("db.dat");
	forget_db();
//	rem_tcl_commands(db_tcls);
	rem_help_reference("db.help");
	rem_builtins(H_load, db_load);
	rem_builtins(H_dcc, db_dcc);
	if ((H_temp = find_bind_table("pub")))
		rem_builtins(H_temp, db_pub);
	if ((H_temp = find_bind_table("msg")))
		rem_builtins(H_temp, db_msg);
	del_lang_section("db");
	module_undepend(MODULE_NAME);
	return NULL;
}

char *db_start();

static Function db_table[] =
{
  (Function) db_start,
  (Function) db_close,
  (Function) db_expmem,
  (Function) db_report,
};

char *db_start(Function * global_funcs)
{
	global = global_funcs;
	Context;
	module_register(MODULE_NAME, db_table, 2, 0);
	add_builtins(H_load, db_load);
	add_builtins(H_dcc, db_dcc);
//	add_tcl_commands(db_tcls);
	add_help_reference("db.help");
	add_lang_section("db");
	server_db_setup(0);
	irc_db_setup(0);

	load_db("db.dat");
	
	add_hook(HOOK_HOURLY, (Function) hook_save_db);
	add_hook(HOOK_USERFILE, (Function) hook_save_db);
//	add_hook(HOOK_REHASH, (Function) hook_reload_db);

	return NULL;
}

