#include <stdio.h>
#include "folder.h"
#include "foldert.h"
#include "profile.h"
#include "end.h"
#include "memory.h"
#include "altime.h"
#include "al.h"
#include "data.h"
#include "mh_folder.h"
#include "msg.h"
#include "ruleset.h"
#include "RSreg.h"
#include "FTreg.h"
/* #include "MTreg.h" */
#include "registry.h"
#include "rule.h"
#include "rulerun.h"
#include "sysdep.h"
#include "verbrgy.h"
/* #include "MsgType.h" */
#include "help.h"

/* Dummy out some unwritten functions */
/* #define AlData_set_option(str)    Bool_TRUE */

/* Defines used for debugging aids */
#ifndef MY_DEBUG
#define MY_DEBUG  0
#endif

#if (MY_DEBUG == 1)
#define Al_rr_debug(s1)  printf("%s\n", s1);
#define AlRr_debug(a) a
#else
#define Al_rr_debug(s1)  
#define AlRr_debug(a) 
#endif

/* Some basic useful constants */
#define MINARGC 1
#define OR ||

/* String constants used for help options, and documentation */
/*---------------------------------------------------------- */
#define NON_STD_USAGE  "runrules ruleset [folder]"

/* Note:  -traffic time  Valid time strings are defined by AlTime_set func. */


   char *notes[5] =
         {"The first non-switch argument is assumed to be the ruleset name.",
	  "The second non-switch argument is assumed to be the folder name.",
	  "If folder is not specified, standard input is used when possible.",
          "Defaults are: -s off -u on",
	  NULL};
   char *examples[4] = {"runrules archiverules +inbox",
                        "runrules -MSmh -RSmyrules -FOinbox",
			"runrules -MSrmail printrules < RMAIL",
			NULL};
   AlH_MASK optional_no_arg_switches      = ALH_help | ALH_nest | ALH_output,
            required_no_arg_switches      = ALH_none,
            optional_fixed_arg_switches   = ALH_state | ALH_data
                                          | ALH_traffic | ALH_uprop | ALH_warn,
            required_fixed_arg_switches   = ALH_none,
	    optional_object_arg_switches  = (ALH_folder | ALH_msgsrc),
            required_object_arg_switches = ALH_ruleset; 

/* ------------------------------------------------------------------------
   Valid Parameters are:                                                   
   ---------------------                                                   
  -MSmsgsrc         Specifies the source of the msg folder (how it should be
                    interpreted). The default is:   -MSmh                  
  -n                Specifies that nested sub-folders are to be read. The 
                    default is to NOT do sub-folders (sub-directories). 
  -o                Runrules is to output information about the number
                    of messages processed, how many were moved, the
                    average number of rules which fired per message, and
                    the total number of rules which fired.
                    The default is not to output this information.
  -s [on/off]       Saving output of the state information. The           
                    default is to not save state information (i.e. - read 
                    state info in when processing a message, if it exists,
                    but do not write out when done processing the message).
                    The -s option will cause all state info read or       
                    created to be saved with the message, it can be       
                    immediately followed by 'on' or 'off' as is appropriate.
  -Ttime            Flags the time that the runrule runner is being fired 
                    as a string 'incoming', 'outgoing', etc..             
  -u [on/off]       Save output of the 'permanent' user property information.
                    The default is to save user property information with 
                    the message (i.e. - read any existing user properties 
                    information in when processing a message, if it exists,
                    and write out all 'permanent' user properties when done
                    processing the message). Note that only user properties
                    which have been set 'permanent' can be saved. The -u  
                    option can be immediately followed by 'on' or 'off',  
                    as is appropriate, to make the intent explicit.       
  -d [option]       Choose Data Collection option desired. If \"-d\" is   
                    used, with no option specified, then all off is used. 
                    The default is all on, if \"-d\" is not specified.    
                    Some valid options are:       on     off              
                    -d option can be used more than once on command line. 
  -w                Suppress top level warning msgs. Warnings are         
                    when msg file is not accessible (no read access), etc.

** Example1:  runrules -n -o test.rul inbox
** Example2:  runrules -Mmh -Soff -Uon -Tincoming -Doff -w -n test.rul inbox
** Example3:  runrules -Mmh -Soff -Uon -Tincoming -Doff -w -n -- -rs.rul inbox

** NOTE ** : The ruleset MUST occur before the folder in the command line.
             If the folder is omitted, standard input is used (when possible).
             The '-' parameters may occur in any order.                   
             Parameters taking arguments are a single capital letter,     
             directly followed by the argument (with no spaces).          
 ------------------------------------------------------------------------ 
*/

/* Note: Data Collection function "Al_set_data_collection_level(str)"       */
/* ----- is assumed to take a string as an argument, and return a Bool_TRUE */
/*       if successful, and a Bool_FALSE if the string passed was invalid.  */
/* ------------------------------------------------------------------------ */
#define INVALID_ONOFF_ARGUMENT \
  "%s :: Invalid option to switch. Should be 'on' or 'off', not: '%s'\n"

/* Note :  Tobey promised the following functions. For now, they are  */
/* defined as the same thing. This may change or not, time will tell. */
/* They are #defined here for ease of changing in the future.         */
/* ------------------------------------------------------------------ */
#define AlRSreg_get_by_name(ruleset_string)  AlRSreg_find (ruleset_string)
#define AlRSreg_get_by_file(ruleset_string)  AlRSreg_find (ruleset_string)


main(argc, argv)
     int argc;
     char *argv[];
 {
   int i, msg_count, remove_count;
   int total_rules_fired_count, rules_fired_on_msg_count;
   Bool use_ruleset_name;
   AlRuleSet the_ruleset;
   AlFolder the_folder;
   AlFolderType folder_type;
   Msg the_msg;
   char *ruleset_string;
   char *folder_string;
   char *folder_type_name;
   Bool output_additional_info;
   Bool turn_on_recursion;
   Bool suppress_external_state_usage;
   Bool suppress_external_uprop_usage;
   Bool allow_bad_ruleset;   /* allow data collection, even if bad ruleset */
   Bool not_more_parameters; /* this stops processing of argv's as options */
                             /* and allows '-' to be the 1st char of a     */
                             /* foldername or ruleset name. (e.g. :        */
                             /* runrules -- -myruleset -myfolder           */
   char *al_time_str;        /* run time specified in command line         */
   AlH_MASK switch_type, switch_code;
   char *switch_arg;


   if (argc <= MINARGC)
     { AlHelp_non_standard_usage(NON_STD_USAGE);
       AlHelp_usage("runrules", 
		    optional_no_arg_switches, required_no_arg_switches,
		    optional_fixed_arg_switches, required_fixed_arg_switches,
		    optional_object_arg_switches, 
		    required_object_arg_switches);
       exit(0);
     }
   else if (AlHelp_user_requested_help(argc, argv) ==  Bool_TRUE)
   { AlHelp_non_standard_usage(NON_STD_USAGE);
     AlHelp_info("runrules", 
		 optional_no_arg_switches, required_no_arg_switches,
		 optional_fixed_arg_switches, required_fixed_arg_switches,
		 optional_object_arg_switches, required_object_arg_switches,
		 notes, examples, "runrules -help");
     exit(0);
   };


   /* Initialize registry of folder types, and other stuff */
   /* ---------------------------------------------------- */
   Al_rr_debug(" RR DEBUG: before Al_init()");
   Al_init();
   Al_rr_debug(" RR DEBUG: after Al_init()");
   folder_type_name = AlFolderT_default_name();
   al_time_str = "";
   output_additional_info = Bool_FALSE;
   allow_bad_ruleset = Bool_FALSE;
   use_ruleset_name = Bool_FALSE;
   not_more_parameters = Bool_FALSE;
   ruleset_string = NULL;
   folder_string = NULL;

   /* Initialize the Folder Default values */
   /* ------------------------------------ */
   turn_on_recursion = Bool_FALSE;
   suppress_external_state_usage
              = Bool_not(AlProfile_get_BL_val(ALPROF_SAVSTAT));
   suppress_external_uprop_usage
              = Bool_not(AlProfile_get_BL_val(ALPROF_SAVPROP));

   /* Handle all of the command line options */
   /* -------------------------------------- */
   i = 1;
   Al_rr_debug(" RR DEBUG: before while()");
   AlRr_debug(fprintf(stderr, "argc = %d\n", argc));
   AlRr_debug(fprintf(stderr, "i = %d, ", i));
   while(Bool_TRUE == AlHelp_get_arg(argv, argc, &i,
                                      &switch_type, &switch_code, &switch_arg))
      { 
	switch (switch_type)
          { case ALH_no_arg_switch:
	          AlRr_debug(fprintf(stderr, "ALH_no_arg_switch:\n"));
		  switch(switch_code)
		    { case ALH_nest:
			    turn_on_recursion = Bool_TRUE;
			    break;

		      case ALH_output:
			    output_additional_info = Bool_TRUE;
			    break;

		      default:
			    AlHelp_non_standard_usage(
			       "runrules ruleset [folder]");
                            AlHelp_usage("runrules",
				   optional_no_arg_switches,
				   required_no_arg_switches,
				   optional_fixed_arg_switches,
				   required_fixed_arg_switches,
				   optional_object_arg_switches,
				   required_object_arg_switches);
		            Al_fatal_error2(
		       "%s :: This switch is not valid for this program: %s\n",
			     argv[0],
			     AlHelp_mask_to_switch(ALH_object_arg_switch,
						   switch_code));
			    break;
		    };
	          break;

            case ALH_fixed_arg_switch:
	          AlRr_debug(fprintf(stderr, "ALH_fixed_arg_switch:\n"));
		  switch(switch_code)
		    { case ALH_state:
			    if (strcmp(switch_arg, "on") == 0)
			      { /* Turn on state saving. */
			        suppress_external_state_usage = Bool_FALSE;
			        AlProfile_set_BL_val(ALPROF_SAVSTAT,
						     Bool_TRUE);
			      }
			    else if (strcmp(switch_arg, "off") == 0)
			      { /* Turn off state saving. */
			        suppress_external_state_usage = Bool_TRUE;
			        AlProfile_set_BL_val(ALPROF_SAVSTAT,
						     Bool_FALSE);
			      }
			    else
			      { Al_fatal_error2(INVALID_ONOFF_ARGUMENT,
						argv[0], switch_arg);
			      };
			    break;

		      case ALH_uprop:
			    if (strcmp(switch_arg, "on") == 0)
			      { /* Turn on user property saving. */
				suppress_external_uprop_usage = Bool_FALSE;
				AlProfile_set_BL_val(ALPROF_SAVPROP,
						     Bool_TRUE);
			      }
			    else if (strcmp(switch_arg, "off") == 0)
			      { /* Turn off user property saving. */
				suppress_external_uprop_usage = Bool_TRUE;
				AlProfile_set_BL_val(ALPROF_SAVPROP, 
						     Bool_FALSE);
			      }
			    else
			      { Al_fatal_error2(INVALID_ONOFF_ARGUMENT,
						argv[0], switch_arg);
			      };
			    break;

		      case ALH_traffic:
			    al_time_str = switch_arg;
			    /* allow data collection, anyway */
			    allow_bad_ruleset = Bool_TRUE;
			    break;

		      case ALH_data:
			    if (AlData_set_option(switch_arg) == Bool_FALSE)
			      { Al_fatal_error2(
		     "%s :: Invalid Data Collect Option specified: -data %s\n",
				  argv[0], switch_arg);
			      };
			    break;

		      case ALH_warn:
			    Al_suppress_warnings();
			    break;

		      default:
			    AlHelp_non_standard_usage(
			       "runrules ruleset [folder]");
                            AlHelp_usage("runrules",
				   optional_no_arg_switches,
				   required_no_arg_switches,
				   optional_fixed_arg_switches,
				   required_fixed_arg_switches,
				   optional_object_arg_switches,
				   required_object_arg_switches);
		            Al_fatal_error2(
		       "%s :: This switch is not valid for this program: %s\n",
			     argv[0],
			     AlHelp_mask_to_switch(ALH_object_arg_switch,
						   switch_code));
			    break;
		    };
	          break;

            case ALH_object_arg_switch:
	          AlRr_debug(fprintf(stderr, "ALH_object_arg_switch:\n"));
		  switch(switch_code)
		    { case ALH_msgsrc:
			    folder_type_name = switch_arg;
			    break;

		      case ALH_folder:
			    if (folder_string == NULL)
			      folder_string = switch_arg;
			    else
			      { AlHelp_non_standard_usage(
				   "runrules ruleset [folder]");
				AlHelp_usage("runrules",
				   optional_no_arg_switches,
				   required_no_arg_switches,
				   optional_fixed_arg_switches,
				   required_fixed_arg_switches,
				   optional_object_arg_switches,
				   required_object_arg_switches);
				Al_fatal_error2(
		"runrules :: Folder was specified twice, \"%s\" and \"%s\".\n",
			          folder_string, switch_arg);
			      };
			    break;

		      case ALH_ruleset:
		            if (ruleset_string == NULL)
			      ruleset_string = switch_arg;
			    else 
			      { AlHelp_non_standard_usage(
				   "runrules ruleset [folder]");
				AlHelp_usage("runrules",
				   optional_no_arg_switches,
				   required_no_arg_switches,
				   optional_fixed_arg_switches,
				   required_fixed_arg_switches,
				   optional_object_arg_switches,
				   required_object_arg_switches);
				Al_fatal_error2(
	       "runrules :: Ruleset was specified twice, \"%s\" and \"%s\".\n",
			          ruleset_string, switch_arg);
			      };
			    break;

		      default:
			    AlHelp_non_standard_usage(
			       "runrules ruleset [folder]");
                            AlHelp_usage("runrules",
				   optional_no_arg_switches,
				   required_no_arg_switches,
				   optional_fixed_arg_switches,
				   required_fixed_arg_switches,
				   optional_object_arg_switches,
				   required_object_arg_switches);
		            Al_fatal_error2(
		       "%s :: This switch is not valid for this program: %s\n",
			     argv[0],
			     AlHelp_mask_to_switch(ALH_object_arg_switch,
						   switch_code));
			    break;
		    };
	          break;

            case ALH_not_a_switch:

	          AlRr_debug(fprintf(stderr, "ALH_not_a_switch:\n"));
		  if (ruleset_string == NULL)     /* Set the rule set name. */
		    ruleset_string = switch_arg;  /* ---------------------- */
		  else if (folder_string == NULL)
		    folder_string = switch_arg;      /* Set the folder name. */
		  else                               /* -------------------- */
		    { AlHelp_non_standard_usage("runrules ruleset [folder]");
                      AlHelp_usage("runrules",
				   optional_no_arg_switches,
				   required_no_arg_switches,
				   optional_fixed_arg_switches,
				   required_fixed_arg_switches,
				   optional_object_arg_switches,
				   required_object_arg_switches);
		      Al_fatal_error2(     /* Extra parameters, Fatal Error. */
		        "%s ::  Error: Invalid extra argument: %s\n",
			argv[0], switch_arg);
		    };
	          break;

            case ALH_bad_switch: 

                  AlHelp_non_standard_usage("runrules ruleset [folder]");
                  AlHelp_usage("runrules",
                   optional_no_arg_switches, required_no_arg_switches,
                   optional_fixed_arg_switches, required_fixed_arg_switches,
		   optional_object_arg_switches, required_object_arg_switches);
                  Al_fatal_error2(
                     "%s :: Invalid switch or switch argument specified: %s\n",
		     argv[0], switch_arg);
	          break;

            default:
		  AlHelp_non_standard_usage("runrules ruleset [folder]");
		  AlHelp_info("runrules",
			      optional_no_arg_switches,
			      required_no_arg_switches,
			      optional_fixed_arg_switches,
			      required_fixed_arg_switches,
			      optional_object_arg_switches,
			      required_object_arg_switches,
			      notes,
			      examples,
			      "runrules -help");
                  Al_fatal_error2(
                     "%s :: Invalid switch or switch argument specified: %s\n",
		     argv[0], ((switch_arg == NULL) ? "?" : switch_arg));
		  break;
	  };
	AlRr_debug(fprintf(stderr, "i = %d, ", i));
      };       /* end of while()  */

   Al_rr_debug(" RR DEBUG: after while()");

   /* Set the time of rules run string, and validate it. */
   /* -------------------------------------------------- */
   if (AlTime_set(al_time_str) == Bool_FALSE)
     { Al_fatal_error2("%s :: Invalid Time (-t option) specified: %s\n",
	 	       argv[0], al_time_str);
     };

   Al_rr_debug(" RR DEBUG: after AlTime_set()");

   /* Create the Rule Set Object */
   /* -------------------------- */
   if (use_ruleset_name == Bool_TRUE)
     { Al_rr_debug(" RR DEBUG: use_ruleset_name == Bool_TRUE");
       if (((the_ruleset = AlRSreg_get_by_name(ruleset_string)) == NULL)
	   && (allow_bad_ruleset == Bool_FALSE))
         { Al_fatal_error2(
                 "%s :: Error in Rule Set. Bad rule set name specified: %s\n",
		 argv[0], ruleset_string);
	 };
     }
   else 
     { Al_rr_debug(" RR DEBUG: use_ruleset_name == Bool_FALSE");
       if (((the_ruleset = AlRSreg_get_by_file(ruleset_string)) == NULL)
	   && (allow_bad_ruleset == Bool_FALSE))
         { Al_fatal_error2(
                "%s :: Error in Rule Set. Bad rule set file specified: %s\n",
		argv[0], ruleset_string);
	 };
     };

   /* Create a Folder object. (Note: Folder is not actually opened until */
   /* the first used, i.e. - AlFolder_get_next_msg() is executed.        */
   /* ------------------------------------------------------------------ */
   Al_rr_debug(" RR DEBUG: before AlFolderT_get_folder_type()");
   if ((folder_type = AlFolderT_get_folder_type(folder_type_name)) == NULL)
     { Al_fatal_error2(
                    "%s ::  Unknown message source specified:  -MS%s\n",
		    argv[0], folder_type_name);
     }
   else       /* Update the profile to reflect any overrides. */
     AlProfile_set_CP_val(ALPROF_MAILUA, folder_type_name);

   Al_rr_debug(" RR DEBUG: before AlFolder_create_from...()");
   if (folder_string != NULL)
     { if ((the_folder = AlFolder_create_from_file(folder_string,
                                                folder_type,
					        turn_on_recursion,
					        suppress_external_state_usage,
						suppress_external_uprop_usage,
						FOLDER_OPEN_RW))
	   == NULL)
         { Al_fatal_error2("%s :: Unable to create a folder from: %s\n",
			  argv[0], folder_string);
	 };
       Al_rr_debug(" RR DEBUG: after AlFolder_create_from_file()");
     }
   else if ((the_folder = (AlFolder)
                            AlFolder_create_from_stdin(folder_type,
					        turn_on_recursion,
					        suppress_external_state_usage,
						suppress_external_uprop_usage))
	   == NULL)
         { Al_fatal_error2(
   "%s :: Unable to create the folder from standard input, of msgsrc: -MS%s\n",
	      argv[0], folder_type_name);
	 };

   /* Initialize:  Open the Folder and get first Message object, then */
   /* --------------------------------------------------------------- */
   if ((the_msg = AlFolder_get_next_msg(the_folder)) == NULL)
     { 
#if (MY_DEBUG == 1)
   AlFolder_print_self(the_folder, stdout);
   AlMsg_print_self(the_msg, stdout);
#endif
       if (AlFolder_get_error_code(the_folder) == 0)
         { Al_fatal_error2(
      "%s :: Error in Folder:: Unable to read first message from folder: %s\n",
            argv[0], folder_string);
	  }
       else
         { Al_fatal_error2("%s :: Error in Folder :: %s\n",
		     argv[0], AlFolder_get_error_msg(the_folder));
	 };
     };

   /* Loop through each Message object, and run the rules on each. */
   /* ------------------------------------------------------------ */
   Al_rr_debug(" RR DEBUG: before for() loop");
   msg_count = 0;
   remove_count = 0;
   total_rules_fired_count = 0;
   rules_fired_on_msg_count = 0;
   for (;
	the_msg != NULL;    /* the_msg equals NULL when none are left to do */
	the_msg = AlFolder_get_next_msg(the_folder))
     { msg_count++;
       Al_rr_debug(" RR DEBUG: before AlData_msg_received()");
       AlData_msg_received(the_msg);    /* do data collection, if on */
       Al_rr_debug(" RR DEBUG: after AlData_msg_received()");
       if (AlFolder_get_error_code(the_folder) != 0)
	 { Al_rr_debug(" RR DEBUG: got error code");
	   if (AlMsg_get_error_code(the_msg) != 0)
	     Al_warning2("Folder Error :: %s\n Msg Error :: %s\n",
			    AlFolder_get_error_msg(the_folder),
			    AlMsg_get_error_msg(the_msg));
	   else if (AlFolder_get_error_code(the_folder) != 0)
	     { Al_warning2("Folder Error :: %s %s\n",
			    AlFolder_get_error_msg(the_folder),
			    " ");
	     }
	   else
	     { Al_warning1(
                 "Error :: Unable to sucessfully read any files from:%s\n",
                 AlFolder_get_name(the_folder));
	     };
	   Al_rr_debug(" RR DEBUG: after getting error code");
	 };
       if (the_ruleset != NULL)
	 { Al_rr_debug(" RR DEBUG: before AlRuleRun_do_rules()");
	   AlRuleRun_do_rules(the_folder, the_msg, the_ruleset);
	   Al_rr_debug(" RR DEBUG: after AlRuleRun_do_rules()");
	 };
       rules_fired_on_msg_count = AlMsg_get_num_rules_fired_state(the_msg);
       total_rules_fired_count += rules_fired_on_msg_count;
/*       move_count += AlMsg_get_how_many_times_moved(the_msg); */
       if (AlMsg_get_removed_flag(the_msg) == Bool_TRUE)
	 remove_count++;
     };
   Al_rr_debug(" RR DEBUG: before AlFolder_destroy()");
   AlFolder_destroy(the_folder);
   Al_rr_debug(" RR DEBUG: before AlRuleSet_destroy()");
   Al_rr_debug(" RR DEBUG: before Al_cleanup()");
   Al_cleanup();
   Al_rr_debug(" RR DEBUG: after Al_cleanup()");

   /* Output additional information, if requested */
   /* ------------------------------------------- */
   if (output_additional_info == Bool_TRUE)
     { printf("Total-number-of-messages: %d\n", msg_count);
       printf("Number-of-messages-removed-from-folder: %d\n", remove_count);
       printf("Average-number-of-rules-fired-per-message: %f\n", 
	      ((float) total_rules_fired_count) / ((float) msg_count));
       printf("Total-number-of-rules-fired: %d\n", total_rules_fired_count);
     };

 };              /* end of Rule Runner main()  */

