// Copyright 1995 Paul Cardon.
// Use is governed by the MiscKit license

#import <stdio.h>
#import <string.h>
#import <sys/file.h>
#import <misckit/MiscString.h>
#import "PanelView.h"

/*
 * $Version$
 * $Log:	receiptfilter.m,v $
 * Revision 1.1  94/01/06  13:02:54  shayman
 * Initial revision
 * 
 * Revision 1.2  94/03/01  13:01:34  pmarc
 * Second revision
 * 
 */

 
void main(int argc, char *argv[])
{

    int scratch;
	FILE *rcfile;
    char tempfile[] = "/tmp/receiptfilter.XXXXXX";
    char linebuf[1024];		// Hope for no e-mail lines longer than this
    BOOL isReceipt = NO;
	BOOL pass = NO;
	BOOL filter = NO;
	BOOL autoAdd = NO;
	BOOL verboseLog = NO;
	BOOL logging = NO;
	BOOL fileOpen = YES; 	// NO only if there is an error.
	id destString, mailerString, rcPathString, logFileString, messageString;
	
	/*
	 * We need an application object to be able to use NXRunAlertPanel()
	 * and to run the modal loop for the custom alert panel I create.
	 */ 
	
    NXApp = [Application new];
    
    /*
     * Copy stdin to a temp file (and make sure it goes away
     * when we're through with it). 
     */
	 
    scratch = mkstemp(tempfile);
    unlink(tempfile);	

    
    /*
     * Read header lines from stdin and write them to temp file.
	 * Search for telltale "Subject: Read Receipt".
	 * Stop search when first blank line is reached.
     */
	 
    while ( fgets(linebuf, sizeof(linebuf)-1, stdin) ) {
    	write(scratch, linebuf, strlen(linebuf));
	
		if ( strcmp(linebuf, "\n") == 0 ) 
			break;

		if ( strcmp(linebuf, "Subject: Read Receipt\n") == 0 ) {
	    	isReceipt = YES;
			continue;
		}

    }

    /*
     * Read message lines from stdin and write them to temp file.
	 * If the message is a read receipt, stick it in a MiscString 
	 * for logging use.
     */
	
	if (isReceipt) {
		messageString = [MiscString newWithString:""];
	
		while ( fgets(linebuf, sizeof(linebuf)-1, stdin) ) {
    		write(scratch, linebuf, strlen(linebuf));
			[messageString cat:linebuf];
		}
	}
	else {
		while ( fgets(linebuf, sizeof(linebuf)-1, stdin) ) 
    		write(scratch, linebuf, strlen(linebuf));
	}

    /*
     * Rewind scratch file. Close stdin. Dup scratch file.
     * Effect is that the scratch file is our process's new stdin.
     */
	 
    lseek( scratch, 0, L_SET );
    close(0);
    dup(scratch);
    
	/*
	 * Initialize the various MiscStrings used throughout the program.
	 */
	 
	/*
	 * The recipient is the last commandline argument.
	 * (Previously we looked at argv[1] but it could be that
	 * sendmail is using a "-oi" option or something - which it
	 * does when you use 3.3 Mail.app)
	 */
	destString = [MiscString newWithString: argv[argc-1]];
	
	mailerString = [MiscString newWithString: "/usr/lib/sendmail"];
	
	rcPathString = [MiscString newWithString: "~/.rfrc"];
	[rcPathString replaceTildeWithHome];
	
	/*
	 * Open ~/.rfrc. If it doesn't appear to exist, create it.
	 * If there are any errors, display panels to warn the user,
	 * and set flags so that it will use the default behavior
	 * associated with an empty configuration file.
	 */
	
	rcfile = fopen([rcPathString stringValue], "r");
	if (rcfile == NULL) {
		NXRunAlertPanel("receiptfilter error", 
			"%s doesn't seem to exist.\nAttempting to create it...\n"
			"Mailing with %s.",
			"OK", NULL, NULL, [rcPathString stringValue], 
			[mailerString stringValue]);
		
		rcfile = fopen([rcPathString stringValue], "w");
		
		if(rcfile == NULL) {
			NXRunAlertPanel("receiptfilter error", 
				"Can't create %s.\nUsing default behavior.",
				"OK", NULL, NULL, [rcPathString stringValue]);
			fileOpen = NO;
		}
	}
	
	/*
	 * Use MiscStrings to parse ~/.rfrc. Check for a mailer other than
	 * /usr/lib/sendmail. 
	 * Check for logging and autoadd features.
	 * If the message is a receipt, try to match the recipient's 
	 * address to one of the lines in ~/.rfrc.
	 */
	
	if (fileOpen) {

		id recordString, firstField, lastField;
	 
		recordString = [[MiscString alloc] init];
		firstField = [[MiscString alloc] init];
		lastField = [[MiscString alloc] init];
			
		while( fgets(linebuf, sizeof(linebuf) - 1, rcfile) ) {
			[recordString setStringValue:linebuf];
			[recordString trimWhiteSpaces];
			if ([recordString length] < 1) 
				continue;		// don't mess with blank lines
			[firstField setStringValue:[[recordString 
				left:1 ] 
				stringValueAndFree]];
			if ([firstField cmp:"M"] == 0) {
				[mailerString setStringValue:[[recordString 
					removeFrom:0 length:1]
					stringValue]];
				[mailerString trimWhiteSpaces];
				continue;
			}
			if ([firstField cmp:"A"] == 0) {
				autoAdd = YES;
				continue;
			}
			if (([firstField cmp:"L"] == 0) || ([firstField cmp:"l"] == 0)) {
				logFileString = [[MiscString alloc] init];
				[logFileString setStringValue:[[recordString 
					removeFrom:0 length:1]
					stringValue]];
				[logFileString trimWhiteSpaces];
				if ([logFileString length] < 1) continue;
				logging = YES;
				if ([firstField cmp:"L"] == 0) verboseLog = YES;
				[logFileString replaceTildeWithHome];
				continue;
			}
			if ( isReceipt ) {
				[lastField setStringValue:[[recordString 
					removeFrom:0 length:1]
					stringValue]];
				[lastField trimWhiteSpaces];
				if ([lastField length] < 1)
					continue;
				if ([destString grep:[lastField stringValue] caseSensitive:YES 
					before:nil middle:nil after:nil]) {
					if ([firstField cmp:"P"] == 0) {
						filter = NO;
						pass = YES;
					}
					if ([firstField cmp:"F"] == 0) {
						pass = NO;
						filter = YES;
					}
				}
			}
		}

		[recordString free];
		[firstField free];
		[lastField free];
		fclose(rcfile);
	}
	
    /*
     * If this message was a return receipt, filter or pass it as specified
	 * in ~/.rfrc.  If the recipient wasn't matched, allow the user to make
	 * the choice.
     */
	 
    if ( isReceipt ) {
		if (!pass && !filter) {

	/*
	 * This part is kind of gross. I had two choices the first being
	 %    [ed. note:  not "kind of".  "is". :-)  -don]
	 %	  [ed. note:  I concur!  ;-)  -paul]
	 * to create a custom alert panel programatically, the second
	 * being to use interface builder and figure out how to incorporate
	 * the NIB into a command line executable. Each way is cleaner for
	 * different reasons. I believe that the following way is just a 
	 * little better. Besides it's a good example for those who may
	 * find themselves in the same situation even in a regular app.
	 */

			id myPanel, panelView;

			NXRect panelRect, 
				   buttonRect1, 
				   buttonRect2, 
				   boxRect, 
				   textRect1, 
				   textRect2,
				   checkRect;
		
			NXSetRect(&panelRect, 370.0, 360.0, 350.0, 175.0);
			myPanel = [[Panel alloc]
						initContent: 	&panelRect
						style:			NX_TITLEDSTYLE
						backing:		NX_BUFFERED
						buttonMask:		0
						defer:			NO];
			[myPanel setTitle:"Read Receipt"];
	
	/*
	 * Initialize the custom view and add the subviews (buttons, 
	 * text, etc.) to it.
	 */
	
			panelView = [[PanelView alloc] 
							initFrame: &panelRect
							memory: autoAdd];
			
	/*
	 * Create a button which has an icon and key equivalent assigned
	 * to it in addition to the normal text label.
	 */		
					
			NXSetRect(&buttonRect1, 274.0, 13.0, 55.0, 30.0);
			[[panelView superview] convertRectToSuperview: &buttonRect1];
			[panelView addSubview:[[[[Button alloc]
									initFrame: &buttonRect1
									title: "Yes"
									tag: 2
									target: panelView
									action: @selector(buttonPress:)
									key: 13
									enabled: YES]
									setImage: 
									  [NXImage findImageNamed:"NXreturnSign"]]
									setIconPosition: NX_ICONRIGHT]];

	/*
	 * Typical button with text label.
	 */
									
			NXSetRect(&buttonRect2, 194.0, 13.0, 55.0, 30.0);
			[[panelView superview] convertRectToSuperview: &buttonRect2];
			[panelView addSubview:[[Button alloc]
									initFrame: &buttonRect2
									title: "No"
									tag: 1
									target: panelView
									action: @selector(buttonPress:)
									key: 0
									enabled: YES]];

	/*
	 * This is one of those horizontal lines you fake in IB with a box.
	 * Guess what, this was the easiest one to do.
	 */

			NXSetRect(&boxRect, 0.0, 130.0, 350.0, 2.0);
			[[panelView superview] convertRectToSuperview: &boxRect];
			[panelView addSubview:[[[Box alloc] initFrame: &boxRect]
									 setTitlePosition: NX_NOTITLE]];

	/*
	 * Now for a check box. It's just a standard button with the type set to
	 * a particular value. Still IB presents it as a separate object.
	 */

			NXSetRect(&checkRect, 35.0, 20.0, 100.0, 15.0);
			[[panelView superview] convertRectToSuperview: &checkRect];
			[panelView addSubview:[[[[[Button alloc]
									initFrame: &checkRect
									title: "Remember"
									tag: 0
									target: panelView
									action: @selector(rememberToggle)
									key: 0
									enabled: YES]
									setType: NX_SWITCH]
									setIconPosition: NX_ICONRIGHT]
									setAlignment: NX_CENTERED]];

	/* 
	 * We use the view to return the id for the check box since the View
	 * class contains a List instance containing all of the subviews
	 * alleviating the need for a new instance variable for each subview.
	 * Adding or removing subviews requires less change to the code this
	 * way too.
	 */
	 
	 /*
	  * Set the default appearance of the check box:
	  * Selected, Unselected, or Disabled.
	  */  

			if (autoAdd)
				[[[panelView subviews] objectAt:3] 
				performClick:[[panelView subviews] objectAt:3]];
			if (!fileOpen)
				[[[panelView subviews] objectAt:3]
				setEnabled: NO];	
			
	/*
	 * Set up the text fields that appear in the panel.
	 */

			NXSetRect(&textRect1, 0.0, 142.0, 350.0, 22.0);
			[[panelView superview] convertRectToSuperview: &textRect1];
			[panelView addSubview:[[[[[[[TextField alloc]
									initFrame: &textRect1]
									setSelectable: NO]
									setBordered: NO]
									setBackgroundGray: NX_LTGRAY]
									setFont:[Font newFont:"Helvetica"
												  size: 18.0]]
									setStringValue:
										"   Send read receipt to:"]];

			NXSetRect(&textRect2, 0.0, 60.0, 350.0, 44.0);
			[[panelView superview] convertRectToSuperview: &textRect2];
			[panelView addSubview:[[[[[[[[TextField alloc]
									initFrame: &textRect2]
									setSelectable: NO]
									setBordered: NO]
									setBackgroundGray: NX_LTGRAY]
									setFont:[Font newFont:"Helvetica"
												  size: 16.0]]
									setAlignment: NX_CENTERED]
									setStringValue:
										[destString stringValue]]];

	/*
	 * Here is the important part:
	 * First I set up the panel and bring it to the front of the 
	 * screen list.
	 * For this to do anything in a command line program, it is then
	 * necessary to activate the app.
	 * Finally, the app is told to run a modal loop for the custom
	 * alert panel which was implemented to stop the loop when the user
	 * makes a choice.
	 */


			[myPanel setContentView:panelView];
			[myPanel display];
			[myPanel makeKeyAndOrderFront:nil];

			[NXApp activateSelf:YES];
			[NXApp runModalFor:myPanel];
	
	/*
	 * Query the view for the user's selections and get rid of the panel.
	 */
	
			autoAdd = [panelView remember];
			pass = [panelView choice];
			filter = !pass;
			[myPanel free];
	
	/*
	 * Now that you've seen the above mess, aren't you glad IB solves most
	 * such problems! :-)
	 %    [ed. note:  But it is good to bang your head on the wall once in
	 %             a while because it feels good when you stop.  :-)  -don]
	 */
	
								
	/*
	 * If desired, add the destination address to the configuration file.
	 */

			if (autoAdd) {
				rcfile = fopen([rcPathString stringValue], "a");
				
				if (rcfile != NULL) {
					if (pass) 
						fprintf(rcfile,"P %s\n",[destString stringValue]);
					if (filter)
						fprintf(rcfile,"F %s\n",[destString stringValue]);
					fclose(rcfile);
				}
			}
			
		}
		
	/*
	 * Perform logging.
	 * Display a panel if there is an error opening the logfile, but
	 * don't let it prevent the message from being sent.
	 */	
	
		if (logging) {
			FILE *logFile;
				
			logFile = fopen([logFileString stringValue], "a");
			if (logFile != NULL) {
				fprintf(logFile,"*************************\n\n");
				fprintf(logFile,"Read receipt to: ");
				fprintf(logFile,"%s",[destString stringValue]);
				if (pass)
					fprintf(logFile," was passed.\n\n");
				else
					fprintf(logFile," was filtered.\n\n");
				if (verboseLog) {
					fprintf(logFile,"The message body follows:\n\n");	
					fprintf(logFile,"%s",[messageString stringValue]);
				}
				fclose(logFile);
			}
			else 
				NXRunAlertPanel("receiptfilter error", 
					"Can't open logfile:\n%s.",
					"OK", NULL, NULL, [logFileString stringValue]);
			[logFileString free];
		}		
		[messageString free];
    }

    [rcPathString free];
	[destString free];
	
	/*
     * Run mailer, let it pick up our arguments and standard input.
	 * If the message is to be filtered, just exit the program.
     */

	if (!filter) { 
	    execv( [mailerString stringValue], argv );

	    /*
	     * shouldn't get here.
	     */
	
	    NXRunAlertPanel("receiptfilter error", 
			"Couldn't execute mailer:\n%s.",
			"OK", NULL, NULL, [mailerString stringValue]);
	}
	
	[mailerString free];
    
    exit(0);
	
}

