/*
    slforum version 1.x - Simple chat room usable via telnet.
    Copyright (C) 1999-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 __SLFORUMROOM_H
#define	__SLFORUMROOM_H

#include <netinet/tcp.h>  // for SOL_TCP, TCP_NODELAY
#include "slforumuser.h"  // for USERLASTFROMSIZ
#include "linkedlist.h"   // for prevtopics list
//#/i/n/c/lude "slfprefs.h"	  // for USE_RECAP, etc

extern const char *slfhomedir;  // see slforumroom.cc



typedef struct topicinfo {
  char    tname  [USERBUFSIZ];  // Can't be longer than length of away msg
  char    tsetter[USERNAMESIZ]; // The name of person who set topic; check
                                // tsetter[0] == '\0' to indicate no topic set
                                // (empty prevtopic array member, etc).
  time_t  ttime;          // the time the topic was set
  LinkedList *tcomments;  // TopicComments, max. one per user
} TopicInfo;


#define TOPICCOMMENTSIZ  (USERAWAYSIZ+12)

typedef struct topiccomment {
  char  setby  [USERNAMESIZ];     // username who set it
  char  comment[TOPICCOMMENTSIZ];  // formatted as "user: message" (%9s: %s)
} TopicComment;


class SLForumRoom
{
public:
	friend class SLForumCmd;

	SLForumRoom( int port, FILE *log_file_=0  );
	~SLForumRoom();

	void	BindSocket();
	
	int	Make_fdset( fd_set *r, fd_set *w, fd_set *e );	
	int	ManageRoom( fd_set *r, fd_set *w, fd_set *e );
	void	FindSlotForNewUser();
	void	LookForTimeouts();  /* j added 980703.2345 */

	void	BroadCast( const char *from, const char *c,
			   int msgdescflags );
	void	Announce( const char *c );
	void	Announce( int msgdescflags, const char *c,
			  bool wants_beep=false );
	void	Announce( const char *c, int hintsetmask, bool wants_beep );
	void	AnnounceTimestamp
			( time_t now, int msgdescflags, bool wants_beep,
			  const char *reason=NULL);


	bool	ListUsers( SLForumUser *u, bool exclude_idleones=false );
	bool	ListAways( SLForumUser *u, bool with_header=false );
	bool	ListPubTimers( SLForumUser *to );

	/* remember to add \n to the end of your logprintfs... */
	void	logprintf (const char *fmt, ...) const;
	void	setLogStdoutToo(bool doit);

	bool log_stdout_too;

	int	GetServerFD()   const;
	int	getServerPort() const;
	const struct	sockaddr_in *GetServerAddress() const;

	const char* getLastPublicMsg() const;

	gid_t getUsersGid() const;  /* no setter: setting adminGid sets both */
	gid_t getAdminGid() const;
	void  setAdminGid(gid_t);

	void room_cleanup();

	void printRoomUptime(SLForumUser *u_to);

	SLForumUser *GetUserByName (const char *nam) const;
	/** jer added 980805.2150 for the duplicate-login search */
	/** and modded 981207.0015 for allow partial matches */
	SLForumUser *GetUserIncremental
	  (const char *nam, int &startidx, bool allowPartial,
	   const SLForumUser *exceptfor = NULL) const;

	/** jer added these two 19990113.1412 for send-to-multiple support.
	 *  If the user isn't logged in, returns NULL.
	 *  (uses same condition as IsUserAround() to determine this,
	 *   just like GetUserByName does.)
	 */
	SLForumUser *GetUserByIndex (int idx, bool evenIfNotAccepted=false)
		const;
	int IndexOf (SLForumUser *u) const;

	void setAutoAccept (bool should_auto_accept_guests);
	bool getAutoAccept () const;

	void setPrintGuestHowtoMsg (bool should_print_guest_howto_message);
	bool getPrintGuestHowtoMsg () const;


	// jer added banner functions 19990212.1630

	/** does a banner, or prints a message that can't yet,
	 *  and returns 0 on success, 1 on not-time-yet, or -1
	 *  (and sets errno) on fail from sys error.
	 */
	int	doBanner(SLForumUser *from);

	/** rets banner dir or NULL if none */
	const char* getBannersDir () const;

	/** rets 0 on success, or -1 (and sets errno) on fail */
	int	setBannersDir (const char *d);

	/** set the required minimum time between two !banner commands;
	 *  can't fail, but will ignore negative values. */
	void	setBannersMinpause (time_t pausetime);

	/** returns number of seconds til can !banner again */
	time_t	getBannersRemainingPause () const;

	/** topic stuff */
	bool	topicsAreOn() const;
	void	setTopicsOnOff (bool turnOn);

	/** This either allows the user to reset the topic, or tells them how
	 *  much time (seconds) remains until they can set it.
	 *  If they are the user who set topic in the first place, always
	 *  returns 0 (they can always set topic).
	 */
	int	topicTimeCheck(const SLForumUser *caller=NULL) const;

	/** Lists the topic, and optionally all the comments to that topic. */
	void	listTopic(TopicInfo *t, SLForumUser *u, bool showCommentsToo,
			  int prevtopnum=0);
	void	setTopic (const char *topicstr, SLForumUser *setBy);
	const char *getTopic()  const;
	const char *getTopicSetter() const;


	// once-per-minute check for user timer expiration
        void LookForTimers();

        // once-per-hour function to evaluate whether or not it is time for
        // an hourly reminder.  Added by MaTTeD 20000129.1245
        void CheckHourChime(); 

	// for SIGHUP handler's use
	void SetLogFile (FILE *newlog);
	

private:
	int	port;
	int 	server_fd;
  	struct 	sockaddr_in server_address;
	
	SLForumUser *user[MAX_CHAT_USERS];
	FILE    *log_file;

	bool	auto_accept_guests;
	bool	print_guest_howto_message;

	gid_t   admin_gid, users_gid;


	void	_BroadCast( const char *from, const char *c,
			   int msgdescflags,
			   bool wants_beep, int hintbitset );

	// jer added these 19990212.1630; see constructor for defaults.
	const char *bannersdir;
	const char **bannerflist;
	char	*bannerflbuf;
	time_t	lastbanner, bdir_mtime;  // for detecting new banners
	int	nbanners;
	time_t	bannerpause;
	time_t	lastRndShuffle;
	int	lastbanneridx;

	time_t  lockoutfile_mtime, lockoutfile_chktime;
	bool    lockoutfile_chktime_is_valid;

	char	virtuserdbfname[PATH_MAX];

	time_t  room_startup_time;

 public:
	bool    who_sort_is_valid;

	slf_tristate	checkLockUserFile (const SLForumUser *u);

	/** tracking for max guest / max remote logins: # of unoccupied slots
	 */
	int empty_slots;

	// 20060601.0700
	void	UpdateUserCount (const SLForumUser *u, bool do_recalc);

 private:
	int	who_unidx[MAX_CHAT_USERS];  // sorted list for !who.

	// for keeping track of who's sent the last line to the
	// room, to suppress initial blank lines. (19990615.1800)
	char	lastpublicfrom[USERNAMESIZ];

	// last public message line (for ^N keyboard shortcut)
	char	lastpublicmsg [USERBUFSIZ];


	// for ::ListUsers, ::UpdateUserCount, USE_USERCOUNT_FILE 20060601.0700
	int	user_count_total, user_count_here;  // idle == total - here
	int	user_count_idle_threshold;
	time_t	user_count_updated;
#if USE_USERCOUNT_FILE
	time_t	user_count_file_updated;
public:
	void	UpdateUserCountFile (time_t now);
private:
	char	user_count_file_fullpath [PATH_MAX];  // if "\0", don't use.
#endif
	// TODO accessors


	//MaTTeD adds 19990731.2245
	TopicInfo topic;

	// more topic... 19990813.2215 jer
	TopicInfo prevtopics[NUM_REMEMBERED_TOPICS];

	bool room_topics_on;  // 20020404.1224 jer

	// Last time the topic was set by a person different from the
	// one who set the previous topic (hijack prevention)
	time_t lastTopicSetterTime;  // 20020802.1830 jer


#if USE_RECAP

private:
	// Recap buffer... 20060528.1140 jer
	//   Used during user::acceptAndWelcomeUser.
	char	recap [RECAP_LINES][USERBUFSIZ];
	int	recap_desc [RECAP_LINES];	// msgdescflags
	time_t	recap_time [RECAP_LINES];	// for embedded timestamps
	char	recap_timetype [RECAP_LINES];   // indicate embedded timestamps
	int	recap_last;	// most recent index; special -1 if empty
	bool	recap_wrapped;	// false if the room has recently started
	time_t	recap_lasttime;  // time of most recent line of recap

	/** For recap_timetype field */
	static const char RECAP_TIMETYPE_NONE  = 0;  // no timestamp this line
	static const char RECAP_TIMETYPE_STAMP = 1;  // has \201 or \202
	static const char RECAP_TIMETYPE_CHIME = 2;  // hourly-daily chime

public:
	time_t	get_recap_lasttime() const;
	int	get_recap_lastindex() const;

	void	SendRecapToUser
	  (SLForumUser* u, time_t lasttime_was, int line_to_ignore =-1 );

#endif  /* USE_RECAP */


private:
	/** rebuild the list of available banner files.
	 *  Return 0 on success, or -1 (and sets errno) on fail.
	 */
	int	refreshBannersList ();


	//For comments to topics.  Most of these commands in slforumroom
        //since they deal with everyone, not the individual user.
        // Added by MaTTeD 19990731.2245 and jer 19990814,19990815.1830

	/** Add/change the comment on a topic from a certain user;
	 *  returns the formatted comment string, suitable for
	 *  announcing to the room: " user: comment"
	 */
        const char *topicAddComment (const char *comment, SLForumUser *setby);


	/** When the topic is changed, all the comments are cleared out. */
	LinkedList *resetComments();

	/** sort the list used by !who, !here, !listaways, !alarm/!timer */
	void sort_unidx(bool by_alarm_time=false);

 public:
	// for debug -- jer added 20001001
	void debug_PrintSlots () const;
};

#endif	// __SLFORUMROOM_H

