/*
 ******************************************************************************
 Project:      OWA EPANET
 Version:      2.3
 Module:       rules.c
 Description:  implements rule-based controls
 Authors:      see AUTHORS
 Copyright:    see AUTHORS
 License:      see LICENSE
 Last Updated: 02/11/2025
 ******************************************************************************
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "types.h"
#include "funcs.h"
#include "hash.h"
#include "text.h"

#ifdef _WIN32
#define snprintf _snprintf
#endif

enum Rulewords {
  r_RULE,
  r_IF,
  r_AND,
  r_OR,
  r_THEN,
  r_ELSE,
  r_PRIORITY,
  r_DISABLED,
  r_ERROR
};
char *Ruleword[] = {w_RULE, w_IF,   w_AND,      w_OR,
                    w_THEN, w_ELSE, w_PRIORITY, w_DISABLED, 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};

// Local functions
static void newrule(Project *);
static int  newpremise(Project *, int);
static int  newaction(Project *);
static int  newpriority(Project *);

static int  evalpremises(Project *, int);
static int  checkpremise(Project *, Spremise *);
static int  checktime(Project *, Spremise *);
static int  checkstatus(Project *, Spremise *);
static int  checkvalue(Project *, Spremise *);

static int  onactionlist(Project *, int, Saction *);
static void updateactionlist(Project *, int, Saction *);
static int  takeactions(Project *);
static void clearactionlist(Rules *);
static void clearrule(Project *, int);

static void writepremise(Spremise *, FILE *, Network *);
static void writeaction(Saction *, FILE *, Network *);
static void getobjtxt(int, int, char *);
static void gettimetxt(double, char *);


void initrules(Project *pr)
//--------------------------------------------------------------
//    Initializes rule base.
//--------------------------------------------------------------
{
    pr->rules.RuleState = r_PRIORITY;
    pr->rules.LastPremise = NULL;
    pr->rules.LastThenAction = NULL;
    pr->rules.LastElseAction = NULL;
    pr->rules.ActionList = NULL;
    pr->network.Rule = NULL;
}

void addrule(Parser *parser, char *tok)
//--------------------------------------------------------------
//    Updates rule count if RULE keyword found in line of input.
//--------------------------------------------------------------
{
    if (match(tok, w_RULE)) parser->MaxRules++;
}

void deleterule(Project *pr, int index)
//-----------------------------------------------------------
//    Deletes a specific rule
//-----------------------------------------------------------
{
    Network *net = &pr->network;

    int i;
    Srule *lastRule;

    // Free memory allocated to rule's premises & actions
    clearrule(pr, index);

    // Shift position of higher indexed rules down one
    for (i = index; i <= net->Nrules - 1; i++)
    {
        net->Rule[i] = net->Rule[i + 1];
    }

    // Remove premises & actions from last (inactive) entry in Rule array
    lastRule = &net->Rule[net->Nrules];
    lastRule->Premises = NULL;
    lastRule->ThenActions = NULL;
    lastRule->ElseActions = NULL;

    // Reduce active rule count by one
    net->Nrules--;
}

int allocrules(Project *pr)
//--------------------------------------------------------------
//    Allocates memory for rule-based controls.
//--------------------------------------------------------------
{
    Network *net = &pr->network;
    int n = pr->parser.MaxRules + 1;

    net->Rule = (Srule *)calloc(n, sizeof(Srule));
    if (net->Rule == NULL) return 101;
    return 0;
}

void freerules(Project *pr)
//--------------------------------------------------------------
//    Frees memory used for rule-based controls.
//--------------------------------------------------------------
{
    int i;

    // Already freed
    if (pr->network.Rule == NULL)
        return;

    for (i = 1; i <= pr->network.Nrules; i++) clearrule(pr, i);
    free(pr->network.Rule);
    pr->network.Rule = NULL;
}

int ruledata(Project *pr)
//--------------------------------------------------------------
//    Parses a line from [RULES] section of input.
//--------------------------------------------------------------
{
    Network *net = &pr->network;
    Parser  *parser = &pr->parser;
    Rules   *rules = &pr->rules;

    int key,      // Keyword code
        err;
    char **Tok = parser->Tok;  // Tokenized line of a rule statement

    // 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:
          // Missing the rule label
          if (parser->Ntokens != 2)
          {
              err = 201;
              break;
          }
          net->Nrules++;
          newrule(pr);
          rules->RuleState = r_RULE;
          rules->Errcode = 0;
          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;

        case r_DISABLED:
          if (rules->RuleState != r_THEN && rules->RuleState != r_ELSE &&
              rules->RuleState != r_PRIORITY)
          {
              err = 221; 
              break;
          }
          net->Rule[net->Nrules].isEnabled = FALSE;
          break;

        default:
          err = 201;
    }

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

void ruleerrmsg(Project *pr)
//-----------------------------------------------------------
//    Report a rule parsing error message
//-----------------------------------------------------------
{
    Network *net = &pr->network;
    Parser  *parser = &pr->parser;
    Rules   *rules = &pr->rules;

    int i;
    char label[MAXMSG + 1];
    char msg[MAXLINE + 1];
    char **Tok = parser->Tok;

    // Get text of error message
    switch (rules->Errcode)
    {
    case 201: strcpy(msg, R_ERR201); break;
    case 202: strcpy(msg, R_ERR202); break;
    case 203: strcpy(msg, R_ERR203); break;
    case 204: strcpy(msg, R_ERR204); break;
    case 207: strcpy(msg, R_ERR207); break;
    case 221: strcpy(msg, R_ERR221); break;
    default: return;
    }

    // Get label of rule being parsed
    if (net->Nrules > 0)
    {
        strncpy(label, t_RULE, MAXMSG);
        strncat(label, " ", MAXMSG);
        strncat(label, net->Rule[net->Nrules].label, MAXMSG);
    }
    else strncpy(label, t_RULES_SECT, MAXMSG);

    // Write rule label and error message to status report
    snprintf(pr->Msg, MAXMSG, "%s", msg);
    strncat(pr->Msg, label, MAXMSG);
    strncat(pr->Msg, ":", MAXMSG);
    writeline(pr, pr->Msg);

    // Write text of rule clause being parsed to status report
    strcpy(msg, Tok[0]);
    for (i = 1; i < parser->Ntokens; i++)
    {
        strncat(msg, " ", MAXLINE);
        strncat(msg, Tok[i], MAXLINE);
    }
    writeline(pr, msg);
}

void adjustrules(Project *pr, int objtype, int index)
//-----------------------------------------------------------
//    Adjusts rules when a specific node or link is deleted.
//-----------------------------------------------------------
{
    Network *net = &pr->network;

    int i, delete;
    Spremise *p;
    Saction *a;

    // Delete rules that refer to objtype and index
    for (i = net->Nrules; i >= 1; i--)
    {
        delete = FALSE;
        p = net->Rule[i].Premises;
        while (p != NULL && !delete)
        {
            if (objtype == p->object && p->index == index) delete = TRUE;
            p = p->next;
        }
        if (objtype == r_LINK)
        {
            a = net->Rule[i].ThenActions;
            while (a != NULL && !delete)
            {
                if (a->link == index) delete = TRUE;
                a = a->next;
            }
            a = net->Rule[i].ElseActions;
            while (a != NULL && !delete)
            {
                if (a->link == index) delete = TRUE;
                a = a->next;
            }
        }
        if (delete) deleterule(pr, i);
    }

    // Adjust all higher object indices to reflect deletion of object index
    for (i = 1; i <= net->Nrules; i++)
    {
        p = net->Rule[i].Premises;
        while (p != NULL)
        {
            if (objtype == p->object && p->index > index) p->index--;
            p = p->next;
        }
        if (objtype == r_LINK)
        {
            a = net->Rule[i].ThenActions;
            while (a != NULL)
            {
                if (a->link > index) a->link--;
                a = a->next;
            }
            a = net->Rule[i].ElseActions;
            while (a != NULL)
            {
                if (a->link > index) a->link--;
                a = a->next;
            }
        }
    }
}

void adjusttankrules(Project *pr, int ndiff)
//-----------------------------------------------------------
//    Adjusts tank indices in rule premises.
//-----------------------------------------------------------
{
    Network *net = &pr->network;

    int i, njuncs;
    Spremise *p;

    njuncs = net->Njuncs;
    for (i = 1; i <= net->Nrules; i++)
    {
        p = net->Rule[i].Premises;
        while (p != NULL)
        {
            if (p->object == r_NODE && p->index > njuncs)
                p->index += ndiff;
            p = p->next;
        }
    }
}

Spremise *getpremise(Spremise *premises, int i)
//----------------------------------------------------------
//    Return the i-th premise in a rule
//----------------------------------------------------------
{
    int count = 0;
    Spremise *p;

    p = premises;
    while (p != NULL)
    {
        count++;
        if (count == i) break;
        p = p->next;
    }
    return p;
}

Saction *getaction(Saction *actions, int i)
//----------------------------------------------------------
//    Return the i-th action from a rule's action list
//----------------------------------------------------------
{
    int count = 0;
    Saction *a;

    a = actions;
    while (a != NULL)
    {
        count++;
        if (count == i) break;
        a = a->next;
    }
    return a;
}

int writerule(Project *pr, FILE *f, int ruleIndex)
//-----------------------------------------------------------------------------
//  Write a rule to an INP file.
//-----------------------------------------------------------------------------
{
    Network  *net = &pr->network;

    Srule      *rule = &net->Rule[ruleIndex];
    Spremise   *p;
    Saction    *a;
    
    // Write each premise clause to the file
    p = rule->Premises;
    fprintf(f, "\nIF   ");
    while (p != NULL)
    {
        writepremise(p, f, net);
        p = p->next;
        if (p) fprintf(f, "\n%-5s", Ruleword[p->logop]);
    }

    // Write each THEN action clause to the file
    a = rule->ThenActions;
    if (a) fprintf(f, "\nTHEN ");
    while (a != NULL)
    {
        writeaction(a, f, net);
        a = a->next;
        if (a) fprintf(f, "\nAND  ");
    }

    // Write each ELSE action clause to the file
    a = rule->ElseActions;
    if (a) fprintf(f, "\nELSE ");
    while (a != NULL)
    {
        writeaction(a, f, net);
        a = a->next;
        if (a) fprintf(f, "\nAND  ");
    }

    // Write the rule's priority to the file
    if (rule->priority > 0) fprintf(f, "\nPRIORITY %f", rule->priority);
    return 0;
}

int checkrules(Project *pr, long dt)
//-----------------------------------------------------
//    Checks which rules should fire at current time.
//-----------------------------------------------------
{
    Network *net = &pr->network;
    Times   *time = &pr->times;
    Rules   *rules = &pr->rules;

    int i;
    int actionCount = 0;    // Number of actions actually taken

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

    // Iterate through each rule
    rules->ActionList = NULL;
    for (i = 1; i <= net->Nrules; i++)
    {
        // skip if the rule is disabled
        if (!net->Rule[i].isEnabled)
        {
            continue;
        }
        // If premises true, add THEN clauses to action list
        if (evalpremises(pr, i) == TRUE)
        {
            updateactionlist(pr, i, net->Rule[i].ThenActions);
        }

        // If premises false, add ELSE actions to list
        else
        {
            if (net->Rule[i].ElseActions != NULL)
            {
                updateactionlist(pr, i, net->Rule[i].ElseActions);
            }
        }
    }

    // Execute actions then clear action list
    if (rules->ActionList != NULL) actionCount = takeactions(pr);
    clearactionlist(rules);
    return actionCount;
}

void updateruleunits(Project *pr, double dcf, double pcf, double hcf, double qcf)
//-----------------------------------------------------------
//    Updates the units of a rule's premises and actions.
//-----------------------------------------------------------
{
    Network *net = &pr->network;
    Slink  *Link = net->Link;

    int i, k;
    double x;
    Spremise *p;
    Saction *a;
    
    for (i = 1; i <= net->Nrules; i++)
    {
        p = net->Rule[i].Premises;
        while (p != NULL)
        {

            switch (p->variable)
            {
            case r_DEMAND:
                p->value *= dcf;
                break;

            case r_HEAD:
            case r_GRADE:
                p->value *= hcf;
                break;

            case r_PRESSURE:
                p->value *= pcf;
                break;

            case r_LEVEL:
                p->value *= hcf;
                break;

            case r_FLOW:
                p->value *= qcf;
                break;

            case r_SETTING:

                switch (Link[p->index].Type)
                {
                case PRV:
                case PSV:
                case PBV:
                    p->value *= pcf;
                    break;
                case FCV:
                    p->value *= qcf;
                    break;
                default:
                    break;
                }
                break;
            
            default:
                break;

            }
            p = p->next;
        }

        a = net->Rule[i].ThenActions;
        while (a != NULL)
        {
            k = a->link;
            x = a->setting;

            // Change link's setting
            if (x != MISSING)
            {
                switch (net->Link[k].Type)
                {
                case PRV:
                case PSV:
                case PBV:
                    a->setting *= pcf;
                    break;
                case FCV:
                    a->setting *= qcf;
                    break;
                default:
                    break;
                }
            }
            a = a->next;
        }
        a = net->Rule[i].ElseActions;
        while (a != NULL)
        {
            k = a->link;
            x = a->setting;

            // Change link's setting
            if (x != MISSING)
            {
                switch (net->Link[k].Type)
                {
                case PRV:
                case PSV:
                case PBV:
                    a->setting *= pcf;
                    break;
                case FCV:
                    a->setting *= qcf;
                    break;
                default:
                    break;
                }
            }
            a = a->next;
        }
    }
}


void newrule(Project *pr)
//----------------------------------------------------------
//    Adds a new rule to the project
//----------------------------------------------------------
{
    Network *net = &pr->network;

    char **Tok = pr->parser.Tok;
    Srule *rule = &net->Rule[net->Nrules];

    strncpy(rule->label, Tok[1], MAXID);
    rule->Premises = NULL;
    rule->ThenActions = NULL;
    rule->ElseActions = NULL;
    rule->priority = 0.0;
    rule->isEnabled = TRUE;
    pr->rules.LastPremise = NULL;
    pr->rules.LastThenAction = NULL;
    pr->rules.LastElseAction = NULL;
}

int newpremise(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)
//---------------------------------------------------------------------
{
    Network *net = &pr->network;
    Parser  *parser = &pr->parser;
    Rules   *rules = &pr->rules;

    int i, j, k, m, r, s, v;
    double x;
    char **Tok = parser->Tok;
    Spremise *p;

    // Check for correct number of tokens
    if (parser->Ntokens != 5 && parser->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 (parser->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[parser->Ntokens - 1], Value)) > IS_NUMBER) s = k;
    else
    {
        if (!getfloat(Tok[parser->Ntokens - 1], &x))
        return (202);
        if (v == r_FILLTIME || v == r_DRAINTIME) x = x * 3600.0;
    }

    // Create new premise structure
    p = (Spremise *)malloc(sizeof(Spremise));
    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->LastPremise == NULL) net->Rule[net->Nrules].Premises = p;
    else rules->LastPremise->next = p;
    rules->LastPremise = p;
    return 0;
}

int newaction(Project *pr)
//----------------------------------------------------------
//   Adds new action to current rule.
//   Format is:
//      THEN/ELSE/AND LINK <id> <variable> IS <value>
//----------------------------------------------------------
{
    Network *net = &pr->network;
    Parser  *parser = &pr->parser;
    Rules   *rules = &pr->rules;

    int j, k, s;
    double x;
    Saction *a;
    char **Tok = parser->Tok;

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

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

    // Cannot control a CV
    if (net->Link[j].Type == 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;
    }

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

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

    // Create a new action structure
    a = (Saction *)malloc(sizeof(Saction));
    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->LastThenAction == NULL)
        {
            net->Rule[net->Nrules].ThenActions = a;
        }
        else rules->LastThenAction->next = a;
        rules->LastThenAction = a;
    }
    else
    {
        a->next = NULL;
        if (rules->LastElseAction == NULL)
        {
            net->Rule[net->Nrules].ElseActions = a;
        }
        else rules->LastElseAction->next = a;
        rules->LastElseAction = a;
    }
    return 0;
}

int newpriority(Project *pr)
//---------------------------------------------------
//    Adds priority rating to current rule
//---------------------------------------------------
{
    Network *net = &pr->network;

    double x;
    char **Tok = pr->parser.Tok;

    if (!getfloat(Tok[1], &x)) return 202;
    net->Rule[net->Nrules].priority = x;
    return 0;
}

int evalpremises(Project *pr, int i)
//----------------------------------------------------------
//    Checks if premises to rule i are true
//----------------------------------------------------------
{
    Network *net = &pr->network;

    int result;
    Spremise *p;

    result = TRUE;
    p = net->Rule[i].Premises;
    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(Project *pr, Spremise *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(Project *pr, Spremise *p)
//------------------------------------------------------------
//    Checks if condition on system time holds
//------------------------------------------------------------
{
    Times *time = &pr->times;
    Rules *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(Project *pr, Spremise *p)
//------------------------------------------------------------
//    Checks if condition on link status holds
//------------------------------------------------------------
{
    Hydraul *hyd = &pr->hydraul;

    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(Project *pr, Spremise *p)
//----------------------------------------------------------
//    Checks if numerical condition on a variable is true.
//    Uses tolerance of 0.001 when testing conditions.
//----------------------------------------------------------
{
    Network *net = &pr->network;
    Hydraul *hyd = &pr->hydraul;

    int     i, j, v;
    double  x,            // A variable's value
            tol = 1.e-3;  // Equality tolerance
    int    Njuncs = net->Njuncs;
    double *Ucf = pr->Ucf;
    double *NodeDemand = hyd->NodeDemand;
    double *LinkFlow = hyd->LinkFlow;
    double *LinkSetting = hyd->LinkSetting;
    Snode  *Node = net->Node;
    Slink  *Link = net->Link;
    Stank  *Tank = net->Tank;

    // Find the value being checked
    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(LinkFlow[i]) * Ucf[FLOW];
        break;

      case r_SETTING:
        if (LinkSetting[i] == MISSING) return 0;
        x = LinkSetting[i];
        switch (Link[i].Type)
        {
          case PRV:
          case PSV:
          case PBV:
            x = x * Ucf[PRESSURE];
            break;
          case 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;
    }

    // Compare value x against the premise
    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 updateactionlist(Project *pr, int i, Saction *actions)
//---------------------------------------------------
//    Adds rule's actions to action list
//--------------------------------------------------
{
    Rules *rules = &pr->rules;

    SactionList *actionItem;
    Saction *a;

    // Iterate through each action of Rule i
    a = actions;
    while (a != NULL)
    {
        // Add action to list if its link not already on it
        if (!onactionlist(pr, i, a))
        {
            actionItem = (SactionList *)malloc(sizeof(SactionList));
            if (actionItem != NULL)
            {
                actionItem->action = a;
                actionItem->ruleIndex = i;
                actionItem->next = rules->ActionList;
                rules->ActionList = actionItem;
            }
        }
        a = a->next;
    }
}

int onactionlist(Project *pr, int i, Saction *a)
//-----------------------------------------------------------------------------
//  Checks if action a from rule i can be added to the action list
//-----------------------------------------------------------------------------
{
    Network *net = &pr->network;

    int link, i1;
    SactionList *actionItem;
    Saction *a1;

    // Search action list for link included in action a
    link = a->link;
    actionItem = pr->rules.ActionList;
    while (actionItem != NULL)
    {
        a1 = actionItem->action;
        i1 = actionItem->ruleIndex;

        // Link appears in list
        if (link == a1->link)
        {
            // Replace its action with 'a' if rule i has higher priority
            if (net->Rule[i].priority > net->Rule[i1].priority)
            {
                actionItem->action = a;
                actionItem->ruleIndex = i;
            }

            // Return indicating that 'a' should not be added to action list
            return 1;
        }
        actionItem = actionItem->next;
    }

    // Return indicating that it's ok to add 'a' to the action list
    return 0;
}

int takeactions(Project *pr)
//-----------------------------------------------------------
//    Implements actions on action list
//-----------------------------------------------------------
{
    Network *net = &pr->network;
    Hydraul *hyd = &pr->hydraul;
    Report  *rpt = &pr->report;
    Rules   *rules = &pr->rules;

    char flag;
    int k, s, n;
    double tol = 1.e-3, v, x;
    Saction *a;
    SactionList *actionItem;

    n = 0;
    actionItem = rules->ActionList;
    while (actionItem != NULL)
    {
        flag = FALSE;
        a = actionItem->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 PRV:
              case PSV:
              case PBV:
                x = x / pr->Ucf[PRESSURE];
                break;
              case 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 (rpt->Statflag)
            {
                writeruleaction(pr, k, net->Rule[actionItem->ruleIndex].label);
            }
        }

        // Move to next action on list
        actionItem = actionItem->next;
    }
    return n;
}

void clearactionlist(Rules *rules)
//----------------------------------------------------------
//    Clears memory used for action list
//----------------------------------------------------------
{
    SactionList *nextItem;
    SactionList *actionItem;
    actionItem = rules->ActionList;
    while (actionItem != NULL)
    {
        nextItem = actionItem->next;
        free(actionItem);
        actionItem = nextItem;
    }
}

void clearrule(Project *pr, int i)
//-----------------------------------------------------------
//  Clears memory used by a rule for premises & actions
//-----------------------------------------------------------
{
    Network *net = &pr->network;

    Spremise *p;
    Spremise *pnext;
    Saction *a;
    Saction *anext;

    p = net->Rule[i].Premises;
    while (p != NULL)
    {
        pnext = p->next;
        free(p);
        p = pnext;
    }
    a = net->Rule[i].ThenActions;
    while (a != NULL)
    {
        anext = a->next;
        free(a);
        a = anext;
    }
    a = net->Rule[i].ElseActions;
    while (a != NULL)
    {
        anext = a->next;
        free(a);
        a = anext;
    }
}


void writepremise(Spremise *p, FILE *f, Network *net)
//-----------------------------------------------------------------------------
//  Write a rule's premise clause to an INP file.
//-----------------------------------------------------------------------------
{
    char s_obj[20];
    char s_id[MAXID + 1];
    char s_value[20];
    int  subtype;

    // Get the type name & ID of object referred to in the premise
    if (p->object == r_NODE)
    {
        subtype = net->Node[p->index].Type;
        getobjtxt(r_NODE, subtype, s_obj);
        strcpy(s_id, net->Node[p->index].ID);
    }
    else if (p->object == r_LINK)
    {
        subtype = net->Link[p->index].Type;
        getobjtxt(r_LINK, subtype, s_obj);
        strcpy(s_id, net->Link[p->index].ID);
    }
    else
    {
        strcpy(s_obj, "SYSTEM");
        strcpy(s_id, "");
    }

    // If premise has no value field, use its status field as a value
    if (p->value == MISSING) strcpy(s_value, Value[p->status]);

    // Otherwise get text of premise's value field
    else
    {
        // For time values convert from seconds to hr:min:sec
        switch (p->variable)
        {
        case r_CLOCKTIME:
        case r_DRAINTIME:
        case r_FILLTIME:
        case r_TIME:
            gettimetxt(p->value, s_value);
            break;
        default: sprintf(s_value, "%.4f", p->value);
        }
    }

    // Write the premise clause to the file
    fprintf(f, "%s %s %s %s %s", s_obj, s_id, Varword[p->variable],
            Operator[p->relop], s_value);
}

void writeaction(Saction *a, FILE *f, Network *net)
//-----------------------------------------------------------------------------
//  Write a rule's action clause to an INP file.
//-----------------------------------------------------------------------------
{
    char s_id[MAXID + 1];
    char s_obj[20];
    char s_var[20];
    char s_value[20];
    int subtype;

    subtype = net->Link[a->link].Type;
    getobjtxt(r_LINK, subtype, s_obj);
    strcpy(s_id, net->Link[a->link].ID);
    if (a->setting == MISSING)
    {
        strcpy(s_var, "STATUS");
        strcpy(s_value, Value[a->status]);
    }
    else
    {
        strcpy(s_var, "SETTING");
        sprintf(s_value, "%.4f", a->setting);
    }
    fprintf(f, "%s %s %s = %s", s_obj, s_id, s_var, s_value);
}

void getobjtxt(int objtype, int subtype, char *objtxt)
//-----------------------------------------------------------------------------
//  Retrieve the text label for a specific type of object.
//-----------------------------------------------------------------------------
{
    if (objtype == r_NODE)
    {
        switch (subtype)
        {
            case JUNCTION:  strcpy(objtxt, "JUNCTION"); break;
            case RESERVOIR: strcpy(objtxt, "RESERVOIR"); break;
            case TANK:      strcpy(objtxt, "TANK"); break;
            default:        strcpy(objtxt, "NODE");
        }
    }
    else if (objtype == r_LINK)
    {
        switch (subtype)
        {
        case CVPIPE:
        case PIPE:  strcpy(objtxt, "PIPE"); break;
        case PUMP:  strcpy(objtxt, "PUMP"); break;
        default:    strcpy(objtxt, "VALVE");
        }
    }
    else strcpy(objtxt, "SYSTEM");
}

void gettimetxt(double secs, char *timetxt)
//-----------------------------------------------------------------------------
//  Convert number of seconds to a text string in hrs:min:sec format.
//-----------------------------------------------------------------------------
{
    int hours = 0, minutes = 0, seconds = 0;
    hours = (int)secs / 3600;
    if (hours > 24 * 7) sprintf(timetxt, "%.4f", secs / 3600.0);
    else
    {
        minutes = (int)((secs - 3600 * hours) / 60);
        seconds = (int)(secs - 3600 * hours - minutes * 60);
        sprintf(timetxt, "%d:%02d:%02d", hours, minutes, seconds);
    }
}
