/* speech.c */

#include "copyright.h"

/* Commands which involve speaking */

#ifdef WANT_ANSI
#ifdef __STDC__
#include <stddef.h>
#endif /* __STDC__ */
#endif /* WANT_ANSI */
#include <ctype.h>

#include "mudconf.h"
#include "config.h"
#include "db.h"
#include "interface.h"
#include "match.h"
#include "externs.h"
#include "flags.h"

int sp_ok(dbref player)
{
	if(!mudconf.robot_speak) {
		if (Robot(player) && !controls(player, Location(player))) {
			notify(player, "Sorry robots may not speak in public");
			return 0;
		}
	}
	return 1;
}

static void say_shout (int target, const char *prefix, int flags,
	dbref player, char *message)
{
	if (flags & SAY_NOTAG)
		raw_broadcast(target, "%s%s", Name(player), message);
	else
		raw_broadcast(target, "%s%s%s", prefix, Name(player), message);
}

static const char *announce_msg = "Announcement: ";
static const char *broadcast_msg = "Broadcast: ";

void do_say (dbref player, dbref cause, int key, char *message)
{
dbref	loc;
char	*buf2, *bp;
int	say_flags, depth;

	/* Convert prefix-coded messages into the normal type */

	say_flags = key & (SAY_NOTAG|SAY_HERE|SAY_ROOM);
	key &= ~(SAY_NOTAG|SAY_HERE|SAY_ROOM);

	if (key == SAY_PREFIX) {
		switch (*message++) {
		case '"':	key = SAY_SAY;
				break;
		case ':':	if (*message == ' ') {
					message++;
					key = SAY_POSE_NOSPC;
				} else {
					key = SAY_POSE;
				}
				break;
		case ';':	key = SAY_POSE_NOSPC;
				break;
		case '\\':	key = SAY_EMIT;
				break;
		default:	return;
		}
	}

	/* Make sure speaker is somewhere if speaking in a place */

	loc = where_is(player);
	switch (key) {
	case SAY_SAY:
	case SAY_POSE:
	case SAY_POSE_NOSPC:
	case SAY_EMIT:
		if (loc == NOTHING) return;
		if (!sp_ok(player)) return;
	}

	/* Send the message on its way  */

	switch (key) {
	case SAY_SAY:
		notify(player, tprintf("You say \"%s\"", message));
		notify_except(loc, player, player,
			tprintf("%s says \"%s\"", Name(player), message), 1);
		break;
	case SAY_POSE:
		notify_all(loc,	player, tprintf("%s %s", Name(player), message), 1);
		break;
	case SAY_POSE_NOSPC:
		notify_all(loc,	player, tprintf("%s%s", Name(player), message), 1);
		break;
	case SAY_EMIT:
		if ((say_flags & SAY_HERE) || !say_flags) {
			notify_all(loc, player, message, 1);
		} else if (say_flags & SAY_ROOM) {
			if ((Typeof(loc) == TYPE_ROOM) &&
			    (say_flags & SAY_HERE)) {
				return;
			}
			depth = 0;
			while((Typeof(loc) != TYPE_ROOM) && (depth++ < 20)) {
				loc = Location(loc);
				if ((loc == NOTHING) || (loc == Location(loc)))
					return;
			}
			if (Typeof(loc) == TYPE_ROOM) {
				notify_all(loc, player, message, 1);
			}
		}
		break;
	case SAY_SHOUT:
		switch (*message) {
		case ':':
			message[0] = ' ';
			say_shout(0, announce_msg, say_flags, player, message);
			break;
		case ';':
			message++;
			say_shout(0, announce_msg, say_flags, player, message);
			break;
		case '"':
			message++;
		default:
			buf2 = alloc_lbuf("do_say.shout");
			bp = buf2;
			safe_str((char *)" shouts \"", buf2, &bp);
			safe_str(message, buf2, &bp);
			safe_chr('"', buf2, &bp);
			*bp = '\0';
			say_shout(0, announce_msg, say_flags, player, buf2);
			free_lbuf(buf2);
		}
		STARTLOG(LOG_SHOUTS,"WIZ","SHOUT")
			log_name(player);
			buf2=alloc_lbuf("do_say.LOG.shout");
			sprintf(buf2, " shouts: '%s'", message);
			log_text(buf2);
			free_lbuf(buf2);
		ENDLOG
		break;
	case SAY_WIZSHOUT:
		switch (*message) {
		case ':':
			message[0] = ' ';
			say_shout(WIZARD, broadcast_msg, say_flags, player,
				message);
			break;
		case ';':
			message++;
			say_shout(WIZARD, broadcast_msg, say_flags, player,
				message);
			break;
		case '"':
			message++;
		default:
			buf2 = alloc_lbuf("do_say.wizshout");
			bp = buf2;
			safe_str((char *)" says \"", buf2, &bp);
			safe_str(message, buf2, &bp);
			safe_chr('"', buf2, &bp);
			*bp = '\0';
			say_shout(WIZARD, broadcast_msg, say_flags, player,
				buf2);
			free_lbuf(buf2);
		}
		STARTLOG(LOG_SHOUTS,"WIZ","BCAST")
			log_name(player);
			buf2=alloc_lbuf("do_say.LOG.wizshout");
			sprintf(buf2, " broadcasts: '%s'", message);
			log_text(buf2);
			free_lbuf(buf2);
		ENDLOG
		break;
	case SAY_WALLPOSE:
		if (say_flags & SAY_NOTAG)
			raw_broadcast(0, "%s %s", Name(player), message);
		else
			raw_broadcast(0, "Announcement: %s %s", Name(player),
				message);
		STARTLOG(LOG_SHOUTS,"WIZ","SHOUT")
			log_name(player);
			buf2=alloc_lbuf("do_say.LOG.wallpose");
			sprintf(buf2, " WALLposes: '%s'", message);
			log_text(buf2);
			free_lbuf(buf2);
		ENDLOG
		break;
	case SAY_WIZPOSE:
		if (say_flags & SAY_NOTAG)
			raw_broadcast(WIZARD, "%s %s", Name(player), message);
		else
			raw_broadcast(WIZARD, "Broadcast: %s %s", Name(player),
				message);
		STARTLOG(LOG_SHOUTS,"WIZ","BCAST")
			log_name(player);
			buf2=alloc_lbuf("do_say.LOG.wizpose");
			sprintf(buf2, " WIZposes: '%s'", message);
			log_text(buf2);
			free_lbuf(buf2);
		ENDLOG
		break;
	case SAY_WALLEMIT:
		if (say_flags & SAY_NOTAG)
			raw_broadcast(0, "%s", message);
		else
			raw_broadcast(0, "Announcement: %s", message);
		STARTLOG(LOG_SHOUTS,"WIZ","SHOUT")
			log_name(player);
			buf2=alloc_lbuf("do_say.LOG.wallemit");
			sprintf(buf2, " WALLemits: '%s'", message);
			log_text(buf2);
			free_lbuf(buf2);
		ENDLOG
		break;
	case SAY_WIZEMIT:
		if (say_flags & SAY_NOTAG)
			raw_broadcast(WIZARD, "%s", message);
		else
			raw_broadcast(WIZARD, "Broadcast: %s", message);
		STARTLOG(LOG_SHOUTS,"WIZ","BCAST")
			log_name(player);
			buf2=alloc_lbuf("do_say.LOG.wizemit");
			sprintf(buf2, " WIZemit: '%s'", message);
			log_text(buf2);
			free_lbuf(buf2);
		ENDLOG
		break;
	case SAY_GRIPE:
		STARTLOG(LOG_ALWAYS,"INF","GRIPE")
			log_name_and_loc(player);
			buf2=alloc_lbuf("do_say.LOG.gripe");
			sprintf(buf2," gripes: '%s'", message);
			log_text(buf2);
			if (!Haven(GOD)) {
				sprintf(buf2, "%s gripes: '%s'", 
					Name(player), message);
				notify_with_cause(GOD, player, buf2);
			}
			free_lbuf(buf2);
			if (!Quiet(player))
				notify(player, "Your complaint has been noted.");
		ENDLOG
		break;
	case SAY_NOTE:
		STARTLOG(LOG_ALWAYS,"INF","NOTE")
			log_name(player);
			buf2=alloc_lbuf("do_say.LOG.note");
			sprintf(buf2," notes: '%s'", message);
			log_text(buf2);
			if (!Haven(GOD)) {
				sprintf(buf2, "%s notes: '%s'", 
					Name(player), message);
				notify_with_cause(GOD, player, buf2);
			}
			free_lbuf(buf2);
			if (!Quiet(player)) notify(player, "Noted.");
		ENDLOG
		break;
	}
}

/* ---------------------------------------------------------------------------
 * do_page: Handle the page command.
 * Page-pose code from shadow@prelude.cc.purdue.
 */

static void page_pose (dbref player, dbref target, char *message)
{
char	*nbuf;

	nbuf = alloc_lbuf("page_pose");
	strcpy(nbuf, Name(player));
	notify_with_cause(target, player,
		tprintf("From afar, %s%s", Name(player), message));
	notify(player,
		tprintf("Long distance to %s: %s%s",
			Name(target), nbuf, message));
	free_lbuf(nbuf);
}

static void page_return (dbref player, dbref target, const char *tag,
	int anum, const char *dflt)
{
dbref	aowner;
int	aflags;
char	*str, *str2;
struct tm *tp;
time_t	t;

	str = atr_pget(target, anum, &aowner, &aflags);
	if (*str) {
		str2 = exec(target, target, EV_FCHECK, str, (char **)NULL, 0);
		t = time(NULL);
		tp = localtime(&t);
		notify_with_cause(player, target,
			tprintf("%s message from %s: %s",
				tag, Name(target), str2));
		notify_with_cause(target, player,
			tprintf("[%d:%02d] %s message sent to %s.",
				tp->tm_hour, tp->tm_min, tag, Name(player)));
		free_lbuf(str2);
	} else if (dflt && *dflt) {
		notify_with_cause(player, target, dflt);
	}
	free_lbuf(str);
}

static int page_check (dbref player, dbref target)
{
	if (!payfor(player, Guest(player) ? 0 : mudconf.pagecost)) {
		notify(player,
			tprintf("You don't have enough %s.",
				mudconf.many_coins));
	} else if (!(Flags(target) & PLAYER_CONNECT)) {
		page_return(player, target, "Away", A_AWAY,
			"Sorry, that player is not connected.");
	} else if (!could_doit(player, target, A_LPAGE)) {
		if (Wizard(target) && Dark(target))
			page_return(player, target, "Away", A_AWAY,
				"Sorry, that player is not connected.");
		else
			page_return(player, target, "Reject", A_REJECT,
				"Sorry, that player is not accepting pages.");
	} else {
		return 1;
	}
	return 0;
}


void do_page(dbref player, dbref cause, int key, char *tname, char *message)
{
dbref	target;
char	*nbuf;

	target = lookup_player(player, tname, 1);
	if (target == NOTHING) {
		notify(player, "I don't recognize that name.");
	} else	if (!page_check(player, target)) {
		;
	} else if (!*message) {
		nbuf = alloc_lbuf("do_page");
		strcpy(nbuf, Name(where_is(player)));
		notify_with_cause(target, player,
			tprintf("You sense that %s is looking for you in %s",
				Name(player), nbuf));
		notify(player,
			tprintf("You notified %s of your location.",
				Name(target)));
		free_lbuf(nbuf);
		page_return(player, target, "Idle", A_IDLE, NULL);
	} else {
		switch (*message) {
		case ':':
			message[0] = ' ';
			page_pose(player, target, message);
			break;
		case ';':
			message++;
			page_pose(player, target, message);
			break;
		case '"':
			message++;
		default:
			notify_with_cause(target, player,
				tprintf("%s pages: %s", Name(player), message));
			notify(player,
				tprintf("You paged %s with '%s'.",
					Name(target), message));
		}
		page_return(player, target, "Idle", A_IDLE, NULL);
	}
}

/* ---------------------------------------------------------------------------
 * do_pemit: Messages to specific players, or to all but specific players.
 */

void whisper_pose (dbref player, dbref target, char *message)
{
char	*buff;

	buff=alloc_lbuf("do_pemit.whisper.pose");
	strcpy (buff, Name(player));
	notify(player,
		tprintf("%s senses \"%s%s\"", Name(target), buff, message));
	notify_with_cause(target, player,
		tprintf("You sense %s%s", buff, message));
	free_lbuf(buff);
}

void do_pemit (dbref player, dbref cause, int key, char *recipient,
	char *message)
{
dbref	target, loc;
char	*buf2, *bp;
int	do_contents, ok_to_do;

	if (key & PEMIT_CONTENTS) {
		do_contents = 1;
		key &= ~PEMIT_CONTENTS;
	} else {
		do_contents = 0;
	}
	ok_to_do = 0;

	switch (key) {
	case PEMIT_FSAY:
	case PEMIT_FPOSE:
	case PEMIT_FPOSE_NS:
	case PEMIT_FEMIT:
		target = match_affected(player, recipient);
		if (target == NOTHING) return;
		ok_to_do = 1;
		break;
	default:
		init_match(player, recipient, TYPE_PLAYER);
		match_everything();
		target = match_result();
	}

	switch (target) {
	case NOTHING:
		switch (key) {
		case PEMIT_WHISPER:
			notify(player, "Whisper to whom?");
			break;
		case PEMIT_PEMIT:
			notify(player, "Emit to whom?");
			break;
		case PEMIT_OEMIT:
			notify(player, "Emit except to whom?");
			break;
		default:
			notify(player, "Sorry.");
		}
		break;
	case AMBIGUOUS:
		notify(player, "I don't know who you mean!");
		break;
	default:
		/* Enforce locality constraints */

		if (!ok_to_do &&
		    (nearby(player,target) || Controls(player, target))) {
			ok_to_do = 1;
		}

		if (!ok_to_do && (key == PEMIT_PEMIT) &&
		    (Typeof(target) == TYPE_PLAYER) && mudconf.pemit_players) {
			if (!page_check(player, target))
				return;
			ok_to_do = 1;
		}

		if (!ok_to_do &&
		    (!mudconf.pemit_any || (key != PEMIT_PEMIT))) {
			notify(player, "You are too far away to do that.");
			return;
		}

		if (do_contents && !Controls(player, target) &&
		    !mudconf.pemit_any) {
			notify(player, "Permission denied.");
			return;
		}

		loc = where_is(target);

		switch (key) {
		case PEMIT_PEMIT:
			if (do_contents) {
				if (Has_contents(target)) {
					notify_all(target, player, message, 1);
				}
			} else {
				notify_with_cause(target, player, message);
			}
			break;
		case PEMIT_OEMIT:
			if (loc != NOTHING)
				notify_except(loc, player, target, message, 1);
			break;
		case PEMIT_WHISPER:
			switch (*message) {
			case ':':
				message[0] = ' ';
				whisper_pose(player, target, message);
				break;
			case ';':
				message++;
				whisper_pose(player, target, message);
				break;
			case '"':
				message++;
			default:
				notify(player,
					tprintf("You whisper \"%s\" to %s.",
						message, Name(target)));
				notify_with_cause(target, player,
					tprintf("%s whispers \"%s\"",
						Name(player), message));
			}
			if ((!mudconf.quiet_whisper) && !Wizard(player)) {
				loc = where_is(player);
				if (loc != NOTHING) {
					buf2=alloc_lbuf("do_pemit.whisper.buzz");
					bp = buf2;
					safe_str(Name(player), buf2, &bp);
					safe_str((char *)" whispers something to ",
						buf2, &bp);
					safe_str(Name(target), buf2, &bp);
					*bp = '\0';
					notify_except2(loc, player, player,
						target, buf2, 1);
					free_lbuf(buf2);
				}
			}
			break;
		case PEMIT_FSAY:
			notify(target, tprintf("You say \"%s\"", message));
			if (loc != NOTHING) {
				notify_except(loc, player, target,
					tprintf("%s says \"%s\"", Name(target),
						message), 1);
			}
			break;
		case PEMIT_FPOSE:
			if (loc != NOTHING)
				notify_all(loc, player,
					tprintf("%s %s", Name(target), message), 1);
			else
				notify(target,
					tprintf("%s %s", Name(target), message));
			break;
		case PEMIT_FPOSE_NS:
			if (loc != NOTHING)
				notify_all(loc, player,
					tprintf("%s%s", Name(target), message), 1);
			else
				notify(target,
					tprintf("%s%s", Name(target), message));
			break;
		case PEMIT_FEMIT:
			if (loc != NOTHING)
				notify_all(loc, player, message, 1);
			else
				notify(target, message);
			break;
		}
	}
}

static void notify_forward(dbref object, dbref player, const char *msg)
{
dbref	exit, target, aowner;
int	aflags;
char	*buf, *cp;
char	*dp1, *dp2;

	if (Audible(object) && check_filter(object, player, A_FILTER, msg)) {
		cp = buf = alloc_lbuf("notify_forward");
		safe_str((char *)"From ", buf, &cp);
		safe_str(Name(object), buf, &cp);
		safe_str((char *)",", buf, &cp);
		*cp = '\0';
		cp = add_prefix(object, player, A_PREFIX, msg, buf);
		free_lbuf(buf);

		dp1 = buf = atr_get(object, A_FORWARDLIST,  &aowner, &aflags);
		if (*buf) do {
			dp2 = parse_to(&dp1, ' ', 0);
			if (*dp2 == '#') {
				target = atoi(dp2+1);
				if (Good_obj(target) && (target != object))
					notify_except(target, player, object,
						cp, 0);
			}
		} while (dp1 != NULL);
		free_lbuf(buf);

		if (Good_obj(Location(object)))
			notify_except(Location(object), player, object, cp, 0);
		free_lbuf(cp);
	}
	DOLIST(exit, Exits(object)) {
		if (Audible(exit) &&
		    ((target = Location(exit)) != object) &&
		    check_filter(exit, player, A_FILTER, msg)) {
			cp = add_prefix(exit, player, A_PREFIX, msg,
				"From a distance,");
			notify_all(target, player, cp, 0);
			free_lbuf(cp);
		}
	}
}

void notify_all(dbref loc, dbref player,
	const char *msg, const int forward)
{
dbref	first;

	notify_puppet(loc, player, msg);
	DOLIST(first, Contents(loc)) {
		notify_puppet(first, player, msg);
	}
	if (forward)
		notify_forward(loc, player, msg);
}

void notify_except(dbref loc, dbref player,
	dbref exception, const char *msg, const int forward)
{
dbref	first;

	if (loc != exception)
		notify_puppet(loc, player, msg);
	DOLIST(first, Contents(loc)) {
		if (first != exception) {
			notify_puppet(first, player, msg);
		}
	}
	if (forward)
		notify_forward(loc, player, msg);
}

void notify_except2(dbref loc, dbref player,
	dbref exc1, dbref exc2, const char *msg, const int forward)
{
dbref	first;

	if ((loc != exc1) && (loc != exc2))
		notify_puppet(loc, player, msg);
	DOLIST(first, Contents(loc)) {
		if (first != exc1 && first != exc2) {
			notify_puppet(first, player, msg);
		}
	}
	if (forward)
		notify_forward(loc, player, msg);
}

int check_filter(dbref object, dbref player, int filter, const char *msg)
{
	int aflags;
	dbref aowner;
	char *buf, *nbuf, *cp, *dp;

	buf = atr_pget(object, filter, &aowner, &aflags);
	if (!*buf) {
		free_lbuf(buf);
		return(1);
	}
	dp = nbuf = exec(object, player, 0, buf, (char **)NULL, 0);
	free_lbuf(buf);
	do {
		cp = parse_to(&dp, ',', EV_STRIP);
		if (quick_wild(cp, (char *)msg)) {
			free_lbuf(nbuf);
			return(0);
		}
	} while (dp != NULL);
	free_lbuf(nbuf);
	return(1);
}

char *add_prefix(dbref object, dbref player, int prefix,
	const char *msg, const char *dflt)
{
int	aflags;
dbref	aowner;
char	*buf, *nbuf, *cp;

	buf = atr_pget(object, prefix,  &aowner, &aflags);
	if (!*buf) {
		cp = buf;
		safe_str((char *)dflt, buf, &cp);
	} else {
		nbuf = exec(object, player, 0, buf, (char **)NULL, 0);
		free_lbuf(buf);
		buf = nbuf;
		cp = &buf[strlen(buf)];
	}
	if (cp != buf)
		safe_str((char *)" ", buf, &cp);
	safe_str((char *)msg, buf, &cp);
	*cp = '\0';
	return(buf);
}