// Filename:	menu.C
// Contents:	the methods for the menu object
// Author:	Greg Shaw
// Created:	7/22/93

/*
This file 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, or (at your option) any
later version.

In addition to the permissions in the GNU General Public License, the
Free Software Foundation gives you unlimited permission to link the
compiled version of this file with other programs, and to distribute
those programs without any restriction coming from the use of this
file.  (The General Public License restrictions do apply in other
respects; for example, they cover modification of the file, and
distribution when not linked into another program.)

This file 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; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#ifndef _MENU_C_
#define _MENU_C_

#include "bbshdr.h"

// Function:	can_view
// Purpose:	return true if the user has access based on the information
//				passed in
// Input:	acl - the access level of the user
//			flags - the flags of the user
//			am - the access level modifier for the user's acccess level
//			fm - the flags modifier
// Output:	true if user has access
// Author:	Greg Shaw
// Created:	7/27/93

int Menu::can_view(int acl, int flags, char am, char fm)
{
	int	uacl;			// user's acl
	int uflags;			// user's flags

	uacl = user.u_acl();
	uflags = user.u_flags();
	if (!((uacl >= acl && am == '>' ) || (uacl < acl && am == '<') ||
		(acl == uacl && am == '=')))
			return(0);		// no access
	if (flags != 0)
	{
		if ((flags & uflags) && fm == '=')
			return(1);	
		if (!(flags & uflags) && fm == '!')
			return(1);
		return(0);
	}
	return(1);		// access
};

// Function:	get_chat_msg
// Purpose:	get a chat message from the chat object (if connected)
// Input:	none
// Output:	chat message is read into internal space
// Author:	Greg Shaw
// Created:	7/22/93

int Menu::get_chat_msg(void)
{
	int x;
	if (x = msg_avail(0), x <0)	// message avail?
		connected = 0;
	else if (x > 0 && receive(chat_msg) == -1)
			connected = 0;
	return(0);
};

// Function:	constructor
// Purpose:	initialize values to sane states
// Input:	none
// Output:	(internal object changes)
// Author:	Greg Shaw
// Created:	7/22/93

Menu::Menu()
{
	prompt = userprompt();
	menu_path[0] = 0;	// erase menu path
	numitems = 0;	// no items yet
	connected = 0;	// not connected
	socknum = 0;	// no socket yet
	last_try = 0L;	// haven't tried yet
	chat_msg[0] = 0;// no chat message yet
};

// Function:	open
// Purpose:	read the commands for the menu from the menu file
// Input:	path - the path to the menu file
// Output:	the menu is read into the object or a non-zero result is returned
// Author:	Greg Shaw
// Created:	7/22/93

int Menu::open(char *path)
{
	FILE	*infile;	// input file
	char	tmpstr[255];	// temporary string
	char	state;		// state of input
	char	num;		// offset into word
	char	comnum;		// command number in array
	char	c;			// input char

//	open_chat();		// attempt to connect to chat server
	if (strcpy(tmpstr,getenv("BBSDIR")), tmpstr == NULL)
	{
		sprintf(tmpstr,"BBSDIR env var not set for %s",username());
		ap_log(tmpstr);
		return(-1);
	}
	strcat(tmpstr,"/menus/");	// add slash
	strcat(tmpstr,path);	// add path
	if (infile = bopen(tmpstr,"r"), infile == NULL)
	{
		sprintf(tmpstr,"Unable to open menu file %s",path);
		ap_log(tmpstr);
		return(-1);
	}
	comnum = 0;		// start at 0
	while (!feof(infile))
	{
		state = 0;
		num = 0;
		c = 0;
		while (c != '\r' && c != '\n' && !feof(infile))
		{
			c = fgetc(infile);
			if (c != '|' && c != '\r' && c != '\n')
				tmpstr[num++] = c;	// add char
			else
			{
				tmpstr[num] = 0;		// add null
				state++;
				switch(state)
				{
					case 1:	// command number
						if (sscanf(tmpstr,"%d",&items[comnum].com_type) != 1 )
							continue;	// we've hit end of menu file
						break;
					case 2: // hot key
						if (num != 0)
						{
							items[comnum].key = tmpstr[0];
						}
						else
							items[comnum].key = 0;
						break;
					case 3:	// access level
						if (num != 0)	// empty?
						{
							if (sscanf(tmpstr,"%d",&items[comnum].acl) != 1)
							{
								sprintf(tmpstr,"menu.acl: %s has an error.",path);
								ap_log(tmpstr);
								bclose(infile);
								return(-1);
							}
						}
						else
							items[comnum].acl = 0;
						break;
					case 4:	// access level modifier
						if (num != 0)	// modifier not null?
						{
							if (tmpstr[0] != '>' && tmpstr[0] != '=' &
								tmpstr[0] != '<')
							{
								sprintf(tmpstr,"menu.aclmo: %s has an error.",path);
								ap_log(tmpstr);
								bclose(infile);
								return(-1);
							}
							else 
								items[comnum].acl_mod = tmpstr[0];
						}
						else
							items[comnum].acl_mod = '>';	// default to 
						break;
					case 5:	// flags
						if (num != 0)	// flags not null?
						{
							if (sscanf(tmpstr,"%d",&items[comnum].flags) != 1)
							{
								sprintf(tmpstr,"menu.flags: %s has an error.",path);
								ap_log(tmpstr);
								bclose(infile);
								return(-1);
							}
						}
						else
							items[comnum].flags = 0;	// no flags is default
						break;
					case 6:	// flags mod
						if (num != 0)	// modifier not null?
						{
							if (tmpstr[0] != '=' && tmpstr[0] != '!')
							{
								sprintf(tmpstr,"menu.flagsmod: %s has an error.",path);
								ap_log(tmpstr);
								bclose(infile);
								return(-1);
							}
							else 
								items[comnum].flags_mod = tmpstr[0];
						}
						else
							items[comnum].flags_mod = '=';	// default to =
						break;
					case 7:	// misc text
						strcpy(items[comnum].misc,tmpstr);
						break;
					case 8:	// command text - skip to end of line
						if (items[comnum].com_type != 0)	// don't care about text out commands
							comnum++;
						if (comnum > 49)	// at the end?
						{
							ap_log("menu: menu has too many items (> 50)");
							bclose(infile);
							return(-1);
						}
						while (c != '\r' && c != '\n' && !feof(infile))
							c = fgetc(infile);
						break;
					default: 	// error
						sprintf(tmpstr,"Invalid switch %d in menu open.",state);
						ap_log(tmpstr);
				}
				num = 0;	// start at next word
			}
		}
	}
	numitems = comnum;
	bclose(infile);
	return(0);
};

// Function:	open_chat
// Purpose:	attempt to open a connection to the chat process 
// Inputs:	none
// Outputs:	connected changes state if connection successful
// Author:	Greg Shaw
// Created:	7/23/93

int Menu::open_chat(void)
{
	time_t 	now;		// current time
	char	hname[30];	// this host's name (where chat runs)

	// attempt to connect to chat object
	if (!connected)
	{
		if (time(&now), now - last_try > 10)	// more than 10 secs?
		{
			if (gethostname(hname,29), hname == NULL)
			{
				ap_log("menu.open: Unable to get current host name");
			}
			else
			{
				if (two_connect(hname,CHAT_MASTER_PORT) == 0)
				{	// got new socket
					connected = 1;
				}
			}
		}
	}
	return(0);
};

// Function:	run
// Purpose:		run the menu
// Input:		(from user)
// Output:		the command that has been selected by the user is returned
// Author:		Greg Shaw
// Created:		7/25/93

Menuitem *Menu::run(char *path)
{
	char c,d;				// dumb char
	char line[255];		// line
	char valkeys[150];	// valid keys in menu
	char timeleft[50];	// amount of time left to user
	char offset;		// offset into string
	char numbracks;		// number of |'s found
	char boogie;		// get out of function -- guy has selected
	char tmpstr[255];	// stupid string
	FILE	*infile;	// menu file
	int  x;				// counter
	int	 charsfound;	// number of chars found after command info
	int	 acl;			// acl
	int	 flags;			// flags
	int	inactivity;		// how long to wait before inactivity logoff
	char am;			// acl mod
	char fm;			// flags mod
	long	 tl;		// timeleft
	time_t now;			// current time
	time_t rightnow;		// current time

	clear();			// clear screen if possible
//	if (connected)		// connect to chat obj
//		get_chat_msg();	// get chat message (if available)
//	if (strlen(chat_msg) != 0)	// message not null
//		sstrcr(chat_msg);		// send message
	if (strcpy(tmpstr,getenv("BBSDIR")), tmpstr == NULL)
	{
		sprintf(tmpstr,"BBSDIR env var not set for %s",username());
		ap_log(tmpstr);
		return(NULL);
	}
	strcat(tmpstr,"/menus/");	// add slash
	strcat(tmpstr,path);	// add path
	if (infile = bopen(tmpstr,"r"), infile == NULL)
	{
		sprintf(tmpstr,"Unable to open menu file %s",path);
		ap_log(tmpstr);
	}
	boogie = 0;
	while (!feof(infile) && !boogie)
	{
		numbracks = 0;
		offset = 0;	// offset into line
		while (numbracks < 7 && !feof(infile))	// nuke all of the command info
		{
			c = fgetc(infile);
			if (c != '|' && c != '\r' && c != '\n')
			{
				line[offset++] = c;
			}
			else	// got info.  process
			{
				line[offset] = 0;
				if (c == '|')
				{
					numbracks++;
					switch(numbracks)
					{
						case 1:	// command number
							continue;
							break;
						case 2: // hot key
						case 7: // misc text
							break;
						case 3:	// access level
							if (offset != 0)	// empty?
							{
								sscanf(line,"%d",&acl);
							}
							else
								acl = 0;
							break;
						case 4:	// access level modifier
							if (offset != 0)	// modifier not null?
							{
									am = line[0];
							}
							else
								am = '>';	// default to >
							break;
						case 5:	// flags
							if (offset != 0)	// flags not null?
							{
								sscanf(line,"%d",&flags);
							}
							else
								flags = 0;	// no flags is default
							break;
						case 6:	// flags mod
							if (offset != 0)	// modifier not null?
							{
									fm = line[0];
							}
							else
								fm = '=';	// default to =
							break;
						default: 	// error
							ap_log("Invalid switch in menu run.");
					}
				}
				offset = 0;
			}
		}  // ok, that's gone.  Now the meat is left.
		if (!can_view(acl, flags, am, fm))	// no access, skip.
		{
			while (c = fgetc(infile), c != '\r' && c != '\n' && !feof(infile));
			acl = 32767;	// avoid lockup if stray \r or \n at end of file
			continue;
		}
		charsfound = 0;
		offset=0;
		while (d = fgetc(infile), d != '\r' && d != '\n' && !feof(infile))
		{
			if (offset % 50 == 0 && offset > 0)
			{
				tmpstr[offset] = 0;
				sstr(tmpstr);
				offset = 0;
				tmpstr[offset++] = d;
			}
			else
				tmpstr[offset++] = d;
			charsfound++;
		}
		if (charsfound > 0)
		{
			tmpstr[offset] = 0;
			sstr(tmpstr);
			cr();
		}
		if (c = valid_char(), c != -1)		// check for char and return true
		{
			bclose(infile);
			if (c == 100)		// 100 means he hit return only (for screen refresh)
				return(NULL);
			else
				return(&items[c]);// if hot key for menu item
		}
	}
	bclose(infile);
	// ok, he didn't hit anything.  Give him a prompt.
	if (showcoms())		// show command keys?
	{		// build string with valid keys
		offset = 0;
		valkeys[offset++] = '[';
		for (x=0; x<numitems; x++)
			if (items[x].key != 0 && can_view(items[x].acl,
				items[x].flags, items[x].acl_mod, items[x].flags_mod))
			{
				valkeys[offset++] = items[x].key;
				valkeys[offset++] = ',';
			}
		valkeys[offset-1] = ']';	// add right bracket
		valkeys[offset++] = ' ';		// add trailing space
		valkeys[offset] = 0;		// add null
	}
	else
		valkeys[0] = 0;
	time(&now);
	// see how much time he has left
	tl = (user.u_timelimit()*60 - (now - user.user_logon()))/60; 
	// subtract time used prior to this logon 
	tl -= user.prevtimeused();	// subtract previous time used
	if (user.credituploads())
		tl += user.credit();	// add credited minutes
	if (tl <= 0)	// should he be gone?
	{
		items[0].com_type = -1;	// boot the user
		return(&items[0]);
	}
	if (showtime())	// show time left? 
	{
		if (tl == 1)
			strcpy(timeleft,MINUTESLEFT1);
		else if (tl < 1)
			strcpy(timeleft,MINUTESLEFT2);
		else
			sprintf(timeleft,MINUTESLEFT3,tl);
	}
	else
		timeleft[0] = 0;
	sstr(timeleft);
	if (prompt != NULL)
	{
		sstr(prompt);
	}
	sstr(valkeys);
	inactivity = inactivity_timeout();
	while (c = valid_char(), c == -1)		// check for char and return true
	{
		time(&rightnow);	// look for inactivity timeout
		if ((rightnow - now) /60 > inactivity)
		{
			sstrcr(INACTIVITYTIMEOUT);
			items[0].com_type = -1;	// boot the user
			ap_log(INACTIVITYTIMEOUT);
			return(&items[0]);
		}
	}
	cr();
	if (c == 100)		// 100 means he hit return only (for screen refresh)
		return(NULL);
	else
		return(&items[c]);				// if hot key for menu item
};

// Function:	valid_char
// Purpose:		look for a character from the user.  If available, check 
//				char against list, return array index into list if found.
// Input:		none
// Output:		the array index of the matching command record
// Author:		Greg Shaw

int Menu::valid_char(void)
{
	char	c;
	char	x;

	c  = 0;
	if (char_avail(0,0) || char_avail(1,1))
		c = gch(0);
	if (c > 0)	// error or zero for no char
	{
		for (x=0; x < numitems; x++)
		{
			if ( c == items[x].key && can_view(items[x].acl,
				items[x].flags, items[x].acl_mod, items[x].flags_mod))
				return(x);
			else if (c == '\r' || c == '\n')
				return(100);
		}
	}
	return(-1);
};

#endif // _MENU_C_
