/*
 ******************************************************************************
 Project:      OWA EPANET
 Version:      2.3
 Module:       project.c
 Description:  project data management routines
 Authors:      see AUTHORS
 Copyright:    see AUTHORS
 License:      see LICENSE
 Last Updated: 04/23/2025
 ******************************************************************************
*/

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

//*** For the Windows SDK _tempnam function ***//
#ifdef _WIN32
#include <windows.h>
#endif

#include "types.h"
#include "funcs.h"

int openproject(Project *pr, const char *inpFile, const char *rptFile,
                      const char *outFile, int allowerrors)
/*----------------------------------------------------------------
 **  Input:   inpFile = name of input file
 **           rptFile = name of report file
 **           outFile = name of binary output file
 **           allowerrors = TRUE if project can be opened with errors
 **  Output:  none
 **  Returns: error code
 **  Purpose: opens an EPANET input file & reads in network data
 **----------------------------------------------------------------
 */
{
    int errcode = 0;
    int hyderrcode = 0;
    int projectopened;

    // Set system flags
    pr->Openflag = FALSE;
    pr->hydraul.OpenHflag = FALSE;
    pr->quality.OpenQflag = FALSE;
    pr->outfile.SaveHflag = FALSE;
    pr->outfile.SaveQflag = FALSE;
    pr->Warnflag = FALSE;
    pr->report.Messageflag = TRUE;
    pr->report.Rptflag = 1;

    // Initialize data arrays to NULL
    initpointers(pr);

    // Open input & report files
    ERRCODE(openfiles(pr, inpFile, rptFile, outFile));
    if (errcode > 0)
    {
        errmsg(pr, errcode);
        return errcode;
    }

    // Allocate memory for project's data arrays
    ERRCODE(netsize(pr));
    ERRCODE(allocdata(pr));

    // Read input data
    ERRCODE(getdata(pr));

    // Close input file
    if (pr->parser.InFile != NULL)
    {
        fclose(pr->parser.InFile);
        pr->parser.InFile = NULL;
    }
    
    // Input file read with no fatal errors
    if (allowerrors) projectopened = (errcode == 0 || errcode == 200);
    else projectopened = (errcode == 0);
    if (projectopened)
    {
        // If using previously saved hydraulics file then open it
        if (pr->outfile.Hydflag == USE)
        {
            hyderrcode = openhydfile(pr);
            if (hyderrcode > 0)
            {
                errmsg(pr, hyderrcode);
                pr->outfile.Hydflag = SCRATCH;
            }
        }            

        // Write input summary to report file
        if (pr->report.Summaryflag) writesummary(pr);
        pr->Openflag = TRUE;
    }
    errmsg(pr, errcode);
    return errcode;
}

int openfiles(Project *pr, const char *f1, const char *f2, const char *f3)
/*----------------------------------------------------------------
**  Input:   f1 = pointer to name of input file
**           f2 = pointer to name of report file
**           f3 = pointer to name of binary output file
**  Output:  none
**  Returns: error code
**  Purpose: opens input & report files
**----------------------------------------------------------------
*/
{
    // Initialize file pointers to NULL
    pr->parser.InFile = NULL;
    pr->report.RptFile = NULL;
    pr->outfile.OutFile = NULL;
    pr->outfile.HydFile = NULL;
    pr->outfile.TmpOutFile = NULL;

    // Save file names
    strncpy(pr->parser.InpFname, f1, MAXFNAME);
    strncpy(pr->report.Rpt1Fname, f2, MAXFNAME);
    strncpy(pr->outfile.OutFname, f3, MAXFNAME);
    if (strlen(f3) > 0) pr->outfile.Outflag = SAVE;
    else
    {
        pr->outfile.Outflag = SCRATCH;
        strcpy(pr->outfile.OutFname, pr->TmpOutFname);
    }

    // Check that file names are not identical
    if (strlen(f1) > 0 && (strcomp(f1, f2) || strcomp(f1, f3))) return 301;
    if (strlen(f3) > 0 && strcomp(f2, f3)) return 301;

    // Attempt to open input and report files
    if (strlen(f1) > 0)
    {
        if ((pr->parser.InFile = fopen(f1, "rt")) == NULL) return 302;
    }
    if (strlen(f2) == 0) pr->report.RptFile = stdout;
    else
    {
        pr->report.RptFile = fopen(f2, "wt");
        if (pr->report.RptFile == NULL) return 303;
    }
    writelogo(pr);
    return 0;
}

int openhydfile(Project *pr)
/*----------------------------------------------------------------
** Input:   none
** Output:  none
** Returns: error code
** Purpose: opens file that saves hydraulics solution
**----------------------------------------------------------------
*/
{
    const int Nnodes = pr->network.Nnodes;
    const int Ntanks = pr->network.Ntanks;
    const int Nlinks = pr->network.Nlinks;
    const int Nvalves = pr->network.Nvalves;
    const int Npumps = pr->network.Npumps;

    INT4 nsize[6]; // Temporary array
    INT4 magic;
    INT4 version;
    int errcode = 0;

    // If HydFile currently open, then close it
    if (pr->outfile.HydFile != NULL)
    {
        fclose(pr->outfile.HydFile);
        pr->outfile.HydFile = NULL;
    }

    // Use Hydflag to determine the type of hydraulics file to use.
    // Write error message if the file cannot be opened.
    pr->outfile.HydFile = NULL;
    switch (pr->outfile.Hydflag)
    {
      case SCRATCH:
        strcpy(pr->outfile.HydFname, pr->TmpHydFname);
        pr->outfile.HydFile = fopen(pr->outfile.HydFname, "w+b");
        break;
      case SAVE:
        pr->outfile.HydFile = fopen(pr->outfile.HydFname, "w+b");
        break;
      case USE:
        pr->outfile.HydFile = fopen(pr->outfile.HydFname, "rb");
        break;
    }
    if (pr->outfile.HydFile == NULL) return 305;

    // If a previous hydraulics solution is not being used, then
    // save the current network size parameters to the file.
    if (pr->outfile.Hydflag != USE)
    {
        magic = MAGICNUMBER;
        version = ENGINE_VERSION;
        nsize[0] = Nnodes;
        nsize[1] = Nlinks;
        nsize[2] = Ntanks;
        nsize[3] = Npumps;
        nsize[4] = Nvalves;
        nsize[5] = (int)pr->times.Dur;
        fwrite(&magic, sizeof(INT4), 1, pr->outfile.HydFile);
        fwrite(&version, sizeof(INT4), 1, pr->outfile.HydFile);
        fwrite(nsize, sizeof(INT4), 6, pr->outfile.HydFile);
    }

    // If a previous hydraulics solution is being used, then
    // make sure its network size parameters match those of
    // the current network
    if (pr->outfile.Hydflag == USE)
    {
        fread(&magic, sizeof(INT4), 1, pr->outfile.HydFile);
        if (magic != MAGICNUMBER) return 306;
        fread(&version, sizeof(INT4), 1, pr->outfile.HydFile);
        if (version != ENGINE_VERSION) return 306;
        if (fread(nsize, sizeof(INT4), 6, pr->outfile.HydFile) < 6) return 306;
        if (nsize[0] != Nnodes || nsize[1] != Nlinks || nsize[2] != Ntanks ||
            nsize[3] != Npumps || nsize[4] != Nvalves ||
            nsize[5] != pr->times.Dur
           ) return 306;
        pr->outfile.SaveHflag = TRUE;
    }

    // Save current position in hydraulics file
    // where storage of hydraulic results begins
    pr->outfile.HydOffset = ftell(pr->outfile.HydFile);
    return errcode;
}

int openoutfile(Project *pr)
/*----------------------------------------------------------------
**  Input:   none
**  Output:  none
**  Returns: error code
**  Purpose: opens binary output file.
**----------------------------------------------------------------
*/
{
    int errcode = 0;

    // Close output file if already opened
    closeoutfile(pr);

    // Try to open binary output file
    pr->outfile.OutFile = fopen(pr->outfile.OutFname, "w+b");
    if (pr->outfile.OutFile == NULL) return 304;

    // Save basic network data & energy usage results
    ERRCODE(savenetdata(pr));
    pr->outfile.OutOffset1 = ftell(pr->outfile.OutFile);
    ERRCODE(saveenergy(pr));
    pr->outfile.OutOffset2 = ftell(pr->outfile.OutFile);

    // Open temporary file if computing time series statistic
    if (!errcode)
    {
        if (pr->report.Tstatflag != SERIES)
        {
            pr->outfile.TmpOutFile = fopen(pr->TmpStatFname, "w+b");
            if (pr->outfile.TmpOutFile == NULL) errcode = 304;
        }
        else pr->outfile.TmpOutFile = pr->outfile.OutFile;
    }
    return errcode;
}

void closeoutfile(Project *pr)
/*----------------------------------------------------------------
**  Input:   none
**  Output:  none
**  Purpose: closes binary output file.
**----------------------------------------------------------------
*/
{
    if (pr->outfile.TmpOutFile != pr->outfile.OutFile)
    {
        if (pr->outfile.TmpOutFile != NULL)
        {
            fclose(pr->outfile.TmpOutFile);
            pr->outfile.TmpOutFile = NULL;
        }
    }
    if (pr->outfile.OutFile != NULL)
    {
        if (pr->outfile.OutFile == pr->outfile.TmpOutFile)
        {
            pr->outfile.TmpOutFile = NULL;
        }
        fclose(pr->outfile.OutFile);
        pr->outfile.OutFile = NULL;
    }
}

void initpointers(Project *pr)
/*----------------------------------------------------------------
**  Input:   none
**  Output:  none
**  Purpose: initializes data array pointers to NULL
**----------------------------------------------------------------
*/
{
    Network* nw = &pr->network;
    nw->Nnodes = 0;
    nw->Ntanks = 0;
    nw->Njuncs = 0;
    nw->Nlinks = 0;
    nw->Npipes = 0;
    nw->Npumps = 0;
    nw->Nvalves = 0;
    nw->Ncontrols = 0;
    nw->Nrules = 0;
    nw->Npats = 0;
    nw->Ncurves = 0;

    pr->hydraul.NodeDemand = NULL;
    pr->hydraul.NodeHead = NULL;
    pr->hydraul.LinkFlow = NULL;
    pr->hydraul.LinkStatus = NULL;
    pr->hydraul.LinkSetting = NULL;
    pr->hydraul.OldStatus = NULL;
    pr->hydraul.P = NULL;
    pr->hydraul.Y = NULL;
    pr->hydraul.Xflow = NULL;
    pr->hydraul.FullDemand = NULL;
    pr->hydraul.DemandFlow = NULL;
    pr->hydraul.EmitterFlow = NULL;
    pr->hydraul.LeakageFlow = NULL;
    pr->hydraul.Leakage = NULL;

    pr->quality.NodeQual = NULL;
    pr->quality.PipeRateCoeff = NULL;

    pr->network.Node = NULL;
    pr->network.Link = NULL;
    pr->network.Tank = NULL;
    pr->network.Pump = NULL;
    pr->network.Valve = NULL;
    pr->network.Pattern = NULL;
    pr->network.Curve = NULL;
    pr->network.Control = NULL;
    pr->network.Adjlist = NULL;
    pr->network.NodeHashTable = NULL;
    pr->network.LinkHashTable = NULL;

    pr->hydraul.smatrix.Aii = NULL;
    pr->hydraul.smatrix.Aij = NULL;
    pr->hydraul.smatrix.F = NULL;
    pr->hydraul.smatrix.Order = NULL;
    pr->hydraul.smatrix.Row = NULL;
    pr->hydraul.smatrix.Ndx = NULL;
    pr->hydraul.smatrix.XLNZ = NULL;
    pr->hydraul.smatrix.NZSUB = NULL;
    pr->hydraul.smatrix.LNZ = NULL;

    pr->report.reportCallback = NULL;

    initrules(pr);
}

int allocdata(Project *pr)
/*----------------------------------------------------------------
**  Input:   none
**  Output:  none
**  Returns: error code
**  Purpose: allocates memory for network data structures
**----------------------------------------------------------------
*/
{
    int n;
    int errcode = 0;

    // Allocate node & link ID hash tables
    pr->network.NodeHashTable = hashtable_create();
    pr->network.LinkHashTable = hashtable_create();
    ERRCODE(MEMCHECK(pr->network.NodeHashTable));
    ERRCODE(MEMCHECK(pr->network.LinkHashTable));

    // Allocate memory for network nodes
    //*************************************************************
    // NOTE: Because network components of a given type are indexed
    // starting from 1, their arrays must be sized 1
    // element larger than the number of components.
    //*************************************************************
    if (!errcode)
    {
        n = pr->parser.MaxNodes + 1;
        pr->network.Node       = (Snode *)calloc(n, sizeof(Snode));
        pr->hydraul.NodeDemand = (double *)calloc(n, sizeof(double));
        pr->hydraul.NodeHead   = (double *)calloc(n, sizeof(double));
        pr->quality.NodeQual   = (double *)calloc(n, sizeof(double));
        pr->hydraul.FullDemand  = (double *)calloc(n, sizeof(double));
        pr->hydraul.DemandFlow  = (double *)calloc(n, sizeof(double));
        pr->hydraul.EmitterFlow = (double *)calloc(n, sizeof(double));
        pr->hydraul.LeakageFlow = (double *)calloc(n, sizeof(double));
        ERRCODE(MEMCHECK(pr->network.Node));
        ERRCODE(MEMCHECK(pr->hydraul.NodeDemand));
        ERRCODE(MEMCHECK(pr->hydraul.NodeHead));
        ERRCODE(MEMCHECK(pr->quality.NodeQual));
        ERRCODE(MEMCHECK(pr->hydraul.FullDemand));
        ERRCODE(MEMCHECK(pr->hydraul.DemandFlow));
        ERRCODE(MEMCHECK(pr->hydraul.EmitterFlow));
        ERRCODE(MEMCHECK(pr->hydraul.LeakageFlow));
    }

    // Allocate memory for network links
    if (!errcode)
    {
        n = pr->parser.MaxLinks + 1;
        pr->network.Link        = (Slink *)calloc(n, sizeof(Slink));
        pr->hydraul.LinkFlow    = (double *)calloc(n, sizeof(double));
        pr->hydraul.LinkSetting = (double *)calloc(n, sizeof(double));
        pr->hydraul.LinkStatus  = (StatusType *)calloc(n, sizeof(StatusType));
        ERRCODE(MEMCHECK(pr->network.Link));
        ERRCODE(MEMCHECK(pr->hydraul.LinkFlow));
        ERRCODE(MEMCHECK(pr->hydraul.LinkSetting));
        ERRCODE(MEMCHECK(pr->hydraul.LinkStatus));
    }

    // Allocate memory for tanks, sources, pumps, valves, and controls
    // (memory for Patterns and Curves arrays expanded as each is added)
    if (!errcode)
    {
        pr->network.Tank =
            (Stank *)calloc(pr->parser.MaxTanks + 1, sizeof(Stank));
        pr->network.Pump =
            (Spump *)calloc(pr->parser.MaxPumps + 1, sizeof(Spump));
        pr->network.Valve =
            (Svalve *)calloc(pr->parser.MaxValves + 1, sizeof(Svalve));
        pr->network.Control =
            (Scontrol *)calloc(pr->parser.MaxControls + 1, sizeof(Scontrol));
        ERRCODE(MEMCHECK(pr->network.Tank));
        ERRCODE(MEMCHECK(pr->network.Pump));
        ERRCODE(MEMCHECK(pr->network.Valve));
        ERRCODE(MEMCHECK(pr->network.Control));
    }

    // Initialize pointers used in nodes and links
    if (!errcode)
    {
        for (n = 0; n <= pr->parser.MaxNodes; n++)
        {
            pr->network.Node[n].D = NULL;    // node demand
            pr->network.Node[n].S = NULL;    // node source
            pr->network.Node[n].Comment = NULL;
            pr->network.Node[n].Tag = NULL;                                           
        }
        for (n = 0; n <= pr->parser.MaxLinks; n++)
        {
            pr->network.Link[n].Vertices = NULL;
            pr->network.Link[n].Comment = NULL;
            pr->network.Link[n].Tag = NULL;                                           
        }
    }

    // Allocate memory for rule base (see RULES.C)
    if (!errcode) errcode = allocrules(pr);
    return errcode;
}

void freedata(Project *pr)
/*----------------------------------------------------------------
**  Input:   none
**  Output:  none
**  Purpose: frees memory allocated for network data structures.
**----------------------------------------------------------------
*/
{
    int j;

    // Free memory for computed results
    free(pr->hydraul.NodeDemand);
    free(pr->hydraul.NodeHead);
    free(pr->hydraul.LinkFlow);
    free(pr->hydraul.LinkSetting);
    free(pr->hydraul.LinkStatus);
    free(pr->hydraul.FullDemand);
    free(pr->hydraul.DemandFlow);
    free(pr->hydraul.EmitterFlow);
    free(pr->hydraul.LeakageFlow);
    free(pr->quality.NodeQual);

    // Free memory used for nodal adjacency lists
    freeadjlists(&pr->network);

    // Free memory for node data
    if (pr->network.Node != NULL)
    {
        for (j = 1; j <= pr->network.Nnodes; j++)
        {
            // Free memory used for demands and WQ source data
            freedemands(&(pr->network.Node[j]));
            free(pr->network.Node[j].S);
            free(pr->network.Node[j].Comment);
            free(pr->network.Node[j].Tag);                                          
        }
        free(pr->network.Node);
    }

    // Free memory for link data
    if (pr->network.Link != NULL)
    {
        for (j = 1; j <= pr->network.Nlinks; j++)
        {
            freelinkvertices(&pr->network.Link[j]);
            free(pr->network.Link[j].Comment);
            free(pr->network.Link[j].Tag);                                          
        }
    }
    free(pr->network.Link);

    // Free memory for other network objects
    free(pr->network.Tank);
    free(pr->network.Pump);
    free(pr->network.Valve);
    free(pr->network.Control);

    // Free memory for time patterns
    if (pr->network.Pattern != NULL)
    {
        for (j = 0; j <= pr->network.Npats; j++)
        {
            free(pr->network.Pattern[j].F);
            free(pr->network.Pattern[j].Comment);
        }
        free(pr->network.Pattern);
    }

    // Free memory for curves
    if (pr->network.Curve != NULL)
    {
        // There is no Curve[0]
        for (j = 1; j <= pr->network.Ncurves; j++)
        {
            free(pr->network.Curve[j].X);
            free(pr->network.Curve[j].Y);
            free(pr->network.Curve[j].Comment);
        }
        free(pr->network.Curve);
    }

    // Free memory for rule base (see RULES.C)
    freerules(pr);

    // Free hash table memory
    if (pr->network.NodeHashTable != NULL)
    {
        hashtable_free(pr->network.NodeHashTable);
    }
    if (pr->network.LinkHashTable != NULL)
    {
        hashtable_free(pr->network.LinkHashTable);
    }
}

Pdemand finddemand(Pdemand d, int index)
/*----------------------------------------------------------------
**  Input:   d = pointer to start of a list of demands
**           index = the position of the demand to retrieve
**  Output:  none
**  Returns: the demand at the requested position
**  Purpose: finds the demand at a given position in a demand list
**----------------------------------------------------------------
*/
{
    int n = 1;
    if (index <= 0)return NULL;
    while (d)
    {
        if (n == index) break;
        n++;
        d = d->next;
    }
    return d;
}

int adddemand(Snode *node, double dbase, int dpat, const char *dname)
/*----------------------------------------------------------------
**  Input:   node = a network junction node
**           dbase = base demand value
**           dpat = demand pattern index
**           dname = name of demand category
**  Output:  returns TRUE if successful, FALSE if not
**  Purpose: adds a new demand category to a node.
**----------------------------------------------------------------
*/
{
    Pdemand demand, lastdemand;

    // Create a new demand struct
    demand = (struct Sdemand *)malloc(sizeof(struct Sdemand));
    if (demand == NULL) return FALSE;

    // Assign it the designated properties
    demand->Base = dbase;
    demand->Pat = dpat;
    demand->Name = NULL;
    if (dname && strlen(dname) > 0) xstrcpy(&demand->Name, dname, MAXID);
    demand->next = NULL;

    // If node has no demands make this its first demand category
    if (node->D == NULL) node->D = demand;

    // Otherwise append this demand to the end of the node's demands list
    else
    {
        lastdemand = node->D;
        while (lastdemand->next) lastdemand = lastdemand->next;
        lastdemand->next = demand;
    }
    return TRUE;
}

void freedemands(Snode *node)
/*----------------------------------------------------------------
**  Input:   node = a network junction node
**  Output:  node
**  Purpose: frees the memory used for a node's list of demands.
**----------------------------------------------------------------
*/
{
    Pdemand nextdemand;
    Pdemand demand = node->D;
    while (demand != NULL)
    {
        nextdemand = demand->next;
        free(demand->Name);
        free(demand);
        demand = nextdemand;
    }
    node->D = NULL;
}

int  addlinkvertex(Slink *link, double x, double y)
/*----------------------------------------------------------------
**  Input:   link = pointer to a network link
**           x = x-coordinate of a new vertex
**           y = y-coordiante of a new vertex
**  Returns: an error code
**  Purpose: adds to a link's collection of vertex points.
**----------------------------------------------------------------
*/
{
    static int CHUNKSIZE = 5;
    int n, newCapacity;
    Pvertices vertices;
    double *newX, *newY;

    vertices = link->Vertices;
    if (vertices == NULL)
    {
        vertices = (struct Svertices *) malloc(sizeof(struct Svertices));
        if (vertices == NULL) return 101;
        vertices->Npts = 0;
        vertices->Capacity = 0;
        vertices->X = NULL;
        vertices->Y = NULL;
        link->Vertices = vertices;
    }
    if (vertices->Npts >= vertices->Capacity)
    {
        newCapacity = vertices->Capacity + CHUNKSIZE;
        newX = realloc(vertices->X, newCapacity * sizeof(double));
        newY = realloc(vertices->Y, newCapacity * sizeof(double));
        if (newX == NULL || newY == NULL)
        {
            free(newX);
            free(newY);
            return 101;
        }
        vertices->Capacity = newCapacity;
        vertices->X = newX;
        vertices->Y = newY;
    }
    n = vertices->Npts;
    vertices->X[n] = x;
    vertices->Y[n] = y;
    vertices->Npts++;
    return 0;
}

void freelinkvertices(Slink *link)
/*----------------------------------------------------------------
**  Input:   vertices = list of link vertex points
**  Output:  none
**  Purpose: frees the memory used for a link's list of vertices.
**----------------------------------------------------------------
*/
{
    if (link->Vertices)
    {
        free(link->Vertices->X);
        free(link->Vertices->Y);
        free(link->Vertices);
        link->Vertices = NULL;
    }
}

int  buildadjlists(Network *net)
/*
**--------------------------------------------------------------
** Input:   none
** Output:  returns error code
** Purpose: builds linked list of links adjacent to each node
**--------------------------------------------------------------
*/
{
    int       i, j, k;
    int       errcode = 0;
    Padjlist  alink;

    // Create an array of adjacency lists
    freeadjlists(net);
    net->Adjlist = (Padjlist *)calloc(net->Nnodes + 1, sizeof(Padjlist));
    if (net->Adjlist == NULL) return 101;
    for (i = 0; i <= net->Nnodes; i++) net->Adjlist[i] = NULL;

    // For each link, update adjacency lists of its end nodes
    for (k = 1; k <= net->Nlinks; k++)
    {
        i = net->Link[k].N1;
        j = net->Link[k].N2;

        // Include link in start node i's list
        alink = (struct Sadjlist *) malloc(sizeof(struct Sadjlist));
        if (alink == NULL)
        {
            errcode = 101;
            break;
        }
        alink->node = j;
        alink->link = k;
        alink->next = net->Adjlist[i];
        net->Adjlist[i] = alink;

        // Include link in end node j's list
        alink = (struct Sadjlist *) malloc(sizeof(struct Sadjlist));
        if (alink == NULL)
        {
            errcode = 101;
            break;
        }
        alink->node = i;
        alink->link = k;
        alink->next = net->Adjlist[j];
        net->Adjlist[j] = alink;
    }
    if (errcode) freeadjlists(net);
    return errcode;
}

void  freeadjlists(Network *net)
/*
**--------------------------------------------------------------
** Input:   none
** Output:  none
** Purpose: frees memory used for nodal adjacency lists
**--------------------------------------------------------------
*/
{
    int   i;
    Padjlist alink;

    if (net->Adjlist == NULL) return;
    for (i = 0; i <= net->Nnodes; i++)
    {
        for (alink = net->Adjlist[i]; alink != NULL; alink = net->Adjlist[i])
        {
            net->Adjlist[i] = alink->next;
            free(alink);
        }
    }
    FREE(net->Adjlist);
}

int incontrols(Project *pr, int objType, int index)
/*----------------------------------------------------------------
**  Input:   objType = type of object (either NODE or LINK)
**           index  = the object's index
**  Output:  none
**  Returns: 1 if any controls contain the object; 0 if not
**  Purpose: determines if any simple or rule-based controls
**           contain a particular node or link.
**----------------------------------------------------------------
*/
{
    Network *net = &pr->network;

    int i, ruleObject;
    Spremise *premise;
    Saction *action;

    // Check simple controls
    for (i = 1; i <= net->Ncontrols; i++)
    {
        if (objType == NODE && net->Control[i].Node == index) return 1;
        if (objType == LINK && net->Control[i].Link == index) return 1;
    }

    // Check rule-based controls
    for (i = 1; i <= net->Nrules; i++)
    {
        // Convert objType to a rule object type
        if (objType == NODE) ruleObject = 6;
        else                 ruleObject = 7;

        // Check rule's premises
        premise = net->Rule[i].Premises;
        while (premise != NULL)
        {
            if (ruleObject == premise->object && premise->index == index) return 1;
            premise = premise->next;
        }

        // Rule actions only need to be checked for link objects
        if (objType == LINK)
        {
            // Check rule's THEN actions
            action = net->Rule[i].ThenActions;
            while (action != NULL)
            {
                if (action->link == index) return 1;
                action = action->next;
            }

            // Check rule's ELSE actions
            action = net->Rule[i].ElseActions;
            while (action != NULL)
            {
                if (action->link == index) return 1;
                action = action->next;
            }
        }
    }
    return 0;
}

int changevalvetype(Project *pr, int index, int type)
/*
**--------------------------------------------------------------
**  Input:   index = link index
**           type = new valve type
**  Output:  returns an error code
**  Purpose: changes a valve's type
**--------------------------------------------------------------
*/
{
    Network *net = &pr->network;
    Slink *link;
    int errcode;
    double setting;

    // Check that new valve type has legal connections
    link = &net->Link[index];
    if (link->Type <= PUMP) return 264;
    errcode = valvecheck(pr, index, type, link->N1, link->N2);
    if (errcode) return errcode;
    
    // Preserve new type's setting in solver units
    setting = link->InitSetting;
    switch (link->Type)
    {
        case FCV:
            setting *= pr->Ucf[FLOW];
            break;
        case PRV:
        case PSV:
        case PBV:
            setting *= pr->Ucf[PRESSURE];
            break;
        case GPV:
            setting = 0.0;
            break;
    }
    switch (type)
    {
        case FCV:
            setting /= pr->Ucf[FLOW];
            break;
        case PRV:
        case PSV:
        case PBV:
            setting /= pr->Ucf[PRESSURE];
            break;
    }
    
    // Save setting
    if (type == GPV) setting = 0.0;
    if (type == PCV) setting = MIN(setting, 100.0);
    link->Kc = setting;
    link->InitSetting = setting;
    
    // Change valve link's type
    link->Type = type;    
    return 0;
}
    
int valvecheck(Project *pr, int index, int type, int j1, int j2)
/*
**--------------------------------------------------------------
**  Input:   index = link index
**           type = valve type
**           j1   = index of upstream node
**           j2   = index of downstream node
**  Output:  returns an error code
**  Purpose: checks for illegal connections between valves
**--------------------------------------------------------------
*/
{
    Network *net = &pr->network;

    int k, vj1, vj2;
    LinkType vtype;
    Slink *link;
    Svalve *valve;

    if (type == PRV || type == PSV || type == FCV)
    {
        // Can't be connected to a fixed grade node
        if (j1 > net->Njuncs || j2 > net->Njuncs) return 219;

        // Examine each existing valve
        for (k = 1; k <= net->Nvalves; k++)
        {
            valve = &net->Valve[k];
            if (valve->Link == index) continue;
            link = &net->Link[valve->Link];
            vj1 = link->N1;
            vj2 = link->N2;
            vtype = link->Type;

            // Cannot have two PRVs sharing downstream nodes or in series
            if (vtype == PRV && type == PRV)
            {
                if (vj2 == j2 || vj2 == j1 || vj1 == j2) return 220;
            }

            // Cannot have two PSVs sharing upstream nodes or in series
            if (vtype == PSV && type == PSV)
            {
                if (vj1 == j1 || vj1 == j2 || vj2 == j1) return 220;
            }

            // Cannot have PSV connected to downstream node of PRV
            if (vtype == PSV && type == PRV && vj1 == j2) return 220;
            if (vtype == PRV && type == PSV && vj2 == j1) return 220;

            // Cannot have PSV connected to downstream node of FCV
            // nor have PRV connected to upstream node of FCV
            if (vtype == FCV && type == PSV && vj2 == j1) return 220;
            if (vtype == FCV && type == PRV && vj1 == j2) return 220;
            if (vtype == PSV && type == FCV && vj1 == j2) return 220;
            if (vtype == PRV && type == FCV && vj2 == j1) return 220;
        }
    }
    return 0;
}

int unlinked(Project *pr)
/*
**--------------------------------------------------------------
** Input:   none
** Output:  returns error code if any unlinked junctions found
** Purpose: checks for unlinked junctions in network
**
** NOTE: unlinked tanks have no effect on computations.
**--------------------------------------------------------------
*/
{
    Network *net = &pr->network;
    int i, count = 0;
	char errmsg[MAXMSG + 1] = "";
    
    for (i = 1; i <= net->Njuncs; i++)
    {
        if (pr->network.Adjlist[i] == NULL)
        {
            count++;
            sprintf(pr->Msg, "Error 234: %s %s", geterrmsg(234, errmsg), net->Node[i].ID);
            writeline(pr, pr->Msg);
        }
        if (count >= 10) break;
    }
    if (count > 0) return 233;
    return 0;
}    

int findnode(Network *network, const char *id)
/*----------------------------------------------------------------
**  Input:   id = node ID
**  Output:  none
**  Returns: index of node with given ID, or 0 if ID not found
**  Purpose: uses hash table to find index of node with given ID
**----------------------------------------------------------------
*/
{
    return (hashtable_find(network->NodeHashTable, id));
}

int findlink(Network *network, const char *id)
/*----------------------------------------------------------------
**  Input:   id = link ID
**  Output:  none
**  Returns: index of link with given ID, or 0 if ID not found
**  Purpose: uses hash table to find index of link with given ID
**----------------------------------------------------------------
*/
{
    return (hashtable_find(network->LinkHashTable, id));
}

int findtank(Network *network, int index)
/*----------------------------------------------------------------
**  Input:   index = node index
**  Output:  none
**  Returns: index of tank with given node index, or NOTFOUND if tank not found
**  Purpose: for use in the deletenode function
**----------------------------------------------------------------
*/
{
    int i;
    for (i = 1; i <= network->Ntanks; i++)
    {
        if (network->Tank[i].Node == index) return i;
    }
    return NOTFOUND;
}

int findpump(Network *network, int index)
/*----------------------------------------------------------------
**  Input:   index = link ID
**  Output:  none
**  Returns: index of pump with given link index, or NOTFOUND if pump not found
**  Purpose: for use in the deletelink function
**----------------------------------------------------------------
*/
{
    int i;
    for (i = 1; i <= network->Npumps; i++)
    {
        if (network->Pump[i].Link == index) return i;
    }
    return NOTFOUND;
}

int findvalve(Network *network, int index)
/*----------------------------------------------------------------
**  Input:   index = link ID
**  Output:  none
**  Returns: index of valve with given link index, or NOTFOUND if valve not found
**  Purpose: for use in the deletelink function
**----------------------------------------------------------------
*/
{
    int i;
    for (i = 1; i <= network->Nvalves; i++)
    {
        if (network->Valve[i].Link == index) return i;
    }
    return NOTFOUND;
}

int findpattern(Network *network, const char *id)
/*----------------------------------------------------------------
**  Input:   id = time pattern ID
**  Output:  none
**  Returns: time pattern index, or -1 if pattern not found
**  Purpose: finds index of time pattern given its ID
**----------------------------------------------------------------
*/
{
    int i;

    // Don't forget to include the "dummy" pattern 0 in the search
    for (i = 0; i <= network->Npats; i++)
    {
        if (strcmp(id, network->Pattern[i].ID) == 0) return i;
    }
    return -1;
}

int findcurve(Network *network, const char *id)
/*----------------------------------------------------------------
**  Input:   id = data curve ID
**  Output:  none
**  Returns: data curve index, or 0 if curve not found
**  Purpose: finds index of data curve given its ID
**----------------------------------------------------------------
*/
{
    int i;
    for (i = 1; i <= network->Ncurves; i++)
    {
        if (strcmp(id, network->Curve[i].ID) == 0) return i;
    }
    return 0;
}

void adjustpattern(int *pat, int index)
/*----------------------------------------------------------------
** Local function that modifies a reference to a deleted time pattern
**----------------------------------------------------------------
*/
{
    if (*pat == index) *pat = 0;
    else if (*pat > index) (*pat)--;
}

void adjustpatterns(Network *network, int index)
/*----------------------------------------------------------------
**  Input:   index = index of time pattern being deleted
**  Output:  none
**  Purpose: modifies references made to a deleted time pattern
**----------------------------------------------------------------
*/
{
    int j;
    Pdemand demand;
    Psource source;

    // Adjust patterns used by junctions
    for (j = 1; j <= network->Nnodes; j++)
    {
        // Adjust demand patterns
        for (demand = network->Node[j].D; demand != NULL; demand = demand->next)
        {
            adjustpattern(&demand->Pat, index);
        }
        // Adjust WQ source patterns
        source = network->Node[j].S;
        if (source) adjustpattern(&source->Pat, index);
    }

    // Adjust patterns used by reservoir tanks
    for (j = 1; j <= network->Ntanks; j++)
    {
        adjustpattern(&network->Tank[j].Pat, index);
    }

    // Adjust patterns used by pumps
    for (j = 1; j <= network->Npumps; j++)
    {
        adjustpattern(&network->Pump[j].Upat, index);
        adjustpattern(&network->Pump[j].Epat, index);
    }
}

void adjustcurve(int *curve, int index)
/*----------------------------------------------------------------
** Local function that modifies a reference to a deleted data curve
**----------------------------------------------------------------
*/
{
    if (*curve == index) *curve = 0;
    else if (*curve > index) (*curve)--;
}

void adjustcurves(Network *network, int index)
/*----------------------------------------------------------------
**  Input:   index = index of data curve being deleted
**  Output:  none
**  Purpose: modifies references made to a deleted data curve
**----------------------------------------------------------------
*/
{
    int j, k, curve;

    // Adjust tank volume curves
    for (j = 1; j <= network->Ntanks; j++)
    {
        adjustcurve(&network->Tank[j].Vcurve, index);
    }

    // Adjust pump curves
    for (j = 1; j <= network->Npumps; j++)
    {
        adjustcurve(&network->Pump[j].Hcurve, index);
        adjustcurve(&network->Pump[j].Ecurve, index);
    }

    // Adjust PCV & GPV curves
    for (j = 1; j <= network->Nvalves; j++)
    {
        k = network->Valve[j].Link;
        if (network->Link[k].Type == PCV)
        {
            if ((curve = network->Valve[j].Curve) > 0)
            {
                adjustcurve(&curve, index);
                network->Valve[j].Curve = curve;
                if (curve == 0)
                    network->Link[k].Kc = 0.0;
            }
        }
        if (network->Link[k].Type == GPV)
        {
            curve = INT(network->Link[k].Kc);
            adjustcurve(&curve, index);
            network->Link[k].Kc = curve;
        }
    }
}


int resizecurve(Scurve *curve, int size)
/*----------------------------------------------------------------
**  Input:   curve = a data curve object
**           size = desired number of curve data points
**  Output:  error code
**  Purpose: resizes a data curve to a desired size
**----------------------------------------------------------------
*/
{
    double *x;
    double *y;

    if (curve->Capacity < size)
    {
        x = (double *)realloc(curve->X, size * sizeof(double));
        if (x == NULL) return 101;
        y = (double *)realloc(curve->Y, size * sizeof(double));
        if (y == NULL)
        {
            free(x);
            return 101;
        }
        curve->X = x;
        curve->Y = y;
        curve->Capacity = size;
    }
    return 0;
}


int setcontrol(EN_Project p, int type, int linkIndex, double setting,
        int nodeIndex, double level, Scontrol *control)
/*----------------------------------------------------------------
**  Input:   type = type of control (see EN_ControlType)
**           linkIndex = index of link being controlled
**           setting = link control setting (e.g., pump speed)
**           nodeIndex = index of node controlling a link (for level controls)
**           level = control activation level (pressure for junction nodes,
**                   water level for tank nodes or time value for time-based
**                   control)
**  Output:  control = control struct whose properties are being set
**  Returns: error code
**  Purpose: assigns properties to a control struct.
**----------------------------------------------------------------
*/
{
    Network *net = &p->network;
    Parser *parser = &p->parser;
    
    long t = 0;
    double lvl = 0.0, s = MISSING;
    double *Ucf = p->Ucf;
    LinkType linktype;
    StatusType status = ACTIVE;
   
    // Cannot control check valve
    linktype = net->Link[linkIndex].Type;
    if (linktype == CVPIPE)  return 207;

    // Check for valid control type and node index
    if (type < 0 || type > TIMEOFDAY) return 251;
    if (type == LOWLEVEL || type == HILEVEL)
    {
        if (nodeIndex < 1 || nodeIndex > net->Nnodes) return 203;
    }
    else nodeIndex = 0;

    // Check if control setting is a status level
    if (setting == SET_OPEN)
    {
        status = OPEN;
        if (linktype == PUMP) s = 1.0;
        if (linktype == GPV)  s = net->Link[linkIndex].Kc;
    }
    else if (setting == SET_CLOSED)
    {
        status = CLOSED;
        if (linktype == PUMP) s = 0.0;
        if (linktype == GPV)  s = net->Link[linkIndex].Kc;
    }
    
    // Convert units of control setting
    else
    {
        s = setting;
        switch (linktype)
        {
            case PIPE:
            case PUMP:
                if (s < 0.0)       return 202;
                else if (s == 0.0) status = CLOSED;
                else               status = OPEN;
                break;
            case PRV:
            case PSV:
            case PBV:
                s /= Ucf[PRESSURE];
                break;
            case FCV:
                s /= Ucf[FLOW];
                break;
            case GPV:
                if (s == 0.0) status = CLOSED;
                else if (s == 1.0) status = OPEN;
                else return 202;
                s = net->Link[linkIndex].Kc;
                break;
        }
    }
    
    // Determine if control level is a pressure, tank level or time value 
    if (type == LOWLEVEL || type == HILEVEL)
    {
        if (nodeIndex > net->Njuncs) lvl = net->Node[nodeIndex].El + level / Ucf[ELEV];
        else lvl = net->Node[nodeIndex].El + level / Ucf[PRESSURE];
    }
    else if (type == TIMER || type == TIMEOFDAY)
    {
        t = (long)level;
        if (t < 0) return 202;
    }
    
    // Assign values to control struct
    control->Link = linkIndex;
    control->Node = nodeIndex;
    control->Type = type;
    control->Status = status;
    control->Setting = s;
    control->Time = t;
    control->Grade = lvl;
    control->isEnabled = TRUE;
    return 0;
}


int  getcomment(Network *network, int object, int index, char *comment)
//----------------------------------------------------------------
//  Input:   object = a type of network object
//           index = index of the specified object
//           comment = the object's comment string
//  Output:  error code
//  Purpose: gets the comment string assigned to an object.
//----------------------------------------------------------------
{
    char *currentcomment;

    // Get pointer to specified object's comment
    switch (object)
    {
    case NODE:
        if (index < 1 || index > network->Nnodes) return 251;
        currentcomment = network->Node[index].Comment;
        break;
    case LINK:
        if (index < 1 || index > network->Nlinks) return 251;
        currentcomment = network->Link[index].Comment;
        break;
    case TIMEPAT:
        if (index < 1 || index > network->Npats) return 251;
        currentcomment = network->Pattern[index].Comment;
        break;
    case CURVE:
        if (index < 1 || index > network->Ncurves) return 251;
        currentcomment = network->Curve[index].Comment;
        break;
    default:
        strcpy(comment, "");
        return 251;
    }

    // Copy the object's comment to the returned string
    if (currentcomment) strcpy(comment, currentcomment);
    else comment[0] = '\0';
    return 0;
}

int setcomment(Network *network, int object, int index, const char *newcomment)
//----------------------------------------------------------------
//  Input:   object = a type of network object
//           index = index of the specified object
//           newcomment = new comment string
//  Output:  error code
//  Purpose: sets the comment string of an object.
//----------------------------------------------------------------
{
    char *comment;

    switch (object)
    {
    case NODE:
        if (index < 1 || index > network->Nnodes) return 251;
        comment = network->Node[index].Comment;
        network->Node[index].Comment = xstrcpy(&comment, newcomment, MAXMSG);
        return 0;

    case LINK:
        if (index < 1 || index > network->Nlinks) return 251;
        comment = network->Link[index].Comment;
        network->Link[index].Comment = xstrcpy(&comment, newcomment, MAXMSG);
        return 0;

    case TIMEPAT:
        if (index < 1 || index > network->Npats) return 251;
        comment = network->Pattern[index].Comment;
        network->Pattern[index].Comment = xstrcpy(&comment, newcomment, MAXMSG);
        return 0;

    case CURVE:
        if (index < 1 || index > network->Ncurves) return 251;
        comment = network->Curve[index].Comment;
        network->Curve[index].Comment = xstrcpy(&comment, newcomment, MAXMSG);
        return 0;

    default: return 251;
    }
}

int  gettag(Network *network, int object, int index, char *tag)
//----------------------------------------------------------------
//  Input:   object = a type of network object
//           index = index of the specified object
//           tag = the object's tag string
//  Output:  error code
//  Purpose: gets the tag string assigned to an object.
//----------------------------------------------------------------
{
    char *currenttag;

    // Get pointer to specified object's tag
    switch (object)
    {
    case NODE:
        if (index < 1 || index > network->Nnodes) return 251;
        currenttag = network->Node[index].Tag;
        break;
    case LINK:
        if (index < 1 || index > network->Nlinks) return 251;
        currenttag = network->Link[index].Tag;
        break;
    default:
        strcpy(tag, "");
        return 251;
    }
    // Copy the object's tag to the returned string
    if (currenttag) strcpy(tag, currenttag);
    else tag[0] = '\0';
    return 0;
}
    
int settag(Network *network, int object, int index, const char *newtag)
//----------------------------------------------------------------
//  Input:   object = a type of network object
//           index = index of the specified object
//           newtag = new tag string
//  Output:  error code
//  Purpose: sets the tag string of an object.
//----------------------------------------------------------------
{
    char *tag;

    switch (object)
    {
    case NODE:
        if (index < 1 || index > network->Nnodes) return 251;
        tag = network->Node[index].Tag;
        network->Node[index].Tag = xstrcpy(&tag, newtag, MAXMSG);
        return 0;

    case LINK:
        if (index < 1 || index > network->Nlinks) return 251;
        tag = network->Link[index].Tag;
        network->Link[index].Tag = xstrcpy(&tag, newtag, MAXMSG);
        return 0;

    default: return 251;
    }
}

int namevalid(const char *name)
//----------------------------------------------------------------
//  Input:   name = name used to ID an object
//  Output:  returns TRUE if name is valid, FALSE if not
//  Purpose: checks that an object's ID name is valid.
//----------------------------------------------------------------
{
    size_t n = strlen(name);
    if (n < 1 || n > MAXID || strpbrk(name, " ;") || name[0] == '"') return FALSE;
    return TRUE;
}

void getTmpName(char *fname)
//----------------------------------------------------------------
//  Input:   fname = file name string
//  Output:  an unused file name
//  Purpose: creates a temporary file name with an "en" prefix
//           or a blank name if an error occurs.
//----------------------------------------------------------------
{
#ifdef _WIN32

    char* name = NULL;

    // --- use Windows _tempnam function to get a pointer to an
    //     unused file name that begins with "en"
    strcpy(fname, "");
    name = _tempnam(NULL, "en");
    if (name)
    {
        // --- copy the file name to fname
        if (strlen(name) < MAXFNAME) strncpy(fname, name, MAXFNAME);

        // --- free the pointer returned by _tempnam
        free(name);
    }
    // --- for non-Windows systems:
#else
    // --- use system function mkstemp() to create a temporary file name
/*    
    int f = -1;
    strcpy(fname, "enXXXXXX");
    f = mkstemp(fname);
    close(f);
    remove(fname);
*/
    strcpy(fname, "enXXXXXX");
    FILE *f = fdopen(mkstemp(fname), "r");
    if (f == NULL) strcpy(fname, "");
    else fclose(f);
    remove(fname);
#endif
}

char *xstrcpy(char **s1, const char *s2, const size_t n)
//----------------------------------------------------------------
//  Input:   s1 = destination string
//           s2 = source string
//           n = maximum size of strings
//  Output:  none
//  Purpose: like strcpy except for dynamic strings.
//  Note:    The calling program is responsible for ensuring that
//           s1 points to a valid memory location or is NULL. E.g.,
//           the following code will likely cause a segment fault:
//             char *s;
//             s = xstrcpy(&s, "Some text");
//           while this would work correctly:
//             char *s = NULL;
//             s = xstrcpy(&s, "Some text");
//----------------------------------------------------------------
{
    size_t n1 = 0, n2 = 0;

    // Find size of source string
    if (s2) n2 = strlen(s2);
    if (n2 > n) n2 = n;

    // Source string is empty -- free destination string
    if (n2 == 0)
    {
        free(*s1);
        *s1 = NULL;
        return NULL;
    }

    // See if size of destination string needs to grow
    if (*s1) n1 = strlen(*s1);
    if (n2 > n1) *s1 = realloc(*s1, (n2 + 1) * sizeof(char));

    // Copy the source string into the destination string
    if (*s1) strncpy(*s1, s2, n2+1);
    return *s1;
}

int strcomp(const char *s1, const char *s2)
/*---------------------------------------------------------------
**  Input:   s1 = character string
**           s2 = character string
**  Output:  none
**  Returns: 1 if s1 is same as s2, 0 otherwise
**  Purpose: case insensitive comparison of strings s1 & s2
**---------------------------------------------------------------
*/
{
    int i;
    for (i = 0; UCHAR(s1[i]) == UCHAR(s2[i]); i++)
    {
        if (!s1[i + 1] && !s2[i + 1]) return 1;
    }
    return 0;
}

double interp(int n, double x[], double y[], double xx)
/*----------------------------------------------------------------
**  Input:   n  = number of data pairs defining a curve
**           x  = x-data values of curve
**           y  = y-data values of curve
**           xx = specified x-value
**  Output:  none
**  Returns: y-value on curve at x = xx
**  Purpose: uses linear interpolation to find y-value on a
**           data curve corresponding to specified x-value.
**  NOTE:    does not extrapolate beyond endpoints of curve.
**----------------------------------------------------------------
*/
{
    int k, m;
    double dx, dy;

    if (n == 0) return 0.0;
    m = n - 1;                        // Highest data index
    if (xx <= x[0]) return (y[0]);    // xx off low end of curve
    for (k = 1; k <= m; k++)          // Bracket xx on curve
    {
        if (x[k] >= xx)               // Interp. over interval
        {
            dx = x[k] - x[k - 1];
            dy = y[k] - y[k - 1];
            if (ABS(dx) < TINY) return (y[k]);
            else return (y[k] - (x[k] - xx) * dy / dx);
        }
    }
    return (y[m]); // xx off high end of curve
}

char *geterrmsg(int errcode, char *msg)
/*----------------------------------------------------------------
**  Input:   errcode = error code
**  Output:  none
**  Returns: pointer to string with error message
**  Purpose: retrieves text of error message
**----------------------------------------------------------------
*/
{
    switch (errcode)
    {

//#define DAT(code,string) case code: sprintf(msg, "%s", string); break;
#define DAT(code,string) case code: strcpy(msg, string); break;
#include "errors.dat"
#undef DAT

    default:
        strcpy(msg, "");
    }
    return (msg);
}

void errmsg(Project *pr, int errcode)
/*----------------------------------------------------------------
**  Input:   errcode = error code
**  Output:  none
**  Purpose: writes error message to report file
**----------------------------------------------------------------
*/
{
    char errmsg[MAXMSG + 1] = "";
    if (errcode == 309) /* Report file write error -  */
    {                   /* Do not write msg to file.  */

    }
    else if (pr->report.RptFile != NULL && pr->report.Messageflag && errcode > 100)
    {
        sprintf(pr->Msg, "Error %d: %s", errcode, geterrmsg(errcode, errmsg));
        writeline(pr, pr->Msg);
    }
}

void writewin(void(*vp)(char *), char *s)
/*----------------------------------------------------------------
**  Input:   text string
**  Output:  none
**  Purpose: passes character string to viewprog() in
**           application which calls the EPANET DLL
**----------------------------------------------------------------
*/
{
    char progmsg[MAXMSG + 1];
    if (vp != NULL)
    {
        strncpy(progmsg, s, MAXMSG);
        vp(progmsg);
    }
}
