public class Constants {


    public static double        PI = 3.141592654;

    /**
     * Max. # of disconnected nodes listed
      */
    public static int           MAXCOUNT = 10;
    /**
     * Max. # title lines
     */
    public static int           MAXTITLE = 3;
    /**
     * Max. input errors reported
     */
    public static final int     MAXERRS = 10;
    //public static final int     MAXMSG = 79;
    //public static final int     MAXID  = 31;
    //public static final int     MAXFNAME = 259;

    /**
     * Epanet binary files code version
     */
    public static final int CODEVERSION=20012;

     /**
     * Epanet binary files ID
     */
    public static final int MAGICNUMBER=516114521;

     /**
     * Epanet binary files version
     */
    public static final int VERSION=200;

    /**
     * Equivalent to zero flow
      */
    public static final double QZERO=1.e-6d;
    /**
     * Big coefficient
     */
    public static final double CBIG=1.e8d;
    /**
     * Small coefficient
     */
    public static final double CSMALL=1.e-6d;

    /**
     * Default max. # hydraulic iterations
     */
    public static final int MAXITER=200;
    /**
     * Default hydraulics convergence ratio
     */
    public static final double  HACC=0.001d;
    /**
     * Default hydraulic head tolerance (ft)
     */
    public static final double  HTOL     =0.0005d;
    /**
     * Default flow rate tolerance (cfs)
     */
    public static final double  QTOL     =0.0001d;

    /**
     * Default water age tolerance (hrs)
     */
    public static final double  AGETOL   =0.01d;
    /**
     * Default concentration tolerance
     */
    public static final double  CHEMTOL  =0.01d;
    /**
     * Default uses no page breaks
     */
    public static final int     PAGESIZE =0;
    /**
     * Default specific gravity
     */
    public static final double  SPGRAV   =1.0d;
    /**
     * Default pump efficiency
     */
    public static final double  EPUMP    =75d;
    /**
     * Default demand pattern ID
     */
    public static final String  DEFPATID ="1";

    /**
     * Default low flow resistance tolerance
     */
    public static final double  RQTOL     =1E-7d;

    /**
     * Default status check frequency
     */
    public static final int     CHECKFREQ =2;
    /**
     * Default # iterations for status checks
     */
    public static final int     MAXCHECK  =10;
    /**
     *  Default damping threshold
     */
    public static final double  DAMPLIMIT =0;

    ///**
    // * Max. # types of network variables
    // */
    //public static final int     MAXVAR = 21;

    public static final double  BIG  =     1.E10d;
    public static final double  TINY =     1.E-6d;

    public static final double  MISSING =  -1.E10d;

    public static final double  GPMperCFS = 448.831d;
    public static final double  AFDperCFS = 1.9837d;
    public static final double  MGDperCFS = 0.64632d;
    public static final double  IMGDperCFS= 0.5382d;
    public static final double  LPSperCFS = 28.317d;
    public static final double  LPMperCFS = 1699.0d;
    public static final double  CMHperCFS = 101.94d;
    public static final double  CMDperCFS = 2446.6d;
    public static final double  MLDperCFS = 2.4466d;
    public static final double  M3perFT3  = 0.028317d;
    public static final double  LperFT3   = 28.317d;
    public static final double  MperFT    = 0.3048d;
    public static final double  MMperFT    = 304.8d;
    public static final double  INperFT  = 12.0d;
    public static final double  PSIperFT  = 0.4333d;
    public static final double  KPAperPSI = 6.895d;
    public static final double  KWperHP   = 0.7457d;
    public static final int     SECperDAY = 86400;

    public static final double  DIFFUS    = 1.3E-8d;
    public static final double  VISCOS    = 1.1E-5 ;
} public class Keywords {
    public static String s_BACKDROP =   "[BACKDROP]";
    public static String s_CONTROLS =   "[CONTROLS]";

    public static String s_COORDS =     "[COORDINATES]";
    public static String s_CURVES =     "[CURVES]";

    public static String s_DEMANDS =    "[DEMANDS]";
    public static String s_EMITTERS =   "[EMITTERS]";
    public static String s_END =        "[END]";

    public static String s_ENERGY =     "[ENERGY]";
    public static String s_JUNCTIONS =  "[JUNCTIONS]";
    public static String s_LABELS =     "[LABELS]";
    public static String s_MIXING =     "[MIXING]";
    public static String s_OPTIONS =    "[OPTIONS]";
    public static String s_PATTERNS =   "[PATTERNS]";
    public static String s_PIPES =      "[PIPES]";
    public static String s_PUMPS =      "[PUMPS]";
    public static String s_QUALITY =    "[QUALITY]";
    public static String s_REACTIONS =  "[REACTIONS]";
    public static String s_REPORT =     "[REPORT]";
    public static String s_RESERVOIRS = "[RESERVOIRS]";
    public static String s_ROUGHNESS =  "[ROUGHNESS]";
    public static String s_RULES =      "[RULES]";

    public static String s_SOURCES =    "[SOURCES]";
    public static String s_STATUS =     "[STATUS]";
    public static String s_TAGS =       "[TAGS]";
    public static String s_TANKS =      "[TANKS]";
    public static String s_TIMES =      "[TIMES]";
    // INP sections types strings
    //public static String s_TITLE =      "[TITL";
    //public static String s_JUNCTIONS =  "[JUNC";
    //public static String s_RESERVOIRS = "[RESE";
    //public static String s_TANKS =      "[TANK";
    //public static String s_PIPES =      "[PIPE";
    //public static String s_PUMPS =      "[PUMP";
    //public static String s_VALVES =     "[VALV";
    //public static String s_CONTROLS =   "[CONT";
    //public static String s_RULES =      "[RULE";
    //public static String s_DEMANDS =    "[DEMA";
    //public static String s_SOURCES =    "[SOUR";
    //public static String s_EMITTERS =   "[EMIT";
    //public static String s_PATTERNS =   "[PATT";
    //public static String s_CURVES =     "[CURV";
    //public static String s_QUALITY =    "[QUAL";
    //public static String s_STATUS =     "[STAT";
    //public static String s_ROUGHNESS =  "[ROUG";
    //public static String s_ENERGY =     "[ENER";
    //public static String s_REACTIONS =  "[REAC";
    //public static String s_MIXING =     "[MIXI";
    //public static String s_REPORT =     "[REPO";
    //public static String s_TIMES =      "[TIME";
    //public static String s_OPTIONS =    "[OPTI";
    //public static String s_COORDS =     "[COOR";
    //public static String s_VERTICES =   "[VERT";
    //public static String s_LABELS =     "[LABE";
    //public static String s_BACKDROP =   "[BACK";
    //public static String s_TAGS =       "[TAGS";
    //public static String s_END =        "[END";
    public static String s_TITLE =      "[TITLE]";
    public static String s_VALVES =     "[VALVES]";
    public static String s_VERTICES =   "[VERTICES]";
    public static String t_ABOVE      ="above";

    public static String t_ACTIVE      ="active";
    public static String t_BACKDROP = "Backdrop";

    public static String t_BELOW      ="below";
    public static String t_CHEMICAL   ="Chemical";
    public static String t_CLOSED      ="closed";
    public static String  t_CM         ="Chezy-Manning";
    public static String t_CONTINUED  =" (continued)";

    public static String t_CONTROL    ="Control";
    public static String t_COORD      = "Coordinate";
    public static String t_CURVE      ="Curve";
    public static String t_DEMAND =     "Demand";
    public static String t_DEMANDFOR  ="Demand for Node";
    public static String t_DIAM =       "Diameter";
    public static String t_DIFFER     ="DIFFERENTIAL";
    public static String  t_DW         ="Darcy-Weisbach";
    public static String t_ELEV =       "Elevation";
    public static String t_EMITTER    ="Emitter";
    public static String t_EMPTYING    ="emptying";

    public static String t_END      = "End";
    public static String t_ENERGY     ="Energy";
    public static String t_FILLING     ="filling";

    public static String t_FLOW =       "Flow";
    public static String t_FRICTION =   "F-Factor";
    public static String t_FUNCCALL   ="function call";
    public static String t_HALTED     =" EXECUTION HALTED.";
    public static String t_HEAD =       "Head";
    public static String t_HEADLOSS =   "Headloss";
    public static String  t_HW         ="Hazen-Williams";
    public static String t_JUNCTION   ="Junction";
    public static String t_LABEL    = "Label";
    public static String t_LENGTH =     "Length";
    public static String  t_LINKID     ="Link";

    public static String t_LINKQUAL =   "Quality";
    public static String t_LINKSTATUS = "State";
    public static String t_MIXING     ="Mixing";

    public static String  t_NODEID     ="Node";
    public static String t_OPEN        ="open";
    public static String t_OPTION     ="Options";
    public static String t_PATTERN    ="Pattern";

    public static String  t_PERDAY     ="/day";
    public static String t_perM3      ="  /m3";
    public static String t_perMGAL    ="/Mgal";
    public static String t_PIPE       ="Pipe";
    public static String t_PRESSURE =   "Pressure";
    public static String t_PUMP       ="Pump";
    public static String t_QUALITY =    "Quality";
    public static String t_REACTION   ="Reaction";
    public static String t_REACTRATE =  "Reaction";
    public static String t_REPORT     ="Report";
    public static String t_RESERVOIR  ="Reservoir";
    public static String t_ROUGHNESS  ="Roughness";

    public static String t_RULE       ="Rule";
    public static String t_RULES_SECT ="[RULES] section";
    public static String t_SETTING =    "Setting";
    public static String t_SOURCE     ="Source";
    public static String t_STATUS     ="Status";

    public static String t_TAG      = "Tag";
    public static String t_TANK       ="Tank";
    public static String t_TEMPCLOSED  ="temporarily closed";
    public static String t_TIME       ="Times";
    public static String t_TITLE = "Title";
    public static String t_VALVE      ="Valve";
    public static String t_VELOCITY =   "Velocity";
    public static String t_VERTICE  = "Vertice";
    public static String t_XFCV        ="open but cannot deliver flow";
    public static String t_XFLOW       ="open but exceeds maximum flow";
    public static String t_XHEAD       ="closed because cannot deliver head";

    public static String t_XPRESSURE   ="open but cannot deliver pressure";
    public static String  u_AFD        ="a-f/d";

    // Units
    public static String  u_CFS        ="cfs";
    public static String  u_CMD        ="m3/d";
    public static String  u_CMH        ="m3/h";
    public static String  u_FEET       ="ft";
    public static String  u_FTperSEC   ="fps";
    public static String  u_GPM        ="gpm";

    public static String  u_HOURS      ="hrs";
    public static String  u_HP         ="hp";
    public static String  u_IMGD       ="Imgd";
    public static String  u_INCHES     ="in";
    public static String  u_KPA        ="kPa";
    public static String  u_KW         ="kw";

    public static String  u_LPM        ="Lpm";
    public static String  u_LPS        ="L/s";
    public static String  u_METERS     ="m";
    public static String  u_MGD        ="mgd";

    public static String  u_MGperL     ="mg/L";
    public static String  u_MINUTES    ="min";

    public static String  u_MLD        ="ML/d";
    public static String  u_MMETERS    ="mm";
    public static String  u_MperSEC    ="m/s";
    public static String  u_per1000FT  ="/kft";

    public static String  u_per1000M   ="/km";
    public static String  u_PERCENT    ="%";
    public static String  u_PSI        ="psi";
    public static String  u_SQFTperSEC ="sq ft/sec";

    public static String  u_SQMperSEC  ="sq m/sec";
    public static String  u_UGperL     ="ug/L";
    public static String   w_2COMP      ="2COMP";
    public static String   w_ABOVE      ="ABOVE";
    public static String   w_ACCURACY   ="ACCU";

    public static String   w_ACTIVE     ="ACTIVE";
    public static String   w_ADD        ="ADD";
    public static String   w_AFD        ="AFD";

    public static String   w_AGE        ="AGE";

    public static String   w_ALL        ="ALL";
    public static String   w_AM         ="AM";
    public static String   w_AVG        ="AVERAGE";
    public static String   w_BELOW      ="BELOW";
    public static String   w_BULK       ="BULK";
    public static String   w_CFS        ="CFS";
    public static String   w_CHECKFREQ  ="CHECKFREQ";
    public static String   w_CHEM       ="CHEM";
    public static String   w_CLOCKTIME  ="CLOCKTIME";
    public static String   w_CLOSED     ="CLOSED";
    public static String   w_CM         ="C-M";
    public static String   w_CMD        ="CMD";
    public static String   w_CMH        ="CMH";
    public static String   w_CONCEN     ="CONCEN";
    public static String   w_CONTINUE   ="CONT";
    public static String   w_CURVE      ="CURV";
    public static String   w_CV         ="CV";
    public static String   w_DAMPLIMIT  ="DAMPLIMIT";
    public static String   w_DAYS       ="DAY";
    public static String   w_DEMAND     ="DEMA";
    public static String   w_DIAM       ="DIAM";
    public static String   w_DIFFUSIVITY="DIFF";
    public static String   w_DMNDCHARGE ="DEMAN";
    public static String   w_DRAINTIME  ="DRAI";
    public static String   w_DURATION   ="DURA";
    public static String   w_DW         ="D-W";
    public static String   w_EFFIC      ="EFFI";
    public static String   w_ELEV       ="ELEV";
    public static String   w_EMITTER    ="EMIT";

    public static String   w_ENERGY     ="ENER";
    public static String   w_FCV        ="FCV";
    public static String   w_FIFO       ="FIFO";

    public static String   w_FILE       ="FILE";
    public static String   w_FILLTIME   ="FILL";
    public static String   w_FLOW       ="FLOW";
    public static String   w_FLOWPACED  ="FLOWPACED";
    public static String   w_FULL       ="FULL";
    public static String   w_GLOBAL     ="GLOB";
    public static String   w_GPM        ="GPM";
    public static String   w_GPV        ="GPV";
    public static String   w_GRADE      ="GRADE";
    public static String   w_HEAD       ="HEAD";
    public static String   w_HEADLOSS   ="HEADL";
    public static String   w_HOURS      ="HOU";
    public static String   w_HTOL       ="HTOL";
    public static String   w_HW         ="H-W";
    public static String   w_HYDRAULIC  ="HYDR";

    public static String   w_IMGD       ="IMGD";
    public static String   w_IS         ="IS";
    public static String   w_JUNC       ="Junc";
    public static String   w_KPA        ="KPA";
    public static String   w_LEVEL      ="LEVEL";
    public static String   w_LIFO       ="LIFO";
    public static String   w_LIMITING   ="LIMIT";
    public static String   w_LINK       ="LINK";
    public static String   w_LPM        ="LPM";
    public static String   w_LPS        ="LPS";
    public static String   w_MAP        ="MAP";
    public static String   w_MASS       ="MASS";
    public static String   w_MAX        ="MAXIMUM";
    public static String   w_MAXCHECK   ="MAXCHECK";
    public static String   w_MESSAGES   ="MESS";
    public static String   w_METERS     ="METERS";
    public static String   w_MGD        ="MGD";
    public static String   w_MIN        ="MINIMUM";
    public static String   w_MINIMUM    ="MINI";
    public static String   w_MINUTES    ="MIN";
    public static String   w_MIXED      ="MIXED";
    public static String   w_MLD        ="MLD";
    public static String   w_MULTIPLY   ="MULT";
    public static String   w_NO         ="NO";
    public static String   w_NODE       ="NODE";
    public static String   w_NONE       ="NONE";
    public static String   w_NOT        ="NOT";
    public static String   w_OPEN       ="OPEN";
    public static String   w_ORDER      ="ORDER";
    public static String   w_PAGE       ="PAGE";
    public static String   w_PATTERN    ="PATT";
    public static String   w_PBV        ="PBV";
    public static String   w_PIPE       ="Pipe";
    public static String   w_PM         ="PM";
    public static String   w_POWER      ="POWE";

    public static String   w_PRECISION  ="PREC";
    public static String   w_PRESSURE   ="PRES";
    public static String   w_PRICE      ="PRICE";
    public static String   w_PRV        ="PRV";
    public static String   w_PSI        ="PSI";
    public static String   w_PSV        ="PSV";
    public static String   w_PUMP       ="Pump";
    public static String   w_QTOL       ="QTOL";
    public static String   w_QUALITY    ="QUAL";
    public static String   w_RANGE      ="RANGE";
    public static String   w_REPORT     ="REPO";
    public static String   w_RESERV     ="Reser";
    public static String   w_ROUGHNESS  ="ROUG";
    public static String   w_RQTOL      ="RQTOL";
    public static String   w_RULE       ="RULE";
    public static String   w_SAVE       ="SAVE";
    public static String   w_SECONDS    ="SEC";
    public static String   w_SEGMENTS   ="SEGM";
    public static String   w_SETPOINT   ="SETPOINT";
    public static String   w_SETTING    ="SETT";
    public static String   w_SI         ="SI";
    public static String   w_SPECGRAV   ="SPEC";
    public static String   w_SPEED      ="SPEE";
    public static String   w_START      ="STAR";
    public static String   w_STATISTIC  ="STAT";
    public static String   w_STATUS     ="STATUS";
    public static String   w_STOP       ="STOP";
    public static String   w_SUMMARY    ="SUMM";
    public static String   w_SYSTEM     ="SYST";

    public static String   w_TANK       ="Tank";
    public static String   w_TCV        ="TCV";
    public static String   w_TIME       ="TIME";

    public static String   w_TOLERANCE  ="TOLER";
    public static String   w_TRACE      ="TRACE";
    public static String   w_TRIALS     ="TRIAL";

    public static String   w_UNBALANCED ="UNBA";
    public static String   w_UNITS      ="UNIT";
    public static String   w_USE        ="USE";
    public static String   w_VALVE      ="Valve";
    public static String   w_VELOCITY   ="VELO";
    public static String   w_VERIFY     ="VERI";
    public static String   w_VISCOSITY  ="VISC";
    public static String   w_VOLUME     ="VOLU";
    public static String   w_WALL       ="WALL";
    public static String   w_YES        ="YES";

    public static String wr_ABOVE    ="ABOVE";
    public static String wr_ACTIVE   ="ACTIVE";
    public static String wr_AND      ="AND";
    public static String wr_BELOW    ="BELOW";
    public static String wr_CLOCKTIME="CLOCKTIME";
    public static String wr_CLOSED   ="CLOSED";
    public static String wr_DEMAND   ="DEMA";
    public static String wr_DRAINTIME="DRAI";
    public static String wr_ELSE     ="ELSE";
    public static String wr_FILLTIME ="FILL";
    public static String wr_FLOW     ="FLOW";
    public static String wr_GRADE    ="GRADE";
    public static String wr_HEAD     ="HEAD";
    public static String wr_IF       ="IF";
    public static String wr_IS       ="IS";
    public static String wr_JUNC     ="Junc";
    public static String wr_LEVEL    ="LEVEL";
    public static String wr_LINK     ="LINK";
    public static String wr_NODE     ="NODE";
    public static String wr_NOT      ="NOT";
    public static String wr_OPEN     ="OPEN";
    public static String wr_OR       ="OR";
    public static String wr_PIPE     ="Pipe";
    public static String wr_POWER    ="POWE";
    public static String wr_PRESSURE ="PRES";
    public static String wr_PRIORITY ="PRIO";
    public static String wr_PUMP     ="Pump";
    public static String wr_RESERV   ="Reser";
    public static String wr_RULE     ="RULE";
    public static String wr_SETTING  ="SETT";
    public static String wr_STATUS   ="STATUS";
    public static String wr_SYSTEM   ="SYST";
    public static String wr_TANK     ="Tank";
    public static String wr_THEN     ="THEN";
    public static String wr_TIME     ="TIME";
    public static String wr_VALVE    ="Valve";

} public class ENException extends Exception{

    /**
     * Error text bundle.
     */
    private static final ResourceBundle errorBundle = PropertyResourceBundle.getBundle("Error");

    /**
     * Array of arguments to be used in the error string creation.
     */
    private Object [] arguments;

    /**
     * Epanet error code.
     */
    private int codeID;

    /**
     * Get error code.
     * @return Code id.
     */
    public int getCodeID(){
        return codeID;
    }

    /**
     * Contructor from error code id.
     * @param id Error code id.
     */
    public ENException(int id){
        arguments = null;
        codeID = id;
    }

    /**
     * Contructor from error code id and multiple arguments.
     * @param id Error code id.
     * @param arg Extra arguments.
     */
    public ENException(int id, Object ... arg){
        codeID = id;
        arguments = arg;
    }

    /**
     * Contructor from other exception and multiple arguments.
     * @param e
     * @param arg
     */
    public ENException(ENException e, Object ... arg){
        arguments = arg;
        codeID = e.getCodeID();
    }

    /**
     * Get arguments array.
     */
    public Object [] getArguments(){
        return arguments;
    }

    /**
     * Handles the exception string conversion.
     * @return Final error string.
     */
    public String toString(){
        String str = errorBundle.getString("ERR"+codeID);
        if(str==null)
            return String.format("Unknown error message (%d)",codeID);
        else if(arguments!=null)
            return String.format(str,arguments);
        return str;
    }

}
 public class Utilities {

    /**
     * Convert string to double.
     * @param str Number string.
     * @return Reference to the converted Double number, null if the conversion was unsuccessful.
     */
    public static Double getDouble(String str){
        Double val;
        try{
            val = Double.parseDouble(str);
            return val;
        }
        catch(NumberFormatException ex)
        {
            return null;
        }
    }

    /**
     * Check if two strings match (case independent), based on the shortest string length.
     * @param a String A.
     * @param b String B.
     * @return Boolean is the two strings are similar.
     */
    public static boolean match(String a, String b){
        if(a.length()==0||b.length()==0)
            return false;

        if(a.length()>b.length())
        {
            String tmp = a.substring(0,b.length());
            if(tmp.equalsIgnoreCase(b))
                return true;
        }
        else
        {
            String tmp = b.substring(0,a.length());
            if(a.equalsIgnoreCase(tmp))
                return true;
        }
        return false;
    }

    /**
     * Parse time string to a double number.
     * @param time  Time string.
     * @param units Units format (PM or AM)
     * @return Time in hours (1.0 is 3600 seconds)
     */
    public static double getHour(String time, String units)
    {
        int    n=0;
        double  [] y = {0.0d,0.0d,0.0d};
        String [] s = time.split(":");


        for(int i = 0;i<s.length && i<=3;i++)
        {
            try{y[i] = Double.parseDouble(s[i]);}catch(NumberFormatException ex){return(-1.0d);}
            n++;
        }

        if (n == 1)
        {
            if (units.length() == 0)
                return(y[0]);

            if (Utilities.match(units, Keywords.w_SECONDS)) return(y[0]/3600.0);
            if (Utilities.match(units,Keywords.w_MINUTES)) return(y[0]/60.0);
            if (Utilities.match(units,Keywords.w_HOURS))   return(y[0]);
            if (Utilities.match(units,Keywords.w_DAYS))    return(y[0]*24.0);
        }

        if (n > 1) y[0] = y[0] + y[1]/60.0 + y[2]/3600.0;

        if (units.length() == 0)
            return(y[0]);

        if (units.equalsIgnoreCase(Keywords.w_AM))
        {
            if (y[0] >= 13.0) return(-1.0);
            if (y[0] >= 12.0) return(y[0]-12.0);
            else return(y[0]);
        }
        if (units.equalsIgnoreCase(Keywords.w_PM))
        {
            if (y[0] >= 13.0) return(y[0]-12.0);
            if (y[0] >= 12.0) return(y[0]);
            else return(y[0]+12.0);
        }
        return(-1.0);
    }

    /**
     * Compute the linear interpolation of a 2d cartesian graph.
     * @param n Number of points.
     * @param x X axis values array (abscissa).
     * @param y Y axis values array (ordinate).
     * @param xx The abscissa value.
     * @return The interpolated value.
     */
    public static double linearInterpolator(int n, List<Double> x, List<Double> y, double xx)
    {
        int    k,m;
        double  dx,dy;

        m = n - 1;
        if (xx <= x.get(0)) return(y.get(0));
        for (k=1; k<=m; k++)
        {
            if (x.get(k) >= xx)
            {
                dx = x.get(k)-x.get(k-1);
                dy = y.get(k)-y.get(k-1);
                if (Math.abs(dx) < Constants.TINY) return(y.get(k));
                else return(y.get(k) - (x.get(k)-xx)*dy/dx);
            }
        }
        return(y.get(m));
    }

    /**
     * Convert time to a string.
     * @param seconds Time to convert, in seconds.
     * @return Time string in epanet format.
     */
    public static String getClockTime(long seconds){
        long h,m,s;
        h = seconds/3600;
        m = (seconds % 3600) / 60;
        s = seconds - 3600*h - 60*m;
        return String.format("%02d:%02d:%02d",h,m,s);
    }

    /**
     * Computes coeffs. for pump curve.
     * @param h0 shutoff head
     * @param h1 design head
     * @param h2 head at max. flow
     * @param q1 design flow
     * @param q2 max. flow
     * @param a pump curve coeffs. (H = a-bQ^c),
     * @param b pump curve coeffs. (H = a-bQ^c),
     * @param c pump curve coeffs. (H = a-bQ^c),
     * @return Returns true if sucessful, false otherwise.
     */
    public static boolean  getPowerCurve(double h0, double h1, double h2, double q1,
                                         double q2, double [] a, double [] b, double [] c)
    {
        double h4,h5;
        if (
                h0      < Constants.TINY ||
                        h0 - h1 < Constants.TINY ||
                        h1 - h2 < Constants.TINY ||
                        q1      < Constants.TINY ||
                        q2 - q1 < Constants.TINY
                ) return false;
        a[0] = h0;
        h4 = h0 - h1;
        h5 = h0 - h2;
        c[0] = Math.log(h5/h4)/Math.log(q2/q1);
        if (c[0] <= 0.0 || c[0] > 20.0) return false;
        b[0] = -h4/Math.pow(q1,c[0]);

        if (b[0] >= 0.0)
            return(false);

        return(true);
    }

    /**
     * Get value signal, if bigger than 0 returns 1, -1 otherwise.
     * @param val Any real number.
     * @return -1 or 1
     */
    public static double getSignal(double val) {
        return val < 0 ? -1d : 1d;
    }

    /**
     * Text messages bundle.
     */
    public static final ResourceBundle textBundle = PropertyResourceBundle.getBundle("Text");

    /**
     * Error messages bundle.
     */
    public static final ResourceBundle errorBundle = PropertyResourceBundle.getBundle("Error");

    /**
     * Get epanet text info string from bundle.
     * @param text String id.
     * @return Info string.
     */
    public static String getText(String text) {
        return textBundle.getString(text);
    }

    /**
     * Get epanet error string from bundle.
     * @param text String id.
     * @return Error string.
     */
    public static String getError(String text) {
        return errorBundle.getString(text);
    }

    /**
     * Get float from byte array.
     * @param array Byte array.
     * @param id Float start byte.
     * @return Parsed float.
     */
    public static float getFloatFromBytes(byte [] array, int id){
        return Float.intBitsToFloat((array[id] << 24) |
                ((array[id+1] & 0xff) << 16) |
                ((array[id+2] & 0xff) << 8 |
                        (array[id+3] & 0xff)));
    }
    /**
     * Get integer from byte array.
     * @param array Byte array.
     * @param id Integer start byte.
     * @return Parsed Integer.
     */
    public static int getIntegerFromBytes(byte [] array, int id){
        return (array[id] << 24) |
                ((array[id+1] & 0xff) << 16) |
                ((array[id+2] & 0xff) << 8 |
                        (array[id+3] & 0xff));
    }

    /**
     * Get file extension string.
     * @param filename
     * @return
     */
    public static String getFileExtension(String filename){
        int lastDot = filename.lastIndexOf(".");
        if(lastDot>0 && lastDot+1< filename.length()){
            return filename.substring(lastDot+1).toLowerCase();
        }

        return "";
    }
    /**
     * Check if the applicationn is running on MacOS
     * @return
     */
    public static boolean isMac(){
        String os = System.getProperty("os.name").toLowerCase();
        return (os.indexOf( "mac" ) >= 0);
    }


} public class FieldsMap {

    /**
     * Network variables
     */
    static public enum Type {
        ELEV        (0,Keywords.t_ELEV     ),   // nodal water quality
        DEMAND      (1,Keywords.t_DEMAND   ),   // nodal demand flow
        HEAD        (2,Keywords.t_HEAD     ),   // link flow velocity
        PRESSURE    (3,Keywords.t_PRESSURE ),   // avg. reaction rate in link
        QUALITY     (4,Keywords.t_QUALITY  ),   // link friction factor
        LENGTH      (5,Keywords.t_LENGTH   ),   // avg. water quality in link
        DIAM        (6,Keywords.t_DIAM     ),   // nodal hydraulic head
        FLOW        (7,Keywords.t_FLOW     ),   // link diameter

        VELOCITY    (8,Keywords.t_VELOCITY ),   // time to fill a tank
        HEADLOSS    (9,Keywords.t_HEADLOSS ),   // link head loss
        LINKQUAL    (10,Keywords.t_LINKQUAL ),   // link status

        STATUS      (11,Keywords.t_STATUS   ),   // tank volume
        SETTING     (12,Keywords.t_SETTING  ),   // simulation time
        REACTRATE   (13,Keywords.t_REACTRATE),   // pump power output
        FRICTION    (14,Keywords.t_FRICTION ),   // link flow rate
        POWER       (15),   // pump/valve setting
        TIME        (16),   // simulation time of day
        VOLUME      (17),   // time to drain a tank
        CLOCKTIME   (18),   // nodal elevation
        FILLTIME    (19),   // link length
        DRAINTIME   (20);   // nodal pressure

        /**
         * Get field type from string.
         * @param text String to be parsed.
         * @return Parsed Field, null the string doesn't match any of the possible types.
         */
        public static Type parse(String text){
            for (Type type : Type.values())
                if (Utilities.match(text, type.parseStr)) return type;
            return null;
        }

        /**
         * Field sequencial id.
         */
        public final int id;

        /**
         * Field string.
         */
        public final String parseStr;
        private Type(int id){this.id = id;this.parseStr = "";}

        private Type(int id, String pStr){this.id = id;this.parseStr = pStr;}
    }

    /**
     * Report fields properties.
     */
    private Map<Type,Field> fields;

    /**
     * Fields units values.
     */
    private Map<Type,Double> units;

    /**
     * Init fields default configuration
     */
    public FieldsMap()
    {
        try{
            fields = new LinkedHashMap<Type,Field>();
            units = new LinkedHashMap<Type,Double>();

            for(Type type: Type.values())
                setField(type,new Field(type.parseStr));

            getField(Type.FRICTION).setPrecision(3);

            for (int i= Type.DEMAND.id;i<= Type.QUALITY.id; i++)
                getField(Type.values()[i]).setEnabled(true);

            for (int i= Type.FLOW.id;i<= Type.HEADLOSS.id; i++)
                getField(Type.values()[i]).setEnabled(true);
        }
        catch (ENException e){
            e.printStackTrace();
        }
    }

    /**
     * Get report field properties from type.
     * @param type Field type.
     * @return Report field.
     * @throws org.addition.epanet.util.ENException If specified type not found.
     */
    public Field getField(Type type) throws ENException {
        Object obj = fields.get(type);
        if(obj==null)
            throw new ENException(201,type.parseStr);
        else
            return (Field)obj;
    }

    /**
     * Get conversion value from field type.
     * @param type Field type.
     * @return Conversion units value (from user units to system units)
     * @throws ENException If specified type not found.
     */
    public Double getUnits(Type type) throws ENException {
        Object obj = units.get(type);
        if(obj==null)
            throw new ENException(201,type.parseStr);
        else
            return (Double)obj;
    }

    /**
     * Update fields and units, after loading the INP.
     * @param targetUnits
     * @param flowFlag
     * @param pressFlag
     * @param qualFlag
     * @param ChemUnits
     * @param SpGrav
     * @param Hstep
     */
    public void prepare(PropertiesMap.UnitsType targetUnits,
                        PropertiesMap.FlowUnitsType flowFlag,
                        PropertiesMap.PressUnitsType pressFlag,
                        PropertiesMap.QualType qualFlag,
                        String ChemUnits,
                        Double SpGrav,
                        Long Hstep) throws ENException
    {
        double  dcf,
                ccf,
                qcf,
                hcf,
                pcf,
                wcf;

        if (targetUnits == PropertiesMap.UnitsType.SI)
        {
            getField(Type.DEMAND).setUnits(flowFlag.parseStr);
            getField(Type.ELEV).setUnits(Keywords.u_METERS);
            getField(Type.HEAD).setUnits(Keywords.u_METERS);

            if (pressFlag == PropertiesMap.PressUnitsType.METERS)
                getField(Type.PRESSURE).setUnits(Keywords.u_METERS);
            else
                getField(Type.PRESSURE).setUnits(Keywords.u_KPA);

            getField(Type.LENGTH).setUnits(Keywords.u_METERS);
            getField(Type.DIAM).setUnits(Keywords.u_MMETERS);
            getField(Type.FLOW).setUnits(flowFlag.parseStr);
            getField(Type.VELOCITY).setUnits(Keywords.u_MperSEC);
            getField(Type.HEADLOSS).setUnits("m"+Keywords.u_per1000M);
            getField(Type.FRICTION).setUnits("");
            getField(Type.POWER).setUnits(Keywords.u_KW);

            dcf = 1000.0* Constants.MperFT;
            qcf = Constants.LPSperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.LPM) qcf = Constants.LPMperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.MLD) qcf = Constants.MLDperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.CMH) qcf = Constants.CMHperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.CMD) qcf = Constants.CMDperCFS;
            hcf = Constants.MperFT;
            if (pressFlag == PropertiesMap.PressUnitsType.METERS) pcf = Constants.MperFT*SpGrav;
            else pcf = Constants.KPAperPSI*Constants.PSIperFT*SpGrav;
            wcf = Constants.KWperHP;
        }
        else
        {
            getField(Type.DEMAND).setUnits(flowFlag.parseStr);
            getField(Type.ELEV).setUnits(Keywords.u_FEET);
            getField(Type.HEAD).setUnits(Keywords.u_FEET);

            getField(Type.PRESSURE).setUnits(Keywords.u_PSI);
            getField(Type.LENGTH).setUnits(Keywords.u_FEET);
            getField(Type.DIAM).setUnits(Keywords.u_INCHES);
            getField(Type.FLOW).setUnits(flowFlag.parseStr);
            getField(Type.VELOCITY).setUnits(Keywords.u_FTperSEC);
            getField(Type.HEADLOSS).setUnits("ft"+Keywords.u_per1000FT);
            getField(Type.FRICTION).setUnits("");
            getField(Type.POWER).setUnits(Keywords.u_HP);


            dcf = 12.0;
            qcf = 1.0;
            if (flowFlag == PropertiesMap.FlowUnitsType.GPM) qcf = Constants.GPMperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.MGD) qcf = Constants.MGDperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.IMGD)qcf = Constants.IMGDperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.AFD) qcf = Constants.AFDperCFS;
            hcf = 1.0;
            pcf = Constants.PSIperFT*SpGrav;
            wcf = 1.0;
        }
        getField(Type.QUALITY).setUnits("");
        ccf = 1.0;
        if (qualFlag == PropertiesMap.QualType.CHEM)
        {
            ccf = 1.0/Constants.LperFT3;
            getField(Type.QUALITY).setUnits(ChemUnits);
            getField(Type.REACTRATE).setUnits(ChemUnits + Keywords.t_PERDAY);
        }
        else if (qualFlag == PropertiesMap.QualType.AGE)
            getField(Type.QUALITY).setUnits(Keywords.u_HOURS);
        else if (qualFlag == PropertiesMap.QualType.TRACE)
            getField(Type.QUALITY).setUnits(Keywords.u_PERCENT);

        setUnits(Type.DEMAND,qcf);
        setUnits(Type.ELEV,hcf);
        setUnits(Type.HEAD,hcf);
        setUnits(Type.PRESSURE,pcf);
        setUnits(Type.QUALITY,ccf);
        setUnits(Type.LENGTH,hcf);
        setUnits(Type.DIAM,dcf);
        setUnits(Type.FLOW,qcf);
        setUnits(Type.VELOCITY,hcf);
        setUnits(Type.HEADLOSS,hcf);
        setUnits(Type.LINKQUAL,ccf);
        setUnits(Type.REACTRATE,ccf);
        setUnits(Type.FRICTION,1.0);
        setUnits(Type.POWER,wcf);
        setUnits(Type.VOLUME,hcf*hcf*hcf);

        if (Hstep < 1800)
        {
            setUnits(Type.TIME,1.0/60.0);
            getField(Type.TIME).setUnits(Keywords.u_MINUTES);
        }
        else
        {
            setUnits(Type.TIME,1.0/3600.0);
            getField(Type.TIME).setUnits(Keywords.u_HOURS);
        }
    }

    /**
     * Revert system units to user units.
     * @param type Field type.
     * @param value Value to be converted.
     * @return Value in user units.
     * @throws ENException
     */
    public double revertUnit(Type type,double value) throws ENException {
        return type!=null?value*getUnits(type):value;
    }

    public double convertUnitToSystem(Type type,double value) throws ENException {
        return value/getUnits(type);
    }

    /**
     * Set field properties.
     * @param type Field type.
     * @param value Report field reference.
     */
    private void setField(Type type,Field value) {
        fields.put(type,value);
    }

    /**
     * Set conversion value from field type.
     * @param type Field type.
     * @param value Field value.
     */
    private void setUnits(Type type,Double value) {
        units.put(type,value);
    }

} public class FieldsMap {

    /**
     * Network variables
     */
    static public enum Type {
        ELEV        (0,Keywords.t_ELEV     ),   // nodal water quality
        DEMAND      (1,Keywords.t_DEMAND   ),   // nodal demand flow
        HEAD        (2,Keywords.t_HEAD     ),   // link flow velocity
        PRESSURE    (3,Keywords.t_PRESSURE ),   // avg. reaction rate in link
        QUALITY     (4,Keywords.t_QUALITY  ),   // link friction factor
        LENGTH      (5,Keywords.t_LENGTH   ),   // avg. water quality in link
        DIAM        (6,Keywords.t_DIAM     ),   // nodal hydraulic head
        FLOW        (7,Keywords.t_FLOW     ),   // link diameter

        VELOCITY    (8,Keywords.t_VELOCITY ),   // time to fill a tank
        HEADLOSS    (9,Keywords.t_HEADLOSS ),   // link head loss
        LINKQUAL    (10,Keywords.t_LINKQUAL ),   // link status

        STATUS      (11,Keywords.t_STATUS   ),   // tank volume
        SETTING     (12,Keywords.t_SETTING  ),   // simulation time
        REACTRATE   (13,Keywords.t_REACTRATE),   // pump power output
        FRICTION    (14,Keywords.t_FRICTION ),   // link flow rate
        POWER       (15),   // pump/valve setting
        TIME        (16),   // simulation time of day
        VOLUME      (17),   // time to drain a tank
        CLOCKTIME   (18),   // nodal elevation
        FILLTIME    (19),   // link length
        DRAINTIME   (20);   // nodal pressure

        /**
         * Get field type from string.
         * @param text String to be parsed.
         * @return Parsed Field, null the string doesn't match any of the possible types.
         */
        public static Type parse(String text){
            for (Type type : Type.values())
                if (Utilities.match(text, type.parseStr)) return type;
            return null;
        }

        /**
         * Field sequencial id.
         */
        public final int id;

        /**
         * Field string.
         */
        public final String parseStr;
        private Type(int id){this.id = id;this.parseStr = "";}

        private Type(int id, String pStr){this.id = id;this.parseStr = pStr;}
    }

    /**
     * Report fields properties.
     */
    private Map<Type,Field> fields;

    /**
     * Fields units values.
     */
    private Map<Type,Double> units;

    /**
     * Init fields default configuration
     */
    public FieldsMap()
    {
        try{
            fields = new LinkedHashMap<Type,Field>();
            units = new LinkedHashMap<Type,Double>();

            for(Type type: Type.values())
                setField(type,new Field(type.parseStr));

            getField(Type.FRICTION).setPrecision(3);

            for (int i= Type.DEMAND.id;i<= Type.QUALITY.id; i++)
                getField(Type.values()[i]).setEnabled(true);

            for (int i= Type.FLOW.id;i<= Type.HEADLOSS.id; i++)
                getField(Type.values()[i]).setEnabled(true);
        }
        catch (ENException e){
            e.printStackTrace();
        }
    }

    /**
     * Get report field properties from type.
     * @param type Field type.
     * @return Report field.
     * @throws org.addition.epanet.util.ENException If specified type not found.
     */
    public Field getField(Type type) throws ENException {
        Object obj = fields.get(type);
        if(obj==null)
            throw new ENException(201,type.parseStr);
        else
            return (Field)obj;
    }

    /**
     * Get conversion value from field type.
     * @param type Field type.
     * @return Conversion units value (from user units to system units)
     * @throws ENException If specified type not found.
     */
    public Double getUnits(Type type) throws ENException {
        Object obj = units.get(type);
        if(obj==null)
            throw new ENException(201,type.parseStr);
        else
            return (Double)obj;
    }

    /**
     * Update fields and units, after loading the INP.
     * @param targetUnits
     * @param flowFlag
     * @param pressFlag
     * @param qualFlag
     * @param ChemUnits
     * @param SpGrav
     * @param Hstep
     */
    public void prepare(PropertiesMap.UnitsType targetUnits,
                        PropertiesMap.FlowUnitsType flowFlag,
                        PropertiesMap.PressUnitsType pressFlag,
                        PropertiesMap.QualType qualFlag,
                        String ChemUnits,
                        Double SpGrav,
                        Long Hstep) throws ENException
    {
        double  dcf,
                ccf,
                qcf,
                hcf,
                pcf,
                wcf;

        if (targetUnits == PropertiesMap.UnitsType.SI)
        {
            getField(Type.DEMAND).setUnits(flowFlag.parseStr);
            getField(Type.ELEV).setUnits(Keywords.u_METERS);
            getField(Type.HEAD).setUnits(Keywords.u_METERS);

            if (pressFlag == PropertiesMap.PressUnitsType.METERS)
                getField(Type.PRESSURE).setUnits(Keywords.u_METERS);
            else
                getField(Type.PRESSURE).setUnits(Keywords.u_KPA);

            getField(Type.LENGTH).setUnits(Keywords.u_METERS);
            getField(Type.DIAM).setUnits(Keywords.u_MMETERS);
            getField(Type.FLOW).setUnits(flowFlag.parseStr);
            getField(Type.VELOCITY).setUnits(Keywords.u_MperSEC);
            getField(Type.HEADLOSS).setUnits("m"+Keywords.u_per1000M);
            getField(Type.FRICTION).setUnits("");
            getField(Type.POWER).setUnits(Keywords.u_KW);

            dcf = 1000.0* Constants.MperFT;
            qcf = Constants.LPSperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.LPM) qcf = Constants.LPMperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.MLD) qcf = Constants.MLDperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.CMH) qcf = Constants.CMHperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.CMD) qcf = Constants.CMDperCFS;
            hcf = Constants.MperFT;
            if (pressFlag == PropertiesMap.PressUnitsType.METERS) pcf = Constants.MperFT*SpGrav;
            else pcf = Constants.KPAperPSI*Constants.PSIperFT*SpGrav;
            wcf = Constants.KWperHP;
        }
        else
        {
            getField(Type.DEMAND).setUnits(flowFlag.parseStr);
            getField(Type.ELEV).setUnits(Keywords.u_FEET);
            getField(Type.HEAD).setUnits(Keywords.u_FEET);

            getField(Type.PRESSURE).setUnits(Keywords.u_PSI);
            getField(Type.LENGTH).setUnits(Keywords.u_FEET);
            getField(Type.DIAM).setUnits(Keywords.u_INCHES);
            getField(Type.FLOW).setUnits(flowFlag.parseStr);
            getField(Type.VELOCITY).setUnits(Keywords.u_FTperSEC);
            getField(Type.HEADLOSS).setUnits("ft"+Keywords.u_per1000FT);
            getField(Type.FRICTION).setUnits("");
            getField(Type.POWER).setUnits(Keywords.u_HP);


            dcf = 12.0;
            qcf = 1.0;
            if (flowFlag == PropertiesMap.FlowUnitsType.GPM) qcf = Constants.GPMperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.MGD) qcf = Constants.MGDperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.IMGD)qcf = Constants.IMGDperCFS;
            if (flowFlag == PropertiesMap.FlowUnitsType.AFD) qcf = Constants.AFDperCFS;
            hcf = 1.0;
            pcf = Constants.PSIperFT*SpGrav;
            wcf = 1.0;
        }
        getField(Type.QUALITY).setUnits("");
        ccf = 1.0;
        if (qualFlag == PropertiesMap.QualType.CHEM)
        {
            ccf = 1.0/Constants.LperFT3;
            getField(Type.QUALITY).setUnits(ChemUnits);
            getField(Type.REACTRATE).setUnits(ChemUnits + Keywords.t_PERDAY);
        }
        else if (qualFlag == PropertiesMap.QualType.AGE)
            getField(Type.QUALITY).setUnits(Keywords.u_HOURS);
        else if (qualFlag == PropertiesMap.QualType.TRACE)
            getField(Type.QUALITY).setUnits(Keywords.u_PERCENT);

        setUnits(Type.DEMAND,qcf);
        setUnits(Type.ELEV,hcf);
        setUnits(Type.HEAD,hcf);
        setUnits(Type.PRESSURE,pcf);
        setUnits(Type.QUALITY,ccf);
        setUnits(Type.LENGTH,hcf);
        setUnits(Type.DIAM,dcf);
        setUnits(Type.FLOW,qcf);
        setUnits(Type.VELOCITY,hcf);
        setUnits(Type.HEADLOSS,hcf);
        setUnits(Type.LINKQUAL,ccf);
        setUnits(Type.REACTRATE,ccf);
        setUnits(Type.FRICTION,1.0);
        setUnits(Type.POWER,wcf);
        setUnits(Type.VOLUME,hcf*hcf*hcf);

        if (Hstep < 1800)
        {
            setUnits(Type.TIME,1.0/60.0);
            getField(Type.TIME).setUnits(Keywords.u_MINUTES);
        }
        else
        {
            setUnits(Type.TIME,1.0/3600.0);
            getField(Type.TIME).setUnits(Keywords.u_HOURS);
        }
    }

    /**
     * Revert system units to user units.
     * @param type Field type.
     * @param value Value to be converted.
     * @return Value in user units.
     * @throws ENException
     */
    public double revertUnit(Type type,double value) throws ENException {
        return type!=null?value*getUnits(type):value;
    }

    public double convertUnitToSystem(Type type,double value) throws ENException {
        return value/getUnits(type);
    }

    /**
     * Set field properties.
     * @param type Field type.
     * @param value Report field reference.
     */
    private void setField(Type type,Field value) {
        fields.put(type,value);
    }

    /**
     * Set conversion value from field type.
     * @param type Field type.
     * @param value Field value.
     */
    private void setUnits(Type type,Double value) {
        units.put(type,value);
    }

} public class Network {

    /**
     * Available files types.
     */
    public enum FileType {
        EXCEL_FILE,
        INP_FILE,
        NULL_FILE,
        XML_FILE,
        XML_GZ_FILE,
    }

    /**
     * Available section types.
     */
    static public enum SectType {
        BACKDROP(26, Keywords.s_BACKDROP, Keywords.t_BACKDROP),
        CONTROLS(7, Keywords.s_CONTROLS, Keywords.t_CONTROL),
        COORDS(23, Keywords.s_COORDS, Keywords.t_COORD),
        CURVES(13, Keywords.s_CURVES, Keywords.t_CURVE),
        DEMANDS(9, Keywords.s_DEMANDS, Keywords.t_DEMAND),
        EMITTERS(11, Keywords.s_EMITTERS, Keywords.t_EMITTER),
        END(28, Keywords.s_END, Keywords.t_END),
        ENERGY(17, Keywords.s_ENERGY, Keywords.t_ENERGY),
        JUNCTIONS(1, Keywords.s_JUNCTIONS, Keywords.t_JUNCTION),
        LABELS(25, Keywords.s_LABELS, Keywords.t_LABEL),
        MIXING(19, Keywords.s_MIXING, Keywords.t_MIXING),
        OPTIONS(22, Keywords.s_OPTIONS, Keywords.t_OPTION),
        PATTERNS(12, Keywords.s_PATTERNS, Keywords.t_PATTERN),
        PIPES(4, Keywords.s_PIPES, Keywords.t_PIPE),
        PUMPS(5, Keywords.s_PUMPS, Keywords.t_PUMP),
        QUALITY(14, Keywords.s_QUALITY, Keywords.t_QUALITY),
        REACTIONS(18, Keywords.s_REACTIONS, Keywords.t_REACTION),
        REPORT(20, Keywords.s_REPORT, Keywords.t_REPORT),
        RESERVOIRS(2, Keywords.s_RESERVOIRS, Keywords.t_RESERVOIR),
        ROUGHNESS(16, Keywords.s_ROUGHNESS, Keywords.t_ROUGHNESS),
        RULES(8, Keywords.s_RULES, Keywords.t_RULE),
        SOURCES(10, Keywords.s_SOURCES, Keywords.t_SOURCE),
        STATUS(15, Keywords.s_STATUS, Keywords.t_STATUS),
        TAGS(27, Keywords.s_TAGS, Keywords.t_TAG),
        TANKS(3, Keywords.s_TANKS, Keywords.t_TANK),
        TIMES(21, Keywords.s_TIMES, Keywords.t_TIME),
        TITLE(0, Keywords.s_TITLE, Keywords.t_TITLE),
        VALVES(6, Keywords.s_VALVES, Keywords.t_VALVE),
        VERTICES(24, Keywords.s_VERTICES, Keywords.t_VERTICE);

        /**
         * Get section type from string.
         *
         * @param text Parse string.
         * @return Section type.
         */
        public static SectType parse(String text) {
            for (SectType type : SectType.values())
                if (Utilities.match(text, type.parseStr)) return type;
            return null;
        }
        /**
         * Sequencial id.
         */
        public final int id;
        /**
         * Parse string id.
         */
        public final String parseStr;

        /**
         * Report string id.
         */
        public final String reportStr;

        private SectType(int id, String text) {
            this.id = id;
            this.parseStr = text;
            this.reportStr = "";
        }

        private SectType(int id, String text, String repStr) {
            this.id = id;
            this.parseStr = text;
            this.reportStr = repStr;
        }
    }

    private List<Control> controls;
    private Map<String, Curve> curves;
    /**
     * Fields map with report variables properties and conversion units.
     */
    private transient FieldsMap fields;
    /**
     * Transient colleciton of junctions.
     */
    private transient Map<String, Node> junctions;
    private List<Label> labels;
    private Map<String, Link> links;
    private Map<String, Node> nodes;
    private Map<String, Pattern> patterns;

    /**
     * Properties Map with simulation configuration properties.
     */
    private PropertiesMap properties;

    /**
     * Transient colleciton of pumps.
     */
    private transient Map<String, Pump> pumps;

    private Map<String, Rule> rules;
    /**
     * Transient collection of tanks(and reservoirs)
     */
    private transient Map<String, Tank> tanks;
    private List<String> titleText;
    /**
     * Transient colleciton of valves.
     */
    private transient Map<String, Valve> valves;


    public Network() {
        titleText = new ArrayList<String>();
        patterns = new LinkedHashMap<String, Pattern>();
        nodes = new LinkedHashMap<String, Node>();
        links = new LinkedHashMap<String, Link>();

        curves = new LinkedHashMap<String, Curve>();
        controls = new ArrayList<Control>();
        labels = new ArrayList<Label>();
        rules = new LinkedHashMap<String, Rule>();

        tanks = null;
        pumps = null;
        valves = null;

        fields = new FieldsMap();

        properties = new PropertiesMap();

        addPattern("", new Pattern());
    }

    public void addControl(Control ctr) {
        controls.add(ctr);
    }


    public void addCurve(String s, Curve cur) {
        cur.setId(s);
        curves.put(s, cur);
    }

    public void addJunction(String id, Node junc) {
        junc.setId(id);
        nodes.put(id, junc);
    }

    public void addPattern(String s, Pattern pat) {
        pat.setId(s);
        patterns.put(s, pat);
    }

    public void addPipe(String id, Link linkRef) {
        linkRef.setId(id);
        links.put(id, linkRef);
    }

    public void addPump(String id, Pump pump) {
        pump.setId(id);
        if (pumps == null)
            pumps = new LinkedHashMap<String, Pump>();
        pumps.put(id, pump);
        links.put(id, pump);
    }

    public void addRule(Rule r) {
        rules.put(r.getLabel(), r);
    }


    public void addTank(String id, Tank tank) {
        tank.setId(id);
        if (tanks == null)
            tanks = new LinkedHashMap<String, Tank>();
        tanks.put(id, tank);
        nodes.put(id, tank);
    }

    public void addValve(String id, Valve valve) {
        valve.setId(id);
        if (valves == null)
            valves = new LinkedHashMap<String, Valve>();
        valves.put(id, valve);
        links.put(id, valve);
    }

    public Control[] getControls() {
        return controls.toArray(new Control[controls.size()]);
    }

    public Curve getCurve(String name) {
        Object obj = curves.get(name);
        if (obj == null)
            return null;
        else {
            return (Curve) obj;
        }
    }

    public Collection<Curve> getCurves() {
        return curves.values();
    }

    public FieldsMap getFieldsMap() {
        return fields;
    }

    public Collection<Node> getJunctions() {
        if (junctions == null) {
            Map<String, Node> tempJunc = new LinkedHashMap<String, Node>();
            for (Node n : nodes.values()) {
                if (!(n instanceof Tank))
                    tempJunc.put(n.getId(), n);
            }
            if (tempJunc.size() == 0)
                return new ArrayList<Node>();
            else
                junctions = tempJunc;
        }
        return junctions.values();
    }

    public List<Label> getLabels() {
        return labels;
    }

    public Link getLink(String name) {
        Object obj = links.get(name);
        if (obj == null)
            return null;
        else {
            return (Link) obj;
        }
    }

    public Collection<Link> getLinks() {
        return links.values();
    }

    public Node getNode(String name) {
        Object obj = nodes.get(name);
        if (obj == null)
            return null;
        else {
            return (Node) obj;
        }
    }

    public Collection<Node> getNodes() {
        return nodes.values();
    }

    public Pattern getPattern(String name) {
        Object obj = patterns.get(name);
        if (obj == null)
            return null;
        else {
            return (Pattern) obj;
        }
    }

    public Collection<Pattern> getPatterns() {
        return patterns.values();
    }

    public PropertiesMap getPropertiesMap() {
        return properties;
    }

    public Collection<Pump> getPumps() {
        if (pumps == null) {
            Map<String, Pump> tempPump = new LinkedHashMap<String, Pump>();
            for (Link l : links.values()) {
                if (l instanceof Pump)
                    tempPump.put(l.getId(), (Pump) l);
            }
            if (tempPump.size() == 0)
                return new ArrayList<Pump>();
            else
                pumps = tempPump;
        }
        return pumps.values();
    }

    public Rule getRule(String ruleName) {
        Object obj = rules.get(ruleName);
        if (obj == null)
            return null;
        else {
            return (Rule) obj;
        }
    }

    public Collection<Rule> getRules() {
        return rules.values();
    }

    public Collection<Tank> getTanks() {
        if (tanks == null) {
            Map<String, Tank> tempTank = new LinkedHashMap<String, Tank>();
            for (Node n : nodes.values()) {
                if (n instanceof Tank)
                    tempTank.put(n.getId(), (Tank) n);
            }
            if (tempTank.size() == 0)
                return new ArrayList<Tank>();
            else
                tanks = tempTank;
        }
        return tanks.values();
    }

    public List<String> getTitleText() {
        return titleText;
    }

    public Collection<Valve> getValves() {
        if (valves == null) {
            Map<String, Valve> tempValve = new LinkedHashMap<String, Valve>();
            for (Link l : links.values()) {
                if (l instanceof Valve)
                    tempValve.put(l.getId(), (Valve) l);
            }
            if (tempValve.size() == 0)
                return new ArrayList<Valve>();
            else
                valves = tempValve;
        }
        return valves.values();
    }

    private Object readResolve() throws ENException {
        updatedUnitsProperty();
        return this;
    }

    public void updatedUnitsProperty() throws ENException {
        fields = new FieldsMap();
        fields.prepare(getPropertiesMap().getUnitsflag(),
                getPropertiesMap().getFlowflag(),
                getPropertiesMap().getPressflag(),
                getPropertiesMap().getQualflag(),
                getPropertiesMap().getChemUnits(),
                getPropertiesMap().getSpGrav(),
                getPropertiesMap().getHstep());
    }

    public String toString() {
        String res = " Network\n";
        res += "  Nodes : " + nodes.size() + "\n";
        res += "  Links : " + links.size() + "\n";
        res += "  Pattern : " + patterns.size() + "\n";
        res += "  Curves : " + curves.size() + "\n";
        res += "  Controls : " + controls.size() + "\n";
        res += "  Labels : " + labels.size() + "\n";
        res += "  Rules : " + rules.size() + "\n";
        if (tanks != null) res += "  Tanks : " + tanks.size() + "\n";
        if (pumps != null) res += "  Pumps : " + pumps.size() + "\n";
        if (valves != null) res += "  Valves : " + valves.size() + "\n";
        return res;
    }

//    public Node getNodeByIndex(int idx) {
//        return  new ArrayList<Node>(nodes.values()).get(idx);
//    }
//    public Link getLinkByIndex(int idx) {
//        return  new ArrayList<Link>(links.values()).get(idx);
//    }
} public class PropertiesMap {

    /**
     * Flow units.
     */
    static public enum FlowUnitsType {
        AFD(Keywords.w_AFD),         //   acre-feet per day
        CFS(Keywords.w_CFS),         //   cubic feet per second
        CMD(Keywords.w_CMD),         //   cubic meters per day
        CMH(Keywords.w_CMH),         //   cubic meters per hour
        GPM(Keywords.w_GPM),         //   gallons per minute
        IMGD(Keywords.w_IMGD),       //   imperial million gal. per day
        LPM(Keywords.w_LPM),         //   liters per minute
        LPS(Keywords.w_LPS),         //   liters per second
        MGD(Keywords.w_MGD),         //   million gallons per day
        MLD(Keywords.w_MLD);         //   megaliters per day

        public static FlowUnitsType parse(String text) {
            for (FlowUnitsType type : FlowUnitsType.values())
                if (Utilities.match(text, type.parseStr)) return type;
            return null;
        }

        public final String parseStr;

        private FlowUnitsType(String parseStr) {
            this.parseStr = parseStr;
        }
    }

    /**
     * Head loss formula.
     */
    static public enum FormType {
        /**
         * Chezy-Manning
         */
        CM(Keywords.w_CM),
        /**
         * Darcy-Weisbach
         */
        DW(Keywords.w_DW),
        /**
         * Hazen-Williams
         */
        HW(Keywords.w_HW);
        /**
         * Parse string id.
         */
        public final String parseStr;

        FormType(String parseStr) {
            this.parseStr = parseStr;
        }
    }

    /**
     * Hydraulics solution option.
     */
    static public enum Hydtype {
        /**
         * Save after current run.
         */
        SAVE,
        /**
         * Use temporary file.
         */
        SCRATCH,
        /**
         * Use from previous run.
         */
        USE
    }

    /**
     * Pressure units.
     */
    static public enum PressUnitsType {
        KPA(Keywords.w_KPA),        // pounds per square inch
        METERS(Keywords.w_METERS),        // kiloPascals
        PSI(Keywords.w_PSI);  // meters

        public final String parseStr;

        PressUnitsType(String parseStr) {
            this.parseStr = parseStr;
        }
    }

    /**
     * Water quality analysis option.
     */
    static public enum QualType {
        /**
         * Analyze water age.
         */
        AGE(2, Keywords.w_AGE),
        /**
         * Analyze a chemical.
         */
        CHEM(1, Keywords.w_CHEM),
        /**
         * No quality analysis.
         */
        NONE(0, Keywords.w_NONE),
        /**
         * Trace % of flow from a source
         */
        TRACE(3, Keywords.w_TRACE);

        /**
         * Parse quality type from string.
         */
        public static QualType parse(String text) {
            for (QualType type : QualType.values())
                if (Utilities.match(text, type.parseStr)) return type;
            return null;
        }

        /**
         * Sequencial id.
         */
        public final int id;

        /**
         * Parse string id.
         */
        public final String parseStr;

        private QualType(int id, String pStr) {
            this.id = id;
            this.parseStr = pStr;
        }
    }

    /**
     * Reporting flag.
     */
    static public enum ReportFlag {
        FALSE, //0
        SOME, //2,
        // //1
        TRUE
    }

    /**
     * Status report options.
     */
    static public enum StatFlag {
        FALSE(Keywords.w_NO),
        FULL(Keywords.w_FULL),
        TRUE(Keywords.w_YES);

        public static StatFlag parse(String text) {
            for (StatFlag type : StatFlag.values())
                if (Utilities.match(text, type.parseStr)) return type;
            return null;
        }

        public final String parseStr;

        private StatFlag(String parseStr) {
            this.parseStr = parseStr;
        }

    }

    /**
     * Time series statistics.
     */
    static public enum TstatType {
        AVG(Keywords.w_AVG),      // none
        MAX(Keywords.w_MAX),       // time-averages
        MIN(Keywords.w_MIN),       // minimum values
        RANGE(Keywords.w_RANGE),       // maximum values
        SERIES(Keywords.w_NONE);     // max - min values

        public final String parseStr;

        private TstatType(String parseStr) {
            this.parseStr = parseStr;
        }
    }


    /**
     * Unit system.
     */
    static public enum UnitsType {
        /**
         * SI (metric)
         */
        SI,
        /**
         * US
         */
        US
    }

    public static final String ALTREPORT = "AltReport";
    public static final String BULKORDER = "BulkOrder";
    public static final String CHECK_FREQ = "CheckFreq";
    public static final String CHEM_NAME = "ChemName";
    public static final String CHEM_UNITS = "ChemUnits";
    public static final String CLIMIT = "Climit";
    public static final String CTOL = "Ctol";
    public static final String DAMP_LIMIT = "DampLimit";
    public static final String DCOST = "Dcost";
    public static final String DEF_PAT_ID = "DefPatID";
    public static final String DIFFUS = "Diffus";
    public static final String DMULT = "Dmult";
    public static final String DUR = "Dur";
    public static final String ECOST = "Ecost";
    public static final String EMAX = "Emax";
    public static final String ENERGYFLAG = "Energyflag";
    public static final String EPAT_ID = "EpatID";
    public static final String EPUMP = "Epump";
    public static final String EXTRA_ITER = "ExtraIter";
    public static final String FLOWFLAG = "Flowflag";
    public static final String FORMFLAG = "Formflag";
    public static final String HACC = "Hacc";
    public static final String HEXP = "Hexp";
    public static final String HSTEP = "Hstep";
    public static final String HTOL = "Htol";
    public static final String HYD_FNAME = "HydFname";
    public static final String HYDFLAG = "Hydflag";
    public static final String KBULK = "Kbulk";
    public static final String KWALL = "Kwall";
    public static final String LINKFLAG = "Linkflag";
    public static final String MAP_FNAME = "MapFname";
    public static final String MAXCHECK = "MaxCheck";
    public static final String MAXITER = "MaxIter";
    public static final String MESSAGEFLAG = "Messageflag";
    public static final String NODEFLAG = "Nodeflag";
    public static final String PAGE_SIZE = "PageSize";
    public static final String PRESSFLAG = "Pressflag";
    public static final String PSTART = "Pstart";
    public static final String PSTEP = "Pstep";
    public static final String QEXP = "Qexp";
    public static final String QSTEP = "Qstep";
    public static final String QTOL = "Qtol";
    public static final String QUALFLAG = "Qualflag";
    public static final String RFACTOR = "Rfactor";
    public static final String RQTOL = "RQtol";
    public static final String RSTART = "Rstart";
    public static final String RSTEP = "Rstep";
    public static final String RULESTEP = "Rulestep";
    public static final String SPGRAV = "SpGrav";
    public static final String STATFLAG = "Statflag";
    public static final String SUMMARYFLAG = "Summaryflag";
    public static final String TANKORDER = "TankOrder";
    public static final String TRACE_NODE = "TraceNode";
    public static final String TSTART = "Tstart";
    public static final String TSTATFLAG = "Tstatflag";
    public static final String UNITSFLAG = "Unitsflag";
    public static final String VISCOS = "Viscos";

    public static final String WALLORDER = "WallOrder";
    public static final String[] EpanetObjectsNames = {TSTATFLAG, HSTEP, DUR, QSTEP, CHECK_FREQ,
            MAXCHECK, DMULT, ALTREPORT, QEXP, HEXP, RQTOL, QTOL, BULKORDER, TANKORDER, WALLORDER,
            RFACTOR, CLIMIT, KBULK, KWALL, DCOST, ECOST, EPAT_ID, EPUMP, PAGE_SIZE, STATFLAG, SUMMARYFLAG,
            MESSAGEFLAG, ENERGYFLAG, NODEFLAG, LINKFLAG, RULESTEP, PSTEP, PSTART, RSTEP, RSTART, TSTART,
            FLOWFLAG, PRESSFLAG, FORMFLAG, HYDFLAG, QUALFLAG, UNITSFLAG, HYD_FNAME, CHEM_NAME, CHEM_UNITS,
            DEF_PAT_ID, MAP_FNAME, TRACE_NODE, EXTRA_ITER, CTOL, DIFFUS, DAMP_LIMIT, VISCOS, SPGRAV, MAXITER,
            HACC, HTOL, EMAX
    };

    private Map<String, Object> values;



    public PropertiesMap() {
        values = new HashMap<String, Object>();
        loadDefaults();
    }

    /**
     * Get an object from the map.
     *
     * @param name Object name.
     * @return Object refernce.
     * @throws ENException If object name not found.
     */
    public Object get(String name) throws ENException {
        return values.get(name);
    }

    public String getAltReport() throws ENException {
        return (String) get(ALTREPORT);
    }

    public Double getBulkOrder() throws ENException {
        return (Double) get(BULKORDER);
    }

    public Integer getCheckFreq() throws ENException {
        return (Integer) get(CHECK_FREQ);
    }

    public String getChemName() throws ENException {
        return (String) get(CHEM_NAME);
    }

    public String getChemUnits() throws ENException {
        return (String) get(CHEM_UNITS);
    }

    public Double getClimit() throws ENException {
        return (Double) get(CLIMIT);
    }

    public Double getCtol() throws ENException {
        return (Double) get(CTOL);
    }

    public Double getDampLimit() throws ENException {
        return (Double) get(DAMP_LIMIT);
    }

    public Double getDcost() throws ENException {
        return (Double) get(DCOST);
    }

    public String getDefPatId() throws ENException {
        return (String) get(DEF_PAT_ID);
    }

    public Double getDiffus() throws ENException {
        return (Double) get(DIFFUS);
    }

    public Double getDmult() throws ENException {
        return (Double) get(DMULT);
    }

    public Long getDuration() throws ENException {
        return (Long) get(DUR);
    }

    public Double getEcost() throws ENException {
        return (Double) get(ECOST);
    }

    public Double getEmax() throws ENException {
        return (Double) get(EMAX);
    }

    public Boolean getEnergyflag() throws ENException {
        return (Boolean) get(ENERGYFLAG);
    }

    public String getEpatId() throws ENException {
        return (String) get(EPAT_ID);
    }

    public Double getEpump() throws ENException {
        return (Double) get(EPUMP);
    }

    public Integer getExtraIter() throws ENException {
        return (Integer) get(EXTRA_ITER);
    }

    public FlowUnitsType getFlowflag() throws ENException {
        return (FlowUnitsType) get(FLOWFLAG);
    }

    public FormType getFormflag() throws ENException {
        return (FormType) get(FORMFLAG);
    }

    public Double getHacc() throws ENException {
        return (Double) get(HACC);
    }

    public Double getHexp() throws ENException {
        return (Double) get(HEXP);
    }

    public Long getHstep() throws ENException {
        return (Long) get(HSTEP);
    }

    public Double getHtol() throws ENException {
        return (Double) get(HTOL);
    }

    public Hydtype getHydflag() throws ENException {
        return (Hydtype) get(HYDFLAG);
    }

    public String getHydFname() throws ENException {
        return (String) get(HYD_FNAME);
    }

    public Double getKbulk() throws ENException {
        return (Double) get(KBULK);
    }

    public Double getKwall() throws ENException {
        return (Double) get(KWALL);
    }

    public ReportFlag getLinkflag() throws ENException {
        return (ReportFlag) get(LINKFLAG);
    }

    public String getMapFname() throws ENException {
        return (String) get(MAP_FNAME);
    }

    public Integer getMaxCheck() throws ENException {
        return (Integer) get(MAXCHECK);
    }

    public Integer getMaxIter() throws ENException {
        return (Integer) get(MAXITER);
    }

    public Boolean getMessageflag() throws ENException {
        return (Boolean) get(MESSAGEFLAG);
    }

    public ReportFlag getNodeflag() throws ENException {
        return (ReportFlag) get(NODEFLAG);
    }

    /**
     * Get objects names in this map.
     *
     * @param exclude_epanet exclude Epanet objects.
     * @return List of objects names.
     */
    public List<String> getObjectsNames(boolean exclude_epanet) {
        List<String> allObjs = new ArrayList<String>(values.keySet());
        if (exclude_epanet)
            allObjs.removeAll(Arrays.asList(EpanetObjectsNames));
        return allObjs;
    }

    public Integer getPageSize() throws ENException {
        return (Integer) get(PAGE_SIZE);
    }

    public PressUnitsType getPressflag() throws ENException {
        return (PressUnitsType) get(PRESSFLAG);
    }

    public Long getPstart() throws ENException {
        return (Long) get(PSTART);
    }

    public Long getPstep() throws ENException {
        return (Long) get(PSTEP);
    }

    public Double getQexp() throws ENException {
        return (Double) get(QEXP);
    }

    public Long getQstep() throws ENException {
        return (Long) get(QSTEP);
    }

    public Double getQtol() throws ENException {
        return (Double) get(QTOL);
    }

    public QualType getQualflag() throws ENException {
        return (QualType) get(QUALFLAG);
    }

    public Double getRfactor() throws ENException {
        return (Double) get(RFACTOR);
    }

    public Double getRQtol() throws ENException {
        return (Double) get(RQTOL);
    }

    public Long getRstart() throws ENException {
        return (Long) get(RSTART);
    }

    public Long getRstep() throws ENException {
        return (Long) get(RSTEP);
    }

    public Long getRulestep() throws ENException {
        return (Long) get(RULESTEP);
    }

    public Double getSpGrav() throws ENException {
        return (Double) get(SPGRAV);
    }

    public StatFlag getStatflag() throws ENException {
        return (StatFlag) get(STATFLAG);
    }

    public Boolean getSummaryflag() throws ENException {
        return (Boolean) get(SUMMARYFLAG);
    }

    public Double getTankOrder() throws ENException {
        return (Double) get(TANKORDER);
    }

    public String getTraceNode() throws ENException {
        return (String) get(TRACE_NODE);
    }

    public Long getTstart() throws ENException {
        return (Long) get(TSTART);
    }

    public TstatType getTstatflag() throws ENException {
        return (TstatType) get(TSTATFLAG);
    }

    public UnitsType getUnitsflag() throws ENException {
        return (UnitsType) get(UNITSFLAG);
    }

    public Double getViscos() throws ENException {
        return (Double) get(VISCOS);
    }

    public Double getWallOrder() throws ENException {
        return (Double) get(WALLORDER);
    }

    /**
     * Init properties with default value.
     */
    private void loadDefaults() {
        put(BULKORDER, new Double(1.0d));     // 1st-order bulk reaction rate
        put(TANKORDER, new Double(1.0d));     // 1st-order tank reaction rate
        put(WALLORDER, new Double(1.0d));     // 1st-order wall reaction rate
        put(RFACTOR, new Double(1.0d));     // No roughness-reaction factor
        put(CLIMIT, new Double(0.0d));     // No limiting potential quality
        put(KBULK, new Double(0.0d));     // No global bulk reaction
        put(KWALL, new Double(0.0d));     // No global wall reaction
        put(DCOST, new Double(0.0d));     // Zero energy demand charge
        put(ECOST, new Double(0.0d));     // Zero unit energy cost
        put(EPAT_ID, "");                   // No energy price pattern
        put(EPUMP, Constants.EPUMP);      // Default pump efficiency
        put(PAGE_SIZE, Constants.PAGESIZE);
        put(STATFLAG, StatFlag.FALSE);
        put(SUMMARYFLAG, true);
        put(MESSAGEFLAG, true);
        put(ENERGYFLAG, false);
        put(NODEFLAG, ReportFlag.FALSE);
        put(LINKFLAG, ReportFlag.FALSE);
        put(TSTATFLAG, TstatType.SERIES);     // Generate time series output
        put(HSTEP, new Long(3600));       // 1 hr hydraulic time step
        put(DUR, new Long(0));          // 0 sec duration (steady state)
        put(QSTEP, new Long(0));          // No pre-set quality time step
        put(RULESTEP, new Long(0));          // No pre-set rule time step
        put(PSTEP, new Long(3600));       // 1 hr time pattern period
        put(PSTART, new Long(0));          // Starting pattern period
        put(RSTEP, new Long(3600));       // 1 hr reporting period
        put(RSTART, new Long(0));          // Start reporting at time 0
        put(TSTART, new Long(0));          // Starting time of day
        put(FLOWFLAG, FlowUnitsType.GPM);    // Flow units are gpm
        put(PRESSFLAG, PressUnitsType.PSI);   // Pressure units are psi
        put(FORMFLAG, FormType.HW);          // Use Hazen-Williams formula
        put(HYDFLAG, Hydtype.SCRATCH);      // No external hydraulics file
        put(QUALFLAG, QualType.NONE);        // No quality simulation
        put(UNITSFLAG, UnitsType.US);         // US unit system
        put(HYD_FNAME, "");
        put(CHEM_NAME, Keywords.t_CHEMICAL);
        put(CHEM_UNITS, Keywords.u_MGperL);    // mg/L
        put(DEF_PAT_ID, Constants.DEFPATID);   // Default demand pattern index
        put(MAP_FNAME, "");
        put(ALTREPORT, "");
        put(TRACE_NODE, "");                   // No source tracing
        put(EXTRA_ITER, new Integer(-1));      // Stop if network unbalanced
        put(CTOL, Constants.MISSING);    // No pre-set quality tolerance
        put(DIFFUS, Constants.MISSING);    // Temporary diffusivity
        put(DAMP_LIMIT, Constants.DAMPLIMIT);
        put(VISCOS, Constants.MISSING);    // Temporary viscosity
        put(SPGRAV, Constants.SPGRAV);     // Default specific gravity
        put(MAXITER, Constants.MAXITER);    // Default max. hydraulic trials
        put(HACC, Constants.HACC);       // Default hydraulic accuracy
        put(HTOL, Constants.HTOL);       // Default head tolerance
        put(QTOL, Constants.QTOL);       // Default flow tolerance
        put(RQTOL, Constants.RQTOL);      // Default hydraulics parameters
        put(HEXP, new Double(0.0d));
        put(QEXP, new Double(2.0d));     // Flow exponent for emitters
        put(CHECK_FREQ, Constants.CHECKFREQ);
        put(MAXCHECK, Constants.MAXCHECK);
        put(DMULT, new Double(1.0d));     // Demand multiplier
        put(EMAX, new Double(0.0d));     // Zero peak energy usage


    }


    /**
     * Insert an object into the map.
     *
     * @param name Object name.
     * @param obj  Object reference.
     */
    public void put(String name, Object obj) {
        values.put(name, obj);
    }

    public void setAltReport(String str) throws ENException {
        put(ALTREPORT, str);
    }

    public void setBulkOrder(Double bulkOrder) throws ENException {
        put(BULKORDER, bulkOrder);
    }

    public void setCheckFreq(int checkFreq) throws ENException {
        put(CHECK_FREQ, checkFreq);
    }

    public void setChemName(String chemName) throws ENException {
        put(CHEM_NAME, chemName);
    }

    public void setChemUnits(String chemUnits) throws ENException {
        put(CHEM_UNITS, chemUnits);
    }

    public void setClimit(Double climit) throws ENException {
        put(CLIMIT, climit);
    }

    public void setCtol(Double ctol) throws ENException {
        put(CTOL, ctol);
    }

    public void setDampLimit(Double dampLimit) throws ENException {
        put(DAMP_LIMIT, dampLimit);
    }

    public void setDcost(Double dcost) throws ENException {
        put(DCOST, dcost);
    }

    public void setDefPatId(String defPatID) throws ENException {
        put(DEF_PAT_ID, defPatID);
    }

    public void setDiffus(Double diffus) throws ENException {
        put(DIFFUS, diffus);
    }

    public void setDmult(Double dmult) throws ENException {
        put(DMULT, dmult);
    }

    public void setDuration(long dur) throws ENException {
        put(DUR, dur);
    }

    public void setEcost(Double ecost) throws ENException {
        put(ECOST, ecost);
    }

    public void setEmax(Double emax) throws ENException {
        put(EMAX, emax);
    }

    public void setEnergyflag(boolean energyflag) throws ENException {
        put(ENERGYFLAG, energyflag);
    }

    public void setEpatId(String epat) throws ENException {
        put(EPAT_ID, epat);
    }

    public void setEpump(Double epump) throws ENException {
        put(EPUMP, epump);
    }

    public void setExtraIter(int extraIter) throws ENException {
        put(EXTRA_ITER, extraIter);
    }

    public void setFlowflag(FlowUnitsType flowflag) throws ENException {
        put(FLOWFLAG, flowflag);
    }

    public void setFormflag(FormType formflag) throws ENException {
        put(FORMFLAG, formflag);
    }

    public void setHacc(Double hacc) throws ENException {
        put(HACC, hacc);
    }

    public void setHexp(Double hexp) throws ENException {
        put(HEXP, hexp);
    }

    public void setHstep(long hstep) throws ENException {
        put(HSTEP, hstep);
    }

    public void setHtol(Double htol) throws ENException {
        put(HTOL, htol);
    }

    public void setHydflag(Hydtype hydflag) throws ENException {
        put(HYDFLAG, hydflag);
    }

    public void setHydFname(String hydFname) throws ENException {
        put(HYD_FNAME, hydFname);
    }

    public void setKbulk(Double kbulk) throws ENException {
        put(KBULK, kbulk);
    }

    public void setKwall(Double kwall) throws ENException {
        put(KWALL, kwall);
    }

    public void setLinkflag(ReportFlag linkflag) throws ENException {
        put(LINKFLAG, linkflag);
    }

    public void setMapFname(String mapFname) throws ENException {
        put(MAP_FNAME, mapFname);
    }

    public void setMaxCheck(int maxCheck) throws ENException {
        put(MAXCHECK, maxCheck);
    }

    public void setMaxIter(int maxIter) throws ENException {
        put(MAXITER, maxIter);
    }

    public void setMessageflag(boolean messageflag) throws ENException {
        put(MESSAGEFLAG, messageflag);
    }

    public void setNodeflag(ReportFlag nodeflag) throws ENException {
        put(NODEFLAG, nodeflag);
    }

    public void setPageSize(int pageSize) throws ENException {
        put(PAGE_SIZE, pageSize);
    }

    public void setPressflag(PressUnitsType pressflag) throws ENException {
        put(PRESSFLAG, pressflag);
    }

    public void setPstart(long pstart) throws ENException {
        put(PSTART, pstart);
    }

    public void setPstep(long pstep) throws ENException {
        put(PSTEP, pstep);
    }

    public void setQexp(Double qexp) throws ENException {
        put(QEXP, qexp);
    }

    public void setQstep(long qstep) throws ENException {
        put(QSTEP, qstep);
    }

    public void setQtol(Double qtol) throws ENException {
        put(QTOL, qtol);
    }

    public void setQualflag(QualType qualflag) throws ENException {
        put(QUALFLAG, qualflag);
    }

    public void setRfactor(Double rfactor) throws ENException {
        put(RFACTOR, rfactor);
    }

    public void setRQtol(Double RQtol) throws ENException {
        put(RQTOL, RQtol);
    }

    public void setRstart(long rstart) throws ENException {
        put(RSTART, rstart);
    }

    public void setRstep(long rstep) throws ENException {
        put(RSTEP, rstep);
    }

    public void setRulestep(long rulestep) throws ENException {
        put(RULESTEP, rulestep);
    }

    public void setSpGrav(Double spGrav) throws ENException {
        put(SPGRAV, spGrav);
    }

    public void setStatflag(StatFlag statflag) throws ENException {
        put(STATFLAG, statflag);
    }

    public void setSummaryflag(boolean summaryflag) throws ENException {
        put(SUMMARYFLAG, summaryflag);
    }

    public void setTankOrder(Double tankOrder) throws ENException {
        put(TANKORDER, tankOrder);
    }

    public void setTraceNode(String traceNode) throws ENException {
        put(TRACE_NODE, traceNode);
    }

    public void setTstart(long tstart) throws ENException {
        put(TSTART, tstart);
    }

    public void setTstatflag(TstatType tstatflag) throws ENException {
        put(TSTATFLAG, tstatflag);
    }

    public void setUnitsflag(UnitsType unitsflag) throws ENException {
        put(UNITSFLAG, unitsflag);
    }

    public void setViscos(Double viscos) throws ENException {
        put(VISCOS, viscos);
    }

    public void setWallOrder(Double wallOrder) throws ENException {
        put(WALLORDER, wallOrder);
    }


} public class Control
{
    /**
     * Control condition type
     */
    static public enum ControlType{
        HILEVEL     (1,Keywords.w_ABOVE),       // act when grade below set level
        LOWLEVEL    (0, Keywords.w_BELOW),       // act when grade above set level
        TIMEOFDAY   (3,Keywords.w_CLOCKTIME),        // act when set time reached
        TIMER       (2,Keywords.w_TIME);   // act when time of day occurs

        public final int    id;
        public final String parseStr;

        private ControlType(int val, String str){id = val;parseStr=str;}
    }

    /**
     * Control grade.
     */
    private double      Grade;
    /**
     * Assigned link reference.
     */
    private Link        Link;
    /**
     * Assigned node reference.
     */
    private Node        Node;
    /**
     * New link setting.
     */
    private double      Setting;
    /**
     * New link status.
     */
    private StatType    Status;
    /**
     * Control time (in seconds).
     */
    private long        Time;
    /**
     * Control type
     */
    private ControlType Type;


    public double getGrade() {
        return Grade;
    }

    public Link getLink() {
        return Link;
    }

    public Node getNode() {
        return Node;
    }

    public double getSetting() {
        return Setting;
    }

    public StatType getStatus() {
        return Status;
    }

    public long getTime() {
        return Time;
    }

    public ControlType getType() {
        return Type;
    }

    public void setGrade(double grade) {
        Grade = grade;
    }

    public void setLink(Link link) {
        Link = link;
    }

    public void setNode(Node node) {
        Node = node;
    }

    public void setSetting(double setting) {
        Setting = setting;
    }

    public void setStatus(StatType status) {
        Status = status;
    }

    public void setTime(long time) {
        Time = time;
    }

    public void setType(ControlType type) {
        Type = type;
    }

} public class Link implements Comparable<Link> {
    /**
     * Init links flow resistance values.
     *
     * @param formflag
     * @param hexp
     * @throws org.addition.epanet.util.ENException
     *
     */
    public void initResistance(PropertiesMap.FormType formflag, Double hexp) {
        double e, d, L;
        this.setFlowResistance(Constants.CSMALL);
        switch (this.getType()) {
            case CV:
            case PIPE:
                e = this.getRoughness();
                d = this.getDiameter();
                L = this.getLenght();
                switch (formflag) {
                    case HW:
                        this.setFlowResistance(4.727 * L / Math.pow(e, hexp) / Math.pow(d, 4.871));
                        break;
                    case DW:
                        this.setFlowResistance(L / 2.0 / 32.2 / d / Math.pow(Constants.PI * Math.pow(d, 2) / 4.0, 2));
                        break;
                    case CM:
                        this.setFlowResistance(Math.pow(4.0 * e / (1.49 * Constants.PI * d * d), 2) *
                                Math.pow((d / 4.0), -1.333) * L);
                }
                break;

            case PUMP:
                this.setFlowResistance(Constants.CBIG);
                break;
        }
    }

    /**
     * Type of link
     */
    static public enum LinkType {
        /**
         * Pipe with check valve.
         */
        CV(0, Keywords.w_CV),
        /**
         * Flow control valve.
         */
        FCV(6, Keywords.w_FCV),
        /**
         * General purpose valve.
         */
        GPV(8, Keywords.w_GPV),
        /**
         * Pressure breaker valve.
         */
        PBV(5, Keywords.w_PBV),
        /**
         * Regular pipe.
         */
        PIPE(1, Keywords.w_PIPE),
        /**
         * Pressure reducing valve.
         */
        PRV(3, Keywords.w_PRV),
        /**
         * Pressure sustaining valve.
         */
        PSV(4, Keywords.w_PSV),
        /**
         * Pump.
         */
        PUMP(2, Keywords.w_PUMP),
        /**
         * Throttle control valve.
         */
        TCV(7, Keywords.w_TCV);

        public static LinkType parse(String text) {
            for (LinkType type : LinkType.values())
                if (Utilities.match(text, type.parseStr)) return type;
            return null;
        }

        /**
         * Sequencial ID.
         */
        public final int id;

        /**
         * Valve type string.
         */
        public final String parseStr;
        ;

        private LinkType(int val, String str) {
            id = val;
            parseStr = str;
        }
    }

    /**
     * Link/Tank/Pump status
     */
    static public enum StatType {
        /**
         * Valve active (partially open).
         */
        ACTIVE(4, Keywords.w_ACTIVE, Keywords.t_ACTIVE),
        /**
         * Closed.
         */
        CLOSED(2, Keywords.w_CLOSED, Keywords.t_CLOSED),
        /**
         * Tank emptying.
         */
        EMPTYING(9, "", Keywords.t_EMPTYING),
        /**
         * Tank filling.
         */
        FILLING(8, "", Keywords.t_FILLING),
        /**
         * Open.
         */
        OPEN(3, Keywords.w_OPEN, Keywords.t_OPEN),
        /**
         * Temporarily closed.
         */
        TEMPCLOSED(1, "", Keywords.t_TEMPCLOSED),
        /**
         * FCV cannot supply flow.
         */
        XFCV(6, "", Keywords.t_XFCV),
        /**
         * Pump exceeds maximum flow.
         */
        XFLOW(5, "", Keywords.t_XFLOW),
        /**
         * Pump cannot deliver head (closed).
         */
        XHEAD(0, "", Keywords.t_XHEAD),
        /**
         * Valve cannot supply pressure.
         */
        XPRESSURE(7, "", Keywords.t_XPRESSURE);

        /**
         * Sequencial id.
         */
        public final int id;

        /**
         * Parse string.
         */
        public final String parseStr;

        /**
         * Report string.
         */
        public final String reportStr;


        private StatType(int val, String str, String rep) {
            id = val;
            parseStr = str;
            reportStr = rep;
        }
    }

    /**
     * Initial species concentrations.
     */
    private double[] c0;
    /**
     * Link comment (parsed from INP or excel file)
     */
    private String comment;
    /**
     * Link diameter (feet).
     */
    private double diameter;
    /**
     * First node.
     */
    private Node first;
    /**
     * Link name.
     */
    private String ID;
    /**
     * Bulk react. coeff.
     */
    private double kb;
    /**
     * Minor loss coeff.
     */
    private double km;
    /**
     * Wall react. coeff.
     */
    private double kw;
    /**
     * Link length (feet).
     */
    private double lenght;
    /**
     * Kinetic parameter values.
     */
    private double[] param;

    /**
     * Flow resistance.
     */
    private double resistance;
    /**
     * Roughness factor.
     */
    private double roughness;
    /**
     * Link report flag.
     */
    private boolean rptFlag;
    /**
     * Second node.
     */
    private Node second;
    /**
     * Link status.
     */
    private StatType status;
    /**
     * Link subtype.
     */
    private LinkType type;
    /**
     * List of points for link path rendering.
     */
    private List<Point> vertices;

    public Link() {
        comment = "";
        vertices = new ArrayList<Point>();
        type = LinkType.CV;
        status = StatType.XHEAD;
    }

    public double[] getC0() {
        return c0;
    }

    public String getComment() {
        return comment;
    }

    public double getDiameter() {
        return diameter;
    }

    public Node getFirst() {
        return first;
    }

    public double getFlowResistance() {
        return resistance;
    }

    public String getId() {
        return ID;
    }

    public double getKb() {
        return kb;
    }

    public double getKm() {
        return km;
    }

    public double getKw() {
        return kw;
    }

    public double getLenght() {
        return lenght;
    }

    public double getNUDiameter(PropertiesMap.UnitsType type) {
        return NUConvert.revertDiameter(type, diameter);
    }

    public double getNULength(PropertiesMap.UnitsType type) {
        return NUConvert.revertDistance(type, lenght);
    }

    public double getNURoughness(PropertiesMap.FlowUnitsType fType, PropertiesMap.PressUnitsType pType, double SpGrav) {
        switch (getType()) {
            case FCV:
                return NUConvert.revertFlow(fType, roughness);
            case PRV:
            case PSV:
            case PBV:
                return NUConvert.revertPressure(pType, SpGrav, roughness);
        }
        return roughness;
    }

    public double[] getParam() {
        return param;
    }

    public double getRoughness() {
        return roughness;
    }

    public Node getSecond() {
        return second;
    }

    public StatType getStat() {
        return status;
    }

    public LinkType getType() {
        return type;
    }

    public List<Point> getVertices() {
        return vertices;
    }

    public boolean isRptFlag() {
        return rptFlag;
    }

    public void setC0(double[] c0) {
        this.c0 = c0;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public void setDiameter(double diameter) {
        this.diameter = diameter;
    }

    public void setDiameterAndUpdate(double diameter, org.addition.epanet.network.Network net) throws ENException {
        double realkm = km * Math.pow(this.diameter, 4.0) / 0.02517;
        this.diameter = diameter;
        km = 0.02517 * realkm / Math.pow(diameter, 4);
        initResistance(net.getPropertiesMap().getFormflag(), net.getPropertiesMap().getHexp());
    }


    public void setFirst(Node n1) {
        first = n1;
    }

    public void setFlowResistance(double r) {
        this.resistance = r;
    }

    public void setId(String id) {
        ID = id;
    }

    public void setKb(double kb) {
        this.kb = kb;
    }

    public void setKm(double km) {
        this.km = km;
    }

    public void setKw(double kw) {
        this.kw = kw;
    }

    public void setLenght(double len) {
        this.lenght = len;
    }

    public void setNUDiameter(PropertiesMap.UnitsType type, double value) {
        diameter = NUConvert.convertDistance(type, value);
    }

    public void setNULenght(PropertiesMap.UnitsType type, double value) {
        lenght = NUConvert.convertDistance(type, value);
    }

    public void setParam(double[] param) {
        this.param = param;
    }

    public void setReportFlag(boolean rptFlag) {
        this.rptFlag = rptFlag;
    }

    public void setRoughness(double kc) {
        this.roughness = kc;
    }

    public void setSecond(Node n2) {
        second = n2;
    }

    public void setStatus(StatType stat) {
        this.status = stat;
    }

    public void setType(LinkType type) {
        this.type = type;
    }

//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//
//        Link link = (Link) o;
//
//        if (ID != null ? !ID.equals(link.ID) : link.ID != null) return false;
//
//        return true;
//    }

    @Override
    public int hashCode() {
        return ID != null ? ID.hashCode() : 0;
    }

    public int compareTo(Link o) {
        return ID.compareTo(o.ID);
    }
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.addition.epanet.Constants;
import org.addition.epanet.network.io.Keywords;
import org.addition.epanet.util.ENException;
import org.addition.epanet.util.Utilities;
import org.addition.epanet.network.FieldsMap;
import org.addition.epanet.network.FieldsMap.*;
import org.addition.epanet.network.Network;
import org.addition.epanet.network.PropertiesMap;
import org.addition.epanet.network.structures.*;
import org.addition.epanet.network.structures.Control.*;
import org.addition.epanet.network.structures.Link.*;
import java.io.*;
    private void composeEmitters(Network net) throws IOException, ENException {
        if(net.getNodes().size()==0)
            return;

        buffer.write(Network.SectType.EMITTERS.parseStr);
        buffer.newLine();
        buffer.write(EMITTERS_SUBTITLE);
        buffer.newLine();

        double uflow = net.getFieldsMap().getUnits(Type.FLOW);
        double upressure = net.getFieldsMap().getUnits(Type.PRESSURE);
        double Qexp = net.getPropertiesMap().getQexp();

        for(Node node : net.getJunctions()){
            if(node.getKe()==0.0) continue;
            double ke = uflow/Math.pow(upressure * node.getKe(), (1.0 / Qexp));
            buffer.write(String.format(" %s\t%s",node.getId(),ke));
            buffer.newLine();
        }

        buffer.newLine();
    }

    private void composeStatus(Network net) throws IOException, ENException {

        if(net.getLinks().size()==0)
            return;

        buffer.write(Network.SectType.STATUS.parseStr);
        buffer.newLine();
        buffer.write(STATUS_SUBTITLE);
        buffer.newLine();

        for(Link link : net.getLinks())
        {
            if (link.getType().id <= LinkType.PUMP.id)
            {
                if (link.getStat() == StatType.CLOSED)
                    buffer.write(String.format(" %s\t%s\n",link.getId(),StatType.CLOSED.parseStr));

                    // Write pump speed here for pumps with old-style pump curve input
                else if (link.getType() == LinkType.PUMP){
                    Pump pump = (Pump)link;
                    if (pump.getHcurve() == null &&
                            pump.getPtype() != Pump.Type.CONST_HP &&
                            pump.getRoughness() != 1.0)
                        buffer.write(String.format(" %s\t%s\n", link.getId(), link.getRoughness()));
                }
            }
            // Write fixed-status PRVs & PSVs (setting = MISSING)
            else if (link.getRoughness() == Constants.MISSING)
            {
                if (link.getStat() == StatType.OPEN)
                    buffer.write(String.format(" %s\t%s\n",link.getId(),StatType.OPEN.parseStr));
                if (link.getStat() == StatType.CLOSED)
                    buffer.write(String.format(" %s\t%s\n", link.getId(), StatType.CLOSED.parseStr));

            }

        }

        buffer.newLine();
    }

    private void composePatterns(Network net) throws IOException, ENException {

        List<Pattern> pats = new ArrayList<Pattern>(net.getPatterns());

        if(pats.size()<=1)
            return;

        buffer.write(Network.SectType.PATTERNS.parseStr);
        buffer.newLine();
        buffer.write(PATTERNS_SUBTITLE);
        buffer.newLine();

        for(int i = 1;i<pats.size();i++)
        {
            Pattern pat = pats.get(i);
            List<Double> F = pat.getFactorsList();
            for (int j=0; j<pats.get(i).getLength(); j++)
            {
                if (j % 6 == 0)
                    buffer.write(String.format(" %s",pat.getId()));
                buffer.write(String.format(" %s",F.get(j)));

                if (j % 6 == 5)
                    buffer.newLine();
            }
            buffer.newLine();
        }

        buffer.newLine();
    }

    private void composeCurves(Network net) throws IOException, ENException {

        List<Curve> curves = new ArrayList<Curve>(net.getCurves());

        if(curves.size()==0)
            return;

        buffer.write(Network.SectType.CURVES.parseStr);
        buffer.newLine();
        buffer.write(CURVE_SUBTITLE);
        buffer.newLine();

        for(Curve c : curves)
        {
            for(int i = 0;i < c.getNpts();i++){
                buffer.write(String.format(" %s\t%s\t%s",
                        c.getId(),c.getX().get(i),c.getY().get(i)));
                buffer.newLine();
            }
        }

        buffer.newLine();
    }

    private void composeControls(Network net) throws IOException, ENException {
        Control [] controls = net.getControls();