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 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);
    }

} 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;}
} public class Curve {

    /**
     * Computed curve coefficients.
     */
    public static class Coeffs{
        public double h0;       // head at zero flow (y-intercept)

        public double r;        // dHead/dFlow (slope)
        public Coeffs(double h0, double r) {
            this.h0 = h0;
            this.r = r;
        }
    }

    // Type of curve
    static public enum CurveType{
        E_CURVE     (2),    // volume curve
        H_CURVE     (3),    // pump curve
        P_CURVE     (1),    // efficiency curve
        V_CURVE     (0);    // head loss curve

        public final int id;

        private CurveType(int val){id = val;}
    }
    /**
     * Curve name.
     */
    private String      id;
    /**
     * Curve type.
     */
    private CurveType   type;
    /**
     * Curve abscissa values.
     */
    private DblList     x;


    /**
     * Curve ordinate values.
     */
    private DblList     y;

    public Curve() {
        this.id = "";
        x = new DblList();
        y = new DblList();
    }

    /**
     * Computes intercept and slope of head v. flow curve at current flow.
     * @param q Flow value.
     * @return
     */
    public Coeffs getCoeff(FieldsMap fMap,double q) throws ENException {
        double h0;
        double r;
        int k1, k2, npts;

        q *= fMap.getUnits(Type.FLOW);

        npts = getNpts();

        k2 = 0;
        while (k2 < npts && x.get(k2) < q) k2++;
        if (k2 == 0) k2++;
        else if (k2 == npts) k2--;
        k1 = k2 - 1;

        r = (y.get(k2) - y.get(k1)) / (x.get(k2) - x.get(k1));
        h0 = y.get(k1) - (r) * x.get(k1);

        h0 = (h0) / fMap.getUnits(Type.HEAD);
        r = (r) * fMap.getUnits(Type.FLOW) / fMap.getUnits(Type.HEAD);

        return new Coeffs(h0, r);
    }

    public String getId() {
        return id;
    }

    /**
     * Get the number of points.
     * @return If the abscissa points count differ from the ordinate it returns -1, otherwise,
     * it returns the abscissa point count.
     */
    public int getNpts() {
        if(x.size()!=y.size()){
            return -1;
        }
        return x.size();
    }

    public CurveType getType() {
        return type;
    }

    public List<Double> getX(){
        return x;
    }

    public List<Double> getY(){
        return y;
    }

    public void setId(String Id) {
        this.id = Id;
    }

    public void setType(CurveType Type) {
        this.type = Type;
    }
} 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;
    }
} public class Node implements Comparable<Node> {
    /**
     * Node id string.
     */
    private String id;
    /**
     * Node elevation(foot).
     */
    private double elevation;

    /**
     * Node demand list.
     */
    private List<Demand> demand;
    /**
     * Water quality source.
     */
    private Source source;
    /**
     * Initial species concentrations.
     */
    private double[] C0;
    /**
     * Emitter coefficient.
     */
    private double Ke;
    /**
     * Node reporting flag.
     */
    private boolean rptFlag;
    /**
     * Node position.
     */
    private Point position;

    /**
     *
     */
    private transient double initDemand;
    /**
     * Node comment.
     */
    private String comment;

    public String getComment() {
        return comment;
    }

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

    public double getInitDemand() {
        return initDemand;
    }

    public void setInitDemand(double initDemand) {
        this.initDemand = initDemand;
    }

    //public NodeType getType() {
    //    return type;
    //}
    //
    //public void setType(NodeType type) {
    //    this.type = type;
    //}

    public Node() {
        C0 = new double[1];
        comment = "";
        initDemand = 0;
        //type = NodeType.JUNC;
        demand = new ArrayList<Demand>();
        position = new Point();
    }

    public Point getPosition() {
        return position;
    }

    public void setPosition(Point position) {
        this.position = position;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public double getElevation() {
        return elevation;
    }

    public void setElevation(double elevation) {
        this.elevation = elevation;
    }

    public List<Demand> getDemand() {
        return demand;
    }

    public Source getSource() {
        return source;
    }

    public void setSource(Source source) {
        this.source = source;
    }

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

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

    public double getKe() {
        return Ke;
    }

    public void setKe(double ke) {
        Ke = ke;
    }

    public boolean isRptFlag() {
        return rptFlag;
    }

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

    public double getNUElevation(PropertiesMap.UnitsType units) {
        return NUConvert.revertDistance(units, elevation);
    }

    public void setNUElevation(PropertiesMap.UnitsType units, double elevation) {
        elevation = NUConvert.convertDistance(units, elevation);
    }

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

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

    public int compareTo(Node o) {
        return id.compareTo(o.id);
    }
} public class Pattern {
    /**
     * Pattern factors list.
     */
    private DblList factors;
    /**
     * Pattern name.
     */
    private String  id;

    public Pattern() {
        this.id = "";
        factors = new DblList();
    }

    public void add(Double factor){
        factors.add(factor);
    }


    public List<Double> getFactorsList(){
        return factors;
    }

    public String getId() {
        return id;
    }

    public int getLength(){
        return factors.size();
    }

    public void setId(String text){
        id = text;
    }
} public class Tank extends Node{

    /**
     * Tank mixing regimes.
      */
    static public enum MixType{
        /**
         * First in, first out model
         */
        FIFO(2,Keywords.w_FIFO),
        /**
         *  Last in, first out model
         */
        LIFO(3,Keywords.w_LIFO),
        /**
         * 1-compartment model
         */
        MIX1(0, Keywords.w_MIXED),
        /**
         * 2-compartment model
         */
        MIX2(1,Keywords.w_2COMP);

        public static MixType parse(String text){
            for (MixType type : MixType.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 MixType(int val, String pStr){id = val;parseStr=pStr;}
    }

    /**
     * Tank area (feet^2).
     */
    private double area;
    /**
     * Tank volume (feet^3).
     */
    //private double v;
    /**
     * Species concentration.
     */
    private double  [] c;
    /**
     * Initial water elev.
     */
    private double h0;
    /**
     * Maximum water elev (feet).
     */
    private double hMax;
    /**
     * Minimum water elev (feet).
     */
    private double hMin;
    /**
     * Reaction coeff. (1/days).
     */
    private double kb;
    /**
     * Type of mixing model
     */
    private MixType mixModel;
    /**
     * Fixed grade time pattern.
     */
    private Pattern pattern;
    /**
     * Initial volume (feet^3).
     */
    private double v0;
    /**
     * Mixing compartment size
     */
    private double  v1max;
    /**
     * Fixed grade time pattern
     */
    private Curve vCurve;
    /**
     * Maximum volume (feet^3).
     */
    private double vMax;
    /**
     * Minimum volume (feet^3).
     */
    private double vMin;


    public Tank() {
        super();
    }

    public double getArea() {
        return area;
    }

    public double [] getConcentration() {
        return c;
    }

    public double getH0() {
        return h0;
    }

    public double getHmax() {
        return hMax;
    }

    public double getHmin() {
        return hMin;
    }

    public double getKb() {
        return kb;
    }

    public MixType getMixModel() {
        return mixModel;
    }

    public double getNUArea(PropertiesMap.UnitsType type){
        return NUConvert.revertArea(type,area);
    }

    public double getNUInitHead(PropertiesMap.UnitsType type){
        return NUConvert.revertDistance(type,h0);
    }

    public double getNUInitVolume(PropertiesMap.UnitsType type){
        return NUConvert.revertVolume(type,v0);
    }

    public double getNUMaximumHead(PropertiesMap.UnitsType type){
        return NUConvert.revertDistance(type,hMax);
    }

    public double getNUMaxVolume(PropertiesMap.UnitsType type){
        return NUConvert.revertVolume(type,vMax);
    }

    public double getNUMinimumHead(PropertiesMap.UnitsType type){
        return NUConvert.revertDistance(type,hMin);
    }

    public double getNUMinVolume(PropertiesMap.UnitsType type){
        return NUConvert.revertVolume(type,vMin);
    }

    public void setNUMinVolume(PropertiesMap.UnitsType type, double value){
        vMin = NUConvert.convertVolume(type,value);
    }


    public double getNUMixCompartimentSize(PropertiesMap.UnitsType type){
        return NUConvert.revertVolume(type,v1max);
    }


    public Pattern getPattern() {
        return pattern;
    }

    public double getV0() {
        return v0;
    }

    public double getV1max() {
        return this.v1max;
    }

    public Curve getVcurve() {
        return vCurve;
    }

    public double getVmax() {
        return vMax;
    }

    public double getVmin() {
        return vMin;
    }

    public void setArea(double a) {
        area = a;
    }

    public void setConcentration(double [] c) {
        this.c = c;
    }

    public void setH0(double h0) {
        this.h0 = h0;
    }

    public void setHmax(double HMax) {
        this.hMax = HMax;
    }

    public void setHmin(double HMin) {
        this.hMin = HMin;
    }

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

    public void setMixModel(MixType mixModel) {
        this.mixModel = mixModel;
    }

    public void setNUArea(PropertiesMap.UnitsType type, double value){
        area = NUConvert.convertArea(type,value);
    }

    public void setNUInitHead(PropertiesMap.UnitsType type, double value){
        h0 = NUConvert.revertDistance(type,value);
    }

    public void setNUInitVolume(PropertiesMap.UnitsType type, double value){
        v0 = NUConvert.convertVolume(type,value);
    }

    //public double getVolume() {
    //    return v;
    //}
    //
    //public void setVolume(double v) {
    //    this.v = v;
    //}

    public void setNUMaximumHead(PropertiesMap.UnitsType type, double value){
        hMax = NUConvert.revertDistance(type,value);
    }

    public void setNUMaxVolume(PropertiesMap.UnitsType type, double value){
        vMax = NUConvert.convertVolume(type,value);
    }

    public void setNUMinimumHead(PropertiesMap.UnitsType type, double value){
        hMin = NUConvert.convertArea(type,value);
    }

    public void setNUMixCompartimentSize(PropertiesMap.UnitsType type, double value){
        v1max = NUConvert.convertVolume(type,value);
    }

    public void setPattern(Pattern pattern) {
        this.pattern = pattern;
    }

    public void setV0(double v0) {
        this.v0 = v0;
    }

    public void setV1max(double vLmax) {
        this.v1max = vLmax;
    }

    public void setVcurve(Curve vcurve) {
        vCurve = vcurve;
    }

    public void setVmax(double vmax) {
        vMax = vmax;
    }

    public void setVmin(double vmin) {
        vMin = vmin;
    }
} 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);
    }


}
import org.addition.epanet.Constants;
import org.addition.epanet.network.FieldsMap;
import org.addition.epanet.network.FieldsMap.Type;
import org.addition.epanet.network.structures.Curve;
import org.addition.epanet.network.structures.Link.StatType;
import org.addition.epanet.network.structures.Node;
import org.addition.epanet.network.structures.Pattern;
import org.addition.epanet.network.structures.Tank;
import org.addition.epanet.util.ENException;
import org.addition.epanet.util.Utilities;
import java.util.List;
/*
 * Copyright (C) 2012  Addition, Lda. (addition at addition dot pt)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 */

package org.addition.epanet.hydraulic.structures;




/**
 *
 */
public class SimulationTank extends SimulationNode {

    private StatType oldStat;

    public SimulationTank(Node ref, int idx) {
        super(ref, idx);
        volume = ((Tank) node).getV0();

        // Init
        head = ((Tank) node).getH0();
        demand = (0.0);
        oldStat = StatType.TEMPCLOSED;
    }


    public Tank getNode() {
        return (Tank) node;
    }


    private double volume;


    public double getArea() {
        return ((Tank) node).getArea();
    }

    public double getHmin() {
        return ((Tank) node).getHmin();
    }

    public double getHmax() {
        return ((Tank) node).getHmax();
    }

    //public double getH0() {
    //    return ((Tank)node).getH0();
    //}

    public double getVmin() {
        return ((Tank) node).getVmin();
    }

    public double getVmax() {
        return ((Tank) node).getVmax();
    }

    public double getV0() {
        return ((Tank) node).getV0();
    }

    //public double getKb() {
    //    return ((Tank)node).getKb();
    //}
    //
    //public double [] getConcentration() {
    //    return ((Tank)node).getConcentration();
    //}

    public Pattern getPattern() {
        return ((Tank) node).getPattern();
    }

    public Curve getVcurve() {
        return ((Tank) node).getVcurve();
    }

    //public MixType getMixModel() {
    //    return ((Tank)node).getMixModel();
    //}
    //
    //public double getV1max() {
    //    return ((Tank)node).getV1max();
    //}


    /// Simulation getters & setters.

    public double getSimVolume() {
        return volume;//((Tank)node).getSimVolume();
    }

    public boolean isReservoir() {
        return ((Tank) node).getArea() == 0;
    }

    public StatType getOldStat() {
        return oldStat;
    }

    public void setOldStat(StatType oldStat) {
        this.oldStat = oldStat;
    }


    /// Simulation methods

    // Finds water volume in tank corresponding to elevation 'h'
    double findVolume(FieldsMap fMap, double h) throws ENException {