package usda.weru.util;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileInputStream;
import de.schlichtherle.truezip.file.TFileOutputStream;
import de.schlichtherle.truezip.file.TFileReader;
import java.awt.*;
import java.io.FileNotFoundException;
import java.util.logging.Level;
import javax.swing.*;

import java.util.*;
import java.text.NumberFormat;
import java.beans.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.List;
import java.util.zip.*;
import java.util.regex.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 
import usda.weru.weps.RegPanel;
import usda.weru.weps.Weps;

/**
 * Class containing utility functions used by Weps.
 * 
 * @author wjr
 * @version 1.0
 */
public class Util {

    private static final Logger LOGGER = LogManager.getLogger(Util.class);
    static boolean Util_weps_db_type_msg_print = true; /* added to stop printing weps db type messasge multiple times - LEW */

    /**
     *
     */
    public static final int MAX_WINDOWS_FILEPATH_LENGTH = 255;

    /**
     * Used to set the focus on a particular field.
     * Must be used in error checking code since the 
     * main dispatching thread changes focus after 
     * any user specified code is executed.
     * 
     * @param c A swing component (JTextField, etc.) that can accept 
     * the focus.
     */
    public static void setFocus(JComponent c) {
        SwingUtilities.invokeLater(new FocusGrabber(c));
    }

    /**
     * Runnable class used by Util.setFocus().
     * 
     * @author wjr
     * @version 1.0
     */
    private static class FocusGrabber implements Runnable {

        JComponent component;

        public FocusGrabber(JComponent component) {
            this.component = component;
        }

        @Override
        public void run() {
            component.grabFocus();
        }
    }

    /**
     *
     * @param parent
     * @param file
     * @return
     */
    public static TFile resolveFileAsRelativeChildOrAbsolute(TFile parent, TFile file) {
        String path = file.getAbsolutePath();
        String topPath = parent.getAbsolutePath();
        if (path.startsWith(topPath) && path.length() > topPath.length()) {
            path = path.substring(topPath.length() + 1);
            return new TFile(path);
        } else {
            return new TFile(file.getAbsoluteFile());
        }
    }

    /**
     *
     * @param file
     * @return
     */
    public static boolean deleteAll(TFile file) {
        return deleteAll(file, null);
    }

    /**
     *
     * @param file
     * @param lockedFiles
     * @return
     */
    public static boolean deleteAll(TFile file, List<TFile> lockedFiles) {
        List<TFile> failedToDelete = new LinkedList<TFile>();
        if (file.isDirectory()) {
            for (TFile child : file.listFiles(file.getArchiveDetector())) {
                boolean success = deleteAll(child, lockedFiles);
                if (!success) {
                    //Didn't delete on the first pass, try again.
                    failedToDelete.add(child);
                }
            }

            //now loop over for a second pass
            Iterator<TFile> i = failedToDelete.iterator();
            while (i.hasNext()) {
                TFile child = i.next();
                boolean deleted = deleteAll(child, lockedFiles);
                if (deleted) {
                    i.remove();
                } else {
                    LOGGER.error("Unable to delete: " + child.getAbsolutePath());

                    //last chance, try and delete on exit.
                    child.deleteOnExit();
                }
            }

            //if we're tracking the locked files include them.
            if (lockedFiles != null) {
                lockedFiles.addAll(failedToDelete);
            }

        }
        // The file is now empty so delete it   
        try {
            file.rm();
            return true;
        } catch (IOException e) {
            LOGGER.warn("Util.deleteAll failed. File:"+file.getAbsolutePath());
            LOGGER.warn(e);
            file.deleteOnExit();
            return false;
        }
    }

    /**
     *
     * @param from
     * @param to
     * @param purge
     * @return
     */
    public static boolean copyDirectory(TFile from, TFile to, boolean purge) {

        //Remove the file if it exists and purging is true.
        if (to.exists() && purge) {
            boolean removed = deleteAll(to);
            if (!removed) {
                LOGGER.error("unable to purge directory: \"" + to.getAbsolutePath() + "\"");
                return false;
            }
        }

        if (from.isDirectory()) {
            if (!to.exists()) {
                boolean created = to.mkdirs();
                if (!created) {
                    LOGGER.error("unable to create directory: \"" + to.getAbsolutePath() + "\"");
                    return false;
                }
            }
            java.io.File[] children = from.listFiles();
            for (java.io.File child : children) {
                TFile childFrom = new TFile(child);
                TFile childTo = new TFile(to, child.getName());
                boolean copied = copyDirectory(childFrom, childTo, purge);
                if (!copied) {
                    LOGGER.error("unable to copy file: \"" + childTo.getAbsolutePath() + "\"");
                    return false;
                }
            }
        } else {
            boolean copied = copyFile(from, to);
            if (!copied) {
                LOGGER.error("unable to copy file: \"" + to.getAbsolutePath() + "\"");
                return false;
            }
        }

        return true;
    }

    /**
     *
     * @param fromFile
     * @param toFile
     * @return
     */
    public static boolean copyFile(String fromFile, String toFile) {
        return copyFile(new TFile(fromFile), new TFile(toFile));
    }

    /**
     *
     * @param from
     * @param to
     * @return
     */
    public static boolean copyFile(TFile from, TFile to) {
        if (from.exists() && from.isFile()) {
            if (from.getAbsoluteFile().equals(to.getAbsoluteFile())) {
                //System.err.println("ERROR copyFile: can't copy file from its own!");
                return false;
            }
            TFile dir = to.getParentFile();
            if (!dir.isDirectory()) {
                boolean created = dir.mkdirs();
                if (!created) {
                    LOGGER.error("Unable to created required directory structure: " + to.getParentFile().getAbsolutePath());
                    return false;
                }
            }
            byte buffer[] = new byte[100000];
            TFileInputStream fromReader = null;
            TFileOutputStream toWriter = null;
            try {
                fromReader = new TFileInputStream(from);
                try {
                    toWriter = new TFileOutputStream(to);
                } catch(FileNotFoundException fnfe) {
                    JOptionPane.showMessageDialog(null,
                        "ERROR: Duplicate directory name in Project directory.\nPlease move or rename directory so naming conflict does not occur to continue loading.",
                        "Duplicate Name Error",
                        JOptionPane.ERROR_MESSAGE);
                    return false;
                }
                int byteRead = 0;
                while ((byteRead = fromReader.read(buffer)) != -1) {
                    toWriter.write(buffer, 0, byteRead);
                    toWriter.flush();
                }
            } catch (IOException e) {
                //System.err.println("error: copy file");
                e.printStackTrace();
                return false;
            } finally {
                try {
                    fromReader.close();
                } catch (IOException e) {
                    LOGGER.error("Error closing file stream", e);
                }

                try {
                    toWriter.close();
                } catch (NullPointerException | IOException e) {
                    if(e instanceof NullPointerException) {
                        System.err.println("Couldn't copy file.");
                        return false;
                    } else {
                        LOGGER.error("Error closing file stream", e);
                    }
                }

            }
            return true;
        } else {
            return false;
        }
    }

    /**
     *
     */
    public static final double OneFootInMeters = 0.3048;

    /**
     *
     */
    public static final double OneMileInKM = 5280 * OneFootInMeters / 1000;

    /**
     *
     */
    public static final double SquareFeetInAcre = 43560.;

    /**
     *
     * @param jtf1
     * @param jtf2
     * @param units
     * @return
     */
    public static String calculateArea(JTextField jtf1, JTextField jtf2, String units) {

        String area = "";
        String s1 = jtf1.getText().trim();
        String s2 = jtf2.getText().trim();

        double val1 = Double.parseDouble(s1);
        double val2 = Double.parseDouble(s2);

        try {
            if (s1.equals("") || s2.equals("")) {
                //System.err.println("U_cA: The String was Empty in Calc AREA ");
            } else if (!(val1 > 0 || val2 > 0)) {
                //System.err.println("U_cA: Cannot calculate the AREA. The values are less than Zero");
            } else {
                //Changed the fraction digits from 0 to 2
                area = calculateArea(s1, s2, units, 2);
            }
        } catch (NumberFormatException e) {
            //System.err.println("In calculate AREA Exception occured " + e);
        }
        return area;
    }

    /**
     *
     * @param txt1
     * @param txt2
     * @param units
     * @param numDigits
     * @return
     */
    public static String calculateArea(String txt1,
            String txt2, String units, int numDigits) {

        double a1 = 0;
        double a2 = 0;
        String myArea = "";

        if (!(txt1.equals("") || txt2.equals(""))) {
            try {
                a1 = Double.parseDouble(txt1);
                a2 = Double.parseDouble(txt2);
            } catch (NumberFormatException e) {
                //System.err.println("U_cA: The String was Empty " + e);
                // not an error; blank lines are OK
            }

            if (!(a1 > 0 || a2 > 0)) {
                return myArea;
            } else {
                double area = a1 * a2;

                if (units.equals(Util.USUnits)) {
                    area /= SquareFeetInAcre;
                } else {
                    area /= 10000.;
                }
                NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
                nf.setMaximumFractionDigits(numDigits);
                nf.setGroupingUsed(false);
                myArea = nf.format(area);
            }
        }
//			//System.out.println("Value of the area 4 ARGUMENTS is " + myArea);
        return myArea;
    }

    /**
     *
     * @param txt1
     * @param txt2
     * @param units
     * @return
     */
    public static final String calculateArea(String txt1,
            String txt2, String units) {

        double a1 = 0;
        double a2 = 0;

        try {
            a1 = Double.parseDouble(txt1);
            a2 = Double.parseDouble(txt2);
        } catch (NumberFormatException e) {
//			//System.err.println("cA: " + e);
            // not an error; blank lines are OK
        }
        double area = a1 * a2;

        if (units.equals(Util.USUnits)) {
            area /= SquareFeetInAcre;
        } else {
            area /= 10000.;
        }
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        nf.setMaximumFractionDigits(2);
        nf.setGroupingUsed(false);
//		//System.out.println("Value of the area STRING 3 ARGUMENTS is " + nf.format(area));
        return nf.format(area);

    }

    /**
     *
     * @param a1
     * @param a2
     * @param units
     * @return
     */
    public static final String calculateArea(double a1, double a2, String units) {
        //Changed the fraction digits from 0 to 2
        return Util.calculateArea(a1, a2, units, 2, RegPanel.SHAPE_RECTANGLE);
    }

    /**
     *
     * @param a1
     * @param a2
     * @param units
     * @param numDigits
     * @return
     */
    public static final String calculateArea(double a1,
            double a2, String units, int numDigits) {
        return calculateArea(a1, a2, units, numDigits, RegPanel.SHAPE_RECTANGLE);
    }

    /**
     *
     * @param a1
     * @param a2
     * @param units
     * @param numDigits
     * @param shape
     * @return
     */
    public static final String calculateArea(double a1,
            double a2, String units, int numDigits, String shape) {

        double area = a1 * a2;

        if (shape.equals(RegPanel.SHAPE_RECTANGLE)) {
        } else if (shape.equals(RegPanel.SHAPE_SQUARE)) {
        } else if (shape.equals(RegPanel.SHAPE_CIRCLE)) {
        } else if (shape.startsWith("half_circle")) {
            area = area / 2;
        } else if (shape.startsWith("quarter_circle")) {
            area = area / 4;
        }

        if (units.equals(Util.USUnits)) {
            area /= SquareFeetInAcre;
            area /= OneFootInMeters * OneFootInMeters;
        } else {
            area /= 10000.;
        }
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        nf.setMaximumFractionDigits(numDigits);
        nf.setGroupingUsed(false);
//		//System.out.println("Value of the area DOUBLE 3 ARGUMENTS is " + nf.format(area));
        return nf.format(area);

    }

    /**
     *
     */
    public static final String USUnits = "US";

    /**
     *
     */
    public static final String SIUnits = "SI";

    /**
     *
     */
    public static final String NoUnits = "No";

    /**
     *
     * @param arg
     * @param units
     * @return
     */
    public static final String convertFeetToMeters(String arg,
            String units) {
        //Changed the fraction digits from 0 to 2
        return convertFeetToMeters(arg, units, 2);
    }

    /**
     *
     * @param arg
     * @param units
     * @param sigDigits
     * @return
     */
    public static final String convertFeetToMeters(String arg,
            String units, int sigDigits) {
        if (units.equals(USUnits) || units.equals(NoUnits)) {
            return arg;
        }
        double val = 0.0;
        try {
            val = Double.parseDouble(arg);
        } catch (NumberFormatException e) {
            //System.err.println("cFTM: error converting feet to meters" + e);
        }
        val *= OneFootInMeters;
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        nf.setMaximumFractionDigits(sigDigits);
        nf.setMinimumFractionDigits(sigDigits);
        nf.setGroupingUsed(false);
        return nf.format(val);
    }

    /**
     *
     * @param arg
     * @param units
     * @return
     */
    public static final String convertMilesToKM(String arg,
            String units) {
        if (units.equals(USUnits) || units.equals(NoUnits)) {
            return arg;
        }
        double val = 0.0;
        try {
            val = Double.parseDouble(arg);
        } catch (NumberFormatException e) {
            //System.err.println("cFTM: error converting feet to meters" + e);
        }
        val *= OneMileInKM;
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        //Changed the fraction digits from 1 to 2
        nf.setMaximumFractionDigits(2);
        nf.setGroupingUsed(false);
        return nf.format(val);
    }

    /**
     *
     * @param arg
     * @param units
     * @return
     */
    public static final String convertMetersToFeet(String arg,
            String units) {
        //Changed the fraction digits from 0 to 2
        return convertMetersToFeet(arg, units, 2);
    }

    /**
     *
     * @param arg
     * @param units
     * @return
     */
    public static final String convertKMToMiles(String arg,
            String units) {
        //Changed the fraction digits from 0 to 2
        return convertKMToMiles(arg, units, 2);
    }

    /**
     *
     * @param arg
     * @param units
     * @param maxDigits
     * @return
     */
    public static final String convertMetersToFeet(String arg,
            String units, int maxDigits) {
        if (units.equals(SIUnits) || units.equals(NoUnits)) {
            return arg;
        }
        double val = 0.0;
        try {
            val = Double.parseDouble(arg);
        } catch (NumberFormatException e) {
            //System.err.println("U_cMTF: error converting meters to feet" + e);
        }
        val /= OneFootInMeters;
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        nf.setMaximumFractionDigits(maxDigits);
        nf.setMinimumFractionDigits(maxDigits);
        nf.setGroupingUsed(false);
        return nf.format(val);
    }

    public static final double convertMetersToFeet(double meter) {
        return meter * 3.28084;
    }
    
    public static final double convertFeetToMeters(double feet) {
        return feet / 3.28084;
    }
    
    /**
     *
     * @param arg
     * @param units
     * @param maxDigits
     * @return
     */
    public static final String convertMetersToFeetAndFormat(String arg,
            String units, int maxDigits) {
        double val = 0.0;
        try {
            val = Double.parseDouble(arg);
        } catch (NumberFormatException e) {
            //System.err.println("cFTM: error converting meters to feet" + e);
        }
        if (units.equals(USUnits)) {
            val /= OneFootInMeters;
        }
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        nf.setMaximumFractionDigits(maxDigits);
        nf.setMinimumFractionDigits(maxDigits);
        nf.setGroupingUsed(false);
        return nf.format(val);
    }

    /**
     *
     * @param arg
     * @param units
     * @param maxDigits
     * @return
     */
    public static final String convertKMToMiles(String arg,
            String units, int maxDigits) {
        if (units.equals(SIUnits) || units.equals(NoUnits)) {
            return arg;
        }
        double val = 0.0;
        try {
            val = Double.parseDouble(arg);
        } catch (NumberFormatException e) {
            //System.err.println("cFTM: error converting meters to feet" + e);
        }
        val /= OneMileInKM;
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        nf.setMaximumFractionDigits(maxDigits);
        nf.setGroupingUsed(false);
        return nf.format(val);
    }

    /**
     *
     * @param arg
     * @param units
     * @return
     */
    public static final String convertMetersToFeet(double arg,
            String units) {
        //Changed the fraction digits from 0 to 2
        return convertMetersToFeet(arg, units, 2);
    }

    /**
     *
     * @param arg
     * @param units
     * @return
     */
    public static final String convertKMToMiles(double arg,
            String units) {
        //Changed the fraction digits from 0 to 2
        return convertKMToMiles(arg, units, 2);
    }

    /**
     *
     * @param arg
     * @param units
     * @param maxDigits
     * @return
     */
    public static final String convertMetersToFeet(double arg,
            String units, int maxDigits) {
        if (units.equals(Util.USUnits)) {
            arg /= OneFootInMeters;
        }

        try {
            NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
            //Commenting out because the maximum fraction digits is set to 2 irrespective of US units or SI units.
            //nf.setMaximumFractionDigits(maxDigits + (units.equals(Util.USUnits) ? 0 : 1));
            nf.setMaximumFractionDigits(maxDigits);
            nf.setGroupingUsed(false);
            return nf.format(arg);
        } catch (NumberFormatException e) {
            //System.out.println("Util_convertMetersToFeet Ln:357 Error :" + e );
            return "";
        }

    }

    /**
     *
     * @param value
     * @param fromUnits
     * @return
     */
    public static double convertBetweenFeetAndMeters(Double value, String fromUnits) {
        if (fromUnits.equals(Util.USUnits)) {
            //In US Units, convert to meters
            return value * OneFootInMeters;
        } else {
            //In SI Units, convert to feet
            return value / OneFootInMeters;
        }
    }

    /**
     *
     * @param arg
     * @param units
     * @param maxDigits
     * @return
     */
    public static final String convertKMToMiles(double arg,
            String units, int maxDigits) {
        if (units.equals(Util.USUnits)) {
            arg /= OneMileInKM;
        }
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        nf.setMaximumFractionDigits(maxDigits
                + (units.equals(Util.USUnits) ? 0 : 1));
        nf.setGroupingUsed(false);
        return nf.format(arg);
    }

    /**
     *
     * @param parent
     * @param jtf
     * @param val
     * @param prop
     * @param changes
     * @param measurementUnits
     */
    public static void checkNumericJTF(Component parent,
            JTextField jtf,
            double val,
            String prop,
            PropertyChangeSupport changes,
            String measurementUnits) {

        //Changed the fraction digits from 0 to 2
        checkNumericJTF(parent, jtf, val, prop, changes, measurementUnits, 2);
    }

    /**
     *
     * @param parent
     * @param jtf
     * @param val
     * @param prop
     * @param changes
     * @param measurementUnits
     */
    public static void checkBigNumericJTF(Component parent,
            JTextField jtf,
            double val,
            String prop,
            PropertyChangeSupport changes,
            String measurementUnits) {

        //Changed the fraction digits from 0 to 2
        checkBigNumericJTF(parent, jtf, val, prop, changes, measurementUnits, 2);
    }

    /**
     *
     * @param parent
     * @param jtf
     * @param val
     * @param prop
     * @param changes
     * @param measurementUnits
     * @param loBound
     * @param hiBound
     */
    public static void checkNumericJTF(Component parent,
            JTextField jtf,
            double val,
            String prop,
            PropertyChangeSupport changes,
            String measurementUnits,
            double loBound,
            double hiBound) {

        //Changed the fraction digits from 0 to 2 - "numDigits" value
        checkNumericJTF(parent, jtf, val, prop, changes, measurementUnits, 2,
                loBound, hiBound);
    }

    /**
     *
     * @param parent
     * @param jtf
     * @param val
     * @param prop
     * @param changes
     * @param measurementUnits
     * @param numDigits
     */
    public static void checkNumericJTF(Component parent,
            JTextField jtf,
            double val,
            String prop,
            PropertyChangeSupport changes,
            String measurementUnits,
            int numDigits) {

        checkNumericJTF(parent, jtf, val, prop, changes, measurementUnits, numDigits,
                -(Double.MAX_VALUE - 1), Double.MAX_VALUE);
//						Double.MIN_VALUE, Double.MAX_VALUE);
    }

    /**
     *
     * @param parent
     * @param jtf
     * @param val
     * @param prop
     * @param changes
     * @param measurementUnits
     * @param numDigits
     */
    public static void checkBigNumericJTF(Component parent,
            JTextField jtf,
            double val,
            String prop,
            PropertyChangeSupport changes,
            String measurementUnits,
            int numDigits) {

        checkBigNumericJTF(parent, jtf, val, prop, changes, measurementUnits, numDigits,
                Double.MIN_VALUE, Double.MAX_VALUE);
    }

    /**
     *
     * @param parent
     * @param jtf
     * @param val
     * @param prop
     * @param changes
     * @param measurementUnits
     * @param numDigits
     * @param loBound
     * @param hiBound
     */
    public static void checkNumericJTF(Component parent,
            JTextField jtf,
            double val,
            String prop,
            PropertyChangeSupport changes,
            String measurementUnits,
            int numDigits,
            double loBound,
            double hiBound) {

////System.out.println("U_cN: " + jtf.getText());		
        try {
            double xtemp = 0.0;
////System.out.println("U_cN: fire 3");

            if (jtf.getText().trim().length() != 0) {
                xtemp = Double.parseDouble(jtf.getText());
            }
////System.out.println("U_cN: fire 4 " + xtemp + " " + loBound + " " + hiBound);
            if (xtemp < loBound && loBound != hiBound) {
//				JOptionPane.showMessageDialog(parent,
//											  "Number specified is too small\n" +
//											  "must be greater than " + loBound,
//											  "Number out of range",
//											  JOptionPane.ERROR_MESSAGE);
//				jtf.setText(Util.convertMetersToFeet(val, measurementUnits));
                jtf.setText(Util.convertMetersToFeet(loBound, measurementUnits));
//				Util.setFocus(jtf);
                return;
            }
////System.out.println("U_cN: fire 2");
            if (xtemp > hiBound && loBound != hiBound) {
//				JOptionPane.showMessageDialog(parent,
//											  "Number specified is too large\n" +
//											  "must be less than " + hiBound,
//											  "Number out of range",
//											  JOptionPane.ERROR_MESSAGE);
//				jtf.setText(Util.convertMetersToFeet(val, measurementUnits));
                jtf.setText(Util.convertMetersToFeet(hiBound, measurementUnits));
//				Util.setFocus(jtf);
                return;
            }
////System.out.println("U_cN: fire 1");
            if (measurementUnits.equals(Util.USUnits)) {
                xtemp *= OneFootInMeters;
            }
            if (xtemp != 0 && Math.abs((xtemp - val) / xtemp) < 0.001) {
                return;
            }
// Use SIUnits since we are in meters no matter what is being displayed
            String temp = Util.convertMetersToFeet(xtemp, Util.SIUnits, numDigits);
////System.out.println("U_cN: fire");
            if (changes != null) {
                changes.firePropertyChange(prop, null, temp);
            }
        } catch (NumberFormatException k) {
//			JOptionPane.showMessageDialog(parent,
//										  "Invalid number format",
//										  "Invalid number",
//										  JOptionPane.ERROR_MESSAGE);
            jtf.setText(Util.convertMetersToFeet(val, measurementUnits));
//			Util.setFocus(jtf);
        }
        //       //System.out.println("Util:checkNumericJTF:value changed to"+jtf.getText());
    }

    /**
     *
     * @param parent
     * @param jtf
     * @param val
     * @param prop
     * @param changes
     * @param measurementUnits
     * @param numDigits
     * @param loBound
     * @param hiBound
     */
    public static void checkBigNumericJTF(Component parent,
            JTextField jtf,
            double val,
            String prop,
            PropertyChangeSupport changes,
            String measurementUnits,
            int numDigits,
            double loBound,
            double hiBound) {

////System.out.println("U_cBNJTF: " + jtf.getText());		
        try {
            double xtemp = 0.0;
            if (jtf.getText().trim().length() != 0) {
                xtemp = Double.parseDouble(jtf.getText());
            }
            if (measurementUnits.equals(Util.USUnits)) {
                xtemp *= OneMileInKM;
            }
////System.out.println("U_cBNJTF: xtemp " + xtemp);
            if (xtemp != 0 && Math.abs((xtemp - val) / xtemp) < 0.001) {
                return;
            }
// Use SIUnits since we are in meters no matter what is being displayed
            String temp = Util.convertKMToMiles(xtemp, Util.SIUnits, numDigits);
////System.out.println("U_cBNJFT: temp " + temp);			
            if (changes != null) {
                changes.firePropertyChange(prop, null, temp);
            }
        } catch (NumberFormatException k) {
//			JOptionPane.showMessageDialog(parent,
//										  "Invalid number format",
//										  "Invalid number",
//										  JOptionPane.ERROR_MESSAGE);
            jtf.setText(Util.convertKMToMiles(val, measurementUnits));
//			Util.setFocus(jtf);
        }
    }

    static String getRelativePath(String filPath, String relPath) {
        try {
            filPath = new TFile(filPath).getCanonicalPath();
            relPath = new TFile(relPath).getCanonicalPath();
        } catch (IOException e) {
            //System.err.println("U_gRP: " + e);
            return filPath;
        }
        if (!filPath.startsWith(relPath)) {
            return filPath;
        } else {
            return filPath.substring(relPath.length() + 1);
        }
    }

    /**
     *
     * @param file
     * @return
     */
    public static Hashtable<String, String> loadToolTipsTable(TFile file) {
        java.util.Hashtable<String, String> ht = new java.util.Hashtable<>();

        try (BufferedReader in = new BufferedReader(new TFileReader(file))) {
            String temp;
            while ((temp = in.readLine()) != null) {
                StringTokenizer st = new StringTokenizer(temp, "=");
                if (st.countTokens() < 2) {
                    continue;
                }
                String id = st.nextToken().trim();
                try {
                    StringBuilder sb = new StringBuilder(st.nextToken());
                    while (st.hasMoreTokens()) {
                        sb.append("=" + st.nextToken());
                    }
                    ht.put(id, sb.toString().trim());
                } catch (java.util.NoSuchElementException e) {
                    // skip invalid lines - comments or nothing after =
                }
            }
        } catch (IOException e) {
            //System.err.println("Unable to load tooltips file");
        }

        return ht;
    }

    /**
     *
     * @param topLevel
     * @param inputFile
     */
    public static void loadToolTips(Container topLevel, TFile inputFile) {
        java.util.Hashtable<String, String> ht = loadToolTipsTable(inputFile);
        Component[] components;

        if (topLevel instanceof JDialog) {
            if (((JDialog) topLevel).getJMenuBar() != null) {
                loadToolTips(((JDialog) topLevel).getJMenuBar(), ht);
            }
            components = ((JDialog) topLevel).getContentPane().getComponents();
        } else if (topLevel instanceof JFrame) {
            if (((JFrame) topLevel).getJMenuBar() != null) {
                loadToolTips(((JFrame) topLevel).getJMenuBar(), ht);
            }
            components = ((JFrame) topLevel).getContentPane().getComponents();

        } else {
            //if the container is not a frame or a dialog or a filechooser, then return
            //System.err.println("Util-loadToolTips:The parent container is neither a JFrame nor a JDialog");
            //System.err.println("Util-loadToolTips:Tool tips are not activated");
            return;
        }
        for (Component component : components) {
            if (component instanceof Container) {
                loadToolTips((Container) component, ht);
            } else {
//				//System.out.println(" is not Container");
            }
        }
    }

    static String className(JComponent jcomp) {
        String name = jcomp.getClass().toString();
        return name.substring(name.lastIndexOf(".") + 1);
    }

    static String cvtKeyStroke(javax.swing.KeyStroke inpk) {
        if (inpk == null) {
            return null;
        }
        String inp = inpk.toString();
        if (inp == null) {
            return inp;
        }
        inp = inp.substring(8);
        inp = inp.substring(0, inp.length() - 2);
        return inp;
    }

    /**
     * Finds the containing frame to anchor JOptionPanes
     * @param j
     * @return 
     */
    public static JFrame getParentJFrame(Component j) {
        for (; j.getParent() != null; j = j.getParent()) {

        }
        return (JFrame) j;
    }

    static void printComponent(JComponent jcomp, java.util.Hashtable<String, String> ht) {
        String tooltip = jcomp.getToolTipText();
//		//System.out.println("lC2: " + tooltip + "=");
        boolean printFlg = false;  // comment out the print statements
        if (printFlg) {
            if (jcomp instanceof JPanel) {
                //System.err.println("lC: |"  + className(jcomp) + " | " +
                //							   tooltip);
            } else if (jcomp instanceof JMenuItem) {
//				//System.err.println("lC: |" + className(jcomp) + " | " +
//								   tooltip + " | " +
//								   ((JMenuItem) jcomp).getText() + " | " +
//								   (char) ((JMenuItem) jcomp).getMnemonic() + " | " +
//								   cvtKeyStroke(((JMenuItem) jcomp).getAccelerator()));
            } else if (jcomp instanceof AbstractButton) {
//				//System.err.println("lC: |" + className(jcomp) + " | " +
//								   tooltip + " | " +
//								   ((AbstractButton) jcomp).getText() + " | " +
//								   (char) ((AbstractButton) jcomp).getMnemonic());
            } else if (jcomp instanceof JLabel) {
                //System.err.println("lC: |" + className(jcomp) + " | " +
//								   tooltip + " | " +
//								   ((JLabel) jcomp).getText());
            } else if (jcomp instanceof JTextField) {
//				//System.err.println("lC: |" + className(jcomp) + " | " +
//								   tooltip  + " | " +
//								   ((JTextField) jcomp).getText());
            } else {
//				//System.err.println("lC: |" + className(jcomp) + " | "
//								   + tooltip);
            }
        }

        if (tooltip != null) {
            if (ht.containsKey(tooltip)) {
                String newText = "<html>" + ht.get(tooltip) + "</html>";
                jcomp.setToolTipText(newText);
            } else {
//				jcomp.setToolTipText(null);
            }
        }
    }

    public static void loadToolTips(/*JPanel*/Container topLevel, java.util.Hashtable<String, String> ht) {

        if (topLevel instanceof JToolBar) {
            Util.debugPrint(true, "loading toolbar");
        }
//else Util.debugPrint(true, "not loading toolbar");		
        if (topLevel instanceof JComponent) {
            printComponent((JComponent) topLevel, ht);
        }
        Component[] components = topLevel.getComponents();
        if (topLevel instanceof JPopupMenu) {
            MenuElement[] me = ((JPopupMenu) topLevel).getSubElements();
            components = new Component[me.length];
            for (int idx = 0; idx < me.length; idx++) {
                components[idx] = me[idx].getComponent();
            }
        }

        if (topLevel instanceof JMenuItem) {
            MenuElement[] me = ((JMenuItem) topLevel).getSubElements();
            components = new Component[me.length];
            for (int idx = 0; idx < me.length; idx++) {
                components[idx] = me[idx].getComponent();
            }
        }
//		//System.out.println("lC: start " + topLevel.getClass());

        if (topLevel instanceof JTabbedPane) {
            JTabbedPane jtp = (JTabbedPane) topLevel;
            int selIdx = jtp.getSelectedIndex();
            components = new Component[jtp.getTabCount()];
            for (int idx = 0; idx < components.length; idx++) {
                jtp.setSelectedIndex(idx);
                components[idx] = jtp.getSelectedComponent();
            }
            jtp.setSelectedIndex(selIdx);
//			components = ((JMenuItem) topLevel).getSubElements();
        }
        for (Component component : components) {
            if (component instanceof JPanel) {
                loadToolTips((JPanel) component, ht);
            } else if (component instanceof JMenuBar) {
                loadToolTips((JMenuBar) component, ht);
            } else if (component instanceof JMenuItem) {
                loadToolTips((JMenuItem) component, ht);
            } else if (component instanceof JPopupMenu) {
                loadToolTips((JPopupMenu) component, ht);
            } else if (component instanceof JToolBar) {
                loadToolTips((JToolBar) component, ht);
            } else if (component instanceof JTabbedPane) {
                loadToolTips((JTabbedPane) component, ht);
            } else if (component instanceof JScrollPane) {
                loadToolTips((JScrollPane) component, ht);
            } else if (component instanceof JViewport) {
                loadToolTips((JViewport) component, ht);
            } else if (component instanceof JComponent) {
                printComponent((JComponent) component, ht);
            }
        }
    }
    private static final double DegreeInRadians = Math.PI / 180;
    private static final double RadiusOfEarth = 6369.3;

    /*
     * Calculates distance between pairs of coordinations expressed in
     * degrees.  May have problems if both points are not in the same
     * hemisphere.
     */
    /**
     *
     * @param lat1
     * @param lon1
     * @param lat2
     * @param lon2
     * @return
     */
    public static double polarDistance(double lat1, double lon1, double lat2, double lon2) {
        double sinOriginLat = Math.sin(lat1 * DegreeInRadians);
        double cosOriginLat = Math.cos(lat1 * DegreeInRadians);
        double originLongitude = lon1 * DegreeInRadians;
        double lonRadians = lon2 * DegreeInRadians;
        double latRadians = lat2 * DegreeInRadians;
        double cosD = sinOriginLat * Math.sin(latRadians) + cosOriginLat * Math.cos(latRadians)
                * Math.cos(originLongitude - lonRadians);
        cosD = Math.acos(cosD);
        double kms = cosD * RadiusOfEarth;
        return kms;
    }

    /**
     *
     * @param inpStr
     * @return
     */
    public static String initialToUpperCase(String inpStr) {
        StringBuilder sb = new StringBuilder(inpStr.toLowerCase().trim());
        sb.replace(0, 1, sb.substring(0, 1).toUpperCase());
        for (int idx = 1; idx < sb.length() - 2; idx++) {
            if (sb.charAt(idx) == ' ') {
                sb.setCharAt(idx + 1, Character.toUpperCase(sb.charAt(idx + 1)));
            }
        }
        return sb.toString();
    }

    /**
     *
     * @param jcomp
     */
    public static void adjustLabelWidth(JComponent jcomp) {

        if (jcomp instanceof JLabel) {
            JLabel jl = (JLabel) jcomp;

        } else if (jcomp instanceof AbstractButton) {
            AbstractButton ab = (AbstractButton) jcomp;
            String text = ab.getText();
            FontMetrics fm = ab.getFontMetrics(ab.getFont());
            int width;
            if(fm != null && text != null) {
                width = fm.stringWidth(text);
            } else {
                //custom title, warning icon
//                JOptionPane.showMessageDialog(null,
//                    "Your Weps.ini file is empty or corrupt.",
//                    "Invalid weps.ini",
//                    JOptionPane.WARNING_MESSAGE);
                System.out.println("Missing width. Setting to 0.");
                width = 0;
            }
            Dimension dim = ab.getSize();
//			//System.out.println("lC: |" + className(jcomp) + " | " +
//								   ab.getText() + " " + width + " " + dim.width +
//							  " " + ab.getInsets().right + " " + ab.getInsets().left);
            if ((width + 25) > dim.width) {
                dim.width = width + 25;
                ab.setSize(dim);
            }
        } else {
//			//System.out.println("U_aLW: other " + jcomp.getClass());
        }
    }

    /**
     *
     * @param topLevel
     */
    public static void adjustLabelWidths(Object topLevel) {
////System.out.println("U_aLW:");
        if (topLevel instanceof JFrame) {
            topLevel = ((JFrame) topLevel).getContentPane();
        }
        Component[] components = ((Container) topLevel).getComponents();

        if (topLevel instanceof JPopupMenu) {
            MenuElement[] me = ((JPopupMenu) topLevel).getSubElements();
            components = new Component[me.length];
            for (int idx = 0; idx < me.length; idx++) {
                components[idx] = me[idx].getComponent();
            }
        }

        if (topLevel instanceof JMenuItem) {
            MenuElement[] me = ((JMenuItem) topLevel).getSubElements();
            components = new Component[me.length];
            for (int idx = 0; idx < me.length; idx++) {
                components[idx] = me[idx].getComponent();
            }
        }

        if (topLevel instanceof JTabbedPane) {
            JTabbedPane jtp = (JTabbedPane) topLevel;
            int selIdx = jtp.getSelectedIndex();
            components = new Component[jtp.getTabCount()];
            for (int idx = 0; idx < components.length; idx++) {
                jtp.setSelectedIndex(idx);
                components[idx] = jtp.getSelectedComponent();
            }
            jtp.setSelectedIndex(selIdx);
        }

        /*	if (topLevel instanceof com.symantec.itools.javax.swing.JButtonGroupPanel) {
         java.util.Enumeration abe = ((com.symantec.itools.javax.swing.JButtonGroupPanel) topLevel).getElements();*/
        if (topLevel instanceof javax.swing.ButtonGroup) {
            java.util.Enumeration<AbstractButton> abe = ((javax.swing.ButtonGroup) topLevel).getElements();
            int cnt = 0;
            for (; abe.hasMoreElements(); abe.nextElement()) {
                cnt++;
            }
//			//System.out.println("U_aLW: JButtonGroupPanel " + cnt);
            components = new Component[cnt];
            //abe = ((com.symantec.itools.javax.swing.JButtonGroupPanel) topLevel).getElements();
            abe = ((javax.swing.ButtonGroup) topLevel).getElements();
            for (int idx = 0; idx < cnt; idx++) {
                components[idx] = (Component) abe.nextElement();
                ////System.out.println("U_aLW: buttons " + components[idx].getClass());				
            }
        }
        for (Component component : components) {
            if (component instanceof JPanel) {
                adjustLabelWidths((JPanel) component);
            } else if (component instanceof JMenuBar) {
                adjustLabelWidths((JMenuBar) component);
            } else if (component instanceof JMenuItem) {
                adjustLabelWidths((JMenuItem) component);
            } else if (component instanceof JPopupMenu) {
                adjustLabelWidths((JPopupMenu) component);
            } else if (component instanceof JToolBar) {
                adjustLabelWidths((JToolBar) component);
            } else if (component instanceof JTabbedPane) {
                adjustLabelWidths((JTabbedPane) component);
            } else if (component instanceof JScrollPane) {
                adjustLabelWidths((JScrollPane) component);
            } else if (component instanceof JViewport) {
                adjustLabelWidths((JViewport) component);
            } else if (component instanceof JComponent) {
                adjustLabelWidth((JComponent) component);
            } else if (component instanceof AbstractButton) {
                adjustLabelWidth((AbstractButton) component);
            } else {
                //System.out.println("U_aLW: " + components[cdx].getClass());
            }
        }
    }

    /**
     *
     * @param printLine
     */
    public static void debugPrint(String printLine) {
        LOGGER.debug(printLine);
    }

    /**
     *
     * @param flg
     * @param printLine
     */
    public static void debugPrint(boolean flg, String printLine) {
        if (!flg) {
            return;
        }
        try {
            throw new Exception("test");
        } catch (Exception ex) {
            //System.err.println(ex.getStackTrace()[1] + ": " + printLine);
//			//System.out.println("\n\n" + ex.printStackTrace());
        }
    }

    /**
     *
     * @param val
     * @param numdig
     * @return
     */
    public static String formatDouble(double val, int numdig) {
        if (Double.isNaN(val)) {
//			//System.out.println("U_fD : ");
            return "NaN";
        }
////System.out.println("U_fD: " + val);		
        if (val < 0.001 && val > -0.001 && val != 0.0) {
            String tmp = Double.toString(val);
            int pdx = tmp.indexOf('.');
            int edx = tmp.indexOf('E');
            if (edx == -1) {
                edx = tmp.indexOf('e');
            }
//			//System.out.println("formatDouble: " + tmp + " " + pdx + " " + edx);
            if ((pdx + numdig) >= edx) {
                return tmp;
            }
            return tmp.substring(0, pdx + numdig + 1) + tmp.substring(edx);
        }
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        java.text.FieldPosition fp = new java.text.FieldPosition(NumberFormat.INTEGER_FIELD);
        nf.setMaximumFractionDigits(numdig);
        nf.setMinimumFractionDigits(numdig);
        nf.setGroupingUsed(false);
        return nf.format(val, new StringBuffer(), fp).toString();
    }
    
        /**
     *
     * @param inpFilNam
     * @return
     */
    public static java.io.File makeZipFile(String inpFilNam) {
        java.io.File inpFil = new TFile(inpFilNam);

        java.io.File outFil;
        try {
            outFil = java.io.File.createTempFile(inpFil.getName(), ".zip");
        } catch (IOException ioe) {
            //could not create the temp file.
            outFil = new TFile(inpFilNam + ".zip");
        }

        if (outFil.exists()) {
            outFil.delete();
        }
        String filename = inpFil.getName();
        java.io.FileOutputStream os = null;
        try {
            os = new java.io.FileOutputStream(outFil);
        } catch (FileNotFoundException ex) {
            //java.util.logging.LogManager.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
            LOGGER.error(ex);
        }
        ZipOutputStream zip_os = new ZipOutputStream(os);
        if (inpFil.isDirectory()) {
            Util.writeDirToZip(zip_os, inpFil, filename);
        } else {
            Util.writeFileToZip(zip_os, inpFil, filename);
        }
        try {
            zip_os.close();
        } catch (IOException ex) {
            //java.util.logging.LogManager.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
            LOGGER.error(ex);
        }
        return outFil;
    }

    /**
     *
     * @param inpFilNam
     * @param ff Filtering directory contents, does not effect if you are only 
     * giving a single file.
     * @return
     */
    public static java.io.File makeZipFile(String inpFilNam, FilenameFilter ff) {
        java.io.File inpFil = new TFile(inpFilNam);

        java.io.File outFil;
        try {
            outFil = java.io.File.createTempFile(inpFil.getName(), ".zip");
        } catch (IOException ioe) {
            //could not create the temp file.
            outFil = new TFile(inpFilNam + ".zip");
        }

        if (outFil.exists()) {
            outFil.delete();
        }
        String filename = inpFil.getName();
        java.io.FileOutputStream os = null;
        try {
            os = new java.io.FileOutputStream(outFil);
        } catch (FileNotFoundException ex) {
            //java.util.logging.LogManager.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
            LOGGER.error(ex);
        }
        ZipOutputStream zip_os = new ZipOutputStream(os);
        if (inpFil.isDirectory()) {
            Util.writeDirToZip(zip_os, inpFil, filename, ff);
        } else {
            Util.writeFileToZip(zip_os, inpFil, filename);
        }
        try {
            zip_os.close();
        } catch (IOException ex) {
            //java.util.logging.LogManager.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
            LOGGER.error(ex);
        }
        return outFil;
    }

    public static void writeDirToZip(ZipOutputStream zip_os, java.io.File curdirFile, String zipPath, FilenameFilter ff) {
        java.io.File[] fileList = curdirFile.listFiles(ff); 
        for (java.io.File fileList1 : fileList) {
            if (fileList1.isDirectory()) {
                writeDirToZip(zip_os, new TFile(fileList1), zipPath + "/" + fileList1.getName(), ff);
            } else {
                if (ff.accept(fileList1, fileList1.getName())) { // Seemingly redundant, but fails without it sometimes
                    writeFileToZip(zip_os, new TFile(fileList1), zipPath);
                }
            }
        }
    }

    public static void writeDirToZip(ZipOutputStream zip_os, java.io.File curdirFile, String zipPath) {
        java.io.File[] fileList = curdirFile.listFiles();
        for (java.io.File fileList1 : fileList) {
            if (fileList1.isDirectory()) {
                writeDirToZip(zip_os, new TFile(fileList1), zipPath + "/" + fileList1.getName());
            } else {
                writeFileToZip(zip_os, new TFile(fileList1), zipPath);
            }
        }
    }

    public static void writeFileToZip(ZipOutputStream zip_os, java.io.File inputFile, String zipPath) {
        TFileInputStream fis = null;
        try {
            String zipName = zipPath + "/" + inputFile.getName();
            ZipEntry zipEntry = new ZipEntry(zipName);
            fis = new TFileInputStream(inputFile);

            byte b[] = new byte[1024];

            zip_os.putNextEntry(zipEntry);
            int len;
            while ((len = fis.read(b)) != -1) {
                zip_os.write(b, 0, len);
            }
            zip_os.closeEntry();
        } catch (IOException e) {
            LOGGER.error("IO Error writing zip file \"" + zipPath + "\"", e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    LOGGER.error("Error closing file stream.", e);
                }
            }
        }
    }
    
    public static void extractZipFile(de.schlichtherle.truezip.zip.ZipFile zipIn, File destDir) throws IOException {
        final Enumeration<? extends de.schlichtherle.truezip.zip.ZipEntry> entries = zipIn.entries();
        while (entries.hasMoreElements()) {
            final de.schlichtherle.truezip.zip.ZipEntry entry = entries.nextElement();
            final String entryName = entry.getName();
                final InputStream in = zipIn.getInputStream(entry);
                File zOutFile = new File(destDir, entryName);
                Files.copy(in, zOutFile.toPath());
                in.close();
            if (entryName.endsWith("zip")) {
                    extractZipFile(new de.schlichtherle.truezip.zip.ZipFile(new File (destDir,entryName)),destDir);
            }
        }
        zipIn.close();
    }

    /**
     * Static helper method for getting a unique file name.  The file's extensions are
     * determined by getExtensions().  The append string is ignored.
     * @param file File object representing the file name to increment.
     * @return File object representing the incremented file.
     */
    public static TFile incrementFileName(TFile file) {
        return incrementFileName(file, null);
    }

    /**
     * Static helper method for getting a unique file name.  The file's extensions are
     * determined by getExtensions().
     * @param file File object representing the file name to increment.
     * @param append String to append between the file name and extensions.  Ignored if 
     * null.
     * @return File object representing the incremented file.
     */
    public static TFile incrementFileName(TFile file, String append) {
        return incrementFileName(file, append, getExtensions(file.getName()));
    }

    /**
     * Static method for getting a unique file name.  The method accepts a String to
     * append, such as "_backup" which will be placed between the file name and passed
     * extensions.  If the file already exists, the method will look for an integer
     * at the end of the file name and increment by 1.  If there is no integer in the 
     * file name the method will append and integer in the format "_#" so that # is a 
     * unique number.
     * @param file File object representing the file name to increment.
     * @param append String to append between the file name and extensions.  Ignored if 
     * null.
     * @param extensions String[] of extensions.
     * @return File object representing the incremented file.
     */
    public static TFile incrementFileName(TFile file, String append, String... extensions) {
        String fileName = file.getName();

        //remove the extensions from the filename
        if (extensions != null) {
            fileName = purgeExtensions(fileName, extensions);
        }

        //add the append string.
        if (append != null) {
            fileName += append;
        }

        //Do we need to increment the numbers?
        TFile tempFile = new TFile(file.getParentFile(), addExtensions(fileName, extensions));
        //Put the extensions back on

        if (tempFile.exists() == false) {
            //file is unique, return it without regard to a number
            return tempFile;
        } else {
            //We need to add numbers so we'll discard this file for now.
            tempFile = null;
        }

        int number;
        String prefix;
        //Find the last underscore
        int underscoreIndex = fileName.lastIndexOf("_");
        if (underscoreIndex > 1) {
            //We have an underscore
            prefix = fileName.substring(0, underscoreIndex + 1);
            try {
                String numberString = fileName.substring(underscoreIndex + 1);
                number = Integer.parseInt(numberString);
            } catch (NumberFormatException e) {
                //The characters after the underscore are not an integer.
                number = 1;
                prefix = fileName + "_";
            }
        } else {
            //No underscore
            number = 1;
            prefix = fileName + "_";
        }

        fileName = prefix + Integer.toString(number);

        tempFile = new TFile(file.getParentFile(), addExtensions(fileName, extensions));
        while (tempFile.exists()) {
            fileName = prefix + Integer.toString(number);
            tempFile = new TFile(file.getParentFile(), addExtensions(fileName, extensions));
            number++;
        }
        return tempFile;
    }
    
    /**
     * Static method for getting a nonunique file name.  The method accepts a String to
     * append, such as "_backup" which will be placed between the file name and passed
     * extensions.
     * @param file File object representing the file name to increment.
     * @param append String to append between the file name and extensions.  Ignored if 
     * null.
     * @param extensions String[] of extensions.
     * @return File object representing the incremented file.
     */
    public static TFile incrementFileNameDuplicate(TFile file, String append, String... extensions) {
        String fileName = file.getName();

        //remove the extensions from the filename
        if (extensions != null) {
            fileName = purgeExtensions(fileName, extensions);
        }

        //add the append string.
        if (append != null) {
            fileName += append;
        }

        //Do we need to increment the numbers?
        TFile tempFile = new TFile(file.getParentFile(), addExtensions(fileName, extensions));
        return tempFile;
    }

    /**
     * Appends each extension in order to the path.
     * @param path Path to append extensions to.
     * @param extensions String[] of extensions to append.
     * @return Appended path.
     */
    public static String addExtensions(String path, String... extensions) {
        if (extensions != null) {
            for (String extension : extensions) {
                path = path.concat(extension);
            }
        }
        return path;
    }

    /**
     * Removes the passed extensions from the string.
     * @param path String to remove extensions from.
     * @param extensions String[] of extensions with the periods at the start of 
     * each extension.
     * @return Purged String.
     */
    public static String purgeExtensions(String path, String... extensions) {
        String newPath = path;
        boolean extensionRemaining = true;

        while (extensionRemaining) {
            for (String extension : extensions) {
                if (newPath.endsWith(extension)) {
                    int end = newPath.lastIndexOf(extension);
                    newPath = newPath.substring(0, end);
                }
            }
            //Check if an extension is remaining.
            extensionRemaining = false;
            for (String extension : extensions) {
                if (newPath.endsWith(extension)) {
                    extensionRemaining = true;
                    break;
                }
            }
        }
        return newPath;
    }

    /**
     * Returns an array of strings of the extensions of the file name.  The periods
     * are included at the start of each extension.
     * @param path String to find the extensions in.
     * @return Array of Strings of the extensions.
     */
    public static String[] getExtensions(String path) {
        Pattern pattern = Pattern.compile("\\.[\\S&&[^\\.]]++");
        Matcher matcher = pattern.matcher(path);
        Vector<String> matches = new Vector<String>();
        while (matcher.find()) {
            int start = matcher.start();
            int end = matcher.end();
            String match = path.substring(start, end);
            if (match.length() < 5) {
                matches.add(match);
            }

        }

        String[] result = new String[matches.size()];
        for (int i = 0; i < matches.size(); i++) {
            result[i] = matches.get(i);
        }
        return result;
    }
    private static Pattern c_parsingPattern;

    private synchronized static Pattern getParsingPattern() {
        if (c_parsingPattern == null) {
            c_parsingPattern = Pattern.compile("(\\$\\{(.*?)\\})");
        }
        return c_parsingPattern;
    }

    public static String parse(String text) {
        return parse(text, null);
    }

    public static String parse(String text, Map<String, String> props) {
        if (text == null) {
            return null;
        }
        Matcher matcher = getParsingPattern().matcher(text);
        StringBuffer buffer = new StringBuffer();
        while (matcher.find()) {
            String property = matcher.group(2);
            String replacement = null;
            if (props != null && props.containsKey(property)) {
                //user can specify more props
                replacement = props.get(property);
            } else {
                replacement = getProperty(property);
            }

            if (replacement != null) {
                //handle the regex quirks with slashes and question marks
                replacement = replacement.replace("\\", "\\\\");
                replacement = replacement.replace("$", "\\$");
                matcher.appendReplacement(buffer, replacement);
            }
        }
        matcher.appendTail(buffer);

        return buffer.toString();
    }

    public static String getProperty(String property) {
        //user directories
        switch (property) {
            case "user.home":
                return About.getUserHome().getAbsolutePath();
            case "weps.home":
                return About.getWepsHome().getAbsolutePath();
            case "USER_HOME_DIR":
            //LOGGER.warn("${USER_HOME_DIR} is deprecated.  Use ${user.documents} instead."); 
            case "user.documents":
                return About.getUserDocuments().getAbsolutePath();
            case "user.weps":
                return About.getUserWeps().getAbsolutePath();
            case "user.working":
                return About.getUserWorking().getAbsolutePath();
            // Get the executables
            case "exe.rdir":
                if (Util.isWindows()) {
                    return ConfigData.getDefault().getData(ConfigData.exeWinRdir);
                } else {
                    return ConfigData.getDefault().getData(ConfigData.exeLinuxRdir);
                }
            case "exe.ext":
                if (Util.isWindows()) {
                    return ConfigData.getDefault().getData(ConfigData.exeWinExt);
                } else {
                    return ConfigData.getDefault().getData(ConfigData.exeLinuxExt);
                }
            // OS specific extension variables to handle CSIP service binary names
            case "Win_exe.ext":
                return ConfigData.getDefault().getData(ConfigData.exeWinExt);
            case "Linux_exe.ext":
                return ConfigData.getDefault().getData(ConfigData.exeLinuxExt);
            // Base Names
            case "weps.bname":
                return ConfigData.getDefault().getData(ConfigData.wepsBname);
            case "cligen.bname":
                return ConfigData.getDefault().getData(ConfigData.cligenBname);
            case "windgen.bname":
                return ConfigData.getDefault().getData(ConfigData.windgenBname);
            case "interp1.bname":
                return ConfigData.getDefault().getData(ConfigData.interp1Bname);
            case "interp2.bname":
                return ConfigData.getDefault().getData(ConfigData.interp2Bname);
            // WEPS db relative directory
            case "weps_db.rdir": {
                String path = ConfigData.getDefault().getData(ConfigData.wepsdbRdir);
                path = pad(path);
                return path;
            }
            case "PROJECT_DIRECTORY":
            //LOGGER.warn("${PROJECT_DIRECTORY} is deprecated.  Use ${project.directory} instead.");
            // Just fall through, warnings not needed at this time.
            case "project.directory": {
                String path = ConfigData.getDefault().getDataParsed(ConfigData.CurrentProj);
                path = pad(path);
                return path;
            }
            case "project.path": {
                String path = ConfigData.getDefault().getDataParsed(ConfigData.ProjDir);
                path = pad(path);
//                System.out.println("here2" +path);
                return path;
            }
            //windgen
            case "WINDGEN_DB":
            //LOGGER.warn("${WINDGEN_DB} is deprecated.  Use ${windgen.data} instead.");
            case "windgen.data": {
                String data = ConfigData.getDefault().getDataParsed(ConfigData.WinData);
                data = pad(data);
                return data;
            }
            case "windgen.index": {
                String index = ConfigData.getDefault().getDataParsed(ConfigData.WinIndex);
                index = pad(index);
                return index;
            }
            //cligen
            case "CLIGEN_DB":
            //LOGGER.warn("${CLIGEN_DB} is deprecated.  Use ${cligen.data} instead.");
            case "cligen.data": {
                String data = ConfigData.getDefault().getDataParsed(ConfigData.CliData);
                data = pad(data);
                return data;
            }
            case "cligen.index": {
                String index = ConfigData.getDefault().getDataParsed(ConfigData.CliIndex);
                index = pad(index);
                return index;
            }
            case "weps.database_dir": {
                // Returns the path to the databases
                return getProperty("weps.databases") + File.separator + getProperty("weps_db.rdir");
            }
            case "weps.databases": {
                TFile file;
                //determine if we are running windows or linux for debug statements
                String os = "not_known";
                if (Util.isWindows()) {
                    os = "Windows";
                } else {
                    os = "Linux";
                }
                
                if (Weps.isWebstart) {
                    file = About.getWepsDatabases();
                    if (Util_weps_db_type_msg_print == true) {
                        Util_weps_db_type_msg_print = false;
                        System.out.println("Util.java: using webstart databases");
                        System.out.println("Util.java: file: " + file + "(" + os + ")");
                        System.out.println("Util.java: parsed file path: " + Util.parse(file.getPath()));
                        LOGGER.info("Util.java: using webstart databases");
                        LOGGER.info("Util.java: file: " + file + "(" + os + ")");
                        LOGGER.info("Util.java: parsed file path: " + Util.parse(file.getPath()));
                    }
                    //System.out.println("file: " + file);
                    // System.out.println("Util.java: file: " + file + "(" + os + ")");                        
                } else {
                    //file = About.getMSIDatabases();
                    file = About.getWepsDatabases();
                    if (Util_weps_db_type_msg_print == true) {
                        Util_weps_db_type_msg_print = false;
                        System.out.println("Util.java: using MSI databases");
                        System.out.println("Util.java: file: " + file + "(" + os + ")");
                        System.out.println("Util.java: parsed file path: " + Util.parse(file.getPath()));
                        LOGGER.info("Util.java: using MSI databases");
                        LOGGER.info("Util.java: file: " + file + "(" + os + ")");
                        LOGGER.info("Util.java: parsed file path: " + Util.parse(file.getPath()));
                    }
                    //System.out.println("file: " + file);
                    // System.out.println("Util.java: file: " + file + "(" + os + ")");
                }
                // System.out.println("Util.java: absolute file path: " + file.getAbsolutePath());
                // System.out.println("Util.java: file path: " + file.getPath());
                //System.out.println("Util.java: parsed absolute file path: " + file(Util.parse(file)).getAbsolutePath());
                // System.out.println("Util.java: parsed file path: " + Util.parse(file.getPath()));
                //return file != null ? file(Util.parse(file)).getAbsolutePath() : null;          
                return file != null ? Util.parse(file.getPath()) : null;
            }

            case "BuildName":
                // So build name can be used in configuration parameters.
                return About.getWsBuildName();
            //deprecated properties

            //system properties 
            default:
                if (System.getProperties().containsKey(property)) {
                    return System.getProperty(property);
                } else if (System.getenv().containsKey(property)) {
                    return System.getenv(property);
                } else {
                    //nothing, return null
                    LOGGER.error("Unable to parse property: " + property);
                    return null;
                }
        }
    }

    private static String pad(String string) {
        if (string == null) {
            return "";
        } else {
            return string;
        }
    }

    public static File fixPath(File file) {
        String pathStart = "C:";
        String path = file.getAbsolutePath();
        //System.out.println("Unfixed Path: " + path);
        path = path.substring(path.lastIndexOf(pathStart));
        //System.out.println("Fixed Path: " + path);
        file = new File(path);
        return file;
    }

    public static String encrypt(String input, String key) {
        return encodeSafeXml(xorCipher(input, key));
    }

    public static String decrypt(String input, String key) {
        return xorCipher(decodeSafeXml(input), key);
    }

    public static String xorCipher(String input, String key) {
        if (input == null || key == null) {
            return null;
        }

        byte[] inputBytes = input.getBytes();
        byte[] keyBytes = key.getBytes();

        int k = 0;

        //loop over each input byte
        for (int i = 0; i < inputBytes.length; i++) {
            inputBytes[i] = (byte) (inputBytes[i] ^ keyBytes[k]);

            //step over the key
            if (k < keyBytes.length - 1) {
                k++;
            } else {
                k = 0;
            }
        }

        return new String(inputBytes);

    }

    private static final String HEXCHARS[]
            = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};

    public static String encodeSafeXml(String input) {
        if (input == null) {
            return null;
        }
        byte[] inputBytes = input.getBytes();

        StringBuilder output = new StringBuilder(input.length() * 2);
        byte ch = 0x00;
        for (int i = 0; i < inputBytes.length; i++) {
            ch = (byte) (inputBytes[i] & 0xF0); //strip off high bit
            ch = (byte) (ch >>> 4); //shift bits down
            ch = (byte) (ch & 0x0F); //prepare high bit

            output.append(HEXCHARS[(int) ch]);

            ch = (byte) (inputBytes[i] & 0x0F); //strip off low bit
            output.append(HEXCHARS[(int) ch]);
        }

        return output.toString();
    }

    public static String decodeSafeXml(String input) {
        if (input == null) {
            return null;
        }
        try {
            byte[] b = new byte[1];
            StringBuilder output = new StringBuilder();
            for (int i = 0; i < input.length(); i = i + 2) {
                String sub = input.substring(i, i + 2);
                b[0] = Byte.parseByte(sub, 16);
                output.append(new String(b));
            }
            return output.toString();
        } catch (NumberFormatException nfe) {
            LOGGER.warn("Unable to decode string: " + input);
            return null;
        }
    }

    public static void logMemoryUsage() {
        final double bytesToMegabytes = 9.5367431640625e-07;
        double totalMem = Runtime.getRuntime().totalMemory() * bytesToMegabytes;
        double maxMem = Runtime.getRuntime().maxMemory() * bytesToMegabytes;
        double freeMem = Runtime.getRuntime().freeMemory() * bytesToMegabytes;
        double usedMem = totalMem - freeMem;

        LOGGER.debug(String.format("Memory Usage: %1$f.2MB used, %2$f.2MB free %3$f.2MB max", usedMem, totalMem, maxMem));
    }

    public static String makePrettyTable(String[]... rows) {
        //check that the rows all have the same number of columns
        int columnCount = -1;
        for (String[] row : rows) {
            if (columnCount == -1) {
                columnCount = row.length;
            } else if (row.length != columnCount) {
                throw new IllegalArgumentException("Table rows do not all have the same number of columns");
            }
        }

        //calculate the column widths
        int[] columnWidths = new int[columnCount];
        //loop over each column
        for (int i = 0; i < columnCount; i++) {
            //loop over each row
            for (String[] row : rows) {
                //calculate the maximum value length for the column for each row
                columnWidths[i] = Math.max(columnWidths[i], row[i] != null ? row[i].trim().length() : 0);
            }//end row loop
        }//end column loop
        StringBuilder table = new StringBuilder();

        //column delimiter
        String delimiter = " | ";

        //append each row with padding
        for (String[] row : rows) {
            for (int i = 0; i < columnCount; i++) {
                if (i > 0) {
                    //not the first column, add the delimiter
                    table.append(delimiter);
                }

                //append the value
                String value = row[i] != null ? row[i].trim() : "";
                table.append(value);

                //only pad if not the last column
                if (i != columnCount - 1) {
                    int padding = columnWidths[i] - value.length();
                    for (int p = 0; p < padding; p++) {
                        table.append(" ");
                    }
                }
            }
            //append new line
            table.append("\n");
        }

        return table.toString();
    }

    public static <C extends Component> C findFirstDecendent(Component comp, Class<C> c) {
        return findDecendent(comp, c, null);
    }

    // inside the first 'if', comp should always be an instance of C
    @SuppressWarnings("unchecked")
    public static <C extends Component> C findDecendent(Component comp, Class<C> c, FindMatcher<C> matcher) {
        if (c.isInstance(comp)) {
            if (matcher != null) {
                return matcher.matches((C) comp) ? (C) comp : null;
            } else {
                return (C) comp;
            }
        } else if (comp instanceof Container) {
            Container box = (Container) comp;
            Component[] children = box.getComponents();
            for (Component child : children) {
                C result = findFirstDecendent(child, c);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }

    public static interface FindMatcher<O> {
        public boolean matches(O o);
    }

    public static boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("win");
    }

    public static boolean is64Bit() {
        return "64".equals(System.getProperty("sun.arch.data.model"));
    }

    public static TFile[] findFiles(TFile root, FileFilter filter, boolean recursive, int limit) {
        List<TFile> results = new LinkedList<>();

        findFiles(results, root, filter, true, recursive, limit);

        return results.toArray(new TFile[results.size()]);
    }

    private static void findFiles(List<TFile> results, TFile file, FileFilter filter,
            boolean root, boolean recursive, int limit) {

        boolean accept = filter.accept(file);

        if (accept) {
            results.add(file);

            //if we have a limit here.
            if (limit > 0 && results.size() == limit) {
                return;
            }
        }

        if (file.isDirectory() && (root || recursive)) {
            TFile[] children = file.listFiles(file.getArchiveDetector());

            if (children != null) {
                for (TFile child : children) {
                    findFiles(results, child, filter, false, recursive, limit);

                    if (limit > 0 && results.size() == limit) {
                        return;
                    }
                }
            }

        }
    }

    public static String fileContents(String path) {
        return fileContents(new TFile(path));
    }

    public static String fileContents(TFile file) {
        StringBuilder contents = new StringBuilder();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new TFileReader(file));
            for (String line = reader.readLine(); line != null; line = reader.readLine()) {
                contents.append(line);
                contents.append("\n");
            }
        } catch (IOException e) {
            LOGGER.warn("Unable to read file contents: " + file.getAbsolutePath(), e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    LOGGER.warn("Unable to close file: " + file.getAbsolutePath(), e);
                }

            }
        }
        return contents.toString();
    }

    public static boolean isValidEmail(String email) {
        if (email == null || email.trim().isEmpty()) {
            return false;
        }

        //regex for emails        
        String regex = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";

        return Pattern.matches(regex, email.trim());

    }

    /**
     * Parses the first decimal component of a version string. 
     * @param version String to parse
     * @return the float value of the first decimal component in the string or -1
     */
    public static float parseVersionStringToFloat(String version) {
        Pattern pattern = Pattern.compile("([0-9]+(\\.[0-9]+)?)");

        Matcher matcher = pattern.matcher(version);

        if (matcher.find()) {
            String floatPart = matcher.group();
            try {
                return Float.parseFloat(floatPart);
            } catch (NumberFormatException nfe) {
                LogManager.getLogger(About.class).error("Unable to parse version string: " + version, nfe);
                return -1;
            }
        } else {
            return -1;
        }
    }

    //Returns true if the child is in the parent's directory tree.
    public static boolean isAncestorOf(TFile parent, TFile child) {
        //Fix for if the restored run is in the current project
        if (child.getParentFile().getName().endsWith(".wjr")) {
            return false;
        }
        while (parent != null && child != null) {
            if (child.equals(parent)) {
                return true;
            }
            if (child.getParentFile() != null) {
                child = new TFile(child.getParentFile());
            } else {
                child = null;
            }
        }
        return false;
    }

    public static boolean isReadOnly(String path) {
        return isReadOnly(new File(path));
    }
    
    public static boolean isReadOnly(File file) {
        if(!file.isFile()){
            //if it does not exist, return false
            return false;
        }
        return !file.canWrite();   
    }
}
