/*
**********************************************************************

RULES.C -- Rule processor module for EPANET

VERSION:    2.00
DATE:       5/8/00
            9/7/00
            10/25/00
            3/1/01
            8/15/07    (2.00.11)
AUTHOR:     L. Rossman
            US EPA - NRMRL

  The entry points for this module are:
     initrules()  -- called from ENopen() in EPANET.C
     addrule()    -- called from netsize() in INPUT2.C
     allocrules() -- called from allocdata() in EPANET.C
     ruledata()   -- called from newline() in INPUT2.C
     freerules()  -- called from freedata() in EPANET.C
     checkrules() -- called from ruletimestep() in HYDRAUL.C

**********************************************************************
*/
#include <stdio.h>
#include <string.h>
#ifndef __APPLE__
#include <malloc.h>
#else
#include <stdlib.h>
#endif
#include "epanet2.h"
#include "funcs.h"
#include "hash.h"
#include "text.h"
#include "types.h"
#define EXTERN extern
#include "vars.h"

enum Rulewords {
  r_RULE,
  r_IF,
  r_AND,
  r_OR,
  r_THEN,
  r_ELSE,
  r_PRIORITY,
  r_ERROR
};
char *Ruleword[] = {w_RULE, w_IF,   w_AND,      w_OR,
                    w_THEN, w_ELSE, w_PRIORITY, NULL};

enum Varwords {
  r_DEMAND,
  r_HEAD,
  r_GRADE,
  r_LEVEL,
  r_PRESSURE,
  r_FLOW,
  r_STATUS,
  r_SETTING,
  r_POWER,
  r_TIME,
  r_CLOCKTIME,
  r_FILLTIME,
  r_DRAINTIME
};
char *Varword[] = {w_DEMAND,    w_HEAD,     w_GRADE,     w_LEVEL, w_PRESSURE,
                   w_FLOW,      w_STATUS,   w_SETTING,   w_POWER, w_TIME,
                   w_CLOCKTIME, w_FILLTIME, w_DRAINTIME, NULL};

enum Objects {
  r_JUNC,
  r_RESERV,
  r_TANK,
  r_PIPE,
  r_PUMP,
  r_VALVE,
  r_NODE,
  r_LINK,
  r_SYSTEM
};
char *Object[] = {w_JUNC,  w_RESERV, w_TANK, w_PIPE,   w_PUMP,
                  w_VALVE, w_NODE,   w_LINK, w_SYSTEM, NULL};

/* NOTE: place "<=" & ">=" before "<" & ">" so that findmatch() works correctly.
 */
enum Operators { EQ, NE, LE, GE, LT, GT, IS, NOT, BELOW, ABOVE };
char *Operator[] = {"=",  "<>",  "<=",    ">=",    "<", ">",
                    w_IS, w_NOT, w_BELOW, w_ABOVE, NULL};

enum Values { IS_NUMBER, IS_OPEN, IS_CLOSED, IS_ACTIVE };
char *Value[] = {"XXXX", w_OPEN, w_CLOSED, w_ACTIVE, NULL};

/* External variables declared in INPUT2.C */

/*
**   Local function prototypes are defined here and not in FUNCS.H
**   because some of them utilize the Premise and Action structures
**   defined locally in this module.
*/
void newrule(EN_Project *pr);
int newpremise(EN_Project *pr, int);
int newaction(EN_Project *pr);
int newpriority(EN_Project *pr);
int evalpremises(EN_Project *pr, int);
void updateactlist(rules_t *rules, int, Action *);
int checkaction(rules_t *rules, int, Action *);
int checkpremise(EN_Project *pr, Premise *);
int checktime(EN_Project *pr, Premise *);
int checkstatus(EN_Project *pr, Premise *);
int checkvalue(EN_Project *pr, Premise *);
int takeactions(EN_Project *pr);
void clearactlist(rules_t *rules);
void clearrules(EN_Project *pr);
void ruleerrmsg(EN_Project *pr, int);
int writeRuleinInp(EN_Project *pr, FILE *f, int RuleIdx);

void initrules(rules_t *rules)
/*
**--------------------------------------------------------------
**    Initializes rule base.
**    Called by ENopen() in EPANET.C module
**--------------------------------------------------------------
*/
{
  rules->RuleState = r_PRIORITY;
  rules->Rule = NULL;
}

void addrule(parser_data_t *par, char *tok)
/*
**--------------------------------------------------------------
**    Updates rule count if RULE keyword found in line of input.
**    Called by netsize() in INPUT2.C module.
**--------------------------------------------------------------
*/
{
  if (match(tok, w_RULE)) {
    par->MaxRules++;
  }
}

int allocrules(EN_Project *pr)
/*
**--------------------------------------------------------------
**    Allocates memory for rule-based controls.
**    Called by allocdata() in EPANET.C module.
**--------------------------------------------------------------
*/
{
  rules_t *rules = &pr->rules;
  parser_data_t *par = &pr->parser;

  rules->Rule = (aRule *)calloc(par->MaxRules + 1, sizeof(aRule));
  if (rules->Rule == NULL) {
    return (101);
  } else {
    return (0);
  }
}

void freerules(EN_Project *pr)
/*
**--------------------------------------------------------------
**    Frees memory used for rule-based controls.
**    Called by freedata() in EPANET.C module.
**--------------------------------------------------------------
*/
{
  clearrules(pr);
  free(pr->rules.Rule);
}

int checkrules(EN_Project *pr, long dt)
/*
**-----------------------------------------------------
**    Checks which rules should fire at current time.
**    Called by ruletimestep() in HYDRAUL.C.
**-----------------------------------------------------
*/
{
  EN_Network *net = &pr->network;
  time_options_t *time = &pr->time_options;
  rules_t *rules = &pr->rules;

  int i, r; /* Number of actions actually taken */

  /* Start of rule evaluation time interval */
  rules->Time1 = time->Htime - dt + 1;

  /* Iterate through each rule */
  rules->ActList = NULL;
  r = 0;
  for (i = 1; i <= net->Nrules; i++) {
    /* If premises true, add THEN clauses to action list. */
    if (evalpremises(pr, i) == TRUE) {
      updateactlist(rules, i, rules->Rule[i].Tchain);
    }

    /* If premises false, add ELSE actions to list. */
    else {
      if (rules->Rule[i].Fchain != NULL) {
        updateactlist(rules, i, rules->Rule[i].Fchain);
      }
    }
  }

  /* Execute actions then clear list. */
  if (rules->ActList != NULL) {
    r = takeactions(pr);
  }
  clearactlist(rules);
  return (r);
}

int ruledata(EN_Project *pr)
/*
**--------------------------------------------------------------
**    Parses a line from [RULES] section of input.
**    Called by newline() in INPUT2.C module.
**    Tok[] is global array of tokens parsed from input line.
**--------------------------------------------------------------
*/
{
  EN_Network *net = &pr->network;
  parser_data_t *par = &pr->parser;

  rules_t *rules = &pr->rules;
  char **Tok = par->Tok;
  
  int key, /* Keyword code */
      err;

  /* Exit if current rule has an error */
  if (rules->RuleState == r_ERROR)
    return (0);

  /* Find the key word that begins the rule statement */
  err = 0;
  key = findmatch(Tok[0], Ruleword);
  switch (key) {
  case -1:
    err = 201; /* Unrecognized keyword */
    break;
  case r_RULE:
    net->Nrules++;
    newrule(pr);
    rules->RuleState = r_RULE;
    break;
  case r_IF:
    if (rules->RuleState != r_RULE) {
      err = 221; /* Mis-placed IF clause */
      break;
    }
    rules->RuleState = r_IF;
    err = newpremise(pr,r_AND);
    break;
  case r_AND:
    if (rules->RuleState == r_IF)
      err = newpremise(pr, r_AND);
    else if (rules->RuleState == r_THEN || rules->RuleState == r_ELSE)
      err = newaction(pr);
    else
      err = 221;
    break;
  case r_OR:
    if (rules->RuleState == r_IF)
      err = newpremise(pr, r_OR);
    else
      err = 221;
    break;
  case r_THEN:
    if (rules->RuleState != r_IF) {
      err = 221; /* Mis-placed THEN clause */
      break;
    }
    rules->RuleState = r_THEN;
    err = newaction(pr);
    break;
  case r_ELSE:
    if (rules->RuleState != r_THEN) {
      err = 221; /* Mis-placed ELSE clause */
      break;
    }
    rules->RuleState = r_ELSE;
    err = newaction(pr);
    break;
  case r_PRIORITY:
    if (rules->RuleState != r_THEN && rules->RuleState != r_ELSE) {
      err = 221;
      break;
    }
    rules->RuleState = r_PRIORITY;
    err = newpriority(pr);
    break;
  default:
    err = 201;
  }

  /* Set RuleState to r_ERROR if errors found */
  if (err) {
    rules->RuleState = r_ERROR;
    ruleerrmsg(pr,err);
    err = 200;
  }
  return (err);
}

void clearactlist(rules_t *rules)
/*
**----------------------------------------------------------
**    Clears memory used for action list
**----------------------------------------------------------
*/
{  
  ActItem *a;
  ActItem *anext;
  a = rules->ActList;
  while (a != NULL) {
    anext = a->next;
    free(a);
    a = anext;
  }
}

void clearrules(EN_Project *pr)
/*
**-----------------------------------------------------------
**    Clears memory used for premises & actions for all rules
**-----------------------------------------------------------
*/
{
  EN_Network *net = &pr->network;

  rules_t *rules = &pr->rules;
  
  Premise *p;
  Premise *pnext;
  Action *a;
  Action *anext;
  int i;
  for (i = 1; i <= net->Nrules; i++) {
    p = rules->Rule[i].Pchain;
    while (p != NULL) {
      pnext = p->next;
      free(p);
      p = pnext;
    }
    a = rules->Rule[i].Tchain;
    while (a != NULL) {
      anext = a->next;
      free(a);
      a = anext;
    }
    a = rules->Rule[i].Fchain;
    while (a != NULL) {
      anext = a->next;
      free(a);
      a = anext;
    }
  }
}

void newrule(EN_Project *pr)
/*
**----------------------------------------------------------
**    Adds new rule to rule base
**----------------------------------------------------------
*/
{
  EN_Network *net = &pr->network;
  parser_data_t *par = &pr->parser;
  rules_t *rules = &pr->rules;
  char **Tok = par->Tok;
  strncpy(rules->Rule[net->Nrules].label, Tok[1], MAXID);
  rules->Rule[net->Nrules].Pchain = NULL;
  rules->Rule[net->Nrules].Tchain = NULL;
  rules->Rule[net->Nrules].Fchain = NULL;
  rules->Rule[net->Nrules].priority = 0.0;
  rules->Plast = NULL;
  rules->Tlast = NULL;
  rules->Flast = NULL;
}

int newpremise(EN_Project *pr, int logop)
/*
**--------------------------------------------------------------------
**   Adds new premise to current rule.
**   Formats are:
**     IF/AND/OR <object> <id> <variable> <operator> <value>
**     IF/AND/OR  SYSTEM <variable> <operator> <value> (units)
**
**   Calls findmatch() and hour() in INPUT2.C.
**   Calls findnode() and findlink() in EPANET.C.
**---------------------------------------------------------------------
*/
{
  EN_Network *net = &pr->network;
  parser_data_t *par = &pr->parser;

  rules_t *rules = &pr->rules;
  char **Tok = par->Tok;
  
  int i, j, k, m, r, s, v;
  double x;
  Premise *p;

  /* Check for correct number of tokens */
  if (par->Ntokens != 5 && par->Ntokens != 6)
    return (201);

  /* Find network object & id if present */
  i = findmatch(Tok[1], Object);
  if (i == r_SYSTEM) {
    j = 0;
    v = findmatch(Tok[2], Varword);
    if (v != r_DEMAND && v != r_TIME && v != r_CLOCKTIME) {
      return (201);
    }
  } else {
    v = findmatch(Tok[3], Varword);
    if (v < 0) {
      return (201);
    }
    switch (i) {
    case r_NODE:
    case r_JUNC:
    case r_RESERV:
    case r_TANK:
      k = r_NODE;
      break;
    case r_LINK:
    case r_PIPE:
    case r_PUMP:
    case r_VALVE:
      k = r_LINK;
      break;
    default:
      return (201);
    }
    i = k;
    if (i == r_NODE) {
      j = findnode(net, Tok[2]);
      if (j == 0)
        return (203);
      switch (v) {
      case r_DEMAND:
      case r_HEAD:
      case r_GRADE:
      case r_LEVEL:
      case r_PRESSURE:
        break;
      case r_FILLTIME:
      case r_DRAINTIME:
        if (j <= net->Njuncs) {
          return (201);
        }
        break;

      default:
        return (201);
      }
    } else {
      j = findlink(net, Tok[2]);
      if (j == 0) {
        return (204);
      }
      switch (v) {
      case r_FLOW:
      case r_STATUS:
      case r_SETTING:
        break;
      default:
        return (201);
      }
    }
  }

  /* Parse relational operator (r) and check for synonyms */
  if (i == r_SYSTEM) {
    m = 3;
  } else {
    m = 4;
  }
  k = findmatch(Tok[m], Operator);
  if (k < 0)
    return (201);
  switch (k) {
  case IS:
    r = EQ;
    break;
  case NOT:
    r = NE;
    break;
  case BELOW:
    r = LT;
    break;
  case ABOVE:
    r = GT;
    break;
  default:
    r = k;
  }

  /* Parse for status (s) or numerical value (x) */
  s = 0;
  x = MISSING;
  if (v == r_TIME || v == r_CLOCKTIME) {
    if (par->Ntokens == 6)
      x = hour(Tok[4], Tok[5]) * 3600.;
    else
      x = hour(Tok[4], "") * 3600.;
    if (x < 0.0)
      return (202);
  } else if ((k = findmatch(Tok[par->Ntokens - 1], Value)) > IS_NUMBER)
    s = k;
  else {
    if (!getfloat(Tok[par->Ntokens - 1], &x))
      return (202);
    if (v == r_FILLTIME || v == r_DRAINTIME)
      x = x * 3600.0; 
  }

  /* Create new premise structure */
  p = (Premise *)malloc(sizeof(Premise));
  if (p == NULL)
    return (101);
  p->object = i;
  p->index = j;
  p->variable = v;
  p->relop = r;
  p->logop = logop;
  p->status = s;
  p->value = x;

  /* Add premise to current rule's premise list */
  p->next = NULL;
  if (rules->Plast == NULL)
    rules->Rule[net->Nrules].Pchain = p;
  else
    rules->Plast->next = p;
  rules->Plast = p;
  return (0);
}

int newaction(EN_Project *pr)
/*
**----------------------------------------------------------
**   Adds new action to current rule.
**   Format is:
**      THEN/ELSE/AND LINK <id> <variable> IS <value>
**
**   Calls findlink() from EPANET.C.
**   Calls getfloat() and findmatch() from INPUT2.C.
**----------------------------------------------------------
*/
{
  EN_Network *net = &pr->network;
  parser_data_t *par = &pr->parser;
  rules_t *rules = &pr->rules;
  char **Tok = par->Tok;

  int j, k, s;
  double x;
  Action *a;

  /* Check for correct number of tokens */
  if (par->Ntokens != 6)
    return (201);

  /* Check that link exists */
  j = findlink(net, Tok[2]);
  if (j == 0)
    return (204);

  /***  Updated 9/7/00  ***/
  /* Cannot control a CV */
  if (net->Link[j].Type == EN_CVPIPE)
    return (207);

  /* Find value for status or setting */
  s = -1;
  x = MISSING;
  if ((k = findmatch(Tok[5], Value)) > IS_NUMBER) {
    s = k;
  } else {
    if (!getfloat(Tok[5], &x)) {
      return (202);
    }
    if (x < 0.0) {
      return (202);
    }
  }

  /*** Updated 9/7/00 ***/
  /* Cannot change setting for a GPV ***/
  if (x != MISSING && net->Link[j].Type == EN_GPV)
    return (202);

  /*** Updated 3/1/01 ***/
  /* Set status for pipe in case setting was specified */
  if (x != MISSING && net->Link[j].Type == EN_PIPE) {
    if (x == 0.0)
      s = IS_CLOSED;
    else
      s = IS_OPEN;
    x = MISSING;
  }

  /* Create a new action structure */
  a = (Action *)malloc(sizeof(Action));
  if (a == NULL)
    return (101);
  a->link = j;
  a->status = s;
  a->setting = x;

  /* Add action to current rule's action list */
  if (rules->RuleState == r_THEN) {
    a->next = NULL;
    if (rules->Tlast == NULL)
      rules->Rule[net->Nrules].Tchain = a;
    else
      rules->Tlast->next = a;
    rules->Tlast = a;
  } else {
    a->next = NULL;
    if (rules->Flast == NULL)
      rules->Rule[net->Nrules].Fchain = a;
    else
      rules->Flast->next = a;
    rules->Flast = a;
  }
  return (0);
}

int newpriority(EN_Project *pr)
/*
**---------------------------------------------------
**    Adds priority rating to current rule
**---------------------------------------------------
*/
{
  EN_Network *net = &pr->network;
  parser_data_t *par = &pr->parser;
  rules_t *rules = &pr->rules;
  char **Tok = par->Tok;
  
  double x;
  if (!getfloat(Tok[1], &x))
    return (202);
  rules->Rule[net->Nrules].priority = x;
  return (0);
}

int evalpremises(EN_Project *pr, int i)
/*
**----------------------------------------------------------
**    Checks if premises to rule i are true
**----------------------------------------------------------
*/
{
  rules_t *rules = &pr->rules;
  
  int result;
  Premise *p;

  result = TRUE;
  p = rules->Rule[i].Pchain;
  while (p != NULL) {
    if (p->logop == r_OR) {
      if (result == FALSE) {
        result = checkpremise(pr,p);
      }
    } else {
      if (result == FALSE)
        return (FALSE);
      result = checkpremise(pr,p);
    }
    p = p->next;
  }
  return (result);
}

int checkpremise(EN_Project *pr, Premise *p)
/*
**----------------------------------------------------------
**    Checks if a particular premise is true
**----------------------------------------------------------
*/
{
  if (p->variable == r_TIME || p->variable == r_CLOCKTIME)
    return (checktime(pr,p));
  else if (p->status > IS_NUMBER)
    return (checkstatus(pr,p));
  else
    return (checkvalue(pr,p));
}

int checktime(EN_Project *pr, Premise *p)
/*
**------------------------------------------------------------
**    Checks if condition on system time holds
**------------------------------------------------------------
*/
{
  time_options_t *time = &pr->time_options;
  rules_t *rules = &pr->rules;
  
  char flag;
  long t1, t2, x;

  /* Get start and end of rule evaluation time interval */
  if (p->variable == r_TIME) {
    t1 = rules->Time1;
    t2 = time->Htime;
  } 
  else if (p->variable == r_CLOCKTIME) {
    t1 = (rules->Time1 + time->Tstart) % SECperDAY;
    t2 = (time->Htime + time->Tstart) % SECperDAY;
  } else
    return (0);

  /* Test premise's time */
  x = (long)(p->value);
  switch (p->relop) {
  /* For inequality, test against current time */
  case LT:
    if (t2 >= x)
      return (0);
    break;
  case LE:
    if (t2 > x)
      return (0);
    break;
  case GT:
    if (t2 <= x)
      return (0);
    break;
  case GE:
    if (t2 < x)
      return (0);
    break;

  /* For equality, test if within interval */
  case EQ:
  case NE:
    flag = FALSE;
    if (t2 < t1) /* E.g., 11:00 am to 1:00 am */
    {
      if (x >= t1 || x <= t2)
        flag = TRUE;
    } else {
      if (x >= t1 && x <= t2)
        flag = TRUE;
    }
    if (p->relop == EQ && flag == FALSE)
      return (0);
    if (p->relop == NE && flag == TRUE)
      return (0);
    break;
  }

  /* If we get to here then premise was satisfied */
  return (1);
}

int checkstatus(EN_Project *pr, Premise *p)
/*
**------------------------------------------------------------
**    Checks if condition on link status holds
**------------------------------------------------------------
*/
{
  hydraulics_t *hyd = &pr->hydraulics;
  
  char i;
  int j;
  switch (p->status) {
  case IS_OPEN:
  case IS_CLOSED:
  case IS_ACTIVE:
    i = hyd->LinkStatus[p->index];
    if (i <= CLOSED)
      j = IS_CLOSED;
    else if (i == ACTIVE)
      j = IS_ACTIVE;
    else
      j = IS_OPEN;
    if (j == p->status && p->relop == EQ)
      return (1);
    if (j != p->status && p->relop == NE)
      return (1);
  }
  return (0);
}

int checkvalue(EN_Project *pr, Premise *p)
/*
**----------------------------------------------------------
**    Checks if numerical condition on a variable is true.
**    Uses tolerance of 0.001 when testing conditions.
**----------------------------------------------------------
*/
{
  EN_Network *net = &pr->network;
  hydraulics_t *hyd = &pr->hydraulics;
  
  Snode *Node = net->Node;
  Slink *Link = net->Link;
  Stank *Tank = net->Tank;
  
  const int Njuncs = net->Njuncs;
  double *Ucf = pr->Ucf;
  double *NodeDemand = hyd->NodeDemand;
  double *LinkFlows = hyd->LinkFlows;
  double *LinkSetting = hyd->LinkSetting;
  
  int i, j, v;
  double x, tol = 1.e-3;

  i = p->index;
  v = p->variable;
  switch (v) {
    case r_DEMAND:
      if (p->object == r_SYSTEM)
        x = hyd->Dsystem * Ucf[DEMAND];
      else
        x = NodeDemand[i] * Ucf[DEMAND];
      break;
      
    case r_HEAD:
    case r_GRADE:
      x = hyd->NodeHead[i] * Ucf[HEAD];
      break;
    case r_PRESSURE:
      x = (hyd->NodeHead[i] - Node[i].El) * Ucf[PRESSURE];
      break;
    case r_LEVEL:
      x = (hyd->NodeHead[i] - Node[i].El) * Ucf[HEAD];
      break;
    case r_FLOW:
      x = ABS(LinkFlows[i]) * Ucf[FLOW];
      break;
    case r_SETTING:
      if (LinkSetting[i] == MISSING)
        return (0);
      x = LinkSetting[i];
      switch (Link[i].Type) {
        case EN_PRV:
        case EN_PSV:
        case EN_PBV:
          x = x * Ucf[PRESSURE];
          break;
        case EN_FCV:
          x = x * Ucf[FLOW];
          break;
        default:
          break;
      }
      break;
    case r_FILLTIME:
      if (i <= Njuncs)
        return (0);
      j = i - Njuncs;
      if (Tank[j].A == 0.0)
        return (0);
      if (NodeDemand[i] <= TINY)
        return (0);
      x = (Tank[j].Vmax - Tank[j].V) / NodeDemand[i];
      break;
    case r_DRAINTIME:
      if (i <= Njuncs)
        return (0);
      j = i - Njuncs;
      if (Tank[j].A == 0.0)
        return (0);
      if (NodeDemand[i] >= -TINY)
        return (0);
      x = (Tank[j].Vmin - Tank[j].V) / NodeDemand[i];
      break;
    default:
      return (0);
  }
  switch (p->relop) {
  case EQ:
    if (ABS(x - p->value) > tol)
      return (0);
    break;
  case NE:
    if (ABS(x - p->value) < tol)
      return (0);
    break;
  case LT:
    if (x > p->value + tol)
      return (0);
    break;
  case LE:
    if (x > p->value - tol)
      return (0);
    break;
  case GT:
    if (x < p->value - tol)
      return (0);
    break;
  case GE:
    if (x < p->value + tol)
      return (0);
    break;
  }
  return (1);
}

void updateactlist(rules_t *rules, int i, Action *actions)
/*
**---------------------------------------------------
**    Adds rule's actions to action list
**---------------------------------------------------
*/
{
  ActItem *item;
  Action *a;

  /* Iterate through each action of Rule i */
  a = actions;
  while (a != NULL) {
    /* Add action to list if not already on it */
    if (!checkaction(rules, i, a)) {
      item = (ActItem *)malloc(sizeof(ActItem));
      if (item != NULL) {
        item->action = a;
        item->ruleindex = i;
        item->next = rules->ActList;
        rules->ActList = item;
      }
    }
    a = a->next;
  }
}

int checkaction(rules_t *rules, int i, Action *a)
/*
**-----------------------------------------------------------
**    Checks if an action is already on the Action List
**-----------------------------------------------------------
*/
{
  int i1, k, k1;
  ActItem *item;
  Action *a1;

  /* Search action list for link named in action */
  k = a->link; /* Action applies to link k */
  item = rules->ActList;
  while (item != NULL) {
    a1 = item->action;
    i1 = item->ruleindex;
    k1 = a1->link;

    /* If link on list then replace action if rule has higher priority. */
    if (k1 == k) {
      if (rules->Rule[i].priority > rules->Rule[i1].priority) {
        item->action = a;
        item->ruleindex = i;
      }
      return (1);
    }
    item = item->next;
  }
  return (0);
}

int takeactions(EN_Project *pr)
/*
**-----------------------------------------------------------
**    Implements actions on action list
**-----------------------------------------------------------
*/
{
  
  EN_Network *net = &pr->network;
  hydraulics_t *hyd = &pr->hydraulics;
  report_options_t *rep = &pr->report;
  rules_t *rules = &pr->rules;
  
  Action *a;
  ActItem *item;
  char flag;
  int k, s, n;
  double tol = 1.e-3, v, x;
  
  n = 0;
  item = rules->ActList;
  while (item != NULL) {
    flag = FALSE;
    a = item->action;
    k = a->link;
    s = hyd->LinkStatus[k];
    v = hyd->LinkSetting[k];
    x = a->setting;

    /* Switch link from closed to open */
    if (a->status == IS_OPEN && s <= CLOSED) {
      setlinkstatus(pr, k, 1, &hyd->LinkStatus[k], &hyd->LinkSetting[k]);
      flag = TRUE;
    }

    /* Switch link from not closed to closed */
    else if (a->status == IS_CLOSED && s > CLOSED) {
      setlinkstatus(pr, k, 0, &hyd->LinkStatus[k], &hyd->LinkSetting[k]);
      flag = TRUE;
    }

    /* Change link's setting */
    else if (x != MISSING) {
      switch (net->Link[k].Type) {
        case EN_PRV:
        case EN_PSV:
        case EN_PBV:
          x = x / pr->Ucf[PRESSURE];
          break;
        case EN_FCV:
          x = x / pr->Ucf[FLOW];
          break;
        default:
          break;
      }
      if (ABS(x - v) > tol) {
        setlinksetting(pr, k, x, &hyd->LinkStatus[k], &hyd->LinkSetting[k]);
        flag = TRUE;
      }
    }

    /* Report rule action */
    if (flag == TRUE) {
      n++;
      if (rep->Statflag)
        writeruleaction(pr, k, rules->Rule[item->ruleindex].label);
    }

    /* Move to next action on list */
    item = item->next;
  }
  return (n);
}

void ruleerrmsg(EN_Project *pr, int err)
/*
**-----------------------------------------------------------
**    Reports error message
**-----------------------------------------------------------
*/
{
  EN_Network *net = &pr->network;
  parser_data_t *par = &pr->parser;
  rules_t *rules = &pr->rules;
  char **Tok = par->Tok;
  
  int i;
  char label[81];
  char fmt[256];
  switch (err) {
  case 201:
    strcpy(fmt, R_ERR201);
    break;
  case 202:
    strcpy(fmt, R_ERR202);
    break;
  case 203:
    strcpy(fmt, R_ERR203);
    break;
  case 204:
    strcpy(fmt, R_ERR204);
    break;

  /***  Updated on 9/7/00  ***/
  case 207:
    strcpy(fmt, R_ERR207);
    break;

  case 221:
    strcpy(fmt, R_ERR221);
    break;
  default:
    return;
  }
  if (net->Nrules > 0) {
    strcpy(label, t_RULE);
    strcat(label, " ");
    strcat(label, rules->Rule[net->Nrules].label);
  } else
    strcpy(label, t_RULES_SECT);
  sprintf(pr->Msg, "%s", fmt);
  strcat(pr->Msg, label);
  strcat(pr->Msg, ":");
  writeline(pr, pr->Msg);
  strcpy(fmt, Tok[0]);
  for (i = 1; i < par->Ntokens; i++) {
    strcat(fmt, " ");
    strcat(fmt, Tok[i]);
  }
  writeline(pr, fmt);
}

int writeRuleinInp(EN_Project *pr, FILE *f, int RuleIdx) {
  /*
  **-----------------------------------------------------------
  **    This function writes a specific rule (rule ID,
  **    premises, true and false actions and prioriry in the
  **    text input file.
  **    INPUT:
  **    - FILE *f : pointer to the .inp file to be written
  **    - int RuleIdx : index of the rule that needs to be written
  **    OUTPUT: error code
  **-----------------------------------------------------------
  */

  EN_Network *net = &pr->network;
  rules_t *rules = &pr->rules;
  
  Slink *Link = net->Link;
  Stank *Tank = net->Tank;
  Snode *Node = net->Node;
  
  // int i,j;
  Premise *p;
  Action *a;
  int hours = 0, minutes = 0, seconds = 0;

  // the first condition/premises is different from the others because it starts
  // with IF (but it is kept in memory as AND)
  p = rules->Rule[RuleIdx].Pchain;
  if (p->value == MISSING) {
    fprintf(f, "\nIF ");
    if ((strncmp(Object[p->object], "NODE", 4) == 0) ||
        (strncmp(Object[p->object], "Junc", 4) == 0) ||
        (strncmp(Object[p->object], "Reser", 5) == 0) ||
        (strncmp(Object[p->object], "Tank", 4) == 0)) {
      if (p->index <= net->Njuncs)
        fprintf(f, "JUNCTION %s %s %s %s", Node[p->index].ID,
                Varword[p->variable], Operator[p->relop], Value[p->status]);
      else if (Tank[p->index - net->Njuncs].A == 0.0)
        fprintf(f, "RESERVOIR %s %s %s %s", Node[p->index].ID,
                Varword[p->variable], Operator[p->relop], Value[p->status]);
      else
        fprintf(f, "TANK %s %s %s %s", Node[p->index].ID, Varword[p->variable],
                Operator[p->relop], Value[p->status]);
    } else { // it is a link
      if (Link[p->index].Type == EN_PIPE || Link[p->index].Type == EN_CVPIPE)
        fprintf(f, "PIPE %s %s %s %s", Link[p->index].ID, Varword[p->variable],
                Operator[p->relop], Value[p->status]);
      else if (Link[p->index].Type == EN_PUMP)
        fprintf(f, "PUMP %s %s %s %s", Link[p->index].ID, Varword[p->variable],
                Operator[p->relop], Value[p->status]);
      else
        fprintf(f, "VALVE %s %s %s %s", Link[p->index].ID, Varword[p->variable],
                Operator[p->relop], Value[p->status]);
    }
  } else {
    if (p->variable == r_TIME) {
      hours = (int)p->value / 3600;
      minutes = (int)((p->value - 3600 * hours) / 60);
      seconds = (int)(p->value - 3600 * hours - minutes * 60);
      fprintf(f, "\nIF %s %s %s %d:%02d:%02d", Object[p->object],
              Varword[p->variable], Operator[p->relop], hours, minutes,
              seconds);
    } else {
      if (p->variable == r_CLOCKTIME) {
        hours = (int)p->value / 3600;
        minutes = (int)((p->value - 3600 * hours) / 60);
        seconds = (int)(p->value - 3600 * hours - minutes * 60);

        if (hours < 12)
          fprintf(f, "\nIF %s %s %s %d:%02d:%02d AM", Object[p->object],
                  Varword[p->variable], Operator[p->relop], hours, minutes,
                  seconds);
        else
          fprintf(f, "\nIF %s %s %s %d:%02d:%02d PM", Object[p->object],
                  Varword[p->variable], Operator[p->relop], hours - 12, minutes,
                  seconds);
      } else {
        if (p->variable == r_FILLTIME || p->variable == r_DRAINTIME)
          fprintf(f, "\nIF TANK %s %s %s %.4lf", Node[p->index].ID,
                  Varword[p->variable], Operator[p->relop], p->value / 3600.0);
        else {
          fprintf(f, "\nIF ");
          if ((strncmp(Object[p->object], "NODE", 4) == 0) ||
              (strncmp(Object[p->object], "Junc", 4) == 0) ||
              (strncmp(Object[p->object], "Reser", 5) == 0) ||
              (strncmp(Object[p->object], "Tank", 4) == 0)) {
            if (p->index <= net->Njuncs)
              fprintf(f, "JUNCTION %s %s %s %.4lf", Node[p->index].ID,
                      Varword[p->variable], Operator[p->relop], p->value);
            else if (Tank[p->index - net->Njuncs].A == 0.0)
              fprintf(f, "RESERVOIR %s %s %s %.4lf", Node[p->index].ID,
                      Varword[p->variable], Operator[p->relop], p->value);
            else
              fprintf(f, "TANK %s %s %s %.4lf", Node[p->index].ID,
                      Varword[p->variable], Operator[p->relop], p->value);
          } else { // it is a link
            if (Link[p->index].Type == EN_PIPE ||
                Link[p->index].Type == EN_CVPIPE)
              fprintf(f, "PIPE %s %s %s %.4lf", Link[p->index].ID,
                      Varword[p->variable], Operator[p->relop], p->value);
            else if (Link[p->index].Type == EN_PUMP)
              fprintf(f, "PUMP %s %s %s %.4lf", Link[p->index].ID,
                      Varword[p->variable], Operator[p->relop], p->value);
            else
              fprintf(f, "VALVE %s %s %s %.4lf", Link[p->index].ID,
                      Varword[p->variable], Operator[p->relop], p->value);
          }
        }
      }
    }
  }

  p = p->next;
  while (p != NULL) // for all other premises/conditions write the corresponding
                    // logicOperator
  {
    if (p->value == MISSING) {
      fprintf(f, "\n%s ", Ruleword[p->logop]);
      if ((strncmp(Object[p->object], "NODE", 4) == 0) ||
          (strncmp(Object[p->object], "Junc", 4) == 0) ||
          (strncmp(Object[p->object], "Reser", 5) == 0) ||
          (strncmp(Object[p->object], "Tank", 4) == 0)) {
        if (p->index <= net->Njuncs)
          fprintf(f, "JUNCTION %s %s %s %s", Node[p->index].ID,
                  Varword[p->variable], Operator[p->relop], Value[p->status]);
        else if (Tank[p->index - net->Njuncs].A == 0.0)
          fprintf(f, "RESERVOIR %s %s %s %s", Node[p->index].ID,
                  Varword[p->variable], Operator[p->relop], Value[p->status]);
        else
          fprintf(f, "TANK %s %s %s %s", Node[p->index].ID,
                  Varword[p->variable], Operator[p->relop], Value[p->status]);
      } else { // it is a link
        if (Link[p->index].Type == EN_PIPE || Link[p->index].Type == EN_CVPIPE)
          fprintf(f, "PIPE %s %s %s %s", Link[p->index].ID,
                  Varword[p->variable], Operator[p->relop], Value[p->status]);
        else if (Link[p->index].Type == EN_PUMP)
          fprintf(f, "PUMP %s %s %s %s", Link[p->index].ID,
                  Varword[p->variable], Operator[p->relop], Value[p->status]);
        else
          fprintf(f, "VALVE %s %s %s %s", Link[p->index].ID,
                  Varword[p->variable], Operator[p->relop], Value[p->status]);
      }
    } else {
      if (p->variable == r_TIME) {
        hours = (int)p->value / 3600;
        minutes = (int)((p->value - 3600 * hours) / 60);
        seconds = (int)(p->value - 3600 * hours - minutes * 60);
        fprintf(f, "\n%s %s %s %s %d:%02d:%02d", Ruleword[p->logop],
                Object[p->object], Varword[p->variable], Operator[p->relop],
                hours, minutes, seconds);
      } else {
        if (p->variable == r_CLOCKTIME) {
          hours = (int)p->value / 3600;
          minutes = (int)((p->value - 3600 * hours) / 60);
          seconds = (int)(p->value - 3600 * hours - minutes * 60);

          if (hours < 12)
            fprintf(f, "\n%s %s %s %s %d:%02d:%02d AM", Ruleword[p->logop],
                    Object[p->object], Varword[p->variable], Operator[p->relop],
                    hours, minutes, seconds);
          else
            fprintf(f, "\n%s %s %s %s %d:%02d:%02d PM", Ruleword[p->logop],
                    Object[p->object], Varword[p->variable], Operator[p->relop],
                    hours - 12, minutes, seconds);
        } else {
          if (p->variable == r_FILLTIME || p->variable == r_DRAINTIME)
            fprintf(f, "\n%s TANK %s %s %s %s %.4lf", Ruleword[p->logop],
                    Object[p->object], Node[p->index].ID, Varword[p->variable],
                    Operator[p->relop], p->value / 3600.0);
          else {
            fprintf(f, "\n%s ", Ruleword[p->logop]);
            if ((strncmp(Object[p->object], "NODE", 4) == 0) ||
                (strncmp(Object[p->object], "Junc", 4) == 0) ||
                (strncmp(Object[p->object], "Reser", 5) == 0) ||
                (strncmp(Object[p->object], "Tank", 4) == 0)) {
              if (p->index <= net->Njuncs)
                fprintf(f, "JUNCTION %s %s %s %.4lf", Node[p->index].ID,
                        Varword[p->variable], Operator[p->relop], p->value);
              else if (Tank[p->index - net->Njuncs].A == 0.0)
                fprintf(f, "RESERVOIR %s %s %s %.4lf", Node[p->index].ID,
                        Varword[p->variable], Operator[p->relop], p->value);
              else
                fprintf(f, "TANK %s %s %s %.4lf", Node[p->index].ID,
                        Varword[p->variable], Operator[p->relop], p->value);
            } else { // it is a link
              if (Link[p->index].Type == EN_PIPE ||
                  Link[p->index].Type == EN_CVPIPE)
                fprintf(f, "PIPE %s %s %s %.4lf", Link[p->index].ID,
                        Varword[p->variable], Operator[p->relop], p->value);
              else if (Link[p->index].Type == EN_PUMP)
                fprintf(f, "PUMP %s %s %s %.4lf", Link[p->index].ID,
                        Varword[p->variable], Operator[p->relop], p->value);
              else
                fprintf(f, "VALVE %s %s %s %.4lf", Link[p->index].ID,
                        Varword[p->variable], Operator[p->relop], p->value);
            }
          }
        }
      }
    }
    p = p->next;
  }

  a = rules->Rule[RuleIdx].Tchain; // The first action in hte list of true actions
                            // starts with THEN
  if (a->setting == MISSING) {
    if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
      fprintf(f, "\nTHEN PIPE %s STATUS IS %s", Link[a->link].ID,
              Value[a->status]);
    else if (Link[a->link].Type == EN_PUMP)
      fprintf(f, "\nTHEN PUMP %s STATUS IS %s", Link[a->link].ID,
              Value[a->status]);
    else
      fprintf(f, "\nTHEN VALVE %s STATUS IS %s", Link[a->link].ID,
              Value[a->status]);
  } else {
    if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
      fprintf(f, "\nTHEN PIPE %s SETTING IS %.4f", Link[a->link].ID,
              a->setting);
    else if (Link[a->link].Type == EN_PUMP)
      fprintf(f, "\nTHEN PUMP %s SETTING IS %.4f", Link[a->link].ID,
              a->setting);
    else
      fprintf(f, "\nTHEN VALVE %s SETTING IS %.4f", Link[a->link].ID,
              a->setting);
  }

  a = a->next; // The other actions in the list of true actions start with AND
  while (a != NULL) {
    if (a->setting == MISSING) {
      if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
        fprintf(f, "\nAND PIPE %s STATUS IS %s", Link[a->link].ID,
                Value[a->status]);
      else if (Link[a->link].Type == EN_PUMP)
        fprintf(f, "\nAND PUMP %s STATUS IS %s", Link[a->link].ID,
                Value[a->status]);
      else
        fprintf(f, "\nAND VALVE %s STATUS IS %s", Link[a->link].ID,
                Value[a->status]);
    } else {
      if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
        fprintf(f, "\nAND PIPE %s SETTING IS %.4f", Link[a->link].ID,
                a->setting);
      else if (Link[a->link].Type == EN_PUMP)
        fprintf(f, "\nAND PUMP %s SETTING IS %.4f", Link[a->link].ID,
                a->setting);
      else
        fprintf(f, "\nAND VALVE %s SETTING IS %.4f", Link[a->link].ID,
                a->setting);
    }

    a = a->next;
  }

  a = rules->Rule[RuleIdx].Fchain; // The first action in the list of false actions
                            // starts with ELSE
  if (a != NULL) {
    if (a->setting == MISSING) {
      if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
        fprintf(f, "\nELSE PIPE %s  STATUS IS %s", Link[a->link].ID,
                Value[a->status]);
      else if (Link[a->link].Type == EN_PUMP)
        fprintf(f, "\nELSE PUMP %s  STATUS IS %s", Link[a->link].ID,
                Value[a->status]);
      else
        fprintf(f, "\nELSE VALVE %s  STATUS IS %s", Link[a->link].ID,
                Value[a->status]);
    } else {
      if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
        fprintf(f, "\nELSE PIPE %s SETTING IS %.4f", Link[a->link].ID,
                a->setting);
      else if (Link[a->link].Type == EN_PUMP)
        fprintf(f, "\nELSE PUMP %s SETTING IS %.4f", Link[a->link].ID,
                a->setting);
      else
        fprintf(f, "\nELSE VALVE %s SETTING IS %.4f", Link[a->link].ID,
                a->setting);
    }

    a = a->next; // The other actions in the list of false actions start with
                 // AND
    while (a != NULL) {
      if (a->setting == MISSING) {
        if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
          fprintf(f, "\nAND PIPE %s  STATUS IS %s", Link[a->link].ID,
                  Value[a->status]);
        else if (Link[a->link].Type == EN_PUMP)
          fprintf(f, "\nAND PUMP %s  STATUS IS %s", Link[a->link].ID,
                  Value[a->status]);
        else
          fprintf(f, "\nAND VALVE %s  STATUS IS %s", Link[a->link].ID,
                  Value[a->status]);
      } else {
        if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
          fprintf(f, "\nAND PIPE %s SETTING IS %.4f", Link[a->link].ID,
                  a->setting);
        else if (Link[a->link].Type == EN_PUMP)
          fprintf(f, "\nAND PUMP %s SETTING IS %.4f", Link[a->link].ID,
                  a->setting);
        else
          fprintf(f, "\nAND VALVE %s SETTING IS %.4f", Link[a->link].ID,
                  a->setting);
      }

      a = a->next;
    }
  }
  if (rules->Rule[RuleIdx].priority != 0)
    fprintf(f, "\nPRIORITY %.4f", rules->Rule[RuleIdx].priority);

  return (0);
}

/***************** END OF RULES.C ******************/
