/*
 * =================================================================
 * Filename:      m_elmer.c
 * =================================================================
 * Description:   Command /elmer: makes someone talk like Elmer J.
 *                Fudd, that is, letters 'l' and 'r' become 'w', for
 *                example, "Shhhhhh! Be vewy, vewy quiet... I'm
 *                hunting wabbits, heh-heh-heh." (More info at
 *                http://www.geocities.com/Hollywood/Park/2326/
 *                character1.html#elmer.) Install this module
 *                on all servers in order to make it work properly.
 * =================================================================
 * Author:        AngryWolf
 * Email:         angrywolf@flashmail.com
 * =================================================================
 * Feedback:
 *
 * I accept bugreports, ideas and opinions, and if you have any
 * questions, just send an email to me!
 *
 * Thank you for using my module!
 *
 * =================================================================
 * Requirements:
 * =================================================================
 *
 * o Unreal >=3.2-beta18
 * o One of the supported operating systems (see unreal32docs.html)
 *
 * =================================================================
 * Installation:
 * =================================================================
 *
 * See http://angrywolf.linktipp.org/compiling.php?lang=en
 *
 * =================================================================
 * Usage:
 * =================================================================
 *
 * Command ELMER:
 * --------------
 *
 * Syntax: /elmer [+|-]<nickname>
 *
 * Examples:
 *
 * - To make someone talk like Elmer:
 *       /elmer SomeNick
 * - To remove the previous setting from someone:
 *       /elmer -SomeNick
 *
 * =================================================================
 * Mirror files:
 * =================================================================
 *
 * http://angrywolf.linktipp.org/m_elmer.c [Germany]
 * http://angrywolf.uw.hu/m_elmer.c [Hungary]
 * http://angrywolf.fw.hu/m_elmer.c [Hungary]
 *
 * =================================================================
 * Changes:
 * =================================================================
 *
 * $Log: m_elmer.c,v $
 * Revision 1.6  2004/03/12 11:59:30  angrywolf
 * - Made some minor fixes.
 *
 * Revision 1.5  2004/03/08 21:25:12  angrywolf
 * - Fixed some bugs that could cause crash if you compile the module
 *   statically (for example, under Windows).
 *
 * Revision 1.4  2004/02/23 17:37:34  angrywolf
 * - Unoccupied flags are not trustable, CVS .2138 took use
 *   of the one I used before to mark users as Elmers.
 *   Therefore I use a separate list now, even if it's
 *   more memory intensive.
 *
 * Revision 1.3  2004/02/17 10:42:24  angrywolf
 * *** empty log message ***
 *
 * Revision 1.2  2004/02/17 10:35:50  angrywolf
 * - Fixed a letter case problem, reported by MichaelJE2.
 *
 * Revision 1.1  2004/01/29 13:59:28  angrywolf
 * Initial revision
 *
 * =================================================================
 */

#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

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

#define MSG_ELMER	"ELMER"
#define TOK_ELMER	"EL"
#define IsParam(x)      (parc > (x) && !BadPtr(parv[(x)]))
#define IsNotParam(x)   (parc <= (x) || BadPtr(parv[(x)]))
#define DelHook(x)	if (x) HookDel(x); x = NULL
#define DelCommand(x)	if (x) CommandDel(x); x = NULL

static Command		*AddCommand(Module *module, char *msg, char *token, iFP func);
static int		m_elmer(aClient *, aClient *, int, char *[]);
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 void		free_elmerflags();

static Command		*CmdElmer;
static Hook		*HookPrivMsg = NULL, *HookChanMsg = NULL, *HookQuit = NULL;

/*
 * Unoccupied flags are not trustable, they may become used anytime.
 * Therefore I use a separate list.
 */

typedef struct _elmerflag ElmerFlag;

struct _elmerflag
{
	ElmerFlag	*prev, *next;
	aClient		*cptr;
};

static ElmerFlag *ElmerFlags;

ModuleHeader MOD_HEADER(m_elmer)
  = {
	"m_elmer",
	"$Id: m_elmer.c,v 1.6 2004/03/12 11:59:30 angrywolf Exp $",
	"Command /elmer",
	"3.2-b8-1",
	NULL 
    };

DLLFUNC int MOD_INIT(m_elmer)(ModuleInfo *modinfo)
{
	ElmerFlags = NULL;

	if (!(CmdElmer = AddCommand(modinfo->handle, MSG_ELMER, TOK_ELMER, m_elmer)))
		return MOD_FAILED;

	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);

	return MOD_SUCCESS;
}

DLLFUNC int MOD_LOAD(m_elmer)(int module_load)
{
	return MOD_SUCCESS;
}

DLLFUNC int MOD_UNLOAD(m_elmer)(int module_unload)
{
	free_elmerflags();

	DelHook(HookQuit);
	DelHook(HookChanMsg);
	DelHook(HookPrivMsg);
	DelCommand(CmdElmer);

	return MOD_SUCCESS;
}

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

	if (CommandExists(msg))
    	{
		config_error("Command %s already exists", msg);
		return NULL;
    	}
    	if (CommandExists(token))
	{
		config_error("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("Error adding command %s: %s", msg,
			ModuleGetErrorStr(module));
#else
		config_error("Error adding command %s", msg);
#endif
		return NULL;
	}

	return cmd;
}

static ElmerFlag *find_elmer(aClient *cptr)
{
	ElmerFlag *e;

	for (e = ElmerFlags; e; e = e->next)
		if (e->cptr == cptr)
			break;

	return e;
}

static void set_elmer(aClient *cptr)
{
	ElmerFlag *e;

	e = (ElmerFlag *) MyMalloc(sizeof(cptr));
	e->cptr = cptr;
	AddListItem(e, ElmerFlags);
}

static void clear_elmer(ElmerFlag *e)
{
	DelListItem(e, ElmerFlags);
	MyFree(e);
}

static void free_elmerflags()
{
	ElmerFlag	*e;
	ListStruct	*next;

	for (e = ElmerFlags; e; e = (ElmerFlag *) next)
	{
		next = (ListStruct *) e->next;
		DelListItem(e, ElmerFlags);
		MyFree(e);
	}
}

static char *convert_to_elmer(char *str)
{
	char *p;

	for (p = str; *p; p++)
		switch(*p)
		{
			case 'l':
			case 'r':
				*p = 'w';
				break;
			case 'L':
			case 'R':
				*p = 'W';
		}

	return str;
}

static int cb_quit(aClient *sptr, char *comment)
{
	ElmerFlag *e;

	if ((e = find_elmer(sptr)))
		clear_elmer(e);

	return 0;
}

static char *cb_privmsg(aClient *cptr, aClient *from, aClient *to, char *str, int notice)
{
	return find_elmer(cptr) ? convert_to_elmer(str) : str;
}

static char *cb_chanmsg(aClient *cptr, aClient *from, aChannel *to, char *str, int notice)
{
	return find_elmer(cptr) ? convert_to_elmer(str) : str;
}

static int m_elmer(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	aClient		*acptr		= NULL;
	ElmerFlag	*is_elmer	= NULL;
	char		*nick		= NULL;
	int		add		= 1;

	if (IsPerson(sptr) && !IsAnOper(sptr))
	{
    		sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name,
            		parv[0]);
                return -1;
	}

	nick = IsParam(1) ? parv[1] : NULL;

	if (nick)
	{
		if (*nick == '+')
			nick++;
		else if (*nick == '-')
		{
			nick++;
			add = 0;
		}
		if (!*nick)
			nick = NULL;
	}

        if (!nick)
	{
	        if (!IsServer(sptr))
	    		sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS),
				me.name, sptr->name, MSG_ELMER);
		return -1;
	}

	acptr = find_person(nick, NULL);

	if (!acptr)
	{
		if (!IsServer(sptr))
	    		sendto_one(sptr, err_str(ERR_NOSUCHNICK),
				me.name, sptr->name, nick);
	        return 0;
	}

	if (!MyConnect(acptr))
	{
		sendto_one(acptr->from, ":%s %s %c%s",
			sptr->name, IsToken(cptr) ? TOK_ELMER : MSG_ELMER,
			add ? '+' : '-', acptr->name);
		return 0;
	}

	is_elmer = find_elmer(acptr);

	if (add)
	{
		if (is_elmer)
		{
			if (!IsServer(sptr))
				sendto_one(sptr,
					":%s %s %s :*** %s is already set to talk like Elmer",
					me.name, IsWebTV(sptr) ? "PRIVMSG" : "NOTICE",
					sptr->name, acptr->name);
			return 0;
		}

		set_elmer(acptr);

		sendto_realops("*** %s made %s!%s@%s talk like Elmer",
			sptr->name, acptr->name, acptr->user->username,
			GetHost(acptr));
		sendto_serv_butone(&me,
            		":%s SMO o :*** %s made %s!%s@%s talk like Elmer",
			me.name, sptr->name, acptr->name, acptr->user->username,
			GetHost(acptr));

		sendto_one(acptr,
			":%s %s %s :*** You are now talking like Elmer",
			me.name, IsWebTV(acptr) ? "PRIVMSG" : "NOTICE",
			acptr->name);
		if (MyConnect(sptr) && acptr != sptr)
			sendto_one(sptr,
				":%s %s %s :*** %s is now talking like Elmer",
				me.name, IsWebTV(acptr) ? "PRIVMSG" : "NOTICE",
				sptr->name, acptr->name);
	}
	else
	{
		if (!is_elmer)
		{
			if (!IsServer(sptr))
				sendto_one(sptr,
					":%s %s %s :*** %s is not talking like Elmer",
					me.name, IsWebTV(sptr) ? "PRIVMSG" : "NOTICE",
					sptr->name, acptr->name);
			return 0;
		}

		clear_elmer(is_elmer);

		sendto_realops("*** %s made %s!%s@%s no longer talk like Elmer",
			sptr->name, acptr->name,
			acptr->user->username, GetHost(acptr));
		sendto_serv_butone(&me,
            		":%s SMO o :*** %s made %s!%s@%s no longer talk like Elmer",
			me.name, sptr->name, acptr->name,
			acptr->user->username, GetHost(acptr));

		sendto_one(acptr,
			":%s %s %s :*** You are no longer talking like Elmer",
			me.name, IsWebTV(acptr) ? "PRIVMSG" : "NOTICE",
			acptr->name);
		if (MyConnect(sptr) && acptr != sptr)
			sendto_one(sptr,
				":%s %s %s :*** %s is no longer talking like Elmer",
				me.name, IsWebTV(acptr) ? "PRIVMSG" : "NOTICE",
				sptr->name, acptr->name);
	}

	return 0;
}
