/*
 * =================================================================
 * Filename:             langhelper.c
 * Description:          Language helper module
 * Written by:           AngryWolf <angrywolf@flashmail.com>
 * Requested by:         Sergios
 * Documentation:        langhelper.txt (comes with the package)
 * =================================================================
 */

#include "config.h"
#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "msg.h"
#include "channel.h"
#include <time.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <io.h>
#endif
#include <fcntl.h>
#include "h.h"
#ifdef STRIPBADWORDS
#include "badwords.h"
#endif
#ifdef _WIN32
#include "version.h"
#endif

#ifndef EXTCMODE
 #error "This module requires extended channel modes to be enabled."
 #error "See the EXTCMODE macro in include/config.h for more information."
 #error "Compilation aborted."
#endif

/* Backward compatibility */
#ifndef EX_DENY
 #define EX_DENY		0
 #define EX_ALLOW		1
 #define EX_ALWAYS_DENY		0
#endif

typedef struct _langlist LangList;
typedef struct _convlist ConvList;
typedef struct _langsetting LangSetting;

typedef struct
{
	EXTCM_PAR_HEADER

	char		*lang;
} aModeLHEntry;

struct _langlist
{
	LangList	*prev, *next;
	char		*name;
	ConvList	*conv;
};

struct _convlist
{
	ConvList	*prev, *next;
	char		*from, *to;
};

struct _langsetting
{
	LangSetting	*prev, *next;
	aClient		*cptr;
	char		*lang;
};

extern void			sendto_one(aClient *to, char *pattern, ...);

#define FLAG_LANGHELPER		'H'
#define MSG_LANGUAGE		"LANGUAGE"
#define TOK_LANGUAGE		NULL
#define HasLangHelper(x)	((x)->mode.extmode & MODE_LANGHELPER)
#define DelHook(x)      	if (x) HookDel(x); x = NULL
#define DelCmode(x)		if (x) CmodeDel(x); x = NULL
#define DelCommand(x)		if (x) CommandDel(x); x = NULL
#define ircstrdup(x,y)		if (x) MyFree(x); if (!y) x = NULL; else x = strdup(y)
#define ircfree(x)		if (x) MyFree(x); x = NULL
#define IsParam(x)		(parc > (x) && !BadPtr(parv[(x)]))
#define IsNotParam(x)		(parc <= (x) || BadPtr(parv[(x)]))

static int			m_language(aClient *cptr, aClient *sptr, int parc, char *parv[]);
static int			cb_config_test(ConfigFile *, ConfigEntry *, int, int *);
static int			cb_config_run(ConfigFile *, ConfigEntry *, int);
static int			cb_config_rehash();
static int			cb_rehash_complete();
static char			*cb_privmsg(aClient *, aClient *, aClient *, char *, int);
static char			*cb_chanmsg(aClient *, aClient *, aChannel *, char *, int);
static int			cb_quit(aClient *, char *);
static char			*convert_msg(LangList *lang, char *text);
static void			InitConf();
static void			FreeConf();
static void			del_LangSettings();
static LangList			*find_Language(char *name);

static Command			*AddCommand(Module *module, char *msg, char *token, iFP func);
static Cmode			*AddCmode(Module *module, CmodeInfo *req, Cmode_t *mode);
static int			chmode_is_ok(aClient *, aChannel *, char *, int, int);
static CmodeParam		*chmode_put_param(CmodeParam *, char *);
static char			*chmode_get_param(CmodeParam *);
static char			*chmode_conv_param(char *);
static void			chmode_free_param(CmodeParam *);
static CmodeParam		*chmode_dup_struct(CmodeParam *);
static int			chmode_sjoin_check(aChannel *, CmodeParam *, CmodeParam *);

static Hook			*HookConfTest, *HookConfRun, *HookConfRehash;
static Hook			*HookPrivMsg, *HookChanMsg, *HookQuit;
static Hook			*HookRehashDone;
static LangList			*Languages, *GlobalLang;
static LangSetting		*LangSettings;
static Cmode_t			MODE_LANGHELPER = 0L;
static Cmode			*CmodeLanghelper = NULL;
static Command			*CmdLanguage;
static char			*global_language = NULL;

ModuleHeader MOD_HEADER(langhelper)
  = {
	"langhelper",
	"$Id: langhelper.c,v 1.4 2004/06/29 19:49:17 angrywolf Exp $",
	"Language helper",
	"3.2-b8-1",
	NULL 
    };

DLLFUNC int MOD_TEST(langhelper)(ModuleInfo *modinfo)
{
	HookConfTest = HookAddEx(modinfo->handle, HOOKTYPE_CONFIGTEST, cb_config_test);

	return MOD_SUCCESS;
}

DLLFUNC int MOD_INIT(langhelper)(ModuleInfo *modinfo)
{
	CmodeInfo CmodeLH;

#ifndef STATIC_LINKING
	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM);
#endif
	InitConf();
	memset(&CmodeLH, 0, sizeof CmodeLH);

	CmodeLH.flag		= FLAG_LANGHELPER;
	CmodeLH.paracount	= 1;
	CmodeLH.is_ok		= chmode_is_ok;
	CmodeLH.put_param	= chmode_put_param;
	CmodeLH.get_param	= chmode_get_param;
	CmodeLH.conv_param	= chmode_conv_param;
	CmodeLH.free_param	= chmode_free_param;
	CmodeLH.sjoin_check	= chmode_sjoin_check;
	CmodeLH.dup_struct	= chmode_dup_struct;

	CmodeLanghelper	= AddCmode(modinfo->handle, &CmodeLH, &MODE_LANGHELPER);
	HookConfRun	= HookAddEx(modinfo->handle, HOOKTYPE_CONFIGRUN, cb_config_run);
	HookConfRehash	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH, cb_config_rehash);
	HookRehashDone	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, cb_rehash_complete);
	HookPrivMsg	= HookAddPCharEx(modinfo->handle, HOOKTYPE_USERMSG, cb_privmsg);
	HookChanMsg	= HookAddPCharEx(modinfo->handle, HOOKTYPE_CHANMSG, cb_chanmsg);
        HookQuit	= HookAddEx(modinfo->handle, HOOKTYPE_LOCAL_QUIT, cb_quit);
	CmdLanguage	= AddCommand(modinfo->handle, MSG_LANGUAGE, TOK_LANGUAGE, m_language);

	if (!CmdLanguage)
		return MOD_FAILED;

        return MOD_SUCCESS;
}

DLLFUNC int MOD_LOAD(langhelper)(int module_load)
{
	cb_rehash_complete();
	return MOD_SUCCESS;
}

DLLFUNC int MOD_UNLOAD(langhelper)(int module_unload)
{
	FreeConf();
	del_LangSettings();

	DelCommand(CmdLanguage);
	DelCmode(CmodeLanghelper);
	DelHook(HookChanMsg);
	DelHook(HookPrivMsg);
	DelHook(HookRehashDone);
	DelHook(HookConfRehash);
	DelHook(HookConfRun);
	DelHook(HookConfTest);
	DelHook(HookQuit);

	return MOD_SUCCESS;
}

static void InitConf()
{
	Languages = NULL;
	GlobalLang = NULL;
	global_language = NULL;
}

static void FreeConf()
{
	ListStruct	*next, *next2;
	LangList	*l;
	ConvList	*c;

	if (global_language)
		MyFree(global_language);

	for (l = Languages; l; l = (LangList *) next)
	{
		next = (ListStruct *) l->next;
		DelListItem(l, Languages);
		MyFree(l->name);
		for (c = l->conv; c; c = (ConvList *) next2)
		{
			next2 = (ListStruct *) c->next;
			DelListItem(c, l->conv);
			MyFree(c->from);
			MyFree(c->to);
			MyFree(c);
		}
		MyFree(l);
	}
}

static int cb_config_rehash()
{
	FreeConf();
	InitConf();

	return 1;
}

static int cb_rehash_complete()
{
	if (global_language)
	{
		if (!(GlobalLang = find_Language(global_language)))
			config_status("set::global-language: "
				"unknown language name %s",
				global_language);
	}

	return 0;
}

static Cmode *AddCmode(Module *module, CmodeInfo *req, Cmode_t *mode)
{
	Cmode *cmode;

	*mode = 0;
	cmode = CmodeAdd(module, *req, mode);

#ifndef STATIC_LINKING
        if (ModuleGetError(module) != MODERR_NOERROR || !cmode)
#else
        if (!cmode)
#endif
	{
#ifndef STATIC_LINKING
		config_error("[langhelper] Error adding channel mode +%c when loading module %s: %s",
			req->flag, MOD_HEADER(langhelper).name, ModuleGetErrorStr(module));
#else
		config_error("[langhelper] Error adding channel mode +%c when loading module %s",
			req->flag, MOD_HEADER(langhelper).name);
#endif
		return NULL;
	}

	return cmode;
}

static Command *AddCommand(Module *module, char *msg, char *token, iFP func)
{
	Command *cmd;

	if (CommandExists(msg))
    	{
		config_error("[langhelper] Command %s already exists", msg);
		return NULL;
    	}
    	if (token && CommandExists(token))
	{
		config_error("[langhelper] Token %s already exists", token);
		return NULL;
    	}

	cmd = CommandAdd(module, msg, token, func, MAXPARA, 0);

#ifndef STATIC_LINKING
	if (ModuleGetError(module) != MODERR_NOERROR || !cmd)
#else
	if (!cmd)
#endif
	{
#ifndef STATIC_LINKING
		config_error("[langhelper] Error adding command %s: %s", msg,
			ModuleGetErrorStr(module));
#else
		config_error("[langhelper] Error adding command %s", msg);
#endif
		return NULL;
	}

	return cmd;
}

LangList *find_Language(char *name)
{
	LangList *l;

	for (l = Languages; l; l = l->next)
		if (!strcmp(name, l->name))
			break;

	return l;
}

ConvList *find_Conversion(ConvList *conv, char *from)
{
	ConvList *c;

	for (c = conv; c; c = c->next)
		if (!strcmp(from, c->from))
			break;

	return c;
}

static LangSetting *find_LangSetting(aClient *cptr)
{
	LangSetting *s;

	for (s = LangSettings; s; s = s->next)
		if (s->cptr == cptr)
			break;

	return s;
}

static LangSetting *do_LangSetting(aClient *cptr, char *lang)
{
	LangSetting *s = find_LangSetting(cptr);

	if (!lang)
	{
		if (s)
		{
			DelListItem(s, LangSettings);
			MyFree(s->lang);
			MyFree(s);
		}
		return NULL;
	}

	if (!s)
	{
		s = (LangSetting *) MyMallocEx(sizeof(LangSetting));
    		s->cptr = cptr;
		AddListItem(s, LangSettings);
	}
	else
		MyFree(s->lang);

	s->lang = strdup(lang);
	return s;
}

static void del_LangSettings()
{
	ListStruct	*next;
	LangSetting	*s;

	for (s = LangSettings; s; s = (LangSetting *) next)
	{
		next = (ListStruct *) s->next;
		DelListItem(s, LangSettings);
		MyFree(s->lang);
		MyFree(s);
	}
}

#define CHECK_EMPTY(ce, parent) \
		if (!(ce)->ce_varname) \
		{ \
			config_error("%s:%i: blank %s item", \
				(ce)->ce_fileptr->cf_filename, \
				(ce)->ce_varlinenum, (parent)->ce_varname); \
			errors++; \
			continue; \
		} \
		if (!(ce)->ce_vardata) \
		{ \
			config_error("%s:%i: %s::%s without value", \
				(ce)->ce_fileptr->cf_filename, \
				(ce)->ce_varlinenum, \
				(parent)->ce_varname, (ce)->ce_varname); \
			errors++; \
			continue; \
		}

static int cb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
	ConfigEntry	*cep;
	int		errors = 0;

	if (type == CONFIG_MAIN)
	{
		if (!strcmp(ce->ce_varname, "language"))
		{
			if (!ce->ce_vardata)
			{
				config_error("%s:%i: %s block without parameter",
					ce->ce_fileptr->cf_filename,
					ce->ce_varlinenum,
					ce->ce_varname);
				errors++;
			}
			else
			{
				for (cep = ce->ce_entries; cep; cep = cep->ce_next)
				{
					CHECK_EMPTY(cep, ce)
				}
			}

			*errs = errors;
			return errors ? -1 : 1;
		}
	}
	else if (type == CONFIG_SET)
	{
		if (!strcmp(ce->ce_varname, "global-language"))
		{
			if (!ce->ce_vardata)
			{
				config_error("%s:%i: set::%s without parameter",
					ce->ce_fileptr->cf_filename,
					ce->ce_varlinenum,
					ce->ce_varname);
				errors++;
			}

			*errs = errors;
			return errors ? -1 : 1;
		}
	}

	return 0;
}

static int cb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
	ConfigEntry	*cep;
	LangList	*l;
	ConvList	*c;

	if (type == CONFIG_MAIN)
	{
		if (!strcmp(ce->ce_varname, "language"))
		{
			if (find_Language(ce->ce_vardata))
			{
				config_status("%s:%i: language %s already exists",
					ce->ce_fileptr->cf_filename,
					ce->ce_varlinenum,
					ce->ce_vardata);
				return 1;
			}

			l = (LangList *) MyMallocEx(sizeof(LangList));
			l->name = strdup(ce->ce_vardata);

			for (cep = ce->ce_entries; cep; cep = cep->ce_next)
			{
				if (find_Conversion(l->conv, cep->ce_varname))
				{
					config_status("%s:%i: multiple definition for %s::%s",
						cep->ce_fileptr->cf_filename,
						cep->ce_varlinenum,
						ce->ce_varname, cep->ce_varname);
					continue;
				}

				c = (ConvList *) MyMallocEx(sizeof(ConvList));
				c->from = strdup(cep->ce_varname);
				c->to = strdup(cep->ce_vardata);
				AddListItem(c, l->conv);
			}

			AddListItem(l, Languages);
			return 1;		
		}
	}
	else if (type == CONFIG_SET)
	{
		if (!strcmp(ce->ce_varname, "global-language"))
		{
			ircstrdup(global_language, ce->ce_vardata);
		}

		return 1;
	}

	return 0;
}

static int cb_quit(aClient *sptr, char *comment)
{
	do_LangSetting(sptr, NULL);
	return 0;
}

static int chmode_is_ok(aClient *sptr, aChannel *chptr, char *param, int type, int what)
{
	if (type == EXCHK_ACCESS || type == EXCHK_ACCESS_ERR)
	{
		if (!IsPerson(sptr) || IsULine(sptr))
			return EX_ALLOW;

		if (!is_chan_op(sptr, chptr))
		{
			if (type == EXCHK_ACCESS_ERR)
				sendto_one(sptr, err_str(ERR_CHANOPRIVSNEEDED),
					me.name, sptr->name, chptr->chname);
			return EX_DENY;
		}

		return EX_ALLOW;
	}
	else if (type == EXCHK_PARAM)
	{
		if (!find_Language(param))
		{
			sendnotice(sptr, "%s: No such language", param);
			return 0;
		}

		return 1;
	}

	return 0;
}

static CmodeParam *chmode_put_param(CmodeParam *param, char *text)
{
	aModeLHEntry *m = (aModeLHEntry *) param;

	if (!m)
	{
		m = (aModeLHEntry *) MyMallocEx(sizeof(aModeLHEntry));
		m->flag = FLAG_LANGHELPER;
	}

	if (m->lang)
		MyFree(m->lang);
	m->lang = strdup(text);

	return (CmodeParam *) m;
}

static char *chmode_get_param(CmodeParam *param)
{
	aModeLHEntry *m = (aModeLHEntry *) param;

	if (!m)
		return NULL;

	return m->lang;
}

static char *chmode_conv_param(char *param)
{
	return param;
}

static void chmode_free_param(CmodeParam *param)
{
	aModeLHEntry *m = (aModeLHEntry *) param;

	MyFree(m->lang);
	MyFree(m);
}

static CmodeParam *chmode_dup_struct(CmodeParam *param)
{
	aModeLHEntry *in, *out;

	out		= (aModeLHEntry *) MyMallocEx(sizeof(aModeLHEntry));
	in		= (aModeLHEntry *) param;
	out->flag	= in->flag;
	out->lang	= strdup(in->lang);

	return (CmodeParam *) out;
}

static int chmode_sjoin_check(aChannel *chptr, CmodeParam *ours, CmodeParam *theirs)
{
	int		ret;
	aModeLHEntry	*o, *t;

	o	= (aModeLHEntry *) ours;
	t	= (aModeLHEntry *) theirs;
	ret	= strcmp(o->lang, t->lang);

	if (!ret)
		return EXSJ_SAME;
	else if (ret > 0)
		return EXSJ_THEYWON;
	else
		return EXSJ_WEWON;
}

static char *cb_privmsg(aClient *cptr, aClient *from, aClient *to, char *text, int notice)
{
	LangSetting	*s;
	LangList	*lang;

	if (!IsPerson(from) || IsULine(from))
		return text;

	if ((s = find_LangSetting(to)) && (lang = find_Language(s->lang)))
		return convert_msg(lang, text);
	else if (GlobalLang)
		return convert_msg(GlobalLang, text);

	return text;		
}

static char *cb_chanmsg(aClient *cptr, aClient *from, aChannel *chptr, char *text, int notice)
{
	LangList *lang;

	if (!IsPerson(from) || IsULine(from))
		return text;

	if (HasLangHelper(chptr))
	{
		aModeLHEntry *mode = (aModeLHEntry *) extcmode_get_struct(
			chptr->mode.extmodeparam, FLAG_LANGHELPER);

		if ((lang = find_Language(mode->lang)))
			return convert_msg(lang, text);
	}
	else if (GlobalLang)
		return convert_msg(GlobalLang, text);

	return text;
}

static char *convert_msg(LangList *lang, char *text)
{
	static char	buf[1024], buf2[1024];
	ConvList	*conv;
	char		*start, *stop;

	for (conv = lang->conv, strcpy(buf, text);
	     conv;
	     conv = conv->next, strcpy(buf, buf2))
	{
		buf2[0] = 0;

		for (start = buf, stop = strstr(buf, conv->from);
		     stop;
		     start = stop + strlen(conv->from), stop = strstr(start, conv->from))
		{
			strncat(buf2, start, stop - start);
			strcat(buf2, conv->to);
		}

		strcat(buf2, start);
	}

	return buf;
}

/*
 * m_language
 *
 * parv[0]: sender prefix
 * parv[1]: language (optional)
 */

static int m_language(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	LangList *lang = NULL;

	if (!MyClient(sptr) || !IsPerson(sptr))
		return 0;

	if (IsParam(1) && !(lang = find_Language(parv[1])))
	{
		sendnotice(sptr, "*** %s: No such language", parv[1]);
		return 0;
	}

	do_LangSetting(sptr, lang ? lang->name : NULL);

	if (lang)
		sendnotice(sptr, "*** Language has been set to %s", parv[1]);
	else
		sendnotice(sptr, "*** Your language setting has been cleared");

	return 0;
}
