/*
    slforum version 1.x - Simple chat room usable via telnet.
    Copyright (C) 1998-2006 Jeremy D. Monin, Dan Chin, Matt Lochansky
	(forum@shadowlands.org).
    Based on jdkchat version 1.3, J.D.Koftinoff Software, Ltd. (GPL'd)

    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 General 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.
*/


#ifndef __SLFORUMUSER_H
#define	__SLFORUMUSER_H


/* Contents of slforumuser.h:
 * 1) A bunch of timedefs and structs, incl. ALL USER PREFS
 * 2) Input substate info, for times when the user is doing something other
 *    than just typing to the room (virtuser pw change, admin menus, etc)
 *    that interacts with the user.
 * 3) Class slforumuser members and methods
 */



typedef unsigned long long userfield_t;  /* for !to-multiple */
	/* MAX_CHAT_USERS in slfprefs.h must be at most 2 less than the number
	 * of bits in this type.  Remember: sizeof(int)==sizeof(long) on
	 * many platforms (use long long)  Make sure to keep it unsigned!
	 * If it's 32 bits, MAX_CHAT_USERS at most 30.
	 * If it's 64 bits, MAX_CHAT_USERS at most 62.
	 * (the topmost bit should never be 1, or >>1 might not clear it,
	 *  despite it being unsigned, due to compiler bugs.)
	 */

class SLForumRoom;
class SLForumUser;

#include "linkedlist.h"

typedef struct _slf_cmd_desc;

/* type marker for slfparam */
typedef enum 
{
	p_is_unused=0, p_is_str, p_is_int, p_is_user, p_is_cmd
} slfparamkind;

/* one !cmd parameter, passed to an slfusercmd handler in slforumcmd */
typedef struct
{
	slfparamkind kind;
	union {
		char *strval;
		int   intval;
		SLForumUser *u;
		const struct _slf_cmd_desc *cmd;
	} ;
} slfparam;


/*                            room           user       argc  argv       */
typedef int (slfusercmd) (SLForumRoom *, SLForumUser *, int, slfparam*);


typedef struct _slf_cmd_desc {
  const char *name;
  slfusercmd *meth;
  const char *parmspec;  // 19990529.1800
  const char *parmhelp;  // 19990212.0830
  const char *deschelp;	 // 19990212.0830
} slf_cmd_desc;



#include "slforumcmd.h"


/* Note that the virtual user structure (struct slf_userinfo) is in slfprefs.h
 * because it's needed all over.
 */


/** If you add _any_ character-buffer field to this class, put
 *  its size here as a constant, and use that constant,
 *  do _not_ do anything like
 *	mybuff[79] = '\0';
 *  or
 *	strncpy (mybuff, src, 79);
 *
 * (use USERMYBUFSIZ-1 instead)
 *
 *  Also be sure to add a line to the constructor to set
 *  the end of the string to \0; see constructor for why.
 */

#define USERBUFSIZ	80	// 1 line long
#define USERNAMESIZ	16
#define USERPROMPTSIZ	256	// should be dns size- 'prompt' is our
				// per-user scratch buffer as well.

#define USERAWAYSIZ	(80-12)	// used for away messages and comments,
				// which have the same format onscreen.

#define LONGFROMSIZ    256	// PAM wants whole thing, we might too later

#define DNSFROMSIZ	20	// since entire DNS-from string is too
				// long for the !who list format.


	/** Tab masks are to remember which whitespace results from hitting
	 *  tab, and which results from hitting spacebar.  One bit per char.
	 *  Tab masks: buffer.tabmask, yank_tabmask, last_tabmask.
	 *  Used by back_one_tab, expand_one_tab, pasteIntoBuffer.
	 *  Copied in a couple other places, cleared in a couple places.
	 */
#define TABMASK_BUFSIZE  ((USERBUFSIZ+31)/32)
	/* AFTER hitting tab, put the new cur_pos through this. */
#define tabmask_get_bit_for_pos(mmm,iii) (mmm[iii/32] & (1 << (iii % 32)))
#define tabmask_set_bit_for_pos(iii) (buf.tabmask[iii/32] |= (1 << (iii % 32)))
#define tabmask_clr_bit_for_pos(iii) (buf.tabmask[iii/32] &= ~(1 << (iii % 32)))


typedef struct {
    char  buffer [USERBUFSIZ];  // input buffer (user's "command line")
    bool  macro_input;  // if true, print special characters specially
                        // (tab, ctrl-whatever, esc, etc.).  If false,
	                // just echo buffer's contents to the user.

    int   cur_pos;      // position in array of end of line (next char here).
    int   cur_len;      // strlen of the buffer when displayed onscreen
                        // (call stringDisplayLength to get it)
    int   tabmask [TABMASK_BUFSIZE];     // see user::back_one_tab
    bool  tel_bin_in, tel_bin_out;  // 8-bit (telnet binary) mode
	/* We initially offer WILL TELOPT_BIN and ask DO TELOPT_BIN
	 * when we first connect.  */
    bool  tel_bin_in_hadit, tel_bin_out_hadit;  // for later renegotiation
} bufinfo;


typedef struct		// ALL USER PREFS GO HERE.
{                       // they are initialized in user::resetFieldsToDiscon()

	bool	local_mode;	/* for funky telnets */

	int	ignore_mask;	/* 19990606.0220 */

	int	idle_here_threshold;  /* if you're less than this many minutes
				       * idle and you're away, you're listed in
				       * the !here.
				       */

	bool	wants_true_idle;   // Reset idle for every key or just public? 
	bool	tab_expand_incl_self;   // include your own name for tab xpan?
	bool	rot10_digits_too;	// pref for c_rot13; defaults to yes
	int	rot_offset;		// pref for c_rot13; defaults to 13 
	int	auto_msgchk_minutes;	// if nonzero, check every n minutes
	int	auto_away_seconds;	// if nonzero, autoaway if this idle

	char	cmd_char;	/* precede commands w/ this; normally '!' */
	char	cmd_char_alt;	/* alternate command char, or \0  */
	int	jetlag;		/* timezone offset from server */
	int	dice_n_sides;   /* number of sides on dice-jdm 20010918.1022 */
	bool	showaidle;      /* do we show AWAY:idle people in the away msg
				 * detail part of !showaway/!who ?
				 */
	bool	show_recap;	/* jer 20060528.1140; ignored unless USE_RECAP.
				 * Although recap may be ifdef'd away,
				 * the pref save/load code always sees it,
				 * in case this user's file has the pref.
				 */

	// for autowho, autotopic  -- dschin 19990922.1145
	int	maxAwayIdle;	/* Max idle time before doing !who on return */
	int	maxTopicIdle;	/* Max second before doing !topic on return  */

	LinkedList *listenlist;	/* words to listen for when we're
				 * ignoring public chat...
				 * 19990215.1900 */

	LinkedList *wblist;	/* people to ban whispering from...
				 * 19990610.1100 */

	LinkedList *macrolist;  /* elem data is of type macro_t (def below) */
} prefs_t;



#ifdef ALLOW_MACROS

#define MACRO_RS  ((char) (30+128))     /* line sep, see below */
#define MACRO_SUB ((char) (26+128))     /* cmd char, see below */

typedef struct {
    char	key;
    bool	hit_enter_afterwards;
    const char*	val;

    bool	editing;  // true when editing this macro (kind of a lock)

    // within a val, ascii 30 (RS, record separator) separates lines.
    // ascii 26 (SUB) substitues where the command char goes.
    // in order to make life easier for anyone editing the file,
    // the high bit is set on these in-band control chars.

} macro_t;

/* convenience defines for macros: */

#define macro_key(LL)  (((macro_t *)(LL))->key)
#define macro_val(LL)  (((macro_t *)(LL))->val)
#define macro_crlf(LL) (((macro_t *)(LL))->hit_enter_afterwards)
#define macro_editing(LL) (((macro_t *)(LL))->editing)

#endif  /* def(ALLOW_MACROS) */


/* Input Substates ("admin substates") for admin menus, virtuser pw change,
 * macro editing, other tasks that use the input line interactively.
 * Note: admin_substate_t admin_substate is the var used here;
 *       time_t substate is totally different and unrelated to admin substates.
 */
/** NOTE: if you add new substates here, be sure to modify
 *  SLForumUser::admInputSubstateBehavTable to match.
 *  (in slforumuser-adminsubstate.inc.cc)
 */
typedef enum
{
  /* normally here */
  ss_nonadmin=0,

  /* admin menu */
  ss_mainmenu,

  /* add */
  ss_addgetuname, ss_addgetpw,      ss_addconfpw,
  ss_addgetgecos, ss_addgetsponsor, ss_addgetemail,
  ss_addaskadmin, ss_add_doit,

  /* edit */
  ss_editgetuname, ss_edit_retrieve,
  ss_editmenu,
  ss_edituname,   ss_editconfuname, ss_edituname_doit,
  ss_editpw,      ss_editconfpw,    ss_editpw_doit,
  ss_editgecos,   ss_editgecos_doit,
  ss_editsponsor, ss_editsponsor_doit,
  ss_editemail,   ss_editemail_doit,
  ss_editisadmin, ss_edit_doit,

  /* del */
  ss_delgetuname, ss_dellookup, ss_delconfirm, ss_del_doit,

  ss_list,
  /* TODO TODO TODO - add LIST substates, if any */

  /* ----- all states past here are user states, not admin substates :
   * set ss_notadmin_FIRST macro (below) to the first non-admin substate.
   * (currently ss_pwch_getoldpw). -----
   */

  /* virt users changing pw.. not really "admin" substates
   * but the substate mechanism set up for the admin modes
   * works nicely for it
   */
  ss_pwch_getoldpw, ss_pwch_getnewpw, ss_pwch_confnewpw, ss_pwch_setnewpw,

  /* macro: waiting for macro name key */
  ss_macro_expand_readkey,
  ss_macro_addedit_readkey,
  ss_macro_addedit_checkkey,
  ss_macro_addedit_askreplace,
  ss_macro_addedit_readline,
  ss_macro_addedit_ctrlq,     /* from _addedit_readline */
  ss_macro_addedit_checkline,
  ss_macro_addedit_askcrlf,
  ss_macro_addedit_doit,
  ss_macro_delete_readkey,
  ss_macro_delete_confirm,  /* see ss_macro_LAST below */

  ss_idlesoon_hitanykey,    /* for 'going idle soon, hit any key to cancel' */

  SS_NUM_SUBSTATES, /* not an actual state, just counts 'em. */
  ss_unused	    /* marks an unused admInputSubstateBehavTable entry */

} admin_substate_t;


/* used by SLForumUser::ManageInputNewCtrlChar: */

#define ss_macro_FIRST ss_macro_expand_readkey    /* first macro substate */
#define ss_macro_LAST  ss_macro_delete_confirm    /* last macro substate  */

/* used by slforumuser-adminsubstate.inc.cc: */

#define ss_notadmin_FIRST ss_pwch_getoldpw      /* first non-admin subst. */
/* (there is no ss_notadmin_LAST; ss_notadmin_FIRST & after are not admin) */


/** This is for the table of admin substates; many of these substates
 *  simply need to get a line of text from the user, and move to a new
 *  substate when <return> is hit.  (setAdmSubstate does the processing
 *  for the state transition, including the bit of logic that actually
 *  needs to be done, such as new-password checking, etc.)  There are
 *  more complex admin substates; their behavior is handled by the
 *  switch within handleAdminLine.  The input-substate-behav-table
 *  handles the default: for that switch.
 */
typedef struct
{
  bool show_input;
  admin_substate_t nextstate;
} admin_input_substate_behav_t;




class SLForumUser
{
public:
  //	SLForumUser( SLForumRoom *current_room);
	SLForumUser( SLForumRoom *current_room, int idx_in_room);
	~SLForumUser();

	friend class SLForumCmd;

	bool	isUserConnected() const;// valid network socket (not empty).
	bool	IsUserAccepted() const;	// validated, logged in, talking.
	bool	IsUserAround()   const;	// accepted() or waiting guest.
	bool	isAdmin()	 const; // admin.
	int	GetFD()          const;
	const char *GetName()    const;
	time_t	GetAwayTime()    const;

	slf_tristate userCanLock()	const;

	bool	ManageUser();		// if rets false, connection has ended.
	int	Send( const char *text );	// send to user without FROM:
	int	Send( int msgdescflags, const char *from, const char *to,
		      const char *text);
		      		// send to user from user,
				// or public if to==NULL
	int	Send( int msgdescflags, const char *from, const char *to,
		      const char *text, int fromtominwid);
				// send to user from user, with a minimum
				// width for the from/to attribution field

		/* if hint not set, send text and set it.  This assumes
		 * we're sending a system message from SLFORUM.
		 * Optional msgdescflags.
		 */
	int	Send
	(const char *text, int hintbitset, bool wants_beep, int msgdescflags=0);

		/* send a system message from some user (SLFORUM, TIMER, etc)
	 	 * with msgdescflags and optional beep.
		 */
	int	Send
	(const char *fromsysusername,
	 int msgdescflags, const char *text, bool wants_beep);

		/* send a system message from SLFORUM, with msgdescflags;
		 * this allows the user to ignore certain types of message
		 * (such as users entering/leaving, or dice rolls) that come
		 * from the system, not from a user.  The msgdescflags should
		 * not have MSG_PUBLIC set.
		 */
	int	Send( int msgdescflags, const char *text, bool wants_beep );

		/* Check the whisperban list before sending. */
	bool	CheckBanned (SLForumUser *from);

	int	uWrite( const char *text);	// send bytes out to user_fd
	int	uWriteSubst( const char *text);	// send bytes w/ cmd char subst
	int	uWrite( const char *buf, int sz);  // send _some_ bytes out

	bool	isUserAway()  const;          // both accepted and away
	bool	isUserGuest() const;
	const char *setAway (const char *msg);
	const char *getAwayMessage () const;

	void	writeLogoutEntry (const char *fname);
	void	sendMessagesTo (SLForumUser *usr); // null for all
	void	sendMessagesAlso (SLForumUser *usr); // null for no effect :-)
	void	sendMessagesToNone ();
	void	sendMessagesFromNone ();

	int	idleTime() const;	// time since last keypress (secs)
	int	idleTime(time_t now) const;
	bool    isAwayFromIdle() const;
	time_t	loginTime() const;	/* time of login (in time(2) form);
					 * use loginTimeStr() for string.
					 * The value returned by loginTime()
					 * is only meaningful for connected,
					 * authenticated users.
					 */
	void	pingAndCheckTimeout();
	bool	WasUserAccepted() const;	/* jer added 980814.0850 so we
					 * can see what they were doing when
					 * they left.  (This way, if someone
					 *  not-yet-successfully-logged-in
					 *  leaves, no "has left" message
					 *  is printed.)
					 * When user is in STATE_GET_LINE, return 1.
					 * Otherwise, return whether user was in
					 * STATE_GET_LINE when they left.
					 */
	bool state_is_load_prefs() const;
	bool CheckForMissedDiscon ();

	const char *loginTimeStr() const;
	const char *loginFrom() const;	/* dns address logged in from; don't
					 * modify the value returned.
					 * This will be a string no longer
					 * than UT_HOSTSIZE in a utmp struct.
					 * Note that this may be larger than
					 * you'd think... Linux defines it
					 * as 256!
					 */
	const char *loginFrom16() const;
					/* Return the logged-in-from
					 * dns name, truncating if necessary
					 * to 16 chars (for !who list).
					 * jer added 980914.1015
					 */

	void talkingTo (int unum, bool stayte);
					/* for chatroom where the list of
					 * logged-in users is an indexed
					 * list.. STATE STATE STATE this
					 * is a temporary hack til we get
					 * ChatGroup objects together.
					 * (jer 19990113.1412)
					 */

	bool talkingTo(int unum) const;
	void talkingFrom (int unum, bool stayte);
	bool talkingFrom(int unum) const;

	/** Given the name of a command (without the initial '!' that marks it
	 *  as a command), return the parameters and one-line description.  If
	 *  the command isn't valid, return NULL for both of these.
	 * (jer 19990212.0830)
	 */
	void getHelpText (const char *cmdname,
			  const char **cmdparams, const char **cmddesc) const;
	void getHelpText (const char *cmdname, bool complete_partial,
			  const char **cmdparams, const char **cmddesc) const;

	int	getIgnoringMask() const; //19990606.0220 (removed
				   // isIgnoringBeeps of 19990318.0345)

	bool	WasUserManualDiscon() const; /* jer added 19990522.1515 so we
					 * can see if a user typed !exit, or if
					 * they were disconnected.
					 */
	void loadPrefs();
	int  savePrefs(); // 19991116.0200 dschin

	void lock_me();

	void setAutoMsgchk(int minutes);   // jer 20000508.2030

	void setAutoAway  (int minutes);   // jer 20000815.1530

	void printLoginDetails (SLForumUser *uto) const;  // jer 20010807.0005
	void writeIdleBuf (char idlebuf[20], time_t now, int *u_idletime=NULL) const; // jer 20010807.0005

#ifdef ALLOW_MACROS
	int	tryExpandMacro(char mc, bool toplevel);  // jer 20001006.0010
	LinkedListLink* tryFindMacro
	    (char mc, LinkedListLink **LLprev=NULL) const;
	bool	tryAddMacro(macro_t *newmac);
	bool	tryDelMacro(char mc);
	int	macro_really_expand (const macro_t *mac);
	void	macro_append_line();
	void	listCurrentMacros();
	void	clearCurrentMacros();
	void	writeMacroToFile (FILE *f, macro_t *mac);
	macro_t *readMacroFromFile (FILE *f);
#endif  /* def(ALLOW_MACROS) */

	time_t	lastping;	/* still there?: jer added 19990702.1900 */

				/* for ignore fields; 19990606.0220 */
	static const int MSG_PUBLIC =   0x1;	// a public message
	static const int MSG_GUEST  =   0x2;	// from a guest
	static const int MSG_BEEP   =   0x4;	// beeping message
	static const int MSG_SYS    =   0x8;	// system message
	static const int MSG_MOVES  =  0x10;	// people arriving/leaving/away
	static const int MSG_BANNER =  0x20;	// it's a banner
	static const int MSG_ROLL   =  0x40;	// from c_roll command
	static const int MSG_P_BEEP =  0x80;	// private beep; c_beep command
	static const int MSG_HCHIME = 0x100;	// hourly chime
	static const int MSG_NOLOG  = 0x200;	// don't log this public line
	static const int MSG_EMOTE  = 0x400;    // emotes (!me)
	static const int MSG_BANNERED = 0x800;  // "banner from %s: %s"
	static const int MSG_KICK   = 0x1000;   // "%s kicks %s"
	static const int MSG_8BALL  = 0x2000;   // "the 8ball has advised..."
	static const int MSG_IDLESOON = 0x4000; // "you will be away idle soon"

	//Timer!  Timer consists of two arguments - number of minutes
        //and an optional message to go with it.
        //Added by MaTTeD 20000126.1815
	// note: if IsTimerSet == false but timer_expire_hour != -1,
	// there _was_ a timer set; timer_expire_hour and _min show
	// when it was set for.  This lets us see the previous timer
	// if we missed it going off.
	// Note: Timers internally use the server's local time;
	//  input and output are converted from and to the client's
	//  local (jetlag) time.
	int		timer_expire_hour, timer_expire_min;
	char		timer_message[USERAWAYSIZ];
	bool		IsTimerSet;
	bool		IsTimerPublic;	// jer added 20000217.0343

protected:

	void 	ClearEntryLine();	// just clear last line on screen
	void 	RedisplayEntryLine();	// also on screen
	void 	ClearBuffer();		// _this_ one clears the buffer content
	void	VisuallyClearBuffer();	// clear buf and show onscreen w/ ^H's


private:

	int	HandleNoConnection();
	int	HandleSendHello();
	int	HandleGetName();
	int	HandleGetPw();
	int	HandleFindDupl();
	int	HandleGetLine();
	int	HandleLockedLine();
	int	HandleAdminLine();
	int	HandleTelCmd();
	int	HandleTelOpt();
	int	negotiate_telopt_binary(int);

	int	slfgetch(char *ptrc);

	void	kick(const char *reason);
	void	rndCommand();	/* j added 980701.1640 */
	int	find_cmd_index(const char *buff, bool complete_partial) const;
		/* j added 19990126.2100 */

	void	tryPrintLoginBanner();

	char*	validateAndDowncaseName(char *buf);  /* j added 980805.2045 */
	/* _sendOut outputs a message from the user,
	 * to the whole room or privately (whispering).
	 * If to_bits == 0, _sendOut broadcasts,
	 * else it sends to the msgsTo user.
	 * j added 980701.1500 
	 */
	void	_sendOut(const char *sendthis, int *tabmask=NULL);  

	void	entry_extract_partial (char *partial, int &psize) const;
	void	talkToMeToo (SLForumUser *newguy);
	void	sendMssagesToNone ();
	int	idx_in_room () const;
	void    resetFrom ();     // 19990224.2315 dschin

	void	try_unlock_me();
	slf_tristate user_can_lock;  // not everyone may have permission to.

	bool	show_input;

	struct	utmp uentry;  /* jer added 980524.2345 */
	time_t	loginat;		/* login time: jer added 980702.0745 */
	time_t	lastkey;		/* idle: jer added 980702.0745 */
	bool	all_keys_reset_idle; /* if false, only public msgs reset idle;
				      * this is a flag used by the engine, not
				      * a user preference.
				      * (pref.wants_true_idle is the user pref)
				      */

public:   /* for SLFRoom to get at */

	prefs_t pref;    /* all user prefs in one place */

	time_t	auto_msgchk_next;	/* check next at this time */
	time_t	auto_msgchk_lastnew;	/* most recent new mail was here */
	bool	auto_msgchk_lastnew_is_valid;   /* if false, no new yet. */
	/* related prefs: auto_away_seconds, auto_msgchk_minutes */

	/** Write time to a buffer, taking heed of this user's timezone offset.
	 *  "incldate" 0=time; 1=time,date; 2=time,date,year.
	 */
	void writeTime
	    (char *buf, size_t bufsize, time_t whicht, int incldate=0)
	    const;

	/** Announce an hourly, daily, or yearly chime.
	 *  Contents of 'ptime' used when pref.jetlag == 0.
	 *  Otherwise, 'now' is used to recalc after timezone offset,
	 *  and ptime contents are ignored, ptime structure is used
	 *  as a temporary workspace instead.
	 */
	void hourlyChime
	    (time_t now, const struct tm *ptime, int msgdescflags);

	char* expandTimeTokens
		(time_t now, const char *srcp, const char *firstToken);

private:  /* back to normal */

	char _expandBuf[USERBUFSIZ];   /* for writeTime expand */

	char ctstr[13];		/* login time string: jer added 980702.1800 */
	char longfrom[LONGFROMSIZ];     /* jer added 20000514.0730 */
	char dnsfrom[DNSFROMSIZ];	/* jer added 980914.1015, since the
				 * usual method of getting DNS is too big for
				 * !who list format.
				 */

	struct in_addr loginfromip;  /* jer 20010807.1355 */

	enum State
	{
		STATE_NO_CONNECTION,	/* When we go to this one, store the
					 * previous state in substate field,
					 * so we know what they were doing
					 * when they left.  -- jer 980814.0850
					 * (see WasUserAccepted() method)
					 */
		STATE_SETUP_CONN,	/* This state is to avoid races;
					 * it's set as soon as possible
					 * in HandleNoConnection().
					 * That method does connection init,
					 * then sets state to STATE_GET_NAME
					 * or STATE_FIND_DUPL.
					 */
		STATE_GET_NAME,
		STATE_JER_GETPW,
		STATE_FIND_DUPL,
		STATE_LOAD_PREFS,       /* Used when loading prefs from the
					 * file (if a user has one); this
					 * prevents error message display if
					 * a file's pref is not defined now
					 * (compile flags different, etc).
					 */
		STATE_GET_LINE,		/* This is the only state which is
					 * considered 'accepted': the user is in
					 * the chatroom and typing (or may be !away).
					 */
		STATE_GUEST_CONFIRM,
		STATE_TEL_CMD, /* got IAC, waiting for WILL/WONT/etc (rfc854)*/
		STATE_TEL_OPT  /* got one o' those, waiting for opt char */
	} state;
	State prevstate;		/* mostly for logout; jer 981005.1015 */
	time_t substate;		/* for data within state; jer 980809.1020.  moved from 'int' to 'time_t' to support wider data (ctrl-d time) */
	bool   substate_tracks_dblchar; /* Are we using substate to see when
					 * they last hit a key, to track
					 * hitting that key twice?
					 * If so, state_d_char is the key, and
					 * hitting any other key when substate_
					 * tracks_dblchar=true will clear
					 * substate_tracks_dblchar.
					 * jdm 20020803.0030
					 */
	admin_substate_t admin_substate; /* admin substate of STATE_GET_LINE */
	admin_substate_t admin_prev_substate;
	
	/** statedata use:
	 *
	 *  STATE_NO_CONNECTION: not used
	 *  STATE_GET_NAME:      not used
	 *  STATE_JER_GETPW:     not used
	 *
	 *  STATE_FIND_DUPL: holds SLForumUser* to more-idle self
	 *	which user is being prompted to kick or not
	 *
	 *  STATE_GET_LINE:
	 *	ss_nonadmin: not used
	 *	ss_mainmenu:?
	 *	ss_list:?
	 *	ss_addeditgetname:?
	 *	ss_addedit:?
	 *	ss_del:?
	 *	ss_pwch_getoldpw:?
	 *	ss_pwch_getnewpw:?
	 *	ss_pwch_confnewpw:?
	 *	ss_pwch_setnewpw: ?
	 *
	 *	ss_macro_addedit_readkey, _checkkey: a char cast to *
	 *	ss_macro_addedit_askreplace: * to the old macro struct
	 *	ss_macro_delete: * to old macro struct
	 *	    (and statedat2 is * to LLprev or NULL if first in list)
	 *
	 *  STATE_TEL_CMD, STATE_TEL_OPT:
	 *	these retain statedata from prev. states
	 *	for use when returning to that state after
	 *	handling the telnet command.
	 */
	void *statedata;	/* for data within state; jer 980809.1020 */
	void *statedat2;	/* so far only within ss_macro_addedit_* */
	char  state_d_char;     /* for data within state that's 1 char; we were
				 * overloading the statedata ptr with a char,
				 * bad kludge.  jer 20020728.0925
				 */
	time_t  waittil;	/* for bad-pw enforced wait; jer 981005.1025 */
	userfield_t to_bits;	/* for multiple !to; jer 19990113.1412 */
	userfield_t from_bits;	/* for multiple !to: jer 19990113.1412 */
	enum	n_talk_to_t {	/* for multiple !to: jer 19990622.1950 */
	  TALKING_TO_ALL,
	  TALKING_TO_ONE,
	  TALKING_TO_MANY
	} n_talking_to;

	int	talktomany_fieldwid;  /* 19990623.1830 */

	int     blanklines;	/* for max_blank_lines: 19990606.0130 */
	bool	cmd_char_disabled;  /* ctrl-o toggles (for pasting stuff) */

	/* related prefs: cmd_char, cmd_char_alt */

	bool	is_locked;	/* locked-out display mode */

	bool	reflow_mode;    /* reflow mode: jer 20010917.2321 */
	bool    lastkey_was_cr; /* for reflow mode (need to track \n\n) */

	/** Read in one char, and place it in the input buffer (if it's
	 *  an input char) or act properly if it's a ctrl char.
	 */
	int	ManageInputBuffer();

	/** used by ManageInputBuffer: types this char into the buffer. */
	int	ManageInputNewChar(char c, bool handle_telnet_iac=true);

	/** used by ManageInputNewChar: try to handle this control char. */
	bool	ManageInputNewCtrlChar
		(char c, int &retval, bool admuserescape);

	/** used by HandleGetLine, and by
	 *  the handler for the admin substate,
	 *  since there's an escape from that substate
	 *  back to 'standard' STATE_GET_LINE.
	 */
	int	ManageChangedInputBuffer
	  (int manageinputbuffer_return_value);

	/* Handles displaying a full line or executing a command, and line
	 * wrap if needed.  (From ManageChangedInputBuffer)
	 */
	void	ManageFullLine (int ret);

	/* Handles user returning from being away.  (From
	 * ManageChangedInputBuffer)
	 */
	void	ManageUserReturn (int ret);

	int	HandleUserCommand(bool was_wrapped);
	void	resetFieldsToDiscon(bool manualDiscon=false);
	void	acceptAndWelcomeUser();
	void	setState(State newstate);	// jer added 981005.1015
	void	setState(State newstate, bool keepSubstate);	// jer added 981006.1350
public:                        // TODO TODO TODO better way?
	void	setAdmSubstate
		(admin_substate_t subst, bool justSetState=false);

	admin_substate_t getAdmSubstate() const;
private:

	/** Set the prompt string; don't directly set the
	 *  contents of the prompt[] buffer, please... doing
	 *  it this way saves the need to strlen() every time
	 *  we word-wrap (which means every input char).
	 */
	void	setPrompt (const char *newprompt);

	/** Set the admin prompt to newprompt, and redisplay input line with
	 *  that prompt.  Call setAdmPrompt(NULL) to use the contents of
	 *  the user's non-admin slforumuser::prompt buffer.  The user's 
	 *  non-admin buffer will also be displayed if buf[0] == cmd_char.
	 *
	 *  Note that as a sideeffect, calling setAdmPrompt clears the buffer
	 *  before changing the prompt.
	 */
	void	setAdmPrompt (const char *newprompt, bool clearbuffer=true);
	void	setPromptFromTo (bool always_do=false);
public:
	int	getHint(int setbitfield) const;
	void	setHint(int setbitfield);

	/** Manipulate the listening-to list */ // 19990215.1900
	void	listenListReset();
	void	listenListAdd (const char *word);
	/** does the line contain anything in the list?
	 *  returns a pointer to the occurence, or NULL if none.
	 *  Don't use this unless you have to, it's not too efficient,
	 *  because ignore-public is not a common case.
	 *  endmatch, on return, points to the char after the matching word.
	 */
	char*	listenListMatch (const char *line, char **endmatch) const;

private:
	/* set up listen list, name, load prefs, and check for admin...
	 * Just as things about entering the room go in AcceptAndWelcomeUser,
	 * things about logging in a real user go here.
	 */
	void	setupRealUserInfo (struct slf_userinfo *ppw);  // 20000201.1230


	// set up whisperban list	(20000610.1100)
	void	wbListReset();
	void	wbListAdd (const char *nam);
	bool	wbListMatch (const char *nam) const;


	// for ManageInputBuffer... takes a tab and makes it the right # of spaces.
	void expand_one_tab();
	// and goes back that # spaces ;-)
	void back_one_tab();

	// for max lines/sec brake.
	bool checkMaxLinesBrake();

	void pasteIntoBuffer
	    (const char *paste, int paste_strlen, int paste_displen,
	     bool paste_macro_input, int* paste_tabmask=NULL);

	void awaitTelCmd();


	/////////////////////////////////////////////////////////////
	// private fields
	//
	bufinfo buf;

	bufinfo yankbuf; /* for ctrl-k/ctrl-y;
			  * jer added 19990811.1545 
			  * If yankbuf.macro_input is diff. then
			  * buf.macro_input while pasting yankbuffer, need to
			  * recalc yank_cur_len.
			  */

	bufinfo lastbuf;   // jer 20000321.1400 - last public line sent (^P)

	char 	name[USERNAMESIZ];
	char	prompt[USERPROMPTSIZ];  // to left of buffer
	int	promptlen;	/* jer added 980709.1715 */
	char	away[USERAWAYSIZ];  /* starts with the : or . after "is away";
				       jer added 980525.2045 */
	char	admprompt[USERBUFSIZ];	/* admin substate prompt */
	int	admpromptlen, realpromptlen;

	bool	is_away;	/* jer added 980525.2045 */
	time_t	away_at;	/* when went away -- dschin 19990922.1145 */
	bool	is_guest;	/* j added 980703.2330 */
	bool	is_admin;
	bool	is_virt;
	bool	running_cmd;	/* Are we running a command?  If so,
				 * nobody should mess with our input line,
				 * since we're doing that already.
				 * - j added 19990622.2050
				 */
	bool	running_macro;	/* Are we expanding a macro?  If so,
				 * our input isn't coming from the user
				 * over telnet, it's coming from the macro
				 * value buffer, and thus we shouldn't
				 * look for telnet commands.
				 * - j added 20010418.0925
				 */

  	struct 	sockaddr_in user_address;	
	int	user_fd;

	SLForumRoom *room;
	int	my_idx_in_room;	/* where in the room's user array are we?
				 * jer 19990113.1412 */

	int	hintbits;	/* bitfield for annoying help messages
				 * sent to user first time a feature is
				 * used, and not sent afterwards (like
				 * "type !to all" to end private message)
				 * -- not sent if corresp. field is 1.
				 * jer added 980709.1700 */

	char	lastmfrom [USERNAMESIZ];
				/* who was the last message from?
				 * used for 19990215.0730 */
	char	lastmto   [USERNAMESIZ];
				/* who was the last message to?
				 * used for 19990616.1500 */

	int	lastm_rpt;	/* repeated number of lines from same user
				 * without an attribution @ left */

	/** for max lines per sec brake */

	int	lines_per_sec [MAX_LINES_SECS_FOR_AVG];  /* 19990922.2100 */
	int	lines_idx;	// curr. index into that array (last time we used it)
	time_t	lastline;  // 'current' time; last time we wrote a line
	int	lines_sum;  // sum of lines_per_sec array

	// for macro_input
	char	macroNameBuf [6];

	char	keystringChar(const char* keyster) const;
	const char* keyDisplayString (char mc, bool spaces_too=false);
	size_t	keyDisplayLength (char mc, bool spaces_too=false);
	size_t	stringDisplayLength
	    (const char *txt, int *strlenvar=NULL, bool spaces_too=false);

#ifdef ALLOW_MACROS

	// for macros
	/* related pref: macrolist */
	char macrostack [MAX_MACRO_CALL_LEVEL];
	int macro_next_level;   /* 1 to MAX_MACRO_CALL_LEVEL during
				 * macro execution, 0 when not running
				 * a macro.
				 */
#endif  /* def(ALLOW_MACROS) */


        static const char *welcome;	
	static const char *commands;
	static const char *rndCommands[];  /* jer added 980701.1640 */
	static const int NUM_RND_COMMANDS;
	static const int CONNECT_TIMEOUT;

 public:
	static const int HINT_TO_ALL = 0x1;  // type "!to all" to end priv msg
	static const int HINT_DID_TO_ALL = 0x2; //further msgs will sent to all
	static const int HINT_ACCEPT = 0x4;  // type "!accept" to...
	static const int HINT_KICK = 0x8;  // type "!kick" to...
	static const int HINT_AWAY = 0x10;  // you're marked as away...
	static const int HINT_PREVTOPIC = 0x20;  // type !prevtopic n to get...
	static const int HINT_SHOWAWAY  = 0x40;// type !showaway to list away..
	static const int HINT_HCHIME  = 0x80;   // type !ignore chime ...
	static const int HINT_CCDISAB = 0x100;   // cmd char disabled, hit...
	static const int HINT_IGN_BANNERED = 0x200; //type !ignore bannered ...
	static const int HINT_MACRO_CTRLQ = 0x400;  // precede ctrls with ^Q...
	static const int HINT_IGN_KICK = 0x800;  // type !ignore kicks...
	static const int HINT_REFLOW = 0x1000;  // two blank lines to...
	static const int HINT_IGN_8BALL = 0x2000;  // type !ignore 8ball...
	static const int HINT_IDLESOON_HITKEY = 0x4000;  // hit key to clear
	static const int HINT_CHG_TOPIC_ANYTIME = 0x8000; // you can change...
	static const int HINT_ADMIN_TOPICS_ON = 0x10000;  // ..!admin topics on

	static const int c_toall_idx;
	static const slf_cmd_desc usercmds[];
	static const admin_input_substate_behav_t admInputSSBehavTable[];

	// for debug -- jer added 20001001
	void debug_printSlotStatus () const;
};

#endif	// __SLFORUMUSER_H
