#include <stddef.h>
#include <assert.h>

#include "sysdep.h"
#include "bool.h"
#include "darray.h"
#include "msg.h"
#include "folder.h"
#include "rule.h"
#include "ruleset.h"
#include "verb.h"
#include "action.h"
#include "predeval.h"
#include "data.h"
#include "verbs.h"

#include "rulerun.h"

#ifdef __STDC__
typedef enum {DONT_STOP, STOP_RULESET} stop_status;
#else
#define DONT_STOP 0
#define STOP_RULESET 1
typedef int stop_status;
#endif

#ifdef __STDC__
static stop_status AlRuleRun_do_actions(Darray);
#else
static stop_status AlRuleRun_do_actions();
#endif


NORET AlRuleRun_domsgs(folder, ruleset)
     AlFolder folder;
     AlRuleSet ruleset;
{
  Msg cur_msg;

  assert (folder != NULL && ruleset != NULL);

  while (cur_msg = AlFolder_get_next_msg(folder))
    AlRuleRun_do_rules(folder, cur_msg, ruleset);
}

/* These variables are used to communicate with the predicates and 
 * actions:
 *
 * Msg AlVerbs_current_msg
 * AlFolder AlVerbs_current_folder
 * AlRule AlVerbs_current_rule
 * AlRuleSet AlVerbs_current _ruleset
 *
 * To allow nested rulesets, the previous value of these 
 * variables is saved and restored.
 */

NORET AlRuleRun_do_rules(folder, msg, ruleset)
     AlFolder folder;
     Msg msg;
     AlRuleSet ruleset;
{
  unsigned int i, num_rules;
  stop_status stop_code;
  Msg saved_current_msg = AlVerbs_current_msg;
  AlFolder saved_current_folder = AlVerbs_current_folder;
  AlRule saved_current_rule = AlVerbs_current_rule;
  AlRuleSet saved_current_ruleset = AlVerbs_current_ruleset;

  Darray the_rules = Darray_create();

  assert(folder != NULL && msg != NULL && ruleset != NULL);

  /* Only handles sequential rulesets for now */
  assert(AlRuleSet_seqp(ruleset));

  AlData_ruleset_invoked(ruleset, msg);
  AlRuleSet_get_rules(ruleset, the_rules);
  num_rules = Darray_len(the_rules);
  for (i = 0; i < num_rules; ++i) {
    AlRule cur_rule = (AlRule)Darray_get(the_rules, i);
    AlVerbs_current_msg = msg;
    AlVerbs_current_folder = folder;
    AlVerbs_current_rule = cur_rule;
    AlVerbs_current_ruleset = ruleset;
    AlData_rule_tested(cur_rule, ruleset, msg);
    if (AlPredEval(msg, AlRule_get_predicate(cur_rule)) == Bool_TRUE) {
      AlData_rule_fired(cur_rule, ruleset, msg);
      AlMsg_incr_num_rules_fired_state(msg, 1);
      stop_code = AlRuleRun_do_actions(AlRule_get_actions(cur_rule));
      if (stop_code == STOP_RULESET)
	break;
    }
  }
  Darray_destroy(the_rules);
  AlVerbs_current_ruleset = saved_current_ruleset;
  AlVerbs_current_rule = saved_current_rule;
  AlVerbs_current_folder = saved_current_folder;
  AlVerbs_current_msg = saved_current_msg;
}

/* This variable is used for communicating with the actions.  If an
 * action wants to stop the rule execution, it should call the
 * AlRuleRun_stop... functions, which set this variable.  To allow for nested
 * ruleset invokations, the previous contents of this variable are saved. 
 * [Note: The previous contents are probably always going to be DONT_STOP, 
 * but I'm not sure of this -jbs] */

static stop_status temp_stop_code = DONT_STOP;

static stop_status AlRuleRun_do_actions(actions)
     Darray actions;
{
  stop_status return_value = DONT_STOP, saved_temp_stop_code = temp_stop_code;
  unsigned int i, num_actions = Darray_len(actions);
  AlVerb_ExecutionFunc func;

  for (i = 0; i < num_actions; ++i) {
    AlAction cur_action = (AlAction)Darray_get(actions, i);
    temp_stop_code = DONT_STOP;
    func = AlVerb_exec_func(AlAction_verb(cur_action));
    (*func)(AlAction_args(cur_action));
    if (temp_stop_code != DONT_STOP) {
      return_value = temp_stop_code;
      break;
    }
  }
  temp_stop_code = saved_temp_stop_code;
  return return_value;
}

NORET AlRuleRun_stop_rs(NOARGS)
{
  temp_stop_code = STOP_RULESET;
}
