/*
 *	Copyright (C) 1984, 1985  SRI International
 *	Copyright (C) 1989, 1992  TGV, Incorporated
 *
 *	Parse message list
 *
 */
#include <stdio.h>
#include "vax-mm.h"
#include <ctype.h>

static int Longer_Shorter_Common();

/*
 *	Header Selection Command List
 */
#define NCOMMANDS	60 		/* Size of command list  */
static struct Command_List {
	int	(*Routine)();		/* Routine to call	 */
	char	*Pattern;		/* Pattern string	 */
	unsigned int Time;		/* Time info		 */
	unsigned int KeyFlags;		/* KeyFlag bits		 */
	} Command_List[NCOMMANDS+1];
static struct Command_List Previous_Command_List[NCOMMANDS+1];
static int Command_Index;

/*
 *	Header Selection Range list
 */
#define INITIAL_RANGE_SIZE 32		/* Size of range list	 */
static struct Range_List {
	int	Start;			/* Start of range	*/
	int	End;			/* End of range		*/
	} *Range_List;
static struct Range_List *Previous_Range_List;
static int Range_Size = 0;
static int Previous_Inverse_Order;
static int Inverse_Order;
static int Thread_Base;


/*
 *	State for message selection and printing and dynamic strings
 */
#define STRING_AREA_SIZE	1024
static int Range_Index;
static int Next_Message_To_Check;
static int Current_Range_Start;
static int Current_Range_End;
static int Current_Range_Inverse_Order;
static char String_Area[STRING_AREA_SIZE];
static int String_Area_Size;
static char Previous_String_Area[STRING_AREA_SIZE];


/*
 *	Routines to handle keywords
 */
static int	All(),		Answered(),	Before(),	Cc_Me(),
		Current(),	Deleted(),	Flagged(),	From(),
		From_Me(),	Inverse(),	Keywords(),	Last(),
		Longer(),	New(),		On(),		Previous_Seq(),
		Recent(),	Seen(),		Shorter(),	Since(),
		Subject(),	Text(),		To(),		To_Me(),
		UnAnswered(),	UnDeleted(),	UnFlagged(),	UnKeywords(),
		UnSeen(),	Printing(), 	Sorted();

static void Extend_Range_List(void);


/*
 *	Message keyword table
 */
static struct {
		int current_entries;
		int maximum_entries;
		struct tbluk_keyword keywords[39];
		} Message =
{
	39,
	39,
/* 0 */{{COMND_ABBREVIATION|
	 COMND_INVISIBLE,	"a",		(int)&Message.keywords[2]},
/* 1 */ {COMND_INVISIBLE,	"after",	(int)Since},
/* 2 */ {0,			"all",		(int)All},
/* 3 */ {0,			"answered",	(int)Answered},
/* 4 */ {0,			"before",	(int)Before},
/* 5 */ {0,			"cc-me",	(int)Cc_Me},
/* 6 */ {0,			"current",	(int)Current},
/* 7 */ {0,			"deleted",	(int)Deleted},
/* 8 */ {COMND_ABBREVIATION|
	 COMND_INVISIBLE,	"f",		(int)&Message.keywords[12]},
/* 9 */ {0,			"flagged",	(int)Flagged},
/* 10*/ {COMND_ABBREVIATION|
	 COMND_INVISIBLE,	"fr",		(int)&Message.keywords[12]},
/* 11*/ {COMND_ABBREVIATION|
	 COMND_INVISIBLE,	"fro",		(int)&Message.keywords[12]},
/* 12*/ {0,			"from",		(int)From},
/* 13*/ {0,			"from-me",	(int)From_Me},
/* 14*/ {0,			"inverse",	(int)Inverse},
/* 15*/ {0,			"keywords",	(int)Keywords},
/* 16*/ {COMND_ABBREVIATION|
	 COMND_INVISIBLE,	"l",		(int)&Message.keywords[17]},
/* 17*/ {0,			"last",		(int)Last},
/* 18*/ {0,			"longer",	(int)Longer},
/* 19*/ {0,			"new",		(int)New},
/* 20*/ {0,			"on",		(int)On},
/* 21*/ {0,		"previous-sequence",	(int)Previous_Seq},
/* 22*/ {COMND_ABBREVIATION|
	 COMND_INVISIBLE,	"p",		(int)&Message.keywords[23]},
/* 23*/ {0,		"printing",		(int)Printing},
/* 24*/ {0,			"recent",	(int)Recent},
/* 25*/ {0,			"seen",		(int)Seen},
/* 26*/ {0,			"shorter",	(int)Shorter},
/* 27*/ {0,			"since",	(int)Since},
/* 28*/	{COMND_INVISIBLE,   	"sorted",   	(int)Sorted},
/* 29*/ {0,			"subject",	(int)Subject},
/* 30*/ {0,			"text",		(int)Text},
/* 31*/ {0,			"to",		(int)To},
/* 32*/ {0,			"to-me",	(int)To_Me},
/* 33*/ {COMND_ABBREVIATION|
	 COMND_INVISIBLE,	"u",		(int)&Message.keywords[38]},
/* 34*/ {0,			"unanswered",	(int)UnAnswered},
/* 35*/ {0,			"undeleted",	(int)UnDeleted},
/* 36*/ {0,			"unflagged",	(int)UnFlagged},
/* 37*/ {0,			"unkeywords",	(int)UnKeywords},
/* 38*/ {0,			"unseen",	(int)UnSeen}
}};


/*
 *	Message parsing functions
 *
 */
static struct comnd_function keyword =
		{COMND_KEYWORD,
		 COMND_HELP_VALID,
		 0,
		 (int)&Message,
		 "message sequence, "};

static struct comnd_function number =
		{COMND_NUMBER,
		 COMND_HELP_VALID | COMND_SUPPRESS_DEFAULT_HELP,
		 &keyword,
		 10,
		 "a single message number\n  or a range of message numbers n:m\n  or a set of message numbers n#m (m messages beginning with n)"
		 };

static struct comnd_function star =
		{COMND_TOKEN,
		 COMND_HELP_VALID | COMND_SUPPRESS_DEFAULT_HELP,
		 &number,
		 (int)"*",
		 "\"*\" to specify the last message"};

static struct comnd_function dot =
		{COMND_TOKEN,
		 COMND_HELP_VALID | COMND_SUPPRESS_DEFAULT_HELP,
		 &star,
		 (int)".",
		 "\".\" to specify the current message"};

static struct comnd_function percent =
		{COMND_TOKEN,
		 COMND_HELP_VALID | COMND_SUPPRESS_DEFAULT_HELP,
		 &dot,
		 (int)"%",
		 "\"%\" to specify the last message"};

static struct comnd_function comma =
		{COMND_COMMA,
		 COMND_HELP_VALID | COMND_SUPPRESS_DEFAULT_HELP,
		 &percent,
		 0,
		"\",\" to enter message-sequence subcommand mode"};

static struct comnd_function confirm =
		{COMND_CONFIRM,
		 0,
		 &percent};



/*
 *	Parse a message list
 */
Parse_Message_List(Default)
char *Default;
{
	register char *cp;
	struct comnd_function *f,*comnd;
	struct tbluk_keyword *t;
	int data;
	int Status;
	int Second_Number = 0;
	int First_Range_Specification = 1;
	int Reset_Range_On_Number = 0;
	int Subcommand_Mode = 0;
	int Times_Through_Top = 0;
	char Separator;

	/*
	 *	Noise
	 */
	Noise("MESSAGES");
	/*
	 *	Set defaults
	 */
	if (Default) {
		comma.default_string = Default;
		comma.flags |=	COMND_DEFAULT_VALID;
	} else {
		comma.flags &= ~COMND_DEFAULT_VALID;
	}
    	if (Range_Size == 0) Extend_Range_List();
	comnd = &comma;
	confirm.flags &= ~COMND_DEFAULT_VALID;
	Range_List[0].Start = -1;
	Range_Index = 0;
	Command_Index = 0;
	Command_List[0].Routine = 0;
	Inverse_Order = 0;
	Next_Message_To_Check = 0;
	String_Area_Size = sizeof(String_Area);
	/*
	 *	Parse the next token
	 */
Top:	Status = comnd_jsys(&Command_State,
			    comnd,
			    &data,
			    &f);
	if (Status < 0) {
		printf("?parse error (to be done later)\n");
		if (Subcommand_Mode) goto Msg_Top_Level;
		longjmp(Goto_Top_Level,0);
	}
	Times_Through_Top++;
	/*
	 *	Dispatch on token type
	 */
again:	switch(f->code) {
		/*
		 *	Token ("%","*" or ".")
		 */
		case COMND_TOKEN:
			/*
			 *	Place the appropriate number in "data"
			 */
			cp = (char *)f->data;
			if ((*cp == '%') || (*cp == '*')) data = Last_Message;
			if (*cp == '.') data = Current_Message;
			/*	and fall into the numeric code	*/
		/*
		 *	Number (Number in "data")
		 */
		case COMND_NUMBER:
			/*
			 *	Check if we must reset the range list
			 */
			if (Reset_Range_On_Number) {
				Reset_Range_On_Number = 0;
				Range_Index = 0;
				Range_List[0].Start = -1;
			}
			/*
			 *	Check for bad number
			 */
Number:			if (data <= 0) {
				printf("\n?bad message number\n");
				if (Subcommand_Mode) goto Msg_Top_Level;
				longjmp(Goto_Top_Level,0);
			}
			/*
			 *	If > last message, make it last message
			 */
			if (data > Last_Message) data = Last_Message;
			/*
			 *	If 1st range specification, reset range list
			 */
			if (First_Range_Specification) {
				First_Range_Specification = 0;
				Range_List[0].Start = -1;
				Range_Index = 0;
			}
			/*
			 *	Make sure there is room in the range list
			 */
			if (Range_Index >= Range_Size-1) Extend_Range_List();
			/*
			 *	2nd number in ":" construct??
			 */
			if (Second_Number) {
				static struct comnd_function comma =
					{COMND_COMMA,
					 COMND_HELP_VALID |
						COMND_SUPPRESS_DEFAULT_HELP,
					 0,
					 0,
					 "\",\" to specify another message number"};
				static struct comnd_function confirm =
					{COMND_CONFIRM,
					 0,
					 &comma};
				/*
				 *	Add the range or set
				 */
				if (Separator == ':')
					Range_List[Range_Index].End = data;
				if (Separator == '#')
					Range_List[Range_Index].End =
					  Range_List[Range_Index].Start + data - 1;
				if (Range_List[Range_Index].End > Last_Message)
					Range_List[Range_Index].End =
								Last_Message;
				Range_Index++;
				Range_List[Range_Index].Start = -1;
				/*
				 *	Look for <CR> or ","
				 */
				Second_Number = 0;
				Status = comnd_jsys(&Command_State,
						    &confirm,
						    &data,
						    &f);
				if (Status < 0) {
					printf("?parse error\n");
					if (Subcommand_Mode) goto Msg_Top_Level;
					longjmp(Goto_Top_Level,0);
				}
				/*
				 *	If confirmed, return (or msg top level)
				 */
				if (f->code == COMND_CONFIRM) {
					if (Subcommand_Mode) goto Msg_Top_Level1;
					if (Last_Message == 0) {
						Range_Index = 0;
						Range_List[0].Start = -1;
					}
					return;
				}
				/*
				 *	Otherwise, take it from the top
				 */
				goto More;
			} else {
				static struct comnd_function confirm =
					{COMND_CONFIRM};
				static struct comnd_function comma =
					{COMND_COMMA ,
					 COMND_HELP_VALID |
						COMND_SUPPRESS_DEFAULT_HELP,
					 &confirm,
					 0,
					 "\",\" to specify another message number"};
				static struct comnd_function sharp =
					{COMND_TOKEN,
					 COMND_HELP_VALID |
						COMND_SUPPRESS_DEFAULT_HELP,
					 &comma,
					 (int)"#",
					 "\"#\" to specify a message set"};
				static struct comnd_function colon =
					{COMND_TOKEN,
					 COMND_HELP_VALID |
						COMND_SUPPRESS_DEFAULT_HELP,
					 &sharp,
					 (int)":",
					 "\":\" to specify a message range"};
				/*
				 *	First number: look for <cr> "," "#"
				 *		or ":" (and accept 1st number)
				 */
				Range_List[Range_Index].Start = data;
				Range_List[Range_Index].End = data;
				Range_List[Range_Index+1].Start = -1;
				Status = comnd_jsys(&Command_State,
						    &colon,
						    &data,
						    &f);
				if (Status < 0) {
					printf("?parse error\n");
					if (Subcommand_Mode) goto Msg_Top_Level;
					longjmp(Goto_Top_Level,0);
				}
				/*
				 *	If confirmed, return (or msg top level)
				 */
				if (f->code == COMND_CONFIRM) {
					if (Subcommand_Mode) goto Msg_Top_Level1;
					if (Last_Message == 0) {
						Range_Index = 0;
						Range_List[0].Start = -1;
					}
					return;
				}
				/*
				 *	If not a comma its a ":" or "#",
				 *	get the 2nd number
				 */
				if (f->code != COMND_COMMA) {
					Second_Number = 1;
					Separator = *((char *)f->data);
				} else {
					/*
					 *	Comma, accept the number
					 */
					Range_Index++;
					Range_List[Range_Index].Start = -1;
				}
				/*
				 *	Get another number
				 */
			More:	{
				  static struct comnd_function number =
					{COMND_NUMBER,
					 COMND_HELP_VALID |
						COMND_SUPPRESS_DEFAULT_HELP,
					 0,
					 10,
					 "a single message number\n  or a range of message numbers n:m\n  or a set of message numbers n#m (m messages beginning with n)"};
				  static struct comnd_function star =
					{COMND_TOKEN,
					 COMND_HELP_VALID |
						COMND_SUPPRESS_DEFAULT_HELP,
					 &number,
					 (int)"*",
					 "\"*\" to specify the last message"};
				  static struct comnd_function dot =
					{COMND_TOKEN,
					 COMND_HELP_VALID |
						COMND_SUPPRESS_DEFAULT_HELP,
					 &star,
					 (int)".",
					 "\".\" to specify the current message"};
				  static struct comnd_function percent =
					{COMND_TOKEN,
					 COMND_HELP_VALID |
						COMND_SUPPRESS_DEFAULT_HELP,
					 &dot,
					 (int)"%",
					 "\"%\" to specify the last message"};
				  /*
				   *	Get the next token & process it
				   */
				  Status = comnd_jsys(&Command_State,
						      &percent,
						      &data,
						      &f);
				  if (Status < 0) {
					printf("?parse error\n");
					if (Subcommand_Mode) goto Msg_Top_Level;
					longjmp(Goto_Top_Level,0);
				  }
				  goto again;
				}
			}
			break;
		/*
		 *	Keyword
		 */
		case COMND_KEYWORD:
			/*
			 *	Call the action routine
			 */
			t = (struct tbluk_keyword *)data;
			Status = (*((int (*)())t->user_data))(&Command_State);
			if (Status < 0) {
				if (Subcommand_Mode) goto Msg_Top_Level;
				longjmp(Goto_Top_Level,0);
			}
			comnd = &confirm;
			goto Top;
		/*
		 *	Comma (enter subcommand mode)
		 */
		case COMND_COMMA:
			Confirm();
			Subcommand_Mode = 1;
			if (comma.flags & COMND_DEFAULT_VALID)
				confirm.flags |= COMND_DEFAULT_VALID;
			else
				confirm.flags &= ~COMND_DEFAULT_VALID;
			confirm.default_string = Default;
			Command_State.prompt = Msg_Seq_Prompt;
			comnd = &confirm;
			Times_Through_Top = 0;
			COMMAND_PARSE_INIT(&Command_State);
			goto Top;
		/*
		 *	Confirmed (possibly exit subcommand mode)
		 */
		case COMND_CONFIRM:
			if (!Subcommand_Mode) break;
			if (Times_Through_Top == 1) break;
	Msg_Top_Level1: confirm.flags &= ~COMND_DEFAULT_VALID;
	Msg_Top_Level:	Times_Through_Top = 0;
			Reset_Range_On_Number = 1;
			COMMAND_PARSE_INIT(&Command_State);
			goto Top;

	}
	/*
	 *	If no ranges specified, then use ALL messages
	 */
	if (Range_Index == 0) {
		Range_List[0].Start = 1;
		Range_List[0].End = Last_Message;
		Range_List[1].Start = -1;
	}
	Range_Index = 0;
	/*
	 *	Confirm
	 */
	Confirm();
	/*
	 *	If empty message file return NO headers
	 */
	if (Last_Message == 0) {
		Range_Index = 0;
		Range_List[0].Start = -1;
	}
}

/*
 *	Routine to add a command to the command list
 */
static int Add_Command(Routine,Pattern,Time,KeyFlags)
int (*Routine)();
char *Pattern;
unsigned int Time;
unsigned int KeyFlags;
{
	register struct Command_List *Command;
	/*
	 *	Make sure there is room
	 */
	if (Command_Index == NCOMMANDS) {
		printf("\n?Header Selection Command List overflow\n");
		return(-1);
	}
	/*
	 *	Add the command
	 */
	Command = &Command_List[Command_Index++];
	Command_List[Command_Index].Routine = 0;
	Command->Routine = Routine;
	Command->Pattern = Pattern;
	Command->Time = Time;
	Command->KeyFlags = KeyFlags;
	return(0);
}


		/************************************/
		/*	Keyword action routines     */
		/************************************/

/*
 *	Local routine to parse a STRING token and return it in dynamic memory
 */
static char *Get_String()
{
	static struct comnd_function text = {COMND_TEXT};
	static struct comnd_function string = {COMND_QUOTED_STRING,0,&text};
	register int i;
	register char *cp,*cp1;
	char *Return_Value;

	/*
	 *	Noise
	 */
	Noise("string");
	/*
	 *	Get the string
	 */
	Return_Value =	String_Area + (sizeof(String_Area) - String_Area_Size);
	Command_State.atom_buffer = Return_Value;
	Command_State.atom_buffer_size = String_Area_Size;
	i = comnd_jsys(&Command_State,&string,0,0);
	Command_State.atom_buffer = 0;
	Command_State.atom_buffer_size = 0;
	if (i < 0) return(0);
	/*
	 *	Strip any quotes
	 */
	if (*Return_Value == '"') {
		cp = Return_Value+1;
		while(*cp) {cp[-1] = *cp; cp++;}
		if (cp[-2] == '"') cp[-2] = 0;
	}
	/*
	 *	Calculate size of string
	 */
	cp = Return_Value;
	i = 0;
	while(*cp++) i++;
	i++;
	String_Area_Size -= i;
	/*
	 *	Return
	 */
	return(Return_Value);
}

/*
 *	Routine to search for a given substring (caseless comparison)
 */
static char *Search(Sub_String,String,Length)
char *Sub_String,*String;
{
	register char *cp,*cp1;
	register int i;
	int j;
	char Uppercase,Lowercase;

	/*
	 *	Point to text
	 */
	cp = String;
	/*
	 *	Get size of text
	 */
	i = Length;
	/*
	 *	Scan for text
	 */
	while(1) {
		/*
		 *	Find the 1st character
		 */
		cp1 = Sub_String;
		Uppercase = *cp1++;
		Lowercase = Uppercase;
		if ((Uppercase >= 'a') && (Uppercase <= 'z'))
					Uppercase += ('A' - 'a');
		if ((Lowercase >= 'A') && (Lowercase <= 'Z'))
					Lowercase += ('a' - 'A');
		while(--i >= 0) {
			if ((*cp == Uppercase) || (*cp == Lowercase)) break;
			cp++;
		}
		if (i < 0) return(0);
		cp++;
		/*
		 *	Try to match the rest of the string
		 */
		for(j = 0; j < i; j++) {
			Lowercase = cp1[j];
			if (Lowercase == 0) break;
			Uppercase = Lowercase;
			if ((Uppercase >= 'a') && (Uppercase <= 'z'))
						Uppercase += ('A' - 'a');
			if ((Lowercase >= 'A') && (Lowercase <= 'Z'))
						Lowercase += ('a' - 'A');
			if ((cp[j] != Uppercase) && (cp[j] != Lowercase)) break;
		}
		if (cp1[j] == 0) return(cp-1);
	}
}


/*
 *	ALL messages
 */
static All()
{
	/*
	 *	Construct ONE entry with all messages in it
	 */
	Range_List[0].Start = 1;
	Range_List[0].End = Last_Message;
	Range_List[1].Start = -1;
	Range_Index = 1;
	return(0);
}

/*
 *	All ANSWERED messages
 */
static Do_Answered(Message_Number)
{return((Messages[Message_Number-1].Flags & MSG_ANSWERED) ? 0 : -1);}

static Answered()
{

	/*
	 *	Add the "ANSWERED" command
	 */
	return(Add_Command(Do_Answered,0,0,0));
}

/*
 *	Messages BEFORE a certain date
 */
static Do_Before(Message_Number,String,Time,Keyflags)
{
register struct Msg *Msg = &Messages[Message_Number-1];
return((Msg->Date < Time) ? 0 : -1);
}

static Before()
{
    	int tv[2], gtv[2];

	Noise("date");
	gtv[0] = Get_Date();
    	gtv[1] = 0;
    	gmt_to_localtime(gtv, tv, 0);
	return(Add_Command(Do_Before,0,tv[0],0));
}

/*
 *	CURRENT message
 */
static Current()
{

	/*
	 *	Check for room on list
	 */
	if (Range_Index >= Range_Size-1) Extend_Range_List();
	/*
	 *	Add current message to end of list
	 */
	Range_List[Range_Index].Start = Current_Message;
	Range_List[Range_Index].End = Current_Message;
	Range_Index++;
	Range_List[Range_Index].Start = -1;
	return(0);
}

/*
 *	All DELETED messages
 */
static Do_Deleted(Message_Number)
{return((Messages[Message_Number-1].Flags & MSG_DELETED) ? 0 : -1);}

static Deleted()
{

	/*
	 *	Add the "DELETED" command
	 */
	return(Add_Command(Do_Deleted,0,0,0));
}

/*
 *	All FLAGGED messages
 */
static Do_Flagged(Message_Number)
{return((Messages[Message_Number-1].Flags & MSG_FLAGGED) ? 0 : -1);}

static Flagged()
{

	/*
	 *	Add the "FLAGGED" command
	 */
	return(Add_Command(Do_Flagged,0,0,0));
}

/*
 *	Message from a given person
 */
static Do_From(Message_Number,String)
char *String;
{
register struct Msg *Msg = &Messages[Message_Number-1];
return(Search(String,
	      MESSAGE_HEADER(Message_Number) + Msg->From_Offset,
	      Msg->From_Size) ? 0 : -1);
}

static From()
{
	char *cp;

	/*
	 *	Get the string
	 */
	cp = Get_String();
	if (!cp) return(-1);
	/*
	 *	Stack the command
	 */
	return(Add_Command(Do_From,cp,0,0));
}

/*
 *	Do messages in Inverse order
 */
static Inverse()
{
	Inverse_Order = Inverse_Order ? 0 : 1;
}

/*
 *	Messages containing given keywords
 */
static Do_Keywords(Message_Number,String,Time,Keyflags)
{
register struct Msg *Msg = &Messages[Message_Number-1];
return((Msg->Keyword_Flags & Keyflags) ? 0 : -1);
}

static Keywords()
{
	int Keyflags;

	/*
	 *	Get the keyflags
	 */
	Keyflags = Get_Keyflags();
	if (Keyflags == 0) return(-1);
	/*
	 *	Stack the command
	 */
	return(Add_Command(Do_Keywords,0,0,Keyflags));
}

/*
 *	The LAST "n" messages
 */
static Last()
{
	static struct comnd_function number =
		{COMND_NUMBER,COMND_DEFAULT_VALID,0,10,0,"1"};
	int data;
	int Status;

	/*
	 *	Noise
	 */
	Noise("number of messages");
	/*
	 *	Get the number
	 */
	Status = comnd_jsys(&Command_State,&number,&data,0);
	if (Status < 0) {
		printf("\nparse error (to be done later)\n");
		return(-1);
	}
	/*
	 *	If we want MORE than the number of messages its an error
	 */
	if ((data > Last_Message) || (data <= 0)) {
		printf("\n?out of range\n");
		return(-1);
	}
	/*
	 *	Add range entry for the last "n" messages
	 */
	if (Range_Index >= Range_Size-1) Extend_Range_List();
	Range_List[Range_Index].End = Last_Message;
	Range_List[Range_Index].Start = Last_Message - data + 1;
	Range_Index++;
	Range_List[Range_Index].Start = -1;
	return(0);
}


/*
 *	NEW messages
 */
static Do_New(Message_Number)
{
	register struct Msg *Msg = &Messages[Message_Number-1];
	return(((Msg->Date > Message_File_Last_Modified) &&
		!(Msg->Flags & MSG_SEEN)) ? 0 : -1);
}

static New()
{
	/*
	 *	Add the "New" command
	 */
	return(Add_Command(Do_New,0,0,0));
}

/*
 *	Messages ON a given date
 */
static Do_On(Message_Number,String,Time,Keyflags)
{
register struct Msg *Msg = &Messages[Message_Number-1];
return(((Msg->Date >= Time) && (Msg->Date < (Time + (24*60*60)))) ? 0 : -1);
}

static On()
{
    	int tv[2], gtv[2];

	Noise("date");
	gtv[0] = Get_Date();
    	gtv[1] = 0;
    	gmt_to_localtime(gtv, tv, 0);
	return(Add_Command(Do_On,0,tv[0],0));
}

/*
 *	The Previous Sequence that was specified
 */
static Previous_Seq()
{
	register int i;
	register char *cp,*cp1;
	char *String;

	/*
	 *	Add to the command list
	 */
	i = 0;
	while(1) {
		if (!Previous_Command_List[i].Routine) break;
		cp = Previous_Command_List[i].Pattern;
		String = 0;
		if (cp) {
			/*
			 *	Transfer to active string space
			 */
			cp1 = String_Area +
				(sizeof(String_Area) - String_Area_Size);
			String = cp1;
			if (String_Area_Size > 1) String_Area_Size--;
			while(*cp1++ = *cp++) {
				if (String_Area_Size <= 1) {cp = 0;break;}
				String_Area_Size--;
			}
		}
		if (Add_Command(Previous_Command_List[i].Routine,
				String,
				Previous_Command_List[i].Time,
				Previous_Command_List[i].KeyFlags) < 0)
					return(-1);
		i++;
	}
	/*
	 *	Add to the range list (in the appropriate order)
	 */
	i = 0;
	if (Previous_Inverse_Order) {
		while(1) {
			if (Previous_Range_List[i].Start <= 0) break;
			i++;
		}
		i--;
		if (i < 0) i = 0;
	}
	while(i >= 0) {
		if (Previous_Range_List[i].Start <= 0) break;
		if (Range_Index >= Range_Size-1) Extend_Range_List();
		if (Previous_Inverse_Order) {
			Range_List[Range_Index].Start =
					Previous_Range_List[i].End;
			Range_List[Range_Index].End =
					Previous_Range_List[i].Start;
			i--;
		} else {
			Range_List[Range_Index].Start =
					Previous_Range_List[i].Start;
			Range_List[Range_Index].End =
					Previous_Range_List[i].End;
			i++;
		}
		Range_Index++;
		Range_List[Range_Index].Start = -1;
	}
	/*
	 *	Done
	 */
	return(0);
}

/*
 *	Printable Messages
 */
static Do_Printing(Message_Number)
{
  return((Messages[Message_Number-1].Flags & MSG_NEEDS_PRINTING) ? 0 : -1);
}

static Printing()
{
	/*
	 *	Add the "PRINTING" command
	 */
	return(Add_Command(Do_Printing, 0, 0, 0));
}

/*
 *	Recent Messages
 */
static Do_Recent(Message_Number)
{
	register struct Msg *Msg = &Messages[Message_Number-1];
	return((Msg->Date > Message_File_Last_Modified) ? 0 : -1);
}

static Recent()
{
	/*
	 *	Add the "Recent" command
	 */
	return(Add_Command(Do_Recent,0,0,0));
}

/*
 *	Messages that have been seen
 */
static Do_Seen(Message_Number)
{return((Messages[Message_Number-1].Flags & MSG_SEEN) ? 0 : -1);}

static Seen()
{
	/*
	 *	Add the "Seen" command
	 */
	return(Add_Command(Do_Seen,0,0,0));
}

/*
 *	Messages Since a given date
 */
static Do_Since(Message_Number,String,Time,Keyflags)
{
register struct Msg *Msg = &Messages[Message_Number-1];
return((Msg->Date >= Time) ? 0 : -1);
}

static Since()
{
    	int tv[2], gtv[2];

	Noise("date");
	gtv[0] = Get_Date();
    	gtv[1] = 0;
    	gmt_to_localtime(gtv, tv, 0);
	return(Add_Command(Do_Since,0,tv[0],0));
}

/*
 *	Messages with a given subject
 */
static Do_Subject(Message_Number,String)
char *String;
{
register struct Msg *Msg = &Messages[Message_Number-1];
return(Search(String,
	      MESSAGE_HEADER(Message_Number) + Msg->Subj_Offset,
	      Msg->Subj_Size) ? 0 : -1);
}

static Subject()
{
	char *cp;

	/*
	 *	Get the string
	 */
	cp = Get_String();
	if (!cp) return(-1);
	/*
	 *	Stack the command
	 */
	return(Add_Command(Do_Subject,cp,0,0));
}

/*
 *	Messages with a given text
 */
static Do_Text(Message_Number,String)
char *String;
{
register struct Msg *Msg = &Messages[Message_Number-1];
return(Search(String,
	      MESSAGE_HEADER(Message_Number) + Msg->Header_Size,
	      Msg->Real_Size - Msg->Header_Size) ? 0 : -1);
}

static Text()
{
	char *cp;

	/*
	 *	Get the string
	 */
	cp = Get_String();
	if (!cp) return(-1);
	/*
	 *	Stack the command
	 */
	return(Add_Command(Do_Text,cp,0,0));
}

/*
 *	Messages to a given person
 */
static Do_To(Message_Number,String)
char *String;
{
	register struct Msg *Msg = &Messages[Message_Number-1];
	register char *cp,*cp1;
	int i;
	char *Header;
	int Header_Size;

	/*
	 *	Skip the internal header
	 */
	Header = MESSAGE_HEADER(Message_Number) + Msg->Header_Size;
	Header_Size = Msg->Body_Offset - Msg->Header_Size;
	/*
	 *	Look for "To: " string
	 */
	cp = Search("\nTo: ",Header,Header_Size);
	if (cp) {
		cp += 4;
		i = Header_Size - (cp - Header);
		cp1 = cp;
		while((i >= 0) && ((*cp == ' ') || (*cp == '\t'))) {
			while(--i >= 0)
				if (*cp1++ == '\n') break;
			i = cp1 - cp;
			if (Search(String,cp,i)) return(0);
			cp = cp1;
		}
	}
	/*
	 *	Look for "Cc: " string
	 */
	cp = Search("\nCc: ",Header,Header_Size);
	if (cp) {
		cp += 4;
		i = Header_Size - (cp - Header);
		cp1 = cp;
		while((i >= 0) && ((*cp == ' ') || (*cp == '\t'))) {
			while(--i >= 0)
				if (*cp1++ == '\n') break;
			i = cp1 - cp;
			if (Search(String,cp,i)) return(0);
			cp = cp1;
		}
	}
	/*
	 *	Not found!
	 */
	return(-1);
}

static To()
{
	char *cp;

	/*
	 *	Get the string
	 */
	cp = Get_String();
	if (!cp) return(-1);
	/*
	 *	Stack the command
	 */
	return(Add_Command(Do_To,cp,0,0));
}

/*
 *	Messages that have not been answered
 */
static Do_UnAnswered(Message_Number)
{return((Messages[Message_Number-1].Flags & MSG_ANSWERED) ? -1 : 0);}

static UnAnswered()
{
	/*
	 *	Add the "UnAnswered" command
	 */
	return(Add_Command(Do_UnAnswered,0,0,0));
}

/*
 *	Messages that have not been deleted
 */
static Do_UnDeleted(Message_Number)
{return((Messages[Message_Number-1].Flags & MSG_DELETED) ? -1 : 0);}

static UnDeleted()
{
	/*
	 *	Add the "UnAnswered" command
	 */
	return(Add_Command(Do_UnDeleted,0,0,0));
}


/*
 *	Messages that have not been flagged
 */
static Do_UnFlagged(Message_Number)
{return((Messages[Message_Number-1].Flags & MSG_FLAGGED) ? -1 : 0);}

static UnFlagged()
{
	/*
	 *	Add the "UnFlagged" command
	 */
	return(Add_Command(Do_UnFlagged,0,0,0));
}


/*
 *	Messages that don't have given keyword flags
 */
static Do_UnKeywords(Message_Number,String,Time,Keyflags)
{
register struct Msg *Msg = &Messages[Message_Number-1];
return((Msg->Keyword_Flags & Keyflags) ? -1 : 0);
}

static UnKeywords()
{
	int Keyflags;

	/*
	 *	Get the keyflags
	 */
	Keyflags = Get_Keyflags();
	if (Keyflags == 0) return(-1);
	/*
	 *	Stack the command
	 */
	return(Add_Command(Do_UnKeywords,0,0,Keyflags));
}

/*
 *	Messages that have not been seen
 */
static Do_UnSeen(Message_Number)
{return((Messages[Message_Number-1].Flags & MSG_SEEN) ? -1 : 0);}

static UnSeen()
{
	/*
	 *	Add the "UnSeen" command
	 */
	return(Add_Command(Do_UnSeen,0,0,0));
}

static void strip_subject_junk (char **cpp, int *lenp) {

    char *cp = *cpp;
    int  len = *lenp;

    while (len > 0 && isspace(*cp)) {cp++; len--;}
    if (len >= 3 && strncasecmp(cp, "RE:", 3) == 0) {
    	len -= 3;
    	cp += 3;
    }
    while (len > 0 && isspace(*cp)) {cp++; len--;}

    *cpp = cp;
    *lenp = len;

}

static Sorted() {

    int current, i, j, cur_subjlen, subjlen, idx;
    struct Msg *Msg;
    char *cur_subj, *subj;
    int selections[256];
    int *Done;

    Done = (int *) mm_malloc(Last_Message * sizeof(int));
    if (Done == 0) {
    	printf("?memory allocation error\n");
    	exit(2);
    }
    bzero(Done, sizeof(int)*Last_Message);
    current = 0;

/*
**  Selections based on subject header
*/
    while (current < Last_Message) {
    	Msg = &Messages[current];
    	cur_subj = MESSAGE_HEADER(current+1) + Msg->Subj_Offset;
    	cur_subjlen = Msg->Subj_Size;
    	strip_subject_junk(&cur_subj, &cur_subjlen);
    	idx = 0;
    	for (i = 0; i < Last_Message; i++) {
    	    if (i == current || Done[i]) continue;
    	    subj = MESSAGE_HEADER(i+1) + Messages[i].Subj_Offset;
            subjlen = Messages[i].Subj_Size;
            strip_subject_junk(&subj, &subjlen);
    	    j = subjlen > cur_subjlen ? cur_subjlen : subjlen;
    	    if (strncasecmp(cur_subj, subj, j) == 0) {
    	    	selections[idx++] = i;
    	    }
      	}

/*
**  Now sort by date
*/
      	if (idx == 0) {
    	    if (Range_Index == Range_Size) Extend_Range_List();
    	    Range_List[Range_Index].Start = current + 1;
    	    Range_List[Range_Index].End   = current + 1;
    	    Done[current] = 1;
    	    Range_Index++;
      	} else {
    	    selections[idx++] = current;
    	    while (1) {
    	    	for (j = 0; (j < idx) && (selections[j] == 0); j++);
    	    	if (j >= idx) break;
    	    	for (i = j+1; i < idx; i++) {
    	    	    if (i == j || selections[i] == 0 || Done[i]) continue;
    	    	    if (Messages[selections[i]].Date < 
    	    	    	    Messages[selections[j]].Date) j = i;
    	    	}
    	    	if (Range_Index == Range_Size) Extend_Range_List();
    	    	Range_List[Range_Index].Start = selections[j] + 1;
    	    	Range_List[Range_Index].End   = selections[j] + 1;
    	    	Done[selections[j]] = 1;
    	    	Range_Index++;
    	    	selections[j] = 0;
    	    }
      	}

/*
**  Find next thread base
*/
    	for (current++; current < Last_Message; current++) {
    	    if (Done[current]) continue;
    	    for (i = 0; i < Range_Index; i++) {
    	    	if (current >= Range_List[i].Start-1 &&
    	    	    current <= Range_List[i].End-1) break;
    	    }
    	    if (i >= Range_Index) break;
      	}
    }

    if (Range_Index == Range_Size) Extend_Range_List();
    Range_List[Range_Index].Start = -1;
    mm_free(Done);
    return 0;

}
/*
 *	Messages that have me in the "CC"/"TO"/"FROM" fields
 */
static int Do_X_Me(Message_Number,String)
char *String;
{
char Buffer[1024];
if (Get_Header_Field(String,Message_Number,Buffer,sizeof(Buffer)) == 0)
				return(-1);
return(Search((char *)Get_Username(),Buffer,strlen(Buffer)) ? 0 : -1);
}

static Cc_Me() {return(Add_Command(Do_X_Me,"\nCc:",0,0));}
static To_Me() {return(Add_Command(Do_X_Me,"\nTo:",0,0));}
static From_Me() {return(Add_Command(Do_X_Me,"\nFrom:",0,0));}

/*
 *	Messages that are longer/shorter than a given length
 */
static int Do_Longer(Message_Number,Dummy,Size)
{return((Messages[Message_Number-1].Size > Size) ? 0 : -1);}

static Longer(){return(Add_Command(Do_Longer,0,Longer_Shorter_Common(),0));}

static int Do_Shorter(Message_Number,Dummy,Size)
{return((Messages[Message_Number-1].Size < Size) ? 0 : -1);}
static Shorter(){return(Add_Command(Do_Shorter,0,Longer_Shorter_Common(),0));}

static int Longer_Shorter_Common()
{
	static struct comnd_function number =
		{COMND_NUMBER,
		 COMND_DEFAULT_VALID,
		 0,
		 10};
	int i;
	char Local[32];

	/*
	 *	Noise
	 */
	Noise("than number of characters");
	/*
	 *	Set the default
	 */
	sprintf(Local,"%d",Short_Message_Length);
	number.default_string = Local;
	/*
	 *	Get the number
	 */
	comnd_jsys(&Command_State,&number,&i,0);
	return(i);
}


/*
 *	Routine to re-initialize message processing
 */
Re_Init_Next_Message(){Next_Message_To_Check = 0;}

/*
 *	Routine to select the NEXT message for processing
 *	(also print the message list [if necessary] and remember the range)
 */
int Next_Message(Print_List)
{
	int Return_Value;
	register int i;

	/*
	 *	Is the message file empty?
	 */
	if (Last_Message == 0) {
		printf(" Message file empty\n");
		return(-1);
	}
	/*
	 *	Check for initialization
	 */
	if (Next_Message_To_Check == 0) {
		/*
		 *	remember the current range/command list
		 */
		i = 0;
		while(1) {
			Previous_Command_List[i].Routine =
						Command_List[i].Routine;
			if (!Command_List[i].Routine) break;
			Previous_Command_List[i].Pattern =
						Command_List[i].Pattern;
			Previous_Command_List[i].Time =
						Command_List[i].Time;
			Previous_Command_List[i].KeyFlags =
						Command_List[i].KeyFlags;
			if (Command_List[i].Pattern) /* move string space */
				Previous_Command_List[i].Pattern =
					Previous_String_Area +
					   (Command_List[i].Pattern -
								String_Area);
			i++;
		}
		if (String_Area_Size < sizeof(String_Area))
			for(i = (sizeof(String_Area)-1) - String_Area_Size;
			    i >= 0;
			    i--) Previous_String_Area[i] = String_Area[i];
		i = 0;
		while(1) {
			Previous_Range_List[i].Start = Range_List[i].Start;
			Previous_Range_List[i].End   = Range_List[i].End;
			if (Range_List[i].Start < 0) break;
			i++;
		}
		Previous_Inverse_Order = Inverse_Order;
		/*
		 *	Select the next message/range to try
		 */
		if (Range_List[0].Start < 0) return(-1);
		if (Inverse_Order) {
			Range_Index = 0;
			while(Range_List[Range_Index].Start > 0)
				Range_Index++;
			if (Range_Index > 0) Range_Index--;
			Current_Range_Inverse_Order = 1;
		} else {
			Range_Index = 0;
			Current_Range_Inverse_Order = 0;
		}
		Current_Range_Start = Range_List[Range_Index].Start;
		Current_Range_End   = Range_List[Range_Index].End;
		if (Current_Range_Start > Current_Range_End) {
			Current_Range_Inverse_Order =
				Current_Range_Inverse_Order ? 0 : 1;
			i = Current_Range_Start;
			Current_Range_Start = Current_Range_End;
			Current_Range_End = i;
		}
		Next_Message_To_Check = Current_Range_Inverse_Order ?
				Current_Range_End : Current_Range_Start;
		/*
		 *	Indicate that no range has been printed
		 */
		Print_Range(-2);

	}
	/*
	 *	Loop until we find a message that meets the selection criteria
	 */
	while(1) {
		/*
		 *	If end of range list we are done
		 */
		if (Range_List[Range_Index].Start < 0) break;
		/*
		 *	In the current range??
		 */
		if ((Next_Message_To_Check >= Current_Range_Start) &&
		    (Next_Message_To_Check <= Current_Range_End)) {
			/*
			 *	Yes, prepare for the
			 *	next time we are called
			 */
			Return_Value = Next_Message_To_Check;
			if (Current_Range_Inverse_Order) {
				Next_Message_To_Check--;
				if (Next_Message_To_Check == 0)
					Next_Message_To_Check =
						Last_Message+1;
			} else {
				Next_Message_To_Check++;
			}
			/*
			 *	Go through the command list and
			 *	see if all the commands are satisfied
			 */
			i = 0;
			while(Command_List[i].Routine) {
				if ((*Command_List[i].Routine)
					(Return_Value,
					 Command_List[i].Pattern,
					 Command_List[i].Time,
					 Command_List[i].KeyFlags) < 0) break;
				i++;
			}
			/*
			 *	Return the successful message if ALL
			 *	the commands were OK (i.e. finished the list)
			 */
			if (!Command_List[i].Routine) {
				if (Print_List) Print_Range(Return_Value);
				return(Return_Value);
			}
			/*
			 *	Lose, try the NEXT message in the sequence
			 */
			continue;
		}
		/*
		 *	Out of this range, try the next range
		 */
		if (Inverse_Order) {
			if (Range_Index == 0) break;
			Range_Index--;
			Current_Range_Inverse_Order = 1;
		} else {
			Range_Index++;
			Current_Range_Inverse_Order = 0;
		}
		Current_Range_Start = Range_List[Range_Index].Start;
		Current_Range_End   = Range_List[Range_Index].End;
		if (Current_Range_Start > Current_Range_End) {
			Current_Range_Inverse_Order =
				Current_Range_Inverse_Order ? 0 : 1;
			i = Current_Range_Start;
			Current_Range_Start = Current_Range_End;
			Current_Range_End = i;
		}
		Next_Message_To_Check = Current_Range_Inverse_Order ?
				Current_Range_End : Current_Range_Start;
	}
	/*
	 *	Flush Print_Range()
	 */
	if (Print_List) Print_Range(-1);
	/*
	 *	Done
	 */
	return(-1);
}

/*
 *	Get previous message
 */
int Previous_Message()
{
	int Saved_Inverse_Order = Inverse_Order;
	int Saved_Range_Index = Range_Index;
	int Saved_Next_Message_To_Check = Next_Message_To_Check;
	int Saved_Current_Range_Start = Current_Range_Start;
	int Saved_Current_Range_End = Current_Range_End;
	int Saved_Current_Range_Inverse = Current_Range_Inverse_Order;
	int Message;

	/*
	 *	Reverse the current direction
	 */
	Current_Range_Inverse_Order = Current_Range_Inverse_Order ? 0 : 1;
	Inverse_Order = Inverse_Order ? 0 : 1;
	/*
	 *	Back up one message number
	 */
	if (Current_Range_Inverse_Order) {
		Next_Message_To_Check--;
		if (Next_Message_To_Check == 0)
			Next_Message_To_Check = Last_Message+1;
	} else {
		if (Next_Message_To_Check > Last_Message)
			Next_Message_To_Check = 0;
		Next_Message_To_Check++;
	}
	/*
	 *	Get the next message
	 */
	Message = Next_Message(0);
	if (Message > 0)
		Message = Next_Message(0);
	if (Message > 0) {
		Current_Range_Inverse_Order =
			Current_Range_Inverse_Order ? 0 : 1;
		Inverse_Order =
			Inverse_Order ? 0 : 1;

		if (Current_Range_Inverse_Order) {
			Next_Message_To_Check--;
			if (Next_Message_To_Check == 0)
				Next_Message_To_Check = Last_Message+1;
		} else {
			if (Next_Message_To_Check > Last_Message)
				Next_Message_To_Check = 0;
			Next_Message_To_Check++;
		}
		Next_Message(0);
	} else {
		Inverse_Order = Saved_Inverse_Order;
		Range_Index = Saved_Range_Index;
		Next_Message_To_Check = Saved_Next_Message_To_Check;
		Current_Range_Start = Saved_Current_Range_Start;
		Current_Range_End = Saved_Current_Range_End;
		Current_Range_Inverse_Order = Saved_Current_Range_Inverse;
		if (Inverse_Order) Message = -1;
		else		   Message = 0;
	}
	return(Message);
}


/*
 *	Routine to print the selected messages
 */
Print_Range(Message_Number)
{
	static int First_Message_In_Range;
	static int Last_Message_In_Range;
	static int First_Range_Printed;

	/*
	 *	Initialize or Flush
	 */
	if (Message_Number < 0) {
		if (Message_Number == -2) {
			/*
			 *	Initialize
			 */
			First_Range_Printed = 0;
			First_Message_In_Range = -1;
			Last_Message_In_Range = -1;
			return;
		}
		/*
		 *	Message Number was -1, fall through (and flush)
		 */
	}
	/*
	 *	Do another message
	 */
	if (First_Message_In_Range < 0) {
		/*
		 *	Start a new range
		 */
		First_Message_In_Range = Message_Number;
		Last_Message_In_Range = Message_Number;
		return;
	}
	if (Message_Number == (Last_Message_In_Range + 1)) {
		/*
		 *	If contiguous with range so far, just expand the range
		 */
		Last_Message_In_Range++;
		return;
	}
	/*
	 *	Flush the current range
	 */
	if (First_Range_Printed) printf(", ");
	First_Range_Printed = 1;
	if (First_Message_In_Range == Last_Message_In_Range)
		printf("%d", First_Message_In_Range);
	else
		printf("%d:%d",First_Message_In_Range,Last_Message_In_Range);
	fflush(stdout);
	/*
	 *	Start a new range
	 */
	First_Message_In_Range = Message_Number;
	Last_Message_In_Range = Message_Number;
	return;
}

static void Extend_Range_List (void) {

    int new_size;
    struct Range_List *rp;

    new_size = Range_Size == 0 ? INITIAL_RANGE_SIZE : Range_Size * 2;
    rp = (struct Range_List *) mm_realloc(Range_List,
    	    	    	    	    new_size*sizeof(struct Range_List));
    if (rp == 0) {
    	printf("?Memory allocation error.\n");
    	exit(0);
    }
    Range_List = rp;
    rp = (struct Range_List *) mm_realloc(Previous_Range_List,
    	    	    	    	new_size*sizeof(struct Range_List));
    if (rp == 0) {
    	printf("?Memory allocation error.\n");
    	exit(0);
    }
    Previous_Range_List = rp;
    Range_Size = new_size;

}
