/*
*********************************************************************
                                                                   
OUTPUT.C -- Binary File Transfer Routines for EPANET Program                
                                                                    
VERSION:    2.00                                              
DATE:       5/8/00
            8/15/07    (2.00.11)
AUTHOR:     L. Rossman
            US EPA - NRMRL
                                                                    
********************************************************************
*/

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

/* Macro to write x[1] to x[n] to file OutFile: */
#define   FSAVE(n)  (fwrite(x+1,sizeof(REAL4),(n),OutFile))

int  savenetdata()
/*
**---------------------------------------------------------------
**   Input:   none
**   Output:  returns error code
**   Purpose: saves input data in original units to binary
**            output file using fixed-sized (4-byte) records
**---------------------------------------------------------------
*/
{
   int   i,nmax;
   INT4  *ibuf;
   REAL4 *x;
   int   errcode = 0;

   /* Allocate buffer arrays */
   nmax = MAX(Nnodes,Nlinks) + 1;
   nmax = MAX(nmax,15);
   ibuf = (INT4 *) calloc(nmax, sizeof(INT4));
   x = (REAL4 *) calloc(nmax, sizeof(REAL4));
   ERRCODE(MEMCHECK(ibuf));
   ERRCODE(MEMCHECK(x));

   if (!errcode)
   {
      /* Write integer variables to OutFile */
      ibuf[0] = MAGICNUMBER;

/*** CODEVERSION replaces VERSION ***/                                         //(2.00.11 - LR)
      ibuf[1] = CODEVERSION;                                                   //(2.00.11 - LR)

      ibuf[2] = Nnodes;
      ibuf[3] = Ntanks;
      ibuf[4] = Nlinks;
      ibuf[5] = Npumps;
      ibuf[6] = Nvalves;
      ibuf[7] = Qualflag;
      ibuf[8] = TraceNode;
      ibuf[9] = Flowflag;
      ibuf[10] = Pressflag;
      ibuf[11] = Tstatflag;
      ibuf[12] = Rstart;
      ibuf[13] = Rstep;
      ibuf[14] = Dur;
      fwrite(ibuf,sizeof(INT4),15,OutFile);

      /* Write string variables to OutFile */
      fwrite(Title[0],sizeof(char),MAXMSG+1,OutFile);
      fwrite(Title[1],sizeof(char),MAXMSG+1,OutFile);
      fwrite(Title[2],sizeof(char),MAXMSG+1,OutFile);
      fwrite(InpFname,sizeof(char),MAXFNAME+1,OutFile);
      fwrite(Rpt2Fname,sizeof(char),MAXFNAME+1,OutFile);
      fwrite(ChemName,sizeof(char),MAXID+1,OutFile);
      fwrite(Field[QUALITY].Units,sizeof(char),MAXID+1,OutFile);

      /* Write node ID information to OutFile */
      for (i=1; i<=Nnodes; i++)
         fwrite(Node[i].ID, MAXID+1, 1, OutFile);

      /* Write link information to OutFile            */
      /* (Note: first transfer values to buffer array,*/
      /* then fwrite buffer array at offset of 1 )    */
      for (i=1; i<=Nlinks; i++)
         fwrite(Link[i].ID, MAXID+1, 1, OutFile);
      for (i=1; i<=Nlinks; i++) ibuf[i] = Link[i].N1;
      fwrite(ibuf+1,sizeof(INT4),Nlinks,OutFile);
      for (i=1; i<=Nlinks; i++) ibuf[i] = Link[i].N2;
      fwrite(ibuf+1,sizeof(INT4),Nlinks,OutFile);
      for (i=1; i<=Nlinks; i++) ibuf[i] = Link[i].Type;
      fwrite(ibuf+1,sizeof(INT4),Nlinks,OutFile);

      /* Write tank information to OutFile.*/
      for (i=1; i<=Ntanks; i++) ibuf[i] = Tank[i].Node;
      fwrite(ibuf+1,sizeof(INT4),Ntanks,OutFile);
      for (i=1; i<=Ntanks; i++) x[i] = (REAL4)Tank[i].A;
      FSAVE(Ntanks);

      /* Save node elevations to OutFile.*/
      for (i=1; i<=Nnodes; i++) x[i] = (REAL4)(Node[i].El*Ucf[ELEV]);
      FSAVE(Nnodes);

      /* Save link lengths & diameters to OutFile.*/
      for (i=1; i<=Nlinks; i++) x[i] = (REAL4)(Link[i].Len*Ucf[ELEV]);
      FSAVE(Nlinks);
      for (i=1; i<=Nlinks; i++)
      {
         if (Link[i].Type != PUMP)
            x[i] = (REAL4)(Link[i].Diam*Ucf[DIAM]);
         else
            x[i] = 0.0f;
      }
      if (FSAVE(Nlinks) < (unsigned)Nlinks) errcode = 308;
   }

   /* Free memory used for buffer arrays */
   free(ibuf);
   free(x);
   return(errcode);
}


int  savehyd(long *htime)
/*
**--------------------------------------------------------------
**   Input:   *htime   = current time                             
**   Output:  returns error code
**   Purpose: saves current hydraulic solution to file HydFile    
**            in binary format                                    
**--------------------------------------------------------------
*/
{
   int i;
   INT4 t;
   int errcode = 0;
   REAL4 *x = (REAL4 *) calloc(MAX(Nnodes,Nlinks) + 1, sizeof(REAL4));
   if ( x == NULL ) return 101;

   /* Save current time (htime) */
   t = *htime;
   fwrite(&t,sizeof(INT4),1,HydFile);

   /* Save current nodal demands (D) */
   for (i=1; i<=Nnodes; i++) x[i] = (REAL4)D[i];
   fwrite(x+1,sizeof(REAL4),Nnodes,HydFile);

   /* Copy heads (H) to buffer of floats (x) and save buffer */
   for (i=1; i<=Nnodes; i++) x[i] = (REAL4)H[i];
   fwrite(x+1,sizeof(REAL4),Nnodes,HydFile);

   /* Force flow in closed links to be zero then save flows */
   for (i=1; i<=Nlinks; i++)
   {
      if (S[i] <= CLOSED) x[i] = 0.0f;
      else x[i] = (REAL4)Q[i];
   }
   fwrite(x+1,sizeof(REAL4),Nlinks,HydFile);

   /* Copy link status to buffer of floats (x) & write buffer */
   for (i=1; i<=Nlinks; i++) x[i] = (REAL4)S[i];
   fwrite(x+1,sizeof(REAL4),Nlinks,HydFile);

   /* Save link settings & check for successful write-to-disk */
   /* (We assume that if any of the previous fwrites failed,  */
   /* then this one will also fail.) */
   for (i=1; i<=Nlinks; i++) x[i] = (REAL4)K[i];
   if (fwrite(x+1,sizeof(REAL4),Nlinks,HydFile) < (unsigned)Nlinks)
      errcode = 308;
   free(x);
   return(errcode);
}                        /* End of savehyd */


int  savehydstep(long *hydstep)
/*
**--------------------------------------------------------------
**   Input:   *hydstep = next time step                           
**   Output:  returns error code
**   Purpose: saves next hydraulic timestep to file HydFile    
**            in binary format                                    
**--------------------------------------------------------------
*/
{
   INT4 t;
   int errcode = 0;
   t = *hydstep;
   if (fwrite(&t,sizeof(INT4),1,HydFile) < 1) errcode = 308;
   if (t == 0) fputc(EOFMARK, HydFile);
   return(errcode);
}


int  saveenergy()
/*
**--------------------------------------------------------------
**   Input:   none                             
**   Output:  returns error code
**   Purpose: saves energy usage by each pump to OutFile    
**            in binary format                                    
**--------------------------------------------------------------
*/
{
    int   i,j;
    INT4  index;
    REAL4 x[6];              /* work array */
    double hdur,             /* total duration in hours */
           t;                /* pumping duration */

    hdur = Dur / 3600.0;
    for (i=1; i<=Npumps; i++)
    {
        if (hdur == 0.0)
        {
            for (j=0; j<5; j++) x[j] = (REAL4)Pump[i].Energy[j];
            x[5] = (REAL4)(Pump[i].Energy[5]*24.0);
        }
        else
        {
            t = Pump[i].Energy[0];
            x[0] = (REAL4)(t/hdur);
            x[1] = 0.0f;
            x[2] = 0.0f;
            x[3] = 0.0f;
            x[4] = 0.0f;
            if (t > 0.0)
            {
                x[1] = (REAL4)(Pump[i].Energy[1]/t);
                x[2] = (REAL4)(Pump[i].Energy[2]/t);
                x[3] = (REAL4)(Pump[i].Energy[3]/t);
            }
            x[4] = (REAL4)Pump[i].Energy[4];
            x[5] = (REAL4)(Pump[i].Energy[5]*24.0/hdur);
        }
        x[0] *= 100.0f;
        x[1] *= 100.0f;
        /* Compute Kw-hr per MilGal (or per cubic meter) */
        if (Unitsflag == SI) x[2] *= (REAL4)(1000.0/LPSperCFS/3600.0);
        else                 x[2] *= (REAL4)(1.0e6/GPMperCFS/60.0);
        for (j=0; j<6; j++) Pump[i].Energy[j] = x[j];
        index = Pump[i].Link;
        if (fwrite(&index,sizeof(INT4),1,OutFile) < 1) return(308);
        if (fwrite(x, sizeof(REAL4), 6, OutFile) < 6) return(308);
    }
    Emax = Emax*Dcost;
    x[0] = (REAL4)Emax;
    if (fwrite(&x[0], sizeof(REAL4), 1, OutFile) < 1) return(308);
    return(0);
}


int  readhyd(long *hydtime)
/*
**--------------------------------------------------------------
**   Input:   none                                                
**   Output:  *hydtime = time of hydraulic solution               
**   Returns: 1 if successful, 0 if not                    
**   Purpose: reads hydraulic solution from file HydFile               
**                                                              
**   NOTE: A hydraulic solution consists of the current time      
**         (hydtime), nodal demands (D) and heads (H), link
**         flows (Q), link status (S), and link settings (K).                                           
**--------------------------------------------------------------
*/
{
   int   i;
   INT4  t;
   int   result = 1;
   REAL4 *x = (REAL4 *) calloc(MAX(Nnodes,Nlinks) + 1, sizeof(REAL4));
   if ( x == NULL ) return 0;

   if (fread(&t,sizeof(INT4),1,HydFile) < 1)  result = 0;
   *hydtime = t;

   if (fread(x+1,sizeof(REAL4),Nnodes,HydFile) < (unsigned)Nnodes) result = 0;
   else for (i=1; i<=Nnodes; i++) D[i] = x[i];

   if (fread(x+1,sizeof(REAL4),Nnodes,HydFile) < (unsigned)Nnodes) result = 0;
   else for (i=1; i<=Nnodes; i++) H[i] = x[i];

   if (fread(x+1,sizeof(REAL4),Nlinks,HydFile) < (unsigned)Nlinks) result = 0;
   else for (i=1; i<=Nlinks; i++) Q[i] = x[i];

   if (fread(x+1,sizeof(REAL4),Nlinks,HydFile) < (unsigned)Nlinks) result = 0;
   else for (i=1; i<=Nlinks; i++) S[i] = (char) x[i];

   if (fread(x+1,sizeof(REAL4),Nlinks,HydFile) < (unsigned)Nlinks) result = 0;
   else for (i=1; i<=Nlinks; i++) K[i] = x[i];

   free(x);
   return result;
}                        /* End of readhyd */


int  readhydstep(long *hydstep)
/*
**--------------------------------------------------------------
**   Input:   none                                                
**   Output:  *hydstep = next hydraulic time step (sec)
**   Returns: 1 if successful, 0 if not                    
**   Purpose: reads hydraulic time step from file HydFile
**--------------------------------------------------------------
*/
{
   INT4  t;
   if (fread(&t,sizeof(INT4),1,HydFile) < 1)  return(0);
   *hydstep = t;
   return(1);
}                        /* End of readhydstep */


int  saveoutput()
/*
**--------------------------------------------------------------
**   Input:   none                                                
**   Output:  returns error code                                  
**   Purpose: writes simulation results to output file            
**--------------------------------------------------------------
*/
{
   int   j;
   int   errcode = 0;
   REAL4 *x = (REAL4 *) calloc(MAX(Nnodes,Nlinks) + 1, sizeof(REAL4));
   if ( x == NULL ) return 101;

   /* Write out node results, then link results */
   for (j=DEMAND; j<=QUALITY; j++)  ERRCODE(nodeoutput(j,x,Ucf[j]));
   for (j=FLOW; j<=FRICTION; j++) ERRCODE(linkoutput(j,x,Ucf[j]));
   return(errcode);
}                        /* End of saveoutput */


int  nodeoutput(int j, REAL4 *x, double ucf)
/*
**--------------------------------------------------------------
**   Input:   j   = type of node variable                         
**            *x  = buffer for node values                        
**            ucf = units conversion factor                       
**   Output:  returns error code                                  
**   Purpose: writes results for node variable j to output file   
**-----------------------------------------------------------------
*/
{
   int   i;

   /* Load computed results (in proper units) into buffer x */
   switch(j)
   {
       case DEMAND:    for (i=1; i<=Nnodes; i++)
                          x[i] = (REAL4)(D[i]*ucf);
                       break;
       case HEAD:      for (i=1; i<=Nnodes; i++)
                          x[i] = (REAL4)(H[i]*ucf);
                       break;
       case PRESSURE:  for (i=1; i<=Nnodes; i++)
                          x[i] = (REAL4)((H[i] - Node[i].El)*ucf);
                       break;
       case QUALITY:   for (i=1; i<=Nnodes; i++)
                          x[i] = (REAL4)(C[i]*ucf);
   }

   /* Write x[1] to x[Nnodes] to output file */
   if (fwrite(x+1,sizeof(REAL4),Nnodes,TmpOutFile) < (unsigned)Nnodes)
      return(308);
   return(0);
}                        /* End of nodeoutput */


int  linkoutput(int j, float *x, double ucf)
/*
**----------------------------------------------------------------
**   Input:   j   = type of link variable                         
**            *x  = buffer for link values                        
**            ucf = units conversion factor                       
**   Output:  returns error code                                  
**   Purpose: writes results for link variable j to output file
**----------------------------------------------------------------
*/
{
   int i;
   double a,h,q,f;

   /* Load computed results (in proper units) into buffer x */
   switch(j)
   {
      case FLOW:      for (i=1; i<=Nlinks; i++)
                         x[i] = (REAL4)(Q[i]*ucf);
                      break;
      case VELOCITY:  for (i=1; i<=Nlinks; i++)
                      {
                         if (Link[i].Type == PUMP) x[i] = 0.0f;
                         else
                         {
                            q = ABS(Q[i]);
                            a = PI*SQR(Link[i].Diam)/4.0;
                            x[i] = (REAL4)(q/a*ucf);
                         }
                      }
                      break;
      case HEADLOSS:  for (i=1; i<=Nlinks; i++)
                      {
                         if (S[i] <= CLOSED) x[i] = 0.0f;
                         else
                         {
                            h = H[Link[i].N1] - H[Link[i].N2];
                            if (Link[i].Type != PUMP) h = ABS(h);
                            if (Link[i].Type <= PIPE)
                               x[i] = (REAL4)(1000.0*h/Link[i].Len);
                            else x[i] = (REAL4)(h*ucf);
                         }
                      }
                      break;
      case LINKQUAL:  for (i=1; i<=Nlinks; i++)
                         x[i] = (REAL4)(avgqual(i)*ucf);
                      break;
      case STATUS:    for (i=1; i<=Nlinks; i++)
                         x[i] = (REAL4)S[i];
                      break;
      case SETTING:   for (i=1; i<=Nlinks; i++)
                      {
                         if (K[i] != MISSING)
                             switch (Link[i].Type)
                             {
                               case CV:   
                               case PIPE: x[i] = (REAL4)K[i];
                                          break;
                               case PUMP: x[i] = (REAL4)K[i];
                                          break;
                               case PRV:
                               case PSV:
                               case PBV:  x[i] = (REAL4)(K[i]*Ucf[PRESSURE]);
                                          break;
                               case FCV:  x[i] = (REAL4)(K[i]*Ucf[FLOW]);
                                          break;
                               case TCV:  x[i] = (REAL4)K[i];
                                          break;
                               default:   x[i] = 0.0f;
                             }
                         else x[i] = 0.0f;
                      }
                      break;
      case REACTRATE: /* Overall reaction rate in mass/L/day */
                      if (Qualflag == NONE) memset(x,0,(Nlinks+1 )*sizeof(REAL4));
                      else for (i=1; i<=Nlinks; i++) x[i] = (REAL4)(R[i]*ucf);
                      break;
      case FRICTION:   /* f = 2ghd/(Lu^2) where f = friction factor */
                       /* u = velocity, g = grav. accel., h = head  */
                       /*loss, d = diam., & L = pipe length         */
                       for (i=1; i<=Nlinks; i++)
                       {
                          if (Link[i].Type <= PIPE && ABS(Q[i]) > TINY)
                          {
                             h = ABS(H[Link[i].N1] - H[Link[i].N2]);
                             f = 39.725*h*pow(Link[i].Diam,5)/Link[i].Len/SQR(Q[i]);
                             x[i] = (REAL4)f;
                          }
                          else x[i] = 0.0f;
                       }
                       break;
   }

   /* Write x[1] to x[Nlinks] to output file */
   if (fwrite(x+1,sizeof(REAL4),Nlinks,TmpOutFile) < (unsigned)Nlinks)
      return(308);
   return(0);
}                        /* End of linkoutput */


int  savefinaloutput()
/*
**--------------------------------------------------------------
**  Input:   none                                          
**  Output:  returns error code                                  
**  Purpose: saves time series statistics, reaction rates &
**            epilog to output file.
**--------------------------------------------------------------
*/
{
   int errcode = 0;
   REAL4 *x;

/* Save time series statistic if computed */
   if (Tstatflag != SERIES && TmpOutFile != NULL)
   {
      x = (REAL4 *) calloc(MAX(Nnodes,Nlinks) + 1, sizeof(REAL4)); 
      if ( x == NULL ) return 101;
      ERRCODE(savetimestat(x,NODEHDR));
      ERRCODE(savetimestat(x,LINKHDR));
      if (!errcode) Nperiods = 1;
      fclose(TmpOutFile);
      free(x);
   }

/* Save avg. reaction rates & file epilog */
   if (OutFile != NULL)
   {
      ERRCODE(savenetreacts(Wbulk,Wwall,Wtank,Wsource));
      ERRCODE(saveepilog());
   }
   return(errcode);
}


int  savetimestat(REAL4 *x, char objtype)
/*
**--------------------------------------------------------------
**   Input:   *x  = buffer for node values
**            objtype = NODEHDR (for nodes) or LINKHDR (for links)                                                
**   Output:  returns error code                                  
**   Purpose: computes time series statistic for nodes or links
**            and saves to normal output file.
**
**   NOTE: This routine is dependent on how the output reporting
**         variables were assigned to FieldType in TYPES.H.
**--------------------------------------------------------------
*/
{
   int   n, n1, n2;
   int   i, j,  p, errcode = 0;
   long  startbyte, skipbytes;
   float *stat1, *stat2, xx;

/*
  Compute number of bytes in temp output file to skip over (skipbytes)
  when moving from one time period to the next for a particular variable.
*/
   if (objtype == NODEHDR)
   {
   /*
      For nodes, we start at 0 and skip over node output for all
      node variables minus 1 plus link output for all link variables.
   */
      startbyte = 0;
      skipbytes = (Nnodes*(QUALITY-DEMAND) +
                   Nlinks*(FRICTION-FLOW+1))*sizeof(REAL4);
      n = Nnodes;
      n1 = DEMAND;
      n2 = QUALITY;
   }
   else
   {
   /*
      For links, we start at the end of all node variables and skip
      over node output for all node variables plus link output for
      all link variables minus 1.
   */
      startbyte = Nnodes*(QUALITY-DEMAND+1)*sizeof(REAL4);
      skipbytes = (Nnodes*(QUALITY-DEMAND+1) +
                   Nlinks*(FRICTION-FLOW))*sizeof(REAL4);
      n = Nlinks;
      n1 = FLOW;
      n2 = FRICTION;
   }
   stat1 = (float *) calloc(n+1, sizeof(float));
   stat2 = (float *) calloc(n+1, sizeof(float));
   ERRCODE(MEMCHECK(stat1));
   ERRCODE(MEMCHECK(stat2));

   /* Process each output reporting variable */
   if (!errcode)
   {
      for (j=n1; j<=n2; j++)
      {
   
         /* Initialize stat arrays */
         if (Tstatflag == AVG) memset(stat1, 0, (n+1)*sizeof(float));
         else for (i=1; i<=n; i++)
         {
            stat1[i] = -MISSING;  /* +1E10 */
            stat2[i] =  MISSING;  /* -1E10 */
         }
   
         /* Position temp output file at start of output */
         fseek(TmpOutFile, startbyte + (j-n1)*n*sizeof(REAL4), SEEK_SET);

         /* Process each time period */
         for (p=1; p<=Nperiods; p++)
         {

            /* Get output results for time period & update stats */
            fread(x+1, sizeof(REAL4), n, TmpOutFile);
            for (i=1; i<=n; i++)
            {
               xx = x[i];
               if (objtype == LINKHDR)
               {
                  if (j == FLOW) xx = ABS(xx);
                  if (j == STATUS)
                  {
                     if (xx >= OPEN) xx = 1.0;
                     else            xx = 0.0;
                  }
               }
               if (Tstatflag == AVG)  stat1[i] += xx;
               else
               {
                  stat1[i] = MIN(stat1[i], xx);
                  stat2[i] = MAX(stat2[i], xx);
               }
            }

            /* Advance file to next period */
            if (p < Nperiods) fseek(TmpOutFile, skipbytes, SEEK_CUR);
         }

         /* Compute resultant stat & save to regular output file */
         switch (Tstatflag)
         {
            case AVG:   for (i=1; i<=n; i++) x[i] = stat1[i]/(float)Nperiods;
                        break;
            case MIN:   for (i=1; i<=n; i++) x[i] = stat1[i];
                        break;
            case MAX:   for (i=1; i<=n; i++) x[i] = stat2[i];
                        break;
            case RANGE: for (i=1; i<=n; i++) x[i] = stat2[i] - stat1[i];
                        break;
         }
         if (objtype == LINKHDR && j == STATUS)
         {
            for (i=1; i<=n; i++)
            {
               if (x[i] < 0.5f) x[i] = CLOSED;
               else             x[i] = OPEN;
            }
         }
         if (fwrite(x+1, sizeof(REAL4), n, OutFile) < (unsigned) n) errcode = 308;

         /* Update internal output variables where applicable */
         if (objtype == NODEHDR) switch (j)
         {
            case DEMAND:  for (i=1; i<=n; i++) D[i] = x[i]/Ucf[DEMAND];
                          break;   
            case HEAD:    for (i=1; i<=n; i++) H[i] = x[i]/Ucf[HEAD];
                          break;   
            case QUALITY: for (i=1; i<=n; i++) C[i] = x[i]/Ucf[QUALITY];
                          break;
         }
         else if (j == FLOW) for (i=1; i<=n; i++) Q[i] = x[i]/Ucf[FLOW];
      }
   }

   /* Free allocated memory */
   free(stat1);
   free(stat2);
   return(errcode);
}


int  savenetreacts(double wbulk, double wwall, double wtank, double wsource)
/*
**-----------------------------------------------------
**  Writes average network-wide reaction rates (in
**  mass/hr) to binary output file.
**-----------------------------------------------------
*/
{
   int errcode = 0;
   double t;
   REAL4 w[4];
   if (Dur > 0) t = (double)Dur/3600.;
   else t = 1.;
   w[0] = (REAL4)(wbulk/t);
   w[1] = (REAL4)(wwall/t);
   w[2] = (REAL4)(wtank/t);
   w[3] = (REAL4)(wsource/t);
   if (fwrite(w,sizeof(REAL4),4,OutFile) < 4) errcode = 308;
   return(errcode);
}


int  saveepilog()
/*
**-------------------------------------------------
**  Writes Nperiods, Warnflag, & Magic Number to 
**  end of binary output file.
**-------------------------------------------------
*/
{
   int errcode = 0;
   INT4 i;
   i = Nperiods;
   if (fwrite(&i,sizeof(INT4),1,OutFile) < 1) errcode = 308;
   i = Warnflag;
   if (fwrite(&i,sizeof(INT4),1,OutFile) < 1) errcode = 308;
   i = MAGICNUMBER;
   if (fwrite(&i,sizeof(INT4),1,OutFile) < 1) errcode = 308;
   return(errcode);
}


/********************** END OF OUTPUT.C **********************/
