package usda.weru.util;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileInputStream;
import de.schlichtherle.truezip.file.TFileWriter;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.UTFDataFormatException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.measure.Measurable;
import javax.measure.Measure;
import javax.measure.quantity.Length;
import javax.measure.quantity.Quantity;
import javax.measure.unit.NonSI;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import org.apache.log4j.Logger;
import org.jdom2.DocType;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jscience.economics.money.Currency;
import usda.weru.weps.location.StationMode;

/**
 * Contains configuration data for WepsUI.
 * 
 * @author wjr
 * @version 2.0
 */
public class ConfigData implements PropertyChangeListener {

    static {
        //The reference currency is always the United States Dollar.
        Currency.setReferenceCurrency(Currency.USD);
    }

    private static final Object LOCK = new Object();

    /**
     *
     */
    public enum AccessLevel {

        /**
         *
         */
        Write,
        /**
         *
         */
        Read,
        /**
         *
         */
        Hidden;

        /**
         *
         * @param name
         * @return
         */
        public static final AccessLevel parse(String name) {
            for (AccessLevel level : AccessLevel.values()) {
                if (level.toString().equalsIgnoreCase(name)) {
                    return level;
                }
            }
            return null;
        }

        /**
         *
         * @param a
         * @param b
         * @return
         */
        public static final AccessLevel higher(AccessLevel a, AccessLevel b) {
            if (a == null && b == null) {
                return null;
            } else if (a == null) {
                return b;
            } else if (b == null) {
                return a;
            } else if (Write.equals(a) || Write.equals(b)) {
                return Write;
            } else if (Read.equals(a) || Read.equals(b)) {
                return Read;
            } else if (Hidden.equals(a) || Hidden.equals(b)) {
                return Hidden;
            } else {
                return null;
            }
        }

        /**
         *
         * @param a
         * @param b
         * @return
         */
        public static final AccessLevel lower(AccessLevel a, AccessLevel b) {
            if (a == null && b == null) {
                return null;
            } else if (a == null) {
                return b;
            } else if (b == null) {
                return a;
            } else if (Hidden.equals(a) || Hidden.equals(b)) {
                return Hidden;
            } else if (Read.equals(a) || Read.equals(b)) {
                return Read;
            } else if (Write.equals(a) || Write.equals(b)) {
                return Write;
            } else {
                return null;
            }
        }
    }
    private static final Logger LOGGER = Logger.getLogger(ConfigData.class);
    private static final String XML_CONFIGURATION = "configuration";
    private static final String XML_VERSION = "version";
    private static final String XML_PARAMETER = "parameter";
    private static final String XML_NAME = "name";
    private static final String XML_VALUE = "value";
    private static final String XML_ACCESS = "access";
    private static final String CD = "CD-";
    public static final String REP = "Rep-";
    private static final String RUNTIME_ONLY = "RUNTIME_ONLY";
    private static boolean NRMVVis = false;
    
    private static boolean toCollapse = false;

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "images")
    public static final String ReportFileName = CD + "report file name";

    /**
     *
     */
    public static final String Units = CD + "measurement";

    /**
     *
     */
    public static final String OutputFreq = CD + "output report frequency";
    
    /**
     * these two parameters control the format of the console output for
     * windgen interpolation.
     */
    public static String WindgenDecimal = "#0.00000";
    public static final String windDecNum= CD + "Windgen Decimal Precision";
    // Submodel output flags

    /**
     *
     */
    public static final String OPHydroDet = CD + "detailed hydro output";

    /**
     *
     */
    public static final String OPSoilDet = CD + "detailed soil output";

    /**
     *
     */
    public static final String OPManageDet = CD + "detailed manage output";

    /**
     *
     */
    public static final String OPCropDet = CD + "detailed crop output";

    /**
     *
     */
    public static final String OPDecompDet = CD + "detailed decomp output";

    /**
     *
     */
    public static final String OPErosionDet = CD + "detailed erosion output";

    /**
     *
     */
    public static final String OPHydroDbg = CD + "debug hydro output";

    /**
     *
     */
    public static final String OPSoilDbg = CD + "debug soil output";

    /**
     *
     */
    public static final String OPManageDbg = CD + "debug manage output";

    /**
     *
     */
    public static final String OPCropDbg = CD + "debug crop output";

    /**
     *
     */
    public static final String OPDecompDbg = CD + "debug decomp output";

    /**
     *
     */
    public static final String OPErosionDbg = CD + "debug erosion output";

    // Windgen specific
    /**
     *
     */
    public static final String WindgenAllowedModes = CD + "windgen.allowedmodes";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "bin")
    public static final String WinExe = CD + "windgen generator";

    /**
     *
     */
    public static final String WinCmd = CD + "windgen cmdline";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db/windgen")
    public static final String WinData = CD + "windgen database";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db/windgen")
    public static final String WinIndex = CD + "windgen index";
//    @FileConfigurationOption(defaultLocation="db/wind_gen")
//    public static final String NRCSWinShape = CD + "nrcs wind shape file";
//    public static final String NRCSWinFlg = CD + "nrcs wind shape flg";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "bin")
    public static final String WindInterp1EXE = CD + "windgen interpolate 1";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "bin")
    public static final String WindInterp2EXE = CD + "windgen interpolate 2";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db/windgen")
    public static final String WindgenInterpolationBoundaryFile = CD + "windgen.interpolate.boundary";

    // Cligen specific
    /**
     *
     */
    public static final String CligenAllowedModes = CD + "cligen.allowedmodes";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "bin")
    public static final String CliExe = CD + "cligen generator";

    /**
     *
     */
    public static final String CliCmd = CD + "cligen cmdline";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db/cligen")
    public static final String CliData = CD + "cligen database";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db/cligen")
    public static final String CliIndex = CD + "cligen index";

    // Weps specfic
    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "bin")
    public static final String WepsExe = CD + "WEPS exe";
    
    public static final String CollapseOp = CD + "collapse non zip";

    /**
     *
     */
    public static final String WepsCmd = CD + "WEPS exe parameter";

    /**
     *
     */
    public static final String CalWepsCmd = CD + "WEPS calibration exe parameter";

    //user specific
    /**
     *
     */
    public static final String UserFullName = CD + "user.fullname";

    // Communications specific
    /**
     *
     */
    public static final String EmailSender = CD + "SenderEmail";

    /**
     *
     */
    public static final String UseOutlookFlg = CD + "Outlook Flg";

    /**
     *
     */
    public static final String MailHost = CD + "SMTP_host";

    /**
     *
     */
    public static final String CommentAddr = CD + "Dest_email";

    /**
     *
     */
    public static final String BugsAddr = CD + "Dest_bug_email";

    /**
     *
     */
    public static final String MantisEmail = CD + "MantisEmail";

    /**
     *
     */
    public static final String MantisURL = CD + "Mantis URL";

    /**
     *
     */
    public static final String MantisUser = CD + "Mantis user";

    /**
     *
     */
    public static final String MantisPassword = CD + "Mantis password";

    /**
     *
     */
    public static final String MantisProject = CD + "Mantis project";
    
    public static final String RunVis = REP + "Run";
    public static final String ManVis = REP + "Man";
    public static final String CropVis = REP + "Crop";
    public static final String CCrVis = REP + "CCr";
    public static final String CIntVis = REP + "CInt";
    public static final String STIRVis = REP + "STIR";
    public static final String DetVis = REP + "Det";
    public static final String ConfVis = REP + "Conf";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db")
    public static final String ManTemp = CD + "management template";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db")
    public static final String ManTempSaveAs = CD + "management template save as";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db")
    public static final String ManSkel = CD + "management skeleton";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db")
    public static final String CropDB = CD + "cropdb";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = ".")
    public static final String MCREW = CD + "MCREW";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db")
    public static final String ManDB = CD + "manoperdb";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "db")
    public static final String SoilDB = CD + "soil database";
    //public static final String SoilDBSpec	= CD + "soil database spec";

    /**
     *
     */
    public static final String SoilDBDisp = CD + "soil database display";

    /**
     *
     */
    public static final String SoilReadonly = CD + "soil.readonly";

    /**
     *
     */
    @FileConfigurationOption(required = false, defaultLocation = "db/soil")
    public static final String SoilOrganicFile = CD + "soil.organicfile";

    /**
     *
     */
    public static final String SoilTestOrganic = CD + "soil.organictest";

    /**
     *
     */
    @MeasureableConfigurationOption(units = "mm", alternativeUnits = "in")
    public static final String SoilMaxOrganicDepth = CD + "soil.maxorganicdepth";

    /**
     *
     */
    @FileConfigurationOption
    public static final String ProjDir = CD + "projects path";

    /**
     *
     */
    @FileConfigurationOption(parentOption = MCREW)
    public static final String McrewDataConfigFile = CD + "mcrew data config file name";

    /**
     *
     */
    public static final String TimeSteps = CD + "time steps";

    /**
     *
     */
    public static final String UseMap = CD + "use map";

    /**
     *
     */
    public static final String DispLatLon = CD + "display latitude longitude";

    /**
     *
     */
    public static final String DispElevation = CD + "display elevation";

    /**
     *
     */
    public static final String DispStCty = CD + "display state county";

    /**
     *
     */
    public static final String ReadonlySlope = CD + "readonly region slope";

    /**
     *
     */
    public static final String ReadonlyRockFragments = CD + "readonly rock fragments";

    /**
     *
     */
    public static final String McrewEdit = CD + "mcrew edit";

    /**
     *
     */
    public static final String SoilEstimate = CD + "soil estimate";
    
    public static final String NRMVVisibility = CD + "NRMV Visible";

    /**
     *
     */
    public static final String SoilAverageStratifiedLayers = CD + "soil.averagestratified";

    /**
     *
     */
    public static final String SoilSkipOrganicSurfaceLayers = CD + "soil.skiporganic";

    /**
     *
     */
    public static final String ShowWeatheFilerCheckboxes = CD + "showweatherfilecheckboxes";

    /**
     *
     */
    public static final String WindFlag = CD + "wind flag";

    /**
     *
     */
    @MeasureableConfigurationOption(units = "km", alternativeUnits = "mi")
    public static final String WindRadius = CD + "wind radius";

    /**
     *
     */
    public static final String ClimateFlag = CD + "climate flag";

    /**
     *
     */
    @MeasureableConfigurationOption(units = "km", alternativeUnits = "mi")
    public static final String ClimateRadius = CD + "climate radius";

    /**
     *
     */
    public static final String SubDailyFlag = CD + "sub-daily flag";
    //public static final String RunTypeDisp = CD + "runtypedisp";

    /**
     *
     */
    public static final String DefaultRunMode = CD + "defaultrunmode";

    /**
     *
     */
    public static final String NRCSRunLen = CD + "nrcsrunlength";

    /**
     *
     */
    public static final String TTInit = CD + "ToolTipInit";

    /**
     *
     */
    public static final String TTDismiss = CD + "ToolTipDismiss";

    /**
     * CurrentProj is a CD entry but its stored value is not used
     */
    @FileConfigurationOption()
    public static final String CurrentProj = CD + "current project";

    /**
     *
     */
    public static final String SWEEP = CD + "SWEEP-";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "bin")
    public static final String SWEEPExe = SWEEP + "exe";

    /**
     *
     */
    public static final String SWEEPCmd = SWEEP + "cmdLine";

    /**
     *
     */
    public static final String SWEEPWorkDir = SWEEP + "work";

    /**
     *
     */
    public static final String DataChanged = "data changed " + CD;

    /**
     *
     */
    public static final String Dates = "dates";

    /**
     *
     */
    public static final String Cycle = "cycle";

    /**
     *
     */
    public static final String NRCS = "NRCS";

    /**
     * Formats for the display of data
     */
    public static final String FormatOperationDate = CD + "operation date format";

    // Runs Location default
    /**
     *
     */
    @FileConfigurationOption(required = false)
    public static final String DefaultRunsLocationTemplate = CD + "default runs location Template";

    /**
     *
     */
    public static final String DefaultRunsLocation = RUNTIME_ONLY + CD + "default runs location";

    /**
     * Files to purge after running a simulation.
     */
    public static final String PurgeRunFiles = CD + "purge.filefilter";

    /**
     * Flag to indicate that .cli & .win files should be deleted from run directory
     */
    public static final String PurgeRunFilesFlg = CD + "purge.flag";

    /**
     * Mcrew's skel translation file
     */
    @FileConfigurationOption()
    public static final String SkelTranslationFile = CD + "skel translations file";

    /**
     * Don't warn about system locale
     */
    public static final String DoNotWarnAboutSystemLocale = CD + "don't warn about system locale";

    /**
     * Don't estimate missing surgo values
     */
    public static final String DoNotEstimateMissingSurgoValues = CD + "do not estimate missing surgo values";

    /**
     *
     */
    public static final String OMFractionThreshold = CD + "organic material fraction threshold";

    /**
     * Use the system default mailer
     */
    public static final String UseDefaultMailClient = CD + "use default mail client";

    /**
     *
     */
    public static final String DoNotReviewWarningsWhenRestoringRun = CD + "do not review warnings when restoring run";

    /**
     *
     */
    @FileConfigurationOption(defaultLocation = "tables")
    public static final String DetailTableFilterFile = CD + "detail table filter file";

    /**
     *
     */
    public static final String SingleProjectMode = CD + "single project mode";

    /**
     *
     */
    public static final String HideProjectFileButtons = CD + "hide project file buttons";

    /**
     *
     */
    public static final String HideTemplatetFileButtons = CD + "hide template file buttons";

    /**
     *
     */
    public static final String DaysToKeepLogs = CD + "logs.daystokeep";

    /**
     *
     */
    public static final String ReportsView = CD + "reports.view";

    /**
     *
     */
    public static final String ReportsGeneratePDF = CD + "reports.generatepdf";

    /**
     *
     */
    public static final String ReportsConfidenceIntervalEnabled = CD + "reports.ci.enabled";
    
    public static final String ReportsCropIntPerDetEnabled = CD + "reports.cipd.enabled";

    /**
     *
     */
    @FileConfigurationOption(required = false)
    public static final String ReportsDirectory = CD + "reports.directory";

    /**
     *
     */
    public static final String ReportsCustomized = CD + "reports.customized";

    /**
     *
     */
    public static final String AllowScriptCreation = CD + "script.allow";

    /**
     *
     */
    public static final String CompatibilityNRCS = CD + "compatibility.nrcs";

    /**
     *
     */
    public static final String BarriersReadonly = CD + "barriers.readonly";

    /**
     *
     */
    @FileConfigurationOption()
    public static final String BarriersFile = CD + "barriers.file";

    /**
     *
     */
    @FileConfigurationOption()
    public static final String GISData = CD + "gis.data";

    /**
     *
     */
    public static final String SiteChooserShowCountry = CD + "sitechooser.showcountry";

    /**
     * enable experimental xml format
     */
    public static final String FormatsXML = CD + "formats.xml";

    /**
     * fuels
     */
    @FileConfigurationOption
    public static final String FuelDatabase = CD + "fuel.database";

    /**
     *
     */
    public static final String FuelDefault = CD + "fuel.default";

    /**
     * label for whether NRMV is on, and whether it should do summary operations
     */
    public static final String NRMVMode = CD + "NRMV mode";

    /**
     * label for how to display submodel output options on the run panel
     */
    public static final String SubmodelOutputs = CD + "Submodel outputs";

    /**
     * below: labels for how to display nrmv output options on the run panel
     */
    public static final String CropOutput = CD + "visibility crop";
    public static final String DAboveOutput = CD + "visibility dabove";
    public static final String DBelowOutput = CD + "visibility dbelow";
    public static final String DecompOutput = CD + "visibility decomp";
    public static final String HLayersOutput = CD + "visibility hlayers";
    public static final String HydroOutput = CD + "visibility hydro";
    public static final String PlotOutput = CD + "visibility plot";
    public static final String SeasonOutput = CD + "visibility season";
    public static final String ShootOutput = CD + "visibility shoot";
    public static final String SoilLayOutput = CD + "visibility soillay";

    private static ConfigData c_default;
    private List<ConfigStorage> c_storage;
    private ConfigStorage c_mainConfig;
    private ConfigStorage c_userConfig;
    private ConfigStorage c_defaultConfig;
    private Map<String, AccessLevel> c_accessLevels;
    private Map<String, String> c_reportMap;
    private Map<String, Boolean> c_reportChangeMap;

    /**
     *
     */
    public static final String PROP_CHANGED = "changed";
    private boolean c_changed = true;
    /**
     * ConfigData throws a property change each time setData is called.
     * Wrapper classes listen to determine if the the event is relevant to
     * them.
     */
    private PropertyChangeSupport c_changes = new PropertyChangeSupport(this);

    private ConfigData() {
        //TODO: does this really make sense to listen to itself?
        c_changes.addPropertyChangeListener(this);

        //default data
        synchronized (LOCK) {

            //special storage with all the default values
            c_defaultConfig = new DefaultConfigStorage();
            c_defaultConfig.load();

            c_storage = new ArrayList<ConfigStorage>();
            c_storage.add(c_defaultConfig);
            c_reportMap = new HashMap<String, String>();
            c_reportChangeMap = new HashMap<String, Boolean>();

        }

    }

    private void fixUp() {
        //if there is no cligen index then use the cligen data and replace the extension with .idx
        String cliIndex = getDataParsed(CliIndex);
        if (cliIndex == null || cliIndex.trim().length() == 0) {
            //no cligen index, build from the clidata value
            String data = getDataParsed(CliData);
            if (data != null && data.trim().length() > 0) {
                cliIndex = Util.purgeExtensions(data, ".par") + ".idx";
                setData(CliIndex, cliIndex);
            }
        }

        //if there is no wingen index then use the windgen data and replace the extension with .idx
        String winIndex = getDataParsed(WinIndex);
        if (winIndex == null || winIndex.trim().length() == 0) {
            //no cligen index, build from the clidata value
            String data = getDataParsed(WinData);
            if (data != null && data.trim().length() > 0) {
                winIndex = Util.purgeExtensions(data, ".wdb") + ".idx";
                setData(WinIndex, winIndex);
            }
        }
    }

    public static boolean isNRMVVis()
    {
        return NRMVVis;
    }
    
    public static boolean isToCollapse()
    {
        return toCollapse;
    }
    
    private void applyCompatibility() {
        //are we in nrcs compatibility mode?
        if ("1".equals(getData(CompatibilityNRCS))) {
            //yes, nrcs install
            setData(DefaultRunMode, ConfigData.NRCS);

            //user is not allowed to change the modes.            
            setAccessLevel(DefaultRunMode, AccessLevel.lower(getAccessLevel(DefaultRunMode), AccessLevel.Read));
            setAccessLevel(NRCSRunLen, AccessLevel.lower(getAccessLevel(NRCSRunLen), AccessLevel.Read));

            //reports
            setData(ReportsCustomized, "nrcs");
        }
    }

    /**
     *
     * @return
     */
    public static ConfigData getDefault() {
        if (c_default == null) {
            c_default = new ConfigData();
        }
        return c_default;
    }

    /**
     *
     * @param main
     * @param user
     */
    public void load(TFile main, TFile user) {
        c_storage = new ArrayList<ConfigStorage>();

        //main config
        if (main != null && main.exists()) {
            LOGGER.info("Config File: \"" + main.getAbsolutePath() + "\"");
            synchronized (LOCK) {
                c_mainConfig = new ConfigStorage(main);
                c_mainConfig.load();
                c_storage.add(c_mainConfig);
            }
        } else {
            LOGGER.warn("Config File: NULL");
            LOGGER.warn("Using a default main configuration.");
        }

        //user's config
        if (user != null) {
            synchronized (LOCK) {
                LOGGER.info("User Config File: \"" + user.getAbsolutePath() + "\"");
                c_userConfig = new ConfigStorage(user);
                c_userConfig.load();
                c_storage.add(0, c_userConfig);
            }
        } else {
            LOGGER.info("User Config File: NULL");
        }

        //special storage with all the default values
        c_storage.add(c_defaultConfig);
        applyCompatibility();
        fixUp();
    }

    /**
     *
     * @return
     */
    public TFile getMainFile() {
        return c_mainConfig.c_file;
    }

    /**
     *
     * @return
     */
    public TFile getUserFile() {
        return c_userConfig.c_file;
    }

    /**
     *
     * @param propertyName
     * @return
     */
    public AccessLevel getAccessLevel(String propertyName) {
        AccessLevel level = null;
        if (c_accessLevels != null) {
            level = c_accessLevels.get(propertyName);
        }
        return level != null ? level : AccessLevel.Write;
    }

    /**
     *
     * @param propertyName
     * @param level
     */
    protected void setAccessLevel(String propertyName, AccessLevel level) {
        if (c_accessLevels == null) {
            c_accessLevels = new HashMap<String, AccessLevel>();
        }
        c_accessLevels.put(propertyName, level);
    }

    /**
     *
     * @return
     */
    public boolean isChanged() {
        return c_changed;
    }

    /**
     * Gets data from hashtable.
     * 
     * @param propertyName Specifies which datum to retrieve using public static final String parameters.     
     * @return null if data is not in storage
     */
    public String getData(String propertyName) {
        synchronized (LOCK) {
            if(propertyName.startsWith(REP))
            {
                return c_reportMap.get(propertyName);
            }
            for (ConfigStorage storage : c_storage) {
                String value = storage.get(propertyName);
                if (value != null) {
                    return value;
                }
            }
            return null;
        }
    }

    /**
     *
     * @param propertyName
     * @return
     */
    public String getDataParsed(String propertyName) {
        return Util.parse(getData(propertyName));
    }

    /**
     * Puts data into hashtable.
     * 
     * @param propertyName Specifies which datum to save using public static final String parameters.
     * @param newData Value of datum saved.
     */
    public void setData(String propertyName, String newData) {
        synchronized (LOCK) {
            if (c_userConfig == null) {
                //no user config can't set values, this could break things!
                return;
            }
        }
        
        
        if(propertyName.equals(windDecNum))
        {
            if(newData != null && !newData.equals(""))
            {
                WindgenDecimal = "#0.";
                try
                {
                    for(int index = 0; index < Integer.parseInt(newData); index ++)
                    {
                        WindgenDecimal += "0";
                    }
                    System.out.println(WindgenDecimal);
                }
                catch(NumberFormatException fail)
                {
                    LOGGER.warn("windDecNum must be an integer.  Resetting to 5.");
                    //Something went wrong.  Reset to Decimal Value
                    setData(windDecNum, "5");
                    return;
                }
            }
            else
            {
                //Something went wrong.  Reset to Decimal Value
                setData(windDecNum, "5");
                return;
            }
        }
        
        if(propertyName.equals(ReportsConfidenceIntervalEnabled))
        {
            changeCI(newData);
        }

        if (newData != null) {
            newData = newData.trim();
        } else {
            newData = "";
        }

        String oldData = getData(propertyName);
        if (newData.equals(oldData)) {
            //no change, leaving
            return;
        }

        //there is a change, do we have permission
        AccessLevel level = getAccessLevel(propertyName);

        //we only need permission on CD properties, others are not saved to file
        if (!CurrentProj.equals(propertyName) && (propertyName.startsWith(CD) || propertyName.startsWith(REP)) && !AccessLevel.Write.equals(level)) {
            //we don't have access level to change this value.
            return;
        }

        synchronized (LOCK) {
            if(propertyName.startsWith(CD) || propertyName.equals(DefaultRunsLocation)) 
                c_userConfig.set(propertyName, newData);
            else if(propertyName.startsWith(REP)) 
            {
                c_reportMap.put(propertyName, newData);
                c_reportChangeMap.put(propertyName, true);
            }
        }

        c_changes.firePropertyChange(propertyName, oldData, newData);

        if (propertyName.equals(DefaultRunsLocationTemplate)) {
            updateDefaultRunsLocation();
        }
        if (propertyName.equals(CurrentProj)) {
            updateDefaultRunsLocation();
        }
    }

    private Collection<String> keys() {
        synchronized (LOCK) {
            List<String> temp = new LinkedList<String>();
            for (ConfigStorage storage : c_storage) {
                for (String key : storage.keys()) {
                    if (!temp.contains(key)) {
                        temp.add(key);
                    }
                }
            }
            return temp;
        }
    }

    /**
     *
     */
    public void fireAll() {
        boolean firedDefaultRunLocation = false;
        for (String key : keys()) {
            if (DefaultRunsLocation.equals(key)) {
                firedDefaultRunLocation = true;
            }
            String value = getData(key);
            c_changes.firePropertyChange(key, null, value);
        }

        if (!firedDefaultRunLocation) {
            updateDefaultRunsLocation();
        }
    }

    /**
     *
     * @param listeners
     */
    public void fireAll(PropertyChangeListener... listeners) {
        boolean firedDefaultRunLocation = false;
        for (String key : keys()) {
            if (DefaultRunsLocation.equals(key)) {
                firedDefaultRunLocation = true;
            }
            String value = getData(key);
            PropertyChangeEvent event = new PropertyChangeEvent(this, key, null, value);
            for (PropertyChangeListener listener : listeners) {
                listener.propertyChange(event);
            }
        }
        if (!firedDefaultRunLocation) {
            updateDefaultRunsLocation();
        }
    }

    /**
     *
     */
    public void save() {
        if (c_userConfig != null) {
            c_userConfig.save();
            boolean old = c_changed;
            c_changed = false;
            c_changes.firePropertyChange(PROP_CHANGED, old, false);
        } else {
            LOGGER.warn("User config is null.  Unable to save configuration.");
        }
    }

    /**
     * 
     * @param l 
     */
    public void addPropertyChangeListener(PropertyChangeListener l) {
        c_changes.addPropertyChangeListener(l);
    }

    /**
     * 
     * @param l 
     */
    public void removePropertyChangeListener(PropertyChangeListener l) {
        c_changes.removePropertyChangeListener(l);
    }

    /**
     * 
     * @param propertyName
     * @param l 
     */
    public void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
        c_changes.addPropertyChangeListener(propertyName, l);
    }

    /**
     * 
     * @param propertyName
     * @param l 
     */
    public void removePropertyChangeListener(String propertyName, PropertyChangeListener l) {
        c_changes.removePropertyChangeListener(propertyName, l);
    }

    /**
     * 
     * @param e 
     */
    @Override
    public void propertyChange(PropertyChangeEvent e) {
        String propertyName = e.getPropertyName();
        if (propertyName == null) {
            return;
        }
        if (!(propertyName.startsWith(CD) || propertyName.startsWith(REP))) {
            return;
        }
        String valstr = (String) e.getNewValue();
        setData(propertyName, valstr);

        if ((propertyName.startsWith(CD) || propertyName.startsWith(REP))) {
            boolean old = c_changed;
            c_changed = true;
            c_changes.firePropertyChange(PROP_CHANGED, old, true);
        }

    }

    public void updateDefaultRunsLocation() {
        try {
            String template = getData(DefaultRunsLocationTemplate);
            template = Util.parse(template);
            TFile file = new TFile(template);
            String parsedPath = file.getAbsolutePath();

            //Update listeners to the parsed template path
            setData(DefaultRunsLocation, parsedPath);
        } catch (NullPointerException e) {
        }
    }

    private class ConfigStorage {

        protected Map<String, String> c_data;
        private TFile c_file;

        protected ConfigStorage() {
        }

        public ConfigStorage(TFile file) {
            this();
            c_file = file;
        }

        public Collection<String> keys() {
            synchronized (this) {
                if (c_data == null) {
                    load();
                }
            }
            return c_data.keySet();
        }

        public String get(String propertyName) {
            synchronized (this) {
                if (c_data == null) {
                    load();
                }
            }
            return c_data.get(propertyName);
        }

        public void set(String propertyName, String value) {
            synchronized (this) {
                if (c_data == null) {
                    load();
                }
            }
            c_data.put(propertyName, value);
        }

        protected void load() {
            try {
                synchronized (this) {
                    c_data = new HashMap<String, String>();
                }

                if (!c_file.exists()) {
                    return;
                }

                SAXBuilder builder = new SAXBuilder();
                TFileInputStream in = new TFileInputStream(c_file);
                Document document = builder.build(in);

                Element root = document.getRootElement();
                for (Element parameter : root.getChildren(XML_PARAMETER)) {
                    String key = parameter.getChildText(XML_NAME);
                    String value = parameter.getChildText(XML_VALUE);
                    if(key.equals(NRMVVisibility)) NRMVVis = value.equalsIgnoreCase("true");
                    if(key.equals(CollapseOp)) toCollapse = value.equals("1");
                    if(key.equals(windDecNum))
                    {
                        if(value != null && !value.equals(""))
                        {
                            WindgenDecimal = "#0.";
                            try
                            {
                                for(int index = 0; index < Integer.parseInt(value); index ++)
                                {
                                    WindgenDecimal += "0";
                                }
                                System.out.println(WindgenDecimal);
                            }
                            catch(NumberFormatException fail)
                            {
                                LOGGER.warn("windDecNum must be an integer.  Resetting to 5.");
                                //Something went wrong.  Resetting to default.
                                WindgenDecimal = "#0.00000";
                                synchronized (this) {
                                    c_data.put(windDecNum, "5");
                                }
                            }
                        }
                        else
                        {
                            //Something went wrong.  Resetting to default.
                            WindgenDecimal = "#0.00000";
                            synchronized (this) {
                                c_data.put(windDecNum, "5");
                            }
                        }
                    }

                    //UPGRADE PATHS
                    //ignore the runtypedisp value from the config, this is only in the runfiledata now
                    if ("CD-runtypedisp".equals(key)) {
                        LOGGER.info("Parameter \"" + key + "\" deprecated  use " + DefaultRunMode);
                        continue;
                    }
                    //END UPGRADE PATHS

                    //read and store the access level
                    //store the value
                    
                    if(key.startsWith(REP)) synchronized (this) 
                    { 
                        c_reportMap.put(key, value != null ? value : ""); 
                        c_reportChangeMap.put(key, false);
                    }
                    else synchronized (this) { c_data.put(key, value != null ? value : ""); }

                    String levelText = parameter.getChildText(XML_ACCESS);
                    AccessLevel level = AccessLevel.parse(levelText);
                    setAccessLevel(key, level);
                    if (level != null) {
                        setAccessLevel(key, level);
                    }
                    if(key.equals(windDecNum) && level == null) setAccessLevel(key, AccessLevel.Hidden);
                }
                synchronized(this)
                {
                        if(!NRMVVis)
                        {
                            set(NRMVMode, "off");
                            set(CropOutput, "Not Visible");
                            set(DAboveOutput, "Not Visible");
                            set(DBelowOutput, "Not Visible");
                            set(DecompOutput, "Not Visible");
                            set(HLayersOutput, "Not Visible");
                            set(HydroOutput, "Not Visible");
                            set(PlotOutput, "Not Visible");
                            set(SeasonOutput, "Not Visible");
                            set(ShootOutput, "Not Visible");
                            set(SoilLayOutput, "Not Visible");
                        }
                }

                /*if (c_data.get(DefaultRunMode) == "NRCS") {
                 c_data.get(LOCK)
                 }*/
            } catch (UTFDataFormatException udfe) {
                LOGGER.error("Unable to load XML configuration file." + c_file.getPath(), udfe);
            } catch (JDOMException jde) {
                if (jde.getMessage().indexOf("Content is not allowed in prolog") > 0) {
                    return;
                }//Assume this is not an xml file.

                LOGGER.error("Unable to load XML configuration file. " + c_file.getPath(), jde);
            } catch (IOException ioe) {
                LOGGER.error("Unable to load XML configuration file." + c_file.getPath(), ioe);
            }
        }

        protected void save() {
            DocType type = new DocType(XML_CONFIGURATION);
            String dtd = "<!ELEMENT configuration (parameter*)>";
            dtd += "<!ELEMENT parameter (name, value)>";
            dtd += "<!ELEMENT name (#PCDATA)>";
            dtd += "<!ELEMENT value (#PCDATA)>";
            dtd += "<!ATTLIST configuration version CDATA #REQUIRED>";
            type.setInternalSubset(dtd);
            Element root = new Element(XML_CONFIGURATION);
            Document document = new Document(root, type);

            root = document.getRootElement().setAttribute(XML_VERSION, "1.0");

            for (String key : keys()) {
                String value = get(key);
                AccessLevel level = getAccessLevel(key);

                //Special case, we write current project even if it's hidden.
                if (!CurrentProj.equals(key) && !AccessLevel.Write.equals(level)) {
                    //skip ones that we aren't allowed to write
                    continue;
                }

                //only save config values
                if (!key.startsWith(CD)) {
                    continue;
                }

                //only save if different from main
                synchronized (LOCK) {
                    String main = c_mainConfig.get(key);
                    if (areStringsEqual(main, value)) {
                        continue;
                    }
                }

                Element node = new Element(XML_PARAMETER);
                root.addContent(node);

                Element nameNode = new Element(XML_NAME);
                nameNode.setText(key);
                node.addContent(nameNode);

                Element valueNode = new Element(XML_VALUE);
                valueNode.setText(value);
                node.addContent(valueNode);
                
                Element accessNode = new Element(XML_ACCESS);
                accessNode.setText(level.toString());
                node.addContent(accessNode);

            }
            for(String key : c_reportMap.keySet())
            {
                String value = c_reportMap.get(key);
                AccessLevel level = getAccessLevel(key);
                Boolean changed = c_reportChangeMap.get(key);
                if(changed == null || !changed.booleanValue()) continue;
                
                if(!key.startsWith(REP)) continue;
                
                Element node = new Element(XML_PARAMETER);
                root.addContent(node);

                Element nameNode = new Element(XML_NAME);
                nameNode.setText(key);
                node.addContent(nameNode);

                Element valueNode = new Element(XML_VALUE);
                valueNode.setText(value);
                node.addContent(valueNode);
                
                Element accessNode = new Element(XML_ACCESS);
                accessNode.setText(level.toString());
                node.addContent(accessNode);
            }

            //Write the XML File
            try {
                //setup the folder structure if needed.
                java.io.File parent = c_file.getParentFile();
                if (parent != null && !parent.exists()) {
                    if (!parent.mkdirs()) {
                        if (!parent.exists()) {
                            throw new IOException("can't create directory");
                        }
                    }
                }
                try (TFileWriter writer = new TFileWriter(c_file)) {
                    XMLOutputter serializer = new XMLOutputter();
                    serializer.setFormat(Format.getPrettyFormat());
                    serializer.output(document, writer);
                }
            } catch (IOException ioe) {
                LOGGER.error("Error writing config file \"" + c_file.getPath() + "\".", ioe);
            }
        }
    }

    //special storage that has all the default values, sits at the bottom of the stack
    private class DefaultConfigStorage extends ConfigStorage {

        @Override
        protected void load() {
            synchronized (this) {
                c_data = new HashMap<String, String>();
            }

            set(WepsExe, "bin/Windows/weps.exe");
            set(WepsCmd, "-W1 -u0 -I2 -t1 -T1");
            set(CalWepsCmd, "-C15 -Z50 -T1");
            
            set(WinExe, "bin/Windows/wind_gen4.exe");
            set(WinCmd, "-f${windgen.data}");
            set(WinData, "${weps.databases}/db/windgen/wind_gen_his_upper_US.wdb");
            set(WinIndex, "${weps.databases}/db/windgen/wind_gen_his_upper_US.idx");
            set(WindgenInterpolationBoundaryFile, "${weps.databases}/db/windgen/interpolation_boundary.pol");
            set(WindInterp1EXE, "bin/Windows/interpolate.exe");
            set(WindInterp2EXE, "bin/Windows/interp_wdb.exe");
            set(WindRadius, "241.4");
            set(WindgenAllowedModes, "file,interpolated,choice,nrcs,gis,nearest");
            set(WindFlag, "0");
            
            set(CliExe, "bin/Windows/cligen.exe");
            set(CliCmd, "-i${cligen.data} -t5 -I3 -F");
            set(CliData, "${weps.databases}/db/cligen/upd_US_cligen_stations.par");
            set(CliIndex, "${weps.databases}/db/cligen/upd_US_cligen_stations.idx");
            set(ClimateRadius, "160.94");
            set(CligenAllowedModes, "file,choice,nrcs,gis,nearest");
            set(ClimateFlag, "0");
            
            set(ManTemp, "${weps.databases}/db/man");
            set(ManTempSaveAs, "${weps.databases}/db/man");
            set(ManSkel, "${weps.databases}/db/skel");
            set(ManDB, "${weps.databases}/db/skel");
            set(CropDB, "${weps.databases}/db/crops");
            set(MCREW, "mcrew_cfg");
            set(ProjDir, "${user.documents}/WEPS Files");
            set(DefaultRunsLocationTemplate, "${user.documents}/WEPS Files/Runs");
            set(McrewDataConfigFile, "dataconfig.xml");
            set(SkelTranslationFile, "${weps.databases}/db/RUSLE2_Translation.txt");
            set(ReportFileName, "${weps.databases}/db/RUSLE2_Translation.txt");
            set(DetailTableFilterFile, "tables/detail_filters.xml");
            set(BarriersFile, "${weps.databases}/db/barriers/barrier.dat");
            
            set(SoilDB, "${weps.databases}/db/soil");
            set(SoilOrganicFile, "${weps.databases}/db/soil/NRCS Generic Soils/Organic Soil.ifc");
            set(OMFractionThreshold, "0.15");
            set(SoilMaxOrganicDepth, "101.6");
            set(SoilTestOrganic, "1");
            set(SoilSkipOrganicSurfaceLayers, "1");
            set(SoilAverageStratifiedLayers, "1");
            set(SoilEstimate, "1");
            set(DoNotEstimateMissingSurgoValues, "0");
            set(SoilReadonly, "0");
            
            set(GISData, "${weps.databases}/db/gis");       
            
            set(OutputFreq, "2");
            set(OPHydroDet, "0");
            set(OPSoilDet, "0");
            set(OPManageDet, "0");
            set(OPCropDet, "0");
            set(OPDecompDet, "0");
            set(OPErosionDet, "1");
            set(OPHydroDbg, "0");
            set(OPSoilDbg, "0");
            set(OPManageDbg, "0");
            set(OPCropDbg, "0");
            set(OPDecompDbg, "0");
            set(OPErosionDbg, "0");
            set(ReportsConfidenceIntervalEnabled, "1");
            set(ReportsCropIntPerDetEnabled, "1");
            set(RunVis, "1");
            set(ManVis, "1");
            set(CropVis, "1");
            set(CCrVis, "1");
            set(CIntVis, "1");
            set(STIRVis, "1");
            set(DetVis, "1");
            set(ConfVis, "1");
            set(ReportsView, "run");
            set(ReportsGeneratePDF, "run,management,cropdetail,cropsummary,cropintervalsum,stir,covercropsummary,covercropdetail,ci");
            set(ReportsDirectory, "reports/");
            
            
            set(UseDefaultMailClient, "1");
            set(UseOutlookFlg, "0");
            set(MailHost, "");
            set(EmailSender, "");
            set(CommentAddr, "weps@ars.usda.gov");
            set(BugsAddr, "weps@ars.usda.gov");
            set(MantisEmail, "");
            set(MantisURL, "https://infosys.ars.usda.gov/issues/api/soap/mantisconnect.php");
            set(MantisUser, "");
            set(MantisPassword, "");
            set(MantisProject, "weps2.gui");
            
            set(DefaultRunMode, "Cycle");
            set(NRCSRunLen, "50");
            set(PurgeRunFiles, "win_gen.win:cli_gen.cli");
            set(PurgeRunFilesFlg, "0");
            set(AllowScriptCreation, "1");
            
            set(Units, "SI");
            set(TimeSteps, "24");
            set(DispLatLon, "1");
            set(DispStCty, "1");
            set(DispElevation, "1");
            set(UseMap, "1");
            set(ReadonlySlope, "0");
            set(ReadonlyRockFragments, "1");
            set(BarriersReadonly, "0");
            set(McrewEdit, "1");
            set(FormatsXML, "0");
            set(DoNotWarnAboutSystemLocale, "1");
            set(DoNotReviewWarningsWhenRestoringRun, "0");
            set(HideProjectFileButtons, "0");
            set(HideTemplatetFileButtons, "0");
            set(SiteChooserShowCountry, "1");
            set(DaysToKeepLogs, "5");
            set(TTInit, "2000");
            set(TTDismiss, "2000");
            set(windDecNum, "5");
            set(WindgenDecimal, "#0.00000");
            set(FormatOperationDate, "dd MMM yy");
            set(CollapseOp, "0");
            set(CompatibilityNRCS, "0");
            set(ReportsCustomized, "");
            set(NRMVMode, "off");
            set(SingleProjectMode, "0");
            set(CurrentProj, "${user.documents}/WEPS Files/Project.wpj");
            set(SubDailyFlag, "0");
            set(FuelDatabase, "${weps.databases}/db/fuels/fuels.xml");
            set(FuelDefault, "diesel");
            set(ShowWeatheFilerCheckboxes, "1");
            
            set(SubmodelOutputs, "notVisible");
            
            setAccessLevel(windDecNum, ConfigData.AccessLevel.Hidden);

            //NRMV Outputs
            set(CropOutput, "Read Write");
            set(DAboveOutput, "Read Write");
            set(DBelowOutput, "Read Write");
            set(DecompOutput, "Read Write");
            set(HLayersOutput, "Read Write");
            set(HydroOutput, "Read Write");
            set(PlotOutput, "Read Write");
            set(SeasonOutput, "Read Write");
            set(ShootOutput, "Read Write");
            set(SoilLayOutput, "Read Write");
        }

        @Override
        protected void save() {
            //do nothing
        }
    }

    private static boolean areStringsEqual(String a, String b) {
        if (a == null && b == null) {
            return true;
        } else if (a != null && b != null) {
            return a.equals(b);
        } else {
            return false;
        }
    }

    /**
     *
     * @return
     */
    public boolean getSoilTestOrganic() {
        return "1".equals(getData(SoilTestOrganic));
    }

    /**
     *
     * @return
     */
    public Measurable<Length> getSoilMaxOrganicDepth() {
        String valueString = getData(SoilMaxOrganicDepth);
        try {
            double depthMiliMeters = Double.valueOf(valueString);
            return Measure.valueOf(depthMiliMeters, SI.MILLIMETER);
        } catch (NumberFormatException e) {
            LOGGER.warn("Unable to parse the soil max organic depth. Defaulting to 4 inches." + valueString);
            return Measure.valueOf(4, NonSI.INCH);
        }
    }

    /**
     *
     * @return
     */
    public boolean isAverageStratifiedSoilLayers() {
        String value = getData(SoilAverageStratifiedLayers);
        return value != null ? value.trim().equals("1") : false;
    }

    /**
     *
     * @return
     */
    public boolean isSkipOrganicSoilSurfaceLayers() {
        String value = getData(SoilSkipOrganicSurfaceLayers);
        return value != null ? value.trim().equals("1") : false;
    }

    //TODO: fire changes when the underlying data changes
    /**
     *
     */
    public static final String PROP_ALLOWED_CLIGEN_STATION_MODES = "allowedCligenStationModes";

    /**
     *
     * @return
     */
    public StationMode[] getAllowedCligenStationModes() {
        return parseAllowedStationModes(getData(CligenAllowedModes));
    }

    /**
     *
     */
    public static final String PROP_ALLOWED_WINDGEN_STATION_MODES = "allowedWindgenStationModes";

    /**
     *
     * @return
     */
    public StationMode[] getAllowedWindgenStationModes() {
        return parseAllowedStationModes(getData(WindgenAllowedModes));
    }

    /**
     *
     */
    public static final String PROP_CLIGEN_SEARCH_RADIUS = "cligenSearchRadius";

    /**
     *
     * @return
     */
    public Measurable<Length> getCligenSearchRadius() {
        String valueString = getData(ClimateRadius);
        try {
            double distanceMeters = Double.parseDouble(valueString.trim());
            return Measure.<Length>valueOf(distanceMeters, SI.KILOMETER);
        } catch (NumberFormatException e) {
            LOGGER.warn("Unable to parse the cligen radius limit. " + valueString);
            return null;
        }
    }

    /**
     *
     */
    public static final String PROP_CLIGEN_SEARCH_LIMIT = "cligenSearchLimit";

    /**
     *
     * @return
     */
    public int getCligenSearchLimit() {
        return 50;
    }

    /**
     *
     */
    public static final String PROP_WINDGEN_SEARCH_RADIUS = "windgenSearchRadius";

    /**
     *
     * @return
     */
    public Measurable<Length> getWindgenSearchRadius() {
        String valueString = getData(WindRadius);
        try {
            double distanceMeters = Double.parseDouble(valueString.trim());
            return Measure.<Length>valueOf(distanceMeters, SI.KILOMETER);
        } catch (NumberFormatException e) {
            LOGGER.warn("Unable to parse the windgen radius limit. " + valueString);
            return null;
        }
    }

    /**
     *
     */
    public static final String PROP_WINDGEN_SEARCH_LIMIT = "windgenSearchLimit";

    /**
     *
     * @return
     */
    public int getWindgenSearchLimit() {
        return 50;
    }

    private static StationMode[] parseAllowedStationModes(String modes) {
        if (modes == null || modes.trim().length() == 0) {
            return new StationMode[0];
        }
        EnumSet<StationMode> c_allowedModes = null;
        String[] parts = modes.split(",");
        for (String part : parts) {
            StationMode mode = StationMode.parse(part.trim());
            if (mode != null) {
                if (c_allowedModes == null) {
                    c_allowedModes = EnumSet.of(mode);
                } else {
                    c_allowedModes.add(mode);
                }
            }
        }

        return c_allowedModes.toArray(new StationMode[c_allowedModes.size()]);
    }

    /**
     *
     */
    public void logConfiguration() {
        try {
            StringBuilder buffer = new StringBuilder();
            List<String> sortedKeys = new LinkedList<String>(keys());
            Collections.sort(sortedKeys);
            for (String key : sortedKeys) {
                //USER
                String value = c_userConfig.get(key);
                String type = "user";

                //MAIN
                if (value == null) {
                    value = c_mainConfig.get(key);
                    type = "main";
                }

                //DEFAULT
                if (value == null) {
                    value = c_defaultConfig.get(key);
                    type = "default";
                }

                if (value == null) {
                    value = "null";
                    type = null;
                }

                buffer.append("\t" + key + (type != null ? "(" + type + ")" : "") + "= " + value + "\n");

                String valueParsed = Util.parse(value);
                if (!value.equals(valueParsed)) {
                    buffer.append("\t" + key + "(parsed) = " + valueParsed + "\n");
                }

            }

            LOGGER.debug("Current Configuration:\n" + buffer.toString());
        } catch (Exception e) {
            LOGGER.warn("Unable to log configuration.", e);
        }
    }

    /**
     *
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public static @interface MeasureableConfigurationOption {

        /**
         *
         * @return
         */
        String units();

        /**
         *
         * @return
         */
        String alternativeUnits();

        /**
         *
         * @return
         */
        boolean isDouble() default true;

    }

    private Map<String, MeasureableConfigurationOption> c_measureableConfigurationOptions;

    private synchronized Map<String, MeasureableConfigurationOption> measureableOptions() {
        if (c_measureableConfigurationOptions == null) {
            c_measureableConfigurationOptions = new HashMap<String, MeasureableConfigurationOption>();
            //loop over all the fields in the configdata and record all those with the FileConfigurationOption annotation
            for (Field field : ConfigData.class.getFields()) {
                try {
                    //if final static and annotated
                    if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())
                            && field.isAnnotationPresent(MeasureableConfigurationOption.class)) {
                        MeasureableConfigurationOption annotation
                                = field.getAnnotation(MeasureableConfigurationOption.class);
                        String propertyName = field.get(null).toString();
                        c_measureableConfigurationOptions.put(propertyName, annotation);
                    }
                } catch (IllegalAccessException | IllegalArgumentException e) {
                    LOGGER.error("Initilizing", e);
                }
            }
        }
        return c_measureableConfigurationOptions;
    }

    /**
     *
     * @param propertyName
     * @return
     */
    public MeasureableConfigurationOption getMeasureableConfigurationOption(String propertyName) {
        return measureableOptions().get(propertyName);
    }

    /**
     *
     * @param propertyName
     * @return
     */
    public Unit<Quantity> getMeasureableUnit(String propertyName) {
        MeasureableConfigurationOption option = getMeasureableConfigurationOption(propertyName);

        if (option != null) {
            @SuppressWarnings("unchecked")
            Unit<Quantity> units = (Unit<Quantity>) Unit.valueOf(option.units());
            return units;
        }
        return null;
    }

    /**
     *
     * @param propertyName
     * @return
     */
    public Unit<Quantity> getMeasureableAlternativeUnit(String propertyName) {
        MeasureableConfigurationOption option = getMeasureableConfigurationOption(propertyName);

        if (option != null) {
            @SuppressWarnings("unchecked")
            Unit<Quantity> units = (Unit<Quantity>) Unit.valueOf(option.alternativeUnits());
            return units;
        }
        return null;
    }

    /**
     *
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public static @interface FileConfigurationOption {

        /**
         *
         * @return
         */
        boolean required() default true;

        /**
         *
         * @return
         */
        String parentOption() default "";

        /**
         *
         * @return
         */
        String defaultLocation() default "";
    }

    private Map<String, FileConfigurationOption> c_fileConfigurationOptions;

    private synchronized Map<String, FileConfigurationOption> fileOptions() {
        if (c_fileConfigurationOptions == null) {
            c_fileConfigurationOptions = new HashMap<String, FileConfigurationOption>();
            // loop over all the fields in the configdata and record all those with the FileConfigurationOption annotation
            for (Field field : ConfigData.class.getFields()) {
                try {
                    // if final static and annotated
                    if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())
                            && field.isAnnotationPresent(FileConfigurationOption.class)) {
                        FileConfigurationOption annotation = field.getAnnotation(FileConfigurationOption.class);
                        String propertyName = field.get(null).toString();
                        c_fileConfigurationOptions.put(propertyName, annotation);
                    }
                } catch (IllegalAccessException | IllegalArgumentException e) {
                    LOGGER.error("Initilizing", e);
                }
            }
        }
        return c_fileConfigurationOptions;
    }

    /**
     *
     * @param propertyName
     * @return
     */
    public boolean isFileConfigurationOption(String propertyName) {
        return fileOptions().containsKey(propertyName);
    }

    /**
     *
     * @param propertyName
     * @return
     */
    public FileConfigurationOption getFileConfigurationOption(String propertyName) {
        return fileOptions().get(propertyName);
    }

    /**
     *
     * @return
     */
    public String[] getFileConfigurationOptions() {
        Collection<String> temp = fileOptions().keySet();
        return temp.toArray(new String[temp.size()]);
    }

    /**
     *
     * @return
     */
    public boolean isFileReferenceErrorInConfigData() {
        boolean failed = false;
        for (Map.Entry<String, FileConfigurationOption> entry : fileOptions().entrySet()) {
            //exit on the first error to be as fast as possible
            String propertyName = entry.getKey();
            FileConfigurationOption meta = entry.getValue();
            if (getAccessLevel(propertyName) != AccessLevel.Hidden) {
                if (meta.required()) {
                    //test if the value exists as a file
                    TFile parentFile = null;
                    String parentPropertyName = meta.parentOption();
                    if (parentPropertyName != null && parentPropertyName.trim().length() > 0) {
                        String parentPath = getDataParsed(parentPropertyName);
                        if (parentPath != null) {
                            parentFile = new TFile(Util.parse(parentPath));
                        }
                    }

                    String path = getDataParsed(propertyName);
                    boolean error = false;
                    if (path != null) {
                        TFile file = new TFile(path);
                        if (!file.isAbsolute() && parentFile != null) {
                            file = new TFile(parentFile, Util.parse(path));
                        }

                        error = !file.exists();
                    }
                    if (error) {
                        failed = true;
                        LOGGER.warn("Unable to resolve file reference for : "
                                + propertyName + "=" + getData(propertyName));
                    }
                }
            }
        }
        return failed;
    }

    /**
     *
     * @return
     */
    public boolean isSiteChooserShowCountries() {
        return "1".equals(getData(SiteChooserShowCountry));
    }

    /**
     *
     * @return
     */
    public boolean isPurgeFlag() {
        return "1".equals(getData(PurgeRunFilesFlg));
    }

    /**
     *
     * @return
     */
    public boolean isSoilReadonly() {
        return "1".equals(getData(SoilReadonly));
    }

    /**
     *
     * @return
     */
    public boolean isFormatXMLAllowed() {
        return "1".equals(getData(FormatsXML));
    }

    /**
     *
     * @return
     */
    public boolean isReportConfidenceIntervalEnabled() {
        return "1".equals(getData(ReportsConfidenceIntervalEnabled));
    }

    public boolean isReportCropIntPerDetEnabled()
    {
        return "1".equals(getData(ReportsCropIntPerDetEnabled));
    }
    
    /**
     *
     * @return
     */
    public String[] getPurgeFilenames() {
        String filter = getData(PurgeRunFiles);
        filter = filter != null ? filter.trim() : null;

        if (filter == null || filter.length() == 0) {
            return new String[0];
        }

        String[] parts = filter.split(":");
        for (int i = 0; i < parts.length; i++) {
            parts[i] = parts[i].trim();
        }
        return parts;
    }

    /**
     * Always returns USD.  Placeholder for future potential support of other
     * currencies.
     * @return USD
     */
    public Currency getCurrency() {
        return Currency.USD;
    }

    /**
     *
     * @return
     */
    public boolean isCompatibilityNRCS() {
        return "1".equals(getData(CompatibilityNRCS));
    }
    
    private void changeCI(String value)
    {
        if(value.equals("1"))
        {
            setData(ReportsGeneratePDF, "run,management,cropdetail,cropsummary,cropintervalsum,stir,covercropsummary,covercropdetail,ci");
        }
        else if(value.equals("0"))
        {
            setData(ReportsGeneratePDF, "run,management,cropdetail,cropsummary,cropintervalsum,stir,covercropsummary,covercropdetail");
        }
    }
    
    public boolean getReportVisibility(String reportName)
    {
        String val = c_reportMap.get(reportName);
        if(val == null) return true;
        else return val.equals("1");
    }

}
