/*
*********************************************************************
                                                                   
REPORT.C -- Reporting Routines for EPANET Program                
                                                                    
VERSION:    2.00                                               
DATE:       5/30/00
            6/24/02
            8/15/07  (2.00.11)
            2/14/08  (2.00.12)
AUTHOR:     L. Rossman
            US EPA - NRMRL
                                                                    
This module contains various procedures (all beginning with       
'write') that are called from other modules to write formatted    
output to a report file.

It also contains function disconnected(), called from writehydwarn()
and writehyderr(), that checks if a hydraulic solution causes a
network to become disconnected. 

The function writeline(S) is used throughout to write a
formatted string S to the report file.                                          
                                                                    
********************************************************************
*/

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <time.h>
#include "hash.h"
#include "text.h"
#include "types.h"
#include "funcs.h"
#define  EXTERN  extern
#include "vars.h"

#define   MAXCOUNT 10     /* Max. # of disconnected nodes listed */
long      LineNum;        /* Current line number     */
long      PageNum;        /* Current page number     */
char      DateStamp[26];  /* Current date & time     */
char      Fprinterr;      /* File write error flag   */

/* Defined in enumstxt.h in EPANET.C */
extern char *NodeTxt[];
extern char *LinkTxt[];
extern char *StatTxt[];
extern char *TstatTxt[];
extern char *LogoTxt[];
extern char *RptFormTxt[];

typedef   REAL4 *Pfloat;
void      writenodetable(Pfloat *);
void      writelinktable(Pfloat *);


int  writereport()
/*
**------------------------------------------------------
**   Input:   none                                      
**   Output:  returns error code                        
**   Purpose: writes formatted output report to file    
**                                                      
**   Calls strcomp() from the EPANET.C module.                                            
**------------------------------------------------------
*/
{
   char  tflag;
   FILE  *tfile;
   int   errcode = 0;

   /* If no secondary report file specified then    */
   /* write formatted output to primary report file. */
   Fprinterr = FALSE;
   if (Rptflag && strlen(Rpt2Fname) == 0 && RptFile != NULL)
   {
      writecon(FMT17);
      writecon(Rpt1Fname);
      if (Energyflag) writeenergy();
      errcode = writeresults();
   }

   /* A secondary report file was specified */
   else if (strlen(Rpt2Fname) > 0)
   {

      /* If secondary report file has same name as either input */
      /* or primary report file then use primary report file.   */
      if (strcomp(Rpt2Fname,InpFname) ||
          strcomp(Rpt2Fname,Rpt1Fname))
      {
         writecon(FMT17);
         writecon(Rpt1Fname);
         if (Energyflag) writeenergy();
         errcode = writeresults();
      }

      /* Otherwise write report to secondary report file. */
      else
      {

         /* Try to open file */
         tfile = RptFile;
         tflag = Rptflag;
         if ((RptFile = fopen(Rpt2Fname,"wt")) == NULL)
         {
            RptFile = tfile;
            Rptflag = tflag;
            errcode = 303;
         }

         /* Write full formatted report to file */
         else
         {
            Rptflag = 1; 
            writecon(FMT17);
            writecon(Rpt2Fname);
            writelogo();
            if (Summaryflag) writesummary();
            if (Energyflag)  writeenergy();
            errcode = writeresults();
            fclose(RptFile);
            RptFile = tfile;
            Rptflag = tflag;
         }
      }
   }

   /* Special error handler for write-to-file error */
   if (Fprinterr) errmsg(309);
   return(errcode);
}                        /* End of writereport */


void  writelogo()
/*
**--------------------------------------------------------------
**   Input:   none                                                
**   Output:  none                                                
**   Purpose: writes program logo to report file.                 
**--------------------------------------------------------------
*/
{
   int    i;
   time_t timer;         /* time_t structure & functions time() & */
                         /* ctime() are defined in time.h         */
   time(&timer);
   strcpy(DateStamp,ctime(&timer));
   PageNum = 1;
   LineNum = 2;
   fprintf(RptFile,FMT18);
   fprintf(RptFile,"%s",DateStamp);
   for (i=0; LogoTxt[i] != NULL; i++) writeline(LogoTxt[i]);
   writeline("");
}                        /* End of writelogo */


void  writesummary()
/*
**--------------------------------------------------------------
**   Input:   none                                                
**   Output:  none                                                
**   Purpose: writes summary system information to report file    
**--------------------------------------------------------------
*/
{
   char s[MAXFNAME+1];
   int  i;
   int  nres = 0;

   for (i=0; i<3; i++)
   {
      if (strlen(Title[i]) > 0)
      {
         sprintf(s,"%-.70s",Title[i]);
         writeline(s);
      }
   }
   writeline(" ");
   sprintf(s,FMT19,InpFname);
   writeline(s);
   sprintf(s,FMT20,Njuncs);
   writeline(s);
   for (i=1; i<=Ntanks; i++)
      if (Tank[i].A == 0.0) nres++;
   sprintf(s,FMT21a,nres);
   writeline(s);
   sprintf(s,FMT21b,Ntanks-nres);
   writeline(s);
   sprintf(s,FMT22,Npipes);
   writeline(s);
   sprintf(s,FMT23,Npumps);
   writeline(s);
   sprintf(s,FMT24,Nvalves);
   writeline(s);
   sprintf(s,FMT25,RptFormTxt[Formflag]);
   writeline(s);
   sprintf(s,FMT26,Hstep*Ucf[TIME],Field[TIME].Units);
   writeline(s);
   sprintf(s,FMT27,Hacc);
   writeline(s);

   sprintf(s,FMT27a,CheckFreq);                                                //(2.00.12 - LR)
   writeline(s);                                                               //(2.00.12 - LR)
   sprintf(s,FMT27b,MaxCheck);                                                 //(2.00.12 - LR)
   writeline(s);                                                               //(2.00.12 - LR)
   sprintf(s,FMT27c,DampLimit);                                                //(2.00.12 - LR)
   writeline(s);                                                               //(2.00.12 - LR)

   sprintf(s,FMT28,MaxIter);
   writeline(s);
   if (Qualflag == NONE || Dur == 0.0)
      sprintf(s,FMT29);
   else if (Qualflag == CHEM)
      sprintf(s,FMT30,ChemName);
   else if (Qualflag == TRACE)
      sprintf(s,FMT31,Node[TraceNode].ID);
   else if (Qualflag == AGE)
      sprintf(s,FMT32);
   writeline(s);
   if (Qualflag != NONE && Dur > 0)
   {
      sprintf(s,FMT33,(float)Qstep/60.0);
      writeline(s);
      sprintf(s,FMT34,Ctol*Ucf[QUALITY],Field[QUALITY].Units);
      writeline(s);
   }
   sprintf(s,FMT36,SpGrav);
   writeline(s);
   sprintf(s,FMT37a,Viscos/VISCOS);
   writeline(s);
   sprintf(s,FMT37b,Diffus/DIFFUS);
   writeline(s);
   sprintf(s,FMT38,Dmult);
   writeline(s);
   sprintf(s,FMT39,Dur*Ucf[TIME],Field[TIME].Units);
   writeline(s);
   if (Rptflag)
   {
      sprintf(s,FMT40);
      writeline(s);
      if (Nodeflag == 0)  writeline(FMT41);
      if (Nodeflag == 1)  writeline(FMT42);
      if (Nodeflag == 2)  writeline(FMT43);
      writelimits(DEMAND,QUALITY);
      if (Linkflag == 0)  writeline(FMT44);
      if (Linkflag == 1)  writeline(FMT45);
      if (Linkflag == 2)  writeline(FMT46);
      writelimits(DIAM,HEADLOSS);
   }
   writeline(" ");
}                        /* End of writesummary */


void  writehydstat(int iter, double relerr)
/*
**--------------------------------------------------------------
**   Input:   iter   = # iterations to find hydraulic solution        
**            relerr = convergence error in hydraulic solution    
**   Output:  none                                                
**   Purpose: writes hydraulic status report for solution found
**            at current time period to report file       
**--------------------------------------------------------------
*/
{
   int    i,n;
   char   newstat;
   char   s1[MAXLINE+1];

/*** Updated 6/24/02 ***/
   char   atime[13];

   /* Display system status */
   strcpy(atime,clocktime(Atime,Htime));
   if (iter > 0)
   {
      if (relerr <= Hacc)
         sprintf(s1,FMT58,atime,iter);
      else
         sprintf(s1,FMT59,atime,iter,relerr);
      writeline(s1);
   }

   /*
      Display status changes for tanks.
      D[n] is net inflow to tank at node n.
      Old tank status is stored in OldStat[]
      at indexes Nlinks+1 to Nlinks+Ntanks.
   */
   for (i=1; i<=Ntanks; i++)
   {
      n = Tank[i].Node;
      if (ABS(D[n]) < 0.001) newstat = CLOSED;
      else if (D[n] >  0.0)  newstat = FILLING;
      else if (D[n] <  0.0)  newstat = EMPTYING;
      else newstat = OldStat[Nlinks+i];
      if (newstat != OldStat[Nlinks+i])
      {
         if (Tank[i].A > 0.0)
            sprintf(s1,FMT50,atime,Node[n].ID,StatTxt[newstat],
               (H[n]-Node[n].El)*Ucf[HEAD],Field[HEAD].Units);
         else sprintf(s1,FMT51,atime,Node[n].ID,StatTxt[newstat]);
         writeline(s1);
         OldStat[Nlinks+i] = newstat;
      }
   }

   /* Display status changes for links */
   for (i=1; i<=Nlinks; i++)
   {
      if (S[i] != OldStat[i])
      {
         if (Htime == 0)
            sprintf(s1,FMT52,atime,LinkTxt[Link[i].Type],Link[i].ID,
               StatTxt[S[i]]);
         else sprintf(s1,FMT53,atime,LinkTxt[Link[i].Type],Link[i].ID,
            StatTxt[OldStat[i]],StatTxt[S[i]]);
         writeline(s1);
         OldStat[i] = S[i];
      }
   }
   writeline(" ");
}                        /* End of writehydstat */


void  writeenergy()
/*
**-------------------------------------------------------------
**   Input:   none                                               
**   Output:  none                                               
**   Purpose: writes energy usage report to report file          
**-------------------------------------------------------------
*/
{
    int    j;
    double csum;
    char   s[MAXLINE+1];
    if (Npumps == 0) return;
    writeline(" ");
    writeheader(ENERHDR,0);
    csum = 0.0;
    for (j=1; j<=Npumps; j++)
    {
        csum += Pump[j].Energy[5];
        if (LineNum == (long)PageSize) writeheader(ENERHDR,1);
        sprintf(s,"%-8s  %6.2f %6.2f %9.2f %9.2f %9.2f %9.2f",
          Link[Pump[j].Link].ID,Pump[j].Energy[0],Pump[j].Energy[1],
          Pump[j].Energy[2],Pump[j].Energy[3],Pump[j].Energy[4],
          Pump[j].Energy[5]);
        writeline(s);
    }
    fillstr(s,'-',63);
    writeline(s);

/*** Updated 6/24/02 ***/
    sprintf(s,FMT74,"",Emax*Dcost);
    writeline(s);
    sprintf(s,FMT75,"",csum+Emax*Dcost);
/*** End of update ***/

    writeline(s);
    writeline(" ");
}                       /* End of writeenergy */


int  writeresults()
/*
**--------------------------------------------------------------
**   Input:   none                                                
**   Output:  returns error code                                  
**   Purpose: writes simulation results to report file            
**--------------------------------------------------------------
*/
{
   Pfloat *x;                /* Array of pointers to floats */
   int    j,m,n,np,nnv,nlv;
   int    errcode = 0;

   /*
   **-----------------------------------------------------------
   **  NOTE:  The OutFile contains results for 4 node variables       
   **         (demand, head, pressure, & quality) and 8 link
   **         variables (flow, velocity, headloss, quality,
   **         status, setting, reaction rate & friction factor)
   **         at each reporting time.                                         
   **-----------------------------------------------------------
   */

   /* Return if no output file */
   if (OutFile == NULL) return(106);

   /* Return if no nodes or links selected for reporting */
   /* or if no node or link report variables enabled.    */
   if (!Nodeflag && !Linkflag) return(errcode);
   nnv = 0;
   for (j=ELEV; j<=QUALITY; j++) nnv += Field[j].Enabled;
   nlv = 0;
   for (j=LENGTH; j<=FRICTION; j++) nlv += Field[j].Enabled;
   if (nnv == 0 && nlv == 0) return(errcode);

   /* Allocate memory for output variables. */
   /* m = larger of # node variables & # link variables */
   /* n = larger of # nodes & # links */
   m = MAX( (QUALITY-DEMAND+1), (FRICTION-FLOW+1) );
   n = MAX( (Nnodes+1), (Nlinks+1));
   x = (Pfloat *) calloc(m, sizeof(Pfloat));
   ERRCODE( MEMCHECK(x) );
   if (errcode) return(errcode);
   for (j=0; j<m; j++)
   {
      x[j] = (REAL4 *) calloc(n, sizeof(REAL4));
      ERRCODE( MEMCHECK(x[j]) );
   }
   if (errcode) return(errcode);

   /* Re-position output file & initialize report time. */
   fseek(OutFile,OutOffset2,SEEK_SET);
   Htime = Rstart;

   /* For each reporting time: */
   for (np=1; np<=Nperiods; np++)
   {

      /* Read in node results & write node table. */
      /* (Remember to offset x[j] by 1 because array is zero-based). */
      for (j=DEMAND; j<=QUALITY; j++)
         fread((x[j-DEMAND])+1,sizeof(REAL4),Nnodes,OutFile);
      if (nnv > 0 && Nodeflag > 0) writenodetable(x);

      /* Read in link results & write link table. */
      for (j=FLOW; j<=FRICTION; j++)
         fread((x[j-FLOW])+1,sizeof(REAL4),Nlinks,OutFile);
      if (nlv > 0 && Linkflag > 0) writelinktable(x);
      Htime += Rstep;
   }

   /* Free allocated memory */
   for (j=0; j<m; j++) free(x[j]);
   free(x);
   return(errcode);
}                        /* End of writereport */


void  writenodetable(Pfloat *x)
/*
**---------------------------------------------------------------
**   Input:   x = pointer to node results for current time
**   Output:  none                                  
**   Purpose: writes node results for current time to report file            
**---------------------------------------------------------------
*/
{
   int    i,j;
   char   s[MAXLINE+1],s1[16];
   double y[MAXVAR];

   /* Write table header */
   writeheader(NODEHDR,0);

   /* For each node: */
   for (i=1; i<=Nnodes; i++)
   {

      /* Place results for each node variable in y */
      y[ELEV] = Node[i].El*Ucf[ELEV];
      for (j=DEMAND; j<=QUALITY; j++) y[j] = *((x[j-DEMAND])+i);

      /* Check if node gets reported on */
      if ((Nodeflag == 1 || Node[i].Rpt) && checklimits(y,ELEV,QUALITY))
      {

         /* Check if new page needed */
         if (LineNum == (long)PageSize) writeheader(NODEHDR,1);

         /* Add node ID and each reported field to string s */
         sprintf(s,"%-15s",Node[i].ID);
         for (j=ELEV; j<=QUALITY; j++)
         {
            if (Field[j].Enabled == TRUE)
            {

/*** Updated 6/24/02 ***/
               if (fabs(y[j]) > 1.e6) sprintf(s1, "%10.2e", y[j]);
               else                   sprintf(s1, "%10.*f", Field[j].Precision, y[j]);
/*** End of update ***/

               strcat(s, s1);
            }
         }

         /* Note if node is a reservoir/tank */
         if (i > Njuncs)
         {
            strcat(s, "  ");
            strcat(s, NodeTxt[getnodetype(i)]);
         }

         /* Write results for node */
         writeline(s);
      }
   }
   writeline(" ");
}


void  writelinktable(Pfloat *x)
/*
**---------------------------------------------------------------
**   Input:   x = pointer to link results for current time
**   Output:  none                                  
**   Purpose: writes link results for current time to report file            
**---------------------------------------------------------------
*/
{
   int    i,j,k;
   char   s[MAXLINE+1],s1[16];
   double y[MAXVAR];

   /* Write table header */
   writeheader(LINKHDR,0);

   /* For each link: */
   for (i=1; i<=Nlinks; i++)
   {

      /* Place results for each link variable in y */
      y[LENGTH] = Link[i].Len*Ucf[LENGTH];
      y[DIAM] = Link[i].Diam*Ucf[DIAM];
      for (j=FLOW; j<=FRICTION; j++) y[j] = *((x[j-FLOW])+i);

      /* Check if link gets reported on */
      if ((Linkflag == 1 || Link[i].Rpt) && checklimits(y,DIAM,FRICTION))
      {

         /* Check if new page needed */
         if (LineNum == (long)PageSize) writeheader(LINKHDR,1);

         /* Add link ID and each reported field to string s */
         sprintf(s,"%-15s",Link[i].ID);
         for (j=LENGTH; j<=FRICTION; j++)
         {
            if (Field[j].Enabled == TRUE)
            {
               if (j == STATUS)
               {
                  if      (y[j] <= CLOSED) k = CLOSED;
                  else if (y[j] == ACTIVE) k = ACTIVE;
                  else                     k = OPEN;
                  sprintf(s1, "%10s", StatTxt[k]);
               }

/*** Updated 6/24/02 ***/
               else
               {
                  if (fabs(y[j]) > 1.e6) sprintf(s1, "%10.2e", y[j]);
                  else                   sprintf(s1, "%10.*f", Field[j].Precision, y[j]);
               }
/*** End of update ***/

               strcat(s, s1);
            }
         }

         /* Note if link is a pump or valve */
         if ( (j = Link[i].Type) > PIPE)
         {
            strcat(s, "  ");
            strcat(s, LinkTxt[j]);
         }

         /* Write results for link */
         writeline(s);
      }
   }
   writeline(" ");
}


void  writeheader(int type, int contin)
/*
**--------------------------------------------------------------
**   Input:   type   = table type                                
**            contin = table continuation flag                    
**   Output:  none                                                
**   Purpose: writes column headings for output report tables             
**--------------------------------------------------------------
*/
{
   char   s[MAXLINE+1],s1[MAXLINE+1],s2[MAXLINE+1],s3[MAXLINE+1];
   int    i,n;

   /* Move to next page if < 11 lines remain on current page. */
   if (Rptflag && LineNum+11 > (long)PageSize)
   {
      while (LineNum < (long)PageSize) writeline(" ");
   }
   writeline(" ");

   /* Hydraulic Status Table */
   if (type == STATHDR)
   {
      sprintf(s,FMT49);
      if (contin) strcat(s,t_CONTINUED);
      writeline(s);
      fillstr(s,'-',70);
      writeline(s);
   }

   /* Energy Usage Table */
   if (type == ENERHDR)
   {
      if (Unitsflag == SI) strcpy(s1,t_perM3);
      else                 strcpy(s1,t_perMGAL);
      sprintf(s,FMT71);
      if (contin) strcat(s,t_CONTINUED);
      writeline(s);
      fillstr(s,'-',63);
      writeline(s);
      sprintf(s,FMT72);
      writeline(s);
      sprintf(s,FMT73,s1);
      writeline(s);
      fillstr(s,'-',63);
      writeline(s);
   }

   /* Node Results Table */
   if (type == NODEHDR)
   {
      if      (Tstatflag == RANGE)  sprintf(s,FMT76,t_DIFFER);
      else if (Tstatflag != SERIES) sprintf(s,FMT76,TstatTxt[Tstatflag]);
      else if (Dur == 0)            sprintf(s,FMT77);
      else                          sprintf(s,FMT78,clocktime(Atime,Htime));
      if (contin) strcat(s,t_CONTINUED);
      writeline(s);
      n = 15;
      sprintf(s2,"%15s","");
      strcpy(s,t_NODEID);
      sprintf(s3,"%-15s",s);
      for (i=ELEV; i<QUALITY; i++) if (Field[i].Enabled == TRUE)
      {
        n += 10;
        sprintf(s,"%10s",Field[i].Name);
        strcat(s2,s);
        sprintf(s,"%10s",Field[i].Units);
        strcat(s3,s);
      }
      if (Field[QUALITY].Enabled == TRUE)
      {
        n += 10;
        sprintf(s,"%10s",ChemName);
        strcat(s2,s);
        sprintf(s,"%10s",ChemUnits);
        strcat(s3,s);
      }
      fillstr(s1,'-',n);
      writeline(s1);
      writeline(s2);
      writeline(s3);
      writeline(s1);
   }

   /* Link Results Table */
   if (type == LINKHDR)
   {
      if      (Tstatflag == RANGE)  sprintf(s,FMT79,t_DIFFER);
      else if (Tstatflag != SERIES) sprintf(s,FMT79,TstatTxt[Tstatflag]);
      else if (Dur == 0)            sprintf(s,FMT80);
      else                          sprintf(s,FMT81,clocktime(Atime,Htime));
      if (contin) strcat(s,t_CONTINUED);
      writeline(s);
      n = 15;
      sprintf(s2,"%15s","");
      strcpy(s,t_LINKID);
      sprintf(s3,"%-15s",s);
      for (i=LENGTH; i<=FRICTION; i++) if (Field[i].Enabled == TRUE)
      {
        n += 10;
        sprintf(s,"%10s",Field[i].Name);
        strcat(s2,s);
        sprintf(s,"%10s",Field[i].Units);
        strcat(s3,s);
      }
      fillstr(s1,'-',n);
      writeline(s1);
      writeline(s2);
      writeline(s3);
      writeline(s1);
  }
}                        /* End of writeheader */


void  writeline(char *s)
/*
**--------------------------------------------------------------
**   Input:   *s = text string                                    
**   Output:  none                                                
**   Purpose: writes a line of output to report file              
**--------------------------------------------------------------
*/
{
   if (RptFile == NULL) return;
   if (Rptflag)
   {
      if (LineNum == (long)PageSize)
      {
         PageNum++;
         if (fprintf(RptFile,FMT82,PageNum,Title[0]) == EOF)
            Fprinterr = TRUE;
         LineNum = 3;
      }
   }
   if (fprintf(RptFile,"\n  %s",s) == EOF) Fprinterr = TRUE;
   LineNum++;
}                        /* End of writeline */


void  writerelerr(int iter, double relerr)
/*
**-----------------------------------------------------------------
**   Input:   iter   = current iteration of hydraulic solution    
**            relerr = current convergence error                  
**   Output:  none                                                
**   Purpose: writes out convergence status of hydraulic solution 
**-----------------------------------------------------------------
*/
{
   if (iter == 0)
   {
      sprintf(Msg, FMT64, clocktime(Atime,Htime));
      writeline(Msg);
   }
   else
   {
      sprintf(Msg,FMT65,iter,relerr);
      writeline(Msg);
   }
}                        /* End of writerelerr */


void  writestatchange(int k, char s1, char s2)
/*
**--------------------------------------------------------------
**   Input:   k  = link index                                     
**            s1 = old link status                                
**            s2 = new link status                                
**   Output:  none                                                
**   Purpose: writes change in link status to output report       
**--------------------------------------------------------------
*/
{
   int    j1,j2;
   double setting;

/* We have a pump/valve setting change instead of a status change */
   if (s1 == s2)
   {

/*** Updated 10/25/00 ***/
      setting = K[k];   //Link[k].Kc;

      switch (Link[k].Type)
      {
         case PRV:
         case PSV:
         case PBV: setting *= Ucf[PRESSURE]; break;
         case FCV: setting *= Ucf[FLOW];
      }
      sprintf(Msg,FMT56,LinkTxt[Link[k].Type],Link[k].ID,setting);
      writeline(Msg);
      return;
   }

/* We have a status change. Write the old & new status types. */
   if      (s1 == ACTIVE) j1 = ACTIVE;
   else if (s1 <= CLOSED) j1 = CLOSED;
   else                   j1 = OPEN;
   if      (s2 == ACTIVE) j2 = ACTIVE;
   else if (s2 <= CLOSED) j2 = CLOSED;
   else                   j2 = OPEN;
   if (j1 != j2)
   {
      sprintf(Msg,FMT57,LinkTxt[Link[k].Type],Link[k].ID,
              StatTxt[j1],StatTxt[j2]);
      writeline(Msg);
   }
}                        /* End of writestatchange */


void writecontrolaction(int k, int i)
/*
----------------------------------------------------------------
**   Input:   k  = link index                                     
**            i  = control index                                
**   Output:  none                                                
**   Purpose: writes control action taken to status report
**--------------------------------------------------------------
*/
{
   int n;
   switch (Control[i].Type)
   {
      case LOWLEVEL:
      case HILEVEL:
         n = Control[i].Node;
         sprintf(Msg,FMT54,clocktime(Atime,Htime),LinkTxt[Link[k].Type],
            Link[k].ID,NodeTxt[getnodetype(n)],Node[n].ID);
         break;
      case TIMER:
      case TIMEOFDAY:
         sprintf(Msg,FMT55,clocktime(Atime,Htime),LinkTxt[Link[k].Type],
            Link[k].ID);
         break;
      default: return;
   }
   writeline(Msg);
}


void writeruleaction(int k, char *ruleID)
/*
**--------------------------------------------------------------
**   Input:   k  = link index                                     
**            *ruleID  = rule ID
**   Output:  none                                                
**   Purpose: writes rule action taken to status report
**--------------------------------------------------------------
*/
{
   sprintf(Msg,FMT63,clocktime(Atime,Htime),LinkTxt[Link[k].Type],
      Link[k].ID,ruleID);
   writeline(Msg);
}


int  writehydwarn(int iter, double relerr)
/*
**--------------------------------------------------------------
**   Input:   iter = # iterations to find hydraulic solution      
**   Output:  warning flag code                                                
**   Purpose: writes hydraulic warning message to report file
**
**   Note: Warning conditions checked in following order:
**         1. System balanced but unstable
**         2. Negative pressures
**         3. FCV cannot supply flow or PRV/PSV cannot maintain pressure
**         4. Pump out of range
**         5. Network disconnected
**         6. System unbalanced
**--------------------------------------------------------------
*/
{
   int  i,j;
   char flag = 0;
   char s;                                                                     //(2.00.11 - LR)

   /* Check if system unstable */
   if (iter > MaxIter && relerr <= Hacc)
   {
      sprintf(Msg,WARN02,clocktime(Atime,Htime));
      if (Messageflag) writeline(Msg);
      flag = 2;
   }

   /* Check for negative pressures */
   for (i=1; i<=Njuncs; i++)
   {
      if (H[i] < Node[i].El && D[i] > 0.0)
      {
         sprintf(Msg,WARN06,clocktime(Atime,Htime));
         if (Messageflag) writeline(Msg);
         flag = 6;
         break;
      }
   }

   /* Check for abnormal valve condition */
   for (i=1; i<=Nvalves; i++)
   {
      j = Valve[i].Link;
      if (S[j] >= XFCV)
      {
         sprintf(Msg,WARN05,LinkTxt[Link[j].Type],Link[j].ID,
            StatTxt[S[j]],clocktime(Atime,Htime));
         if (Messageflag) writeline(Msg);
         flag = 5;
      }
   }

   /* Check for abnormal pump condition */
   for (i=1; i<=Npumps; i++)
   {
      j = Pump[i].Link;
      s = S[j];                                                                //(2.00.11 - LR)
      if (S[j] >= OPEN)                                                        //(2.00.11 - LR)
      {                                                                        //(2.00.11 - LR)
          if (Q[j] > K[j]*Pump[i].Qmax) s = XFLOW;                             //(2.00.11 - LR)
          if (Q[j] < 0.0) s = XHEAD;                                           //(2.00.11 - LR)
      }                                                                        //(2.00.11 - LR)
      if (s == XHEAD || s == XFLOW)                                            //(2.00.11 - LR)
      {                                    
         sprintf(Msg,WARN04,Link[j].ID,StatTxt[s],                             //(2.00.11 - LR)
                 clocktime(Atime,Htime));
         if (Messageflag) writeline(Msg);
         flag = 4;
      }
   }

   /* Check if system is unbalanced */
   if (iter > MaxIter && relerr > Hacc)
   {
      sprintf(Msg,WARN01,clocktime(Atime,Htime));
      if (ExtraIter == -1) strcat(Msg,t_HALTED);
      if (Messageflag) writeline(Msg);
      flag = 1;
   }

   /* Check for disconnected network */
   /* & update global warning flag   */
   if (flag > 0)
   {
      disconnected();
      Warnflag = flag;
   }
   return(flag);
}                        /* End of writehydwarn */


void  writehyderr(int errnode)
/*
**-----------------------------------------------------------
**   Input:   none                                          
**   Output:  none                                          
**   Purpose: outputs status & checks connectivity when     
**            network hydraulic equations cannot be solved. 
**-----------------------------------------------------------
*/
{
   sprintf(Msg,FMT62,clocktime(Atime,Htime),Node[errnode].ID);
   if (Messageflag) writeline(Msg);
   writehydstat(0,0);
   disconnected();
}                        /* End of writehyderr */


int  disconnected()
/*
**-------------------------------------------------------------------
**   Input:   None                                                  
**   Output:  Returns number of disconnected nodes                  
**   Purpose: Tests current hydraulic solution to see if any closed 
**            links have caused the network to become disconnected. 
**-------------------------------------------------------------------
*/
{
   int  i, j;
   int  count, mcount;
   int  errcode = 0;
   int  *nodelist;
   char *marked;

   /* Allocate memory for node list & marked list */
   nodelist = (int *)  calloc(Nnodes+1,sizeof(int));
   marked   = (char *) calloc(Nnodes+1,sizeof(char));
   ERRCODE(MEMCHECK(nodelist));
   ERRCODE(MEMCHECK(marked));
   if (errcode) return(0);

   /* Place tanks on node list and marked list */
   for (i=1; i<=Ntanks; i++)
   {
      j = Njuncs + i;
      nodelist[i] = j;
      marked[j] = 1;
   }

   /* Place junctions with negative demands on the lists */
   mcount = Ntanks;
   for (i=1; i<=Njuncs; i++)
   {
      if (D[i] < 0.0)
      {
         mcount++;
         nodelist[mcount] = i;
         marked[i] = 1;
      }
   }

   /* Mark all nodes that can be connected to tanks */
   /* and count number of nodes remaining unmarked. */
   marknodes(mcount,nodelist,marked);
   j = 0;
   count = 0;
   for (i=1; i<=Njuncs; i++)
   {
      if (!marked[i] && D[i] != 0.0)  /* Skip if no demand */
      {
         count++;
         if (count <= MAXCOUNT && Messageflag)
         {
            sprintf(Msg,WARN03a,Node[i].ID,clocktime(Atime,Htime));
            writeline(Msg);
         }
         j = i;                       /* Last unmarked node */
      }
   }

   /* Report number of unmarked nodes and find closed link */
   /* on path from node j back to a tank.                  */
   if (count > 0 && Messageflag)
   {
      if (count > MAXCOUNT)
      {
         sprintf(Msg, WARN03b, count-MAXCOUNT, clocktime(Atime,Htime));
         writeline(Msg);
      }
      getclosedlink(j,marked);
   }

   /* Free allocated memory */
   free(nodelist);
   free(marked);
   return(count);
}                   /* End of disconnected() */


void  marknodes(int m, int *nodelist, char *marked)
/*
**----------------------------------------------------------------
**   Input:   m = number of source nodes
**            nodelist[] = list of nodes to be traced from          
**            marked[]   = TRUE if node connected to source         
**   Output:  None.                                                 
**   Purpose: Marks all junction nodes connected to tanks.          
**----------------------------------------------------------------
*/
{
   int   i, j, k, n;
   Padjlist alink;

   /* Scan each successive entry of node list */
   n = 1;
   while (n <= m )
   {

      /* Scan all nodes connected to current node */
      i = nodelist[n];
      for (alink = Adjlist[i]; alink != NULL; alink = alink->next)
      {

         /* Get indexes of connecting link and node */
         k = alink->link;
         j = alink->node;
         if (marked[j]) continue;

         /* Check if valve connection is in correct direction */
         switch (Link[k].Type)
         {
            case CV:
            case PRV:
            case PSV: if (j == Link[k].N1) continue;
         }

         /* Mark connection node if link not closed */
         if (S[k] > CLOSED)
         {
            marked[j] = 1;
            m++;
            nodelist[m] = j;
         }
      }
      n++;
   }
}                   /* End of marknodes() */


void getclosedlink(int i, char *marked)
/*
**----------------------------------------------------------------
**   Input:   i = junction index                                    
**            marked[] = marks nodes already examined               
**   Output:  None.                                                 
**   Purpose: Determines if a closed link connects to junction i.   
**----------------------------------------------------------------
*/
{
   int j,k;
   Padjlist alink;
   marked[i] = 2;
   for (alink = Adjlist[i]; alink != NULL; alink = alink->next)
   {
      k = alink->link;
      j = alink->node;
      if (marked[j] == 2) continue;
      if (marked[j] == 1)
      {
         sprintf(Msg, WARN03c, Link[k].ID);
         writeline(Msg);
         return;
      }
      else getclosedlink(j,marked);
   }
}
      

void  writelimits(int j1, int j2)
/*
**--------------------------------------------------------------
**   Input:   j1 = index of first output variable                 
**            j2 = index of last output variable                  
**   Output:  none                                                
**   Purpose: writes reporting criteria to output report          
**--------------------------------------------------------------
*/
{
   int  j;
   for (j=j1; j<=j2; j++)
   {
      if (Field[j].RptLim[LOW] < BIG)
      {
         sprintf(Msg,FMT47,
                 Field[j].Name,Field[j].RptLim[LOW],Field[j].Units);
         writeline(Msg);
      }
      if (Field[j].RptLim[HI] > -BIG)
      {
         sprintf(Msg,FMT48,
                 Field[j].Name,Field[j].RptLim[HI],Field[j].Units);
         writeline(Msg);
      }
   }
}                        /* End of writelimits */
   

int  checklimits(double *y, int j1, int j2)
/*
**--------------------------------------------------------------
**   Input:   *y = array of output results                        
**            j1 = index of first output variable                 
**            j2 = index of last output variable                  
**   Output:  returns 1 if criteria met, 0 otherwise              
**   Purpose: checks if output reporting criteria is met          
**--------------------------------------------------------------
*/
{
   int j;
   for (j=j1; j<=j2; j++)
   {
      if (y[j] > Field[j].RptLim[LOW]
      ||  y[j] < Field[j].RptLim[HI]) return(0);
   }
   return(1);
}                        /* End of checklim */


void writetime(char *fmt)
/*
**----------------------------------------------------------------
**   Input:   fmt = format string                             
**   Output:  none                             
**   Purpose: writes starting/ending time of a run to report file              
**----------------------------------------------------------------
*/
{
  time_t timer;
  time(&timer);
  sprintf(Msg, fmt, ctime(&timer));
  writeline(Msg);
}


char *clocktime(char *atime, long seconds)
/*
**--------------------------------------------------------------
**   Input:   seconds = time in seconds                             
**   Output:  atime = time in hrs:min                             
**            (returns pointer to atime)                          
**   Purpose: converts time in seconds to hours:minutes format              
**--------------------------------------------------------------
*/
{
/*** Updated 6/24/02 ***/
    int h,m,s;
    h = seconds/3600;
    m = (seconds % 3600) / 60;
    s = seconds - 3600*h - 60*m;
    sprintf(atime, "%01d:%02d:%02d", h,m,s);
    return(atime);
}                        /* End of clocktime */


char *fillstr(char *s, char ch, int n)
/*
**---------------------------------------------------------
**  Fills n bytes of s to character ch.
**  NOTE: does not check for overwriting s. 
**---------------------------------------------------------
*/
{
   int i;
   for (i=0; i<=n; i++) s[i] = ch;
   s[n+1] = '\0';
   return(s);
}


int  getnodetype(int i)
/*
**---------------------------------------------------------
**  Determines type of node with index i
**  (junction = 0, reservoir = 1, tank = 2). 
**---------------------------------------------------------
*/
{
   if (i <= Njuncs) return(0);
   if (Tank[i-Njuncs].A == 0.0) return(1);
   return(2);
}

/********************* END OF REPORT.C ********************/
