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.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import tec.uom.se.quantity.Quantities;
import systems.uom.common.USCustomary;
import javax.measure.quantity.Length;
import tec.uom.se.unit.MetricPrefix;
import si.uom.SI;
import javax.measure.Unit;
import javax.measure.Quantity;
import tec.uom.se.unit.BaseUnit;
import javax.measure.quantity.Dimensionless;
import javax.swing.JOptionPane;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.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 static usda.weru.weps.serverControl.ServerControlData.cdKeyAddEndpoint;
import static usda.weru.weps.serverControl.ServerControlData.cdKeyWepsBase;


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

    public static final BaseUnit<Dimensionless> USD = new BaseUnit<>("usd");
    private static final Object LOCK = new Object();

    public enum AccessLevel {

        Write,
        Read,
        Hidden,
        Overridden;

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

        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 if (Overridden.equals(a) || Overridden.equals(b)) {
                return Overridden;
            } else {
                return null;
            }
        }

        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 (Overridden.equals(a) || Overridden.equals(b)) {
                return Overridden;
            } 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 = LogManager.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 XML_ACCESS_NRCS = "accessNRCS"; // Access generated for only NRCS specific configurations
    private static final String XML_ACCESS_NRCS_DEV = "accessNRCSdev"; // Access generated for only NRCS and DEV specific configurations
    public  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 SingleFileMode = true;
    private static boolean toCollapse = false;
    public static final String exeWinExt = CD + "Exe Win ext";
    public static final String exeLinuxExt = CD + "Exe Linux ext";
    public static final String exeWinRdir = CD + "Exe Win rdir";
    public static final String exeLinuxRdir = CD + "Exe Linux rdir";
    public static final String wepsBname = CD + "weps bname";
    public static final String cligenBname = CD + "cligen bname";
    public static final String windgenBname = CD + "windgen bname";
    public static final String interp1Bname = CD + "interp1 bname";
    public static final String interp2Bname = CD + "interp2 bname";
    public static final String wepsdbRdir = CD + "weps db rdir";
    public static final String isNRCSmode = CD + "isNRCSmode";
    public static final String isDEVmode = CD + "isDEVmode";
    //Directories Tab
    public static final String opRecordsCRLMOD = CD + "operation records crlmod";
    public static final String cropRecordsCRLMOD = CD + "crop records crlmod";
    public static final String residueRecordsCRLMOD = CD + "residue records crlmod";
    @FileConfigurationOption(defaultLocation = "db")
    public static final String localResidueRecords = CD + "shared_cropres_folder";
    @FileConfigurationOption(defaultLocation = "db")
    public static final String shopLocation = CD + "shared_oper_folder";
    //Cligen Tab
    public static final String cligenDataCRLMOD = CD + "cligen data crlmod";
    public static final String cligenIndexCRLMOD = CD + "cligen index crlmod";
    public static final String prismNormalsCRLMOD = CD + "prism normals crlmod";
    //Windgen Tab
    public static final String windgenDataCRLMOD = CD + "windgen data crlmod";
    public static final String windgenIndexCRLMOD = CD + "windgen index crlmod";
    //GIS Tab
    public static final String layerModeSuf = "Mode";
    public static final String layersModeValOutline = "outline";
    public static final String layersModeValFill = "fill";
    public static final String layerVisibleSuf = "Visible";
    public static final String layerEnabledSuf = "Enabled";
    public static final String layerOpacitySuf = "Opacity";
    public static final String cachedSoilLayerBase = CD + "imageryCachedSoilLayer";
    public static final String cachedSoilLayerVisible = cachedSoilLayerBase + layerVisibleSuf;
    public static final String cachedSoilLayerEnabled = cachedSoilLayerBase + layerEnabledSuf;
    public static final String cachedPrismLayerBase = CD + "imageryCachedPrismLayer";
    public static final String cachedPrismLayerVisible = cachedPrismLayerBase + layerVisibleSuf;
    public static final String cachedPrismLayerEnabled = cachedPrismLayerBase + layerEnabledSuf;
    public static final String usStatesLayerBase = CD + "imageryUsStatesLayer";
    public static final String usStatesLayerMode = usStatesLayerBase + layerModeSuf;
    public static final String usStatesLayerVisible = usStatesLayerBase + layerVisibleSuf;
    public static final String usStatesLayeEnabled = usStatesLayerBase + layerEnabledSuf;
    public static final String usStatesLayerOpacity = usStatesLayerBase + layerOpacitySuf;
    public static final String usCountiesLayerBase = CD + "imageryCountiesLayer";
    public static final String usCountiesLayerVisible = usCountiesLayerBase + layerVisibleSuf;
    public static final String usCountiesLayerEnabled = usCountiesLayerBase + layerEnabledSuf;
    public static final String worldBordersLayerBase = CD + "imageryWorldBordersLayer";
    public static final String worldBordersLayerMode = worldBordersLayerBase + layerModeSuf;
    public static final String worldBordersLayerVisible = worldBordersLayerBase + layerVisibleSuf;
    public static final String worldBordersLayerEnabled = cachedSoilLayerBase + layerEnabledSuf;
    public static final String chinaProvLayerBase = CD + "imageryChinaProvLayer";
    public static final String chinaProvLayerMode = chinaProvLayerBase + layerModeSuf;
    public static final String chinaProvLayerVisible = chinaProvLayerBase + layerVisibleSuf;
    public static final String chinaProvLayerEnabled = chinaProvLayerBase + layerEnabledSuf;
    public static final String chinaProvLayerOpacity = chinaProvLayerBase + layerOpacitySuf;
    public static final String wingenTriangulationLayerBase = CD + "wingenTriangulationLayer";
    public static final String wingenTriangulationLayerVisible = wingenTriangulationLayerBase + layerVisibleSuf;
    public static final String wingenTriangulationLayerEnabled = wingenTriangulationLayerBase + layerEnabledSuf;
    public static final String wingenReflectedLayerBase = CD + "wingenReflectedLayer";
    public static final String wingenReflectedLayerVisible = wingenReflectedLayerBase + layerVisibleSuf;
    public static final String wingenReflectedLayerEnabled = wingenReflectedLayerBase + layerEnabledSuf;
    public static final String wingenBoundaryLayerBase = CD + "wingenBoundaryLayer";
    public static final String wingenBoundaryLayerVisible = wingenBoundaryLayerBase + layerVisibleSuf;
    public static final String wingenBoundaryLayerEnabled = wingenBoundaryLayerBase + layerEnabledSuf;
    public static final String wingenRegionsLayerBase = CD + "wingenRegionsLayer";
    public static final String wingenRegionsLayerVisible = wingenRegionsLayerBase + layerVisibleSuf;
    public static final String wingenRegionsLayerEnabled = wingenRegionsLayerBase + layerEnabledSuf;
    public static final String wingenStationsLayerBase = CD + "wingenStationsLayer";
    public static final String wingenStationsLayerVisible = wingenStationsLayerBase + layerVisibleSuf;
    public static final String wingenStationsLayerEnabled = wingenStationsLayerBase + layerEnabledSuf;
    public static final String cligenBoundaryLayerBase = CD + "cligenBoundaryLayer";
    public static final String cligenBoundaryLayerVisible = cligenBoundaryLayerBase + layerVisibleSuf;
    public static final String cligenBoundaryLayerEnabled = cligenBoundaryLayerBase + layerEnabledSuf;
    public static final String cligenRegionsLayerBase = CD + "cligenRegionsLayer";
    public static final String cligenRegionsLayerVisible = cligenRegionsLayerBase + layerVisibleSuf;
    public static final String cligenRegionsLayerEnabled = cligenRegionsLayerBase + layerEnabledSuf;
    public static final String cligenStationsLayerBase = CD + "clienStationsLayer";
    public static final String cligenStationsLayerVisible = cligenStationsLayerBase + layerVisibleSuf;
    public static final String cligenStationsLayerEnabled = cligenStationsLayerBase + layerEnabledSuf;
    public static final String cmzLayerBase = CD + "cmzLayer";
    public static final String cmzLayerVisible = cmzLayerBase + layerVisibleSuf;
    public static final String cmzLayerEnabled = cmzLayerBase + layerEnabledSuf;
    public static final String imageryNationalmapLayerBase = CD + "imageryNationalmapLayer";
    public static final String imageryNationalmapLayerVisible = imageryNationalmapLayerBase + layerVisibleSuf;
    public static final String imageryNationalmapLayerEnabled = imageryNationalmapLayerBase + layerEnabledSuf;
    public static final String imageryNationalmapLayerOpacity = imageryNationalmapLayerBase + layerOpacitySuf;
    public static final String imageryArcgisLayerBase = CD + "imageryArcgisLayer";
    public static final String imageryArcgisLayerVisible = imageryArcgisLayerBase + layerVisibleSuf;
    public static final String imageryArcgisLayerEnabled = imageryArcgisLayerBase + layerEnabledSuf;
    public static final String imageryArcgisLayerOpacity = imageryArcgisLayerBase + layerOpacitySuf;
    public static final String imageryArcgisNatgeoLayerBase = CD + "imageryArcgisNatgeoLayer";
    public static final String imageryArcgisNatgeoLayerVisible = imageryArcgisNatgeoLayerBase + layerVisibleSuf;
    public static final String imageryArcgisNatgeoEnabled = imageryArcgisNatgeoLayerBase + layerEnabledSuf;
    public static final String imageryArcgisNatgeoLayerOpacity = imageryArcgisNatgeoLayerBase + layerOpacitySuf;
    public static final String topoArcgisLayerBase = CD + "topoArcgisLayer";
    public static final String topoArcgisLayerVisible = topoArcgisLayerBase + layerVisibleSuf;
    public static final String topoArcgisLayerEnabled = topoArcgisLayerBase + layerEnabledSuf;
    public static final String topoArcgisLayerOpacity = topoArcgisLayerBase + layerOpacitySuf;
    public static final String streetsArcgisLayerBase = CD + "streetsArcgisLayer";
    public static final String streetsArcgisLayerVisible = streetsArcgisLayerBase + layerVisibleSuf;
    public static final String streetsArcgisLayerEnabled = streetsArcgisLayerBase + layerEnabledSuf;
    public static final String streetsArcgisLayerOpacity = streetsArcgisLayerBase + layerOpacitySuf;
    public static final String transportOvlArcgisLayerBase = CD + "transportOvlArcgisLayer";
    public static final String transportOvlArcgisLayerVisible = transportOvlArcgisLayerBase + layerVisibleSuf;
    public static final String transportOvlArcgisLayerEnabled = transportOvlArcgisLayerBase + layerEnabledSuf;
    public static final String transportOvlArcgisLayerOpacity = transportOvlArcgisLayerBase + layerOpacitySuf;
    public static final String rangeTownshipLayerBase = CD + "rangeTownshipLayer";
    public static final String rangeTownshipLayerVisible = rangeTownshipLayerBase + layerVisibleSuf;
    public static final String rangeTownshipLayerEnabled = rangeTownshipLayerBase + layerEnabledSuf;
    public static final String rangeTownshipLayerOpacity = rangeTownshipLayerBase + layerOpacitySuf;
    public static final String layerCacheSizeLimit = CD + "layerCacheSizeLimit";;
    public static final String viewerDefaultZoom = CD + "ImageryViewerInitialZoom";
    public static final String viewerInitialSizeWidth = CD + "ImageryViewerInitialSizeWidth";
    public static final String viewerInitialSizeHeight = CD + "ImageryViewerInitialSizeHeight";
    
    //EL - for database reports -> these read in systemOpDB, ManTemp, systemCropDB but can store other locations too.
    public static final String OPERATION_DB = CD + "choosen_operation_folder";
    public static final String MANAGEMENT_DB = CD + "choosen_management_folder";
    public static final String CROP_DB = CD + "choosen_crops_folder";
    
//    public static final String viewerMaximumSizeWidth = CD + "ImageryViewerMaximumSizeWidth";
//    public static final String viewerMaximumSizeHeight = CD + "ImageryViewerMaximumSizeHeight";
    public static final String geotoolsTileExtentCount = CD + "GeotoolsTileExtentCount";
    //Old Man File Behavior
    public static final String oldMessageDisplay = CD + "Out_of_date_mgt_message_display";
    public static final String oldMessageDefault = CD + "Out_of_date_mgt_default";
    public static final String oldMessageOptions = CD + "Out_of_date_mgt_options";
    //Invalid Cligen Mode Display
    public static final String invalidCligenDisplay = CD + "Cligen_invalid_mode_detection";
    public static final String invalidCligenStation = CD + "Cligen_invalid_station_detection";
    //Radio Button Version Display
    public static final String mcrewVersionDisplay = CD + "mcrew Display set mgt version no radio buttons";
    //Dropdown Display Checkboxes
    public static final String manOpDbCheck = CD + "system_oper_cb";
    public static final String cropDbCheck = CD + "system_cropres_cb";
    public static final String sysManTemp = CD + "system_man_cb";
    public static final String locManTemp = CD + "local_man_cb";
    public static final String shaManTemp = CD + "shared_man_cb";
    public static final String systemSoilDataDir_CB = CD + "soil database-cb";
    public static final String localSoilDataDir_CB = CD + "LocalSoilDB-cb";
    public static final String localSoilDataMartDir_CB = CD + "LocalSDMDBLocation-cb";
    public static final String nrcsSoilDataMartURL_CB = CD + "WS-SDM-soils-params-host-cb";
    public static final String crlmodEnabled = CD + "CrLmod_man_cb";
    public static final String locCropCheck = CD + "local_cropres_cb";
    public static final String shaCropCheck = CD + "shared_cropres_cb";
    public static final String crlmodCropCheck = CD + "CrLmod_cropres_cb";
    public static final String loopCheck = CD + "local_oper_cb";
    public static final String shopCheck = CD + "shared_oper_cb";
    public static final String cropCheck = CD + "CrLmod_oper_cb";
    public static final String auxRefresh = CD + "display-aux-refresh";
    //Uncategorized CrLmod Operation List
    public static final String manVersionButtonsHide = CD + "mcrew-manverbuttonshide";
    public static final String noCat = CD + "crlmod-opr-nocat";    
    @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";
    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";
    @FileConfigurationOption(defaultLocation = "db")
    public static final String LocalCropDB = CD + "local_cropres_folder";    
    @FileConfigurationOption(defaultLocation = "db")
    public static final String SharedManDB = CD + "shared_man_folder";
    @FileConfigurationOption(defaultLocation = "db")
    public static final String LocalOpDB = CD + "local_oper_folder";    
    @FileConfigurationOption(defaultLocation = "db")
    public static final String LocalSoilDB = CD + "LocalSoilDB";
    public static final String LocalSDMDB = CD + "LocalSDMDBLocation";
    // Windgen specific
    public static final String WindgenAllowedModes = CD + "windgen.allowedmodes";
    public static final String WindgenEnabledModes = CD + "windgen.enabledmodes";
    public static final String WindgenDefaultMode = CD + "windgen.defaultmode";
    @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";
    public static final String CligenEnabledModes = CD + "cligen.enabledmodes";
    public static final String CligenDefaultMode = CD + "cligen.defaultmode";
    @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";
    public static final String UserFullName = CD + "user.fullname";
    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 AddDirectoryButton = CD + "AddDirectoryButton";    
    public static final String MantisMaxSize = CD + "MantisMaxSize";
    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 + "system_man_folder";
    @FileConfigurationOption(defaultLocation = "db")
    public static final String LocalManDB = CD + "local_man_folder";
    @FileConfigurationOption(defaultLocation = "db")
    public static final String ManSkel = CD + "management skeleton";
    @FileConfigurationOption(defaultLocation = "db")
    public static final String SystemCropDB = CD + "system_cropres_folder";
    @FileConfigurationOption(defaultLocation = "db")
    public static final String SystemUPGMDB = CD + "system_upgm_folder";
    @FileConfigurationOption(defaultLocation = ".")
    public static final String MCREW = CD + "MCREW";
    @FileConfigurationOption(defaultLocation = "db")
    public static final String SystemOpDB = CD + "system_oper_folder";
    @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";
    public static final String cachedCsipSoilDir_CB = CD + "WS-cached-csip-soils-dir-cb";
    public static final String csipSoilService_CB = CD + "WS-csip-soils-service-cb";
    public static final String SDMSoilSortOrder = CD + "SDM_soil_sort_order";
    public static final String SDMSoilSortMethod = CD + "SDM_soil_sort_method";
   
    @FileConfigurationOption
    public static final String ProjDir = CD + "projects path";
    public static final String DefaultProjName = CD + "default project name";
    @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 EnableEmptyManagementFilePopupMessage = CD + "disable-MCREW-access-with-no-file-specified";
    public static final String MCREWAutosort = CD + "mcrew autosort";    
    public static final String SingleFileLock = CD + "Single File Lock";
    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 cycleRunLen = CD + "cyclerunlength";
    public static final String TTInit = CD + "ToolTipInit";
    public static final String TTDismiss = CD + "ToolTipDismiss";
    @FileConfigurationOption()
    public static final String CurrentProj = CD + "current project";
    public static final String SetProjectOverwrite = CD + "setProjectOverwrite";
    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";
    public static final String FormatOperationDate = CD + "operation date format";
    @FileConfigurationOption(required = false)
    public static final String DefaultRunsLocationTemplate = CD + "default runs location Template";
    public static final String DefaultRunsLocation = RUNTIME_ONLY + CD + "default runs location";
    public static final String NewProjectDefaultRunsLocation = CD + "default runs location";
    public static final String PurgeRunFiles = CD + "purge.filefilter";
    public static final String PurgeRunFilesFlg = CD + "purge.flag";
    @FileConfigurationOption()
    public static final String SkelTranslationFile = CD + "skel translations file";
    public static final String DoNotWarnAboutSystemLocale = CD + "don't warn about system locale";
    public static final String DoNotEstimateMissingSurgoValues = CD + "do not estimate missing surgo values";
    public static final String OMFractionThreshold = CD + "organic material fraction threshold";
//    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";
    public static final String FormatsXML = CD + "formats.xml";
    @FileConfigurationOption
    public static final String FuelDatabase = CD + "fuel.database";
    public static final String FuelDefault = CD + "fuel.default";
    public static final String NRMVMode = CD + "NRMV mode";
    public static final String SubmodelOutputs = CD + "Submodel outputs";
    
    // Tags for the default locations of some important filechoosers.
    public static final String MGT_P_BUTTON_DEFAULT = CD + "mgt_P_button_default";
    public static final String MGT_T_BUTTON_DEFAULT = CD + "mgt_T_button_default";
    public static final String MCREW_OPEN_DEFAULT =   CD + "MCREW_Open_default";
    public static final String MCREW_OPEN_COPY_OF_CRLMOD_TEMPLATE_DEFAULT = CD + "MCREW_Open_Copy_of_CRLMOD_template_default";
    public static final String MCREW_OPEN_COPY_OF_SYSTEM_TEMPLATE_DEFAULT = CD + "MCREW_Open_Copy_of_System_template_default";
    
    /**
     * 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;

    private PropertyChangeSupport c_changes = new PropertyChangeSupport(this);

    private ConfigData() {
        //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);
            }
        }
        
        // cligen / windgen
        // if only allowedModes specified in user cfgs,
        // then default enabledmodes may be larger than allowed.
        // Correct it here
        String allowed = getData(WindgenAllowedModes);
        String enabled = getData(WindgenEnabledModes);
        String[] parts = enabled.split(",");
        for (String part : parts) {
            if (!allowed.contains(part)) {
                enabled = enabled.replace(part, "");
            }
        }
        while (enabled.contains(",,")) {
            enabled = enabled.replaceAll(",,", ",");
        }
        while (enabled.startsWith(",")) {
            enabled = enabled.substring(1);
        }
        while (enabled.endsWith(",")) {
            enabled = enabled.substring(0,enabled.length()-1);
        }
        setData(WindgenEnabledModes, enabled);
        
        allowed = getData(CligenAllowedModes);
        enabled = getData(CligenEnabledModes);
        parts = enabled.split(",");
        for (String part : parts) {
            if (!allowed.contains(part)) {
                enabled = enabled.replace(part, "");
            }
        }
        while (enabled.contains(",,")) {
            enabled = enabled.replaceAll(",,", ",");
        }
        while (enabled.startsWith(",")) {
            enabled = enabled.substring(1);
        }
        while (enabled.endsWith(",")) {
            enabled = enabled.substring(0,enabled.length()-1);
        }
        setData(CligenEnabledModes, enabled);
    }
    
    public static boolean isNRMVVis() {
        return NRMVVis;
    }
    
    public static boolean isSingleFileMode() { return SingleFileMode; }
    
    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");
        }
    }

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

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

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

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

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

    public void setAccessLevel(String propertyName, AccessLevel level) {
        if (c_accessLevels == null) {
            c_accessLevels = new HashMap<String, AccessLevel>();
        }
        c_accessLevels.put(propertyName, level);
    }

    public boolean isChanged() {
        return c_changed;
    }

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

    public String getDataParsed(String propertyName) {
        return Util.parse(getData(propertyName));
    }

    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
        // MEH: added Overridden and it is also writeable.
        if (!CurrentProj.equals(propertyName) && (propertyName.startsWith(CD) || propertyName.startsWith(REP)) && !AccessLevel.Write.equals(level) && !AccessLevel.Overridden.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();
        }
    }

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

    public void addPropertyChangeListener(PropertyChangeListener l) {
        c_changes.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        c_changes.removePropertyChangeListener(l);
    }

    public void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
        c_changes.addPropertyChangeListener(propertyName, l);
    }

    public void removePropertyChangeListener(String propertyName, PropertyChangeListener l) {
        c_changes.removePropertyChangeListener(propertyName, l);
    }

    @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(e);
        }

    }

    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);
        }
        
        /**
         * private method that is used to output all the configuration params and
         * filenames to the console in the color purple for visibility -CKM
         * @param str the string to be printed
         */
        private void debug_purple(String str) {
            System.out.println("\033[4;35m" + str + "\033[0m");
        }

        protected void load() {
            
            // For debugging what is being read from  config files
            final boolean _DEBUG_CONFIG = false;
            
            try {
                synchronized (this) {
                    c_data = new HashMap<String, String>();
                }
                
                if (_DEBUG_CONFIG) debug_purple("CONFIGURATION FILE PATH: " + c_file.getAbsolutePath());
                
                if (!c_file.exists()) {
                    if (_DEBUG_CONFIG) debug_purple("\t---> Configuration file failed to open, returning.");
                    return;
                }

                SAXBuilder builder = new SAXBuilder();
                TFileInputStream in = new TFileInputStream(c_file);
                Document document = builder.build(in);
                
                // boolean for access specific readings
                boolean isNRCS = false;
                boolean isNRCSandDEV = false;
                boolean isDEV = false; // No standalone dev mode yet, but still used to set isNRCSandDEV

                Element root = document.getRootElement();
                for (Element parameter : root.getChildren(XML_PARAMETER)) {
                    String key = parameter.getChildText(XML_NAME);
                    String value = parameter.getChildText(XML_VALUE);
                    
                    if (_DEBUG_CONFIG) debug_purple("\tKEY: " + key);
                    if (_DEBUG_CONFIG) debug_purple("\t\tVALUE:  " + value);
                    

                    if (key.equals(isNRCSmode)) { // check to see if NRCS mode or not. Used for determining access levels dependant on NRCS
                        if (value.equals("1"))
                            isNRCS = true;
                    }
                    if (key.equals(isDEVmode)) { // If dev mode 
                        if (value.equals("1")) 
                            isDEV = true;
                    }
                    
                    if (isNRCS && isDEV)
                        isNRCSandDEV = true;
                    
                    if(key.equals(NRMVVisibility)) NRMVVis = value.trim().equalsIgnoreCase("true");
                    if(key.equals(CollapseOp)) toCollapse = value.equals("1");
                    if(key.equals(SingleFileLock)) SingleFileMode = value.trim().equalsIgnoreCase("true");
                    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 = "";
                    
                    if (isNRCSandDEV) {                                             // NRCS && DEV mode
                        levelText = parameter.getChildText(XML_ACCESS_NRCS_DEV);
                        
                        if (levelText == null) {
                            // Check for just NRCS
                            levelText = parameter.getChildText(XML_ACCESS_NRCS);
                            
                            if (levelText == null) {
                                levelText = parameter.getChildText(XML_ACCESS);
                            }
                        }
                    } 
                    else if (isNRCS) {                                              // Just NRCS mode
                            levelText = parameter.getChildText(XML_ACCESS_NRCS);
                            
                            if (levelText == null) {
                                levelText = parameter.getChildText(XML_ACCESS);
                            }
                    }
                    else if (isDEV) {                                               // Just DEV mode
                        // Nothing specific to just dev yet
                        levelText = parameter.getChildText(XML_ACCESS);
                    } 
                    else {                                                          // Regular
                        levelText = parameter.getChildText(XML_ACCESS); // 
                    }
                    
                    
                    if (_DEBUG_CONFIG) debug_purple("\t\tACCESS: " + levelText + "\n");
                    
                    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 (java.io.FileNotFoundException entity_error) {
                /* This means one of the entity includes in the master configuration
                file has a path that does not exist. Inform user and then kill program */
                LOGGER.error("Unable to load XML configuration file. [ENTITY ISSUE]" + c_file.getPath(), entity_error);
                
                JOptionPane.showMessageDialog(null, "There was an issue in loading the file:\n" +
                        entity_error.getMessage() + "\n\n" +
                        "from the provided master configuration file: \n" + 
                        c_file.getPath() + 
                        "\n\n" +
                        "Please check that the specified file/path in the ENTITY tag is correct and exists.", "Configuration Error", JOptionPane.ERROR_MESSAGE);
                System.exit(-1);

            } 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);
                
                // inform top level user.
                JOptionPane.showMessageDialog(null, "There was an issue in loading the configuration parameters from\n" +
                        "the provided configuration file: \"" + c_file.getAbsolutePath() + "\".\n" +
                        "The file exists, but likely has incorrect XML formatting for a Weps configuration file. \n" +
                        "Please check that this is an intended Weps configuration file.", "Configuration Error", JOptionPane.ERROR_MESSAGE);
                System.exit(-1);
                
            } 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) && !AccessLevel.Overridden.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>();
        }

        //start new parameters
        set(wepsdbRdir, "db"); /* default relative path if not cfg file entry */
        set(opRecordsCRLMOD, "No CRLMOD operation records folder specified yet");
        set(cropRecordsCRLMOD, "No CRLMOD crop records folder specified yet");
        set(residueRecordsCRLMOD, "No CRLMOD residue records folder specified yet");
        set(localResidueRecords, "No local residue records folder specified yet");
        set(cligenDataCRLMOD, "No cligen data records (.par) file specified yet");
        set(cligenIndexCRLMOD, "No cligen data records index (.idx) file specified yet");
        set(prismNormalsCRLMOD, "No PRISM data file specified yet");
        set(windgenDataCRLMOD, "No windgen data records (.wdb) file specified yet");
        set(windgenIndexCRLMOD, "No windgen data records index (.idx) file specified yet");
        set(oldMessageDisplay, "1");
        set(oldMessageDefault, "1");
        set(oldMessageOptions, "1");
        set(invalidCligenDisplay, "1");
        set(invalidCligenStation, "1");
        set(mcrewVersionDisplay, "1");
        set(manOpDbCheck, "1");
        set(cropDbCheck, "1");
        set(sysManTemp, "1");
        set(locManTemp, "1");
        set(shaManTemp, "0");
        set(systemSoilDataDir_CB,"1");
        set(localSoilDataDir_CB,"1");
        set(localSoilDataMartDir_CB,"1");
        set(nrcsSoilDataMartURL_CB,"1");
        set(crlmodEnabled, "1");
        set(locCropCheck, "1");
        set(shaCropCheck, "1");
        set(crlmodCropCheck, "1");
        set(loopCheck, "1");
        set(shopCheck, "1");
        set(cropCheck, "1");
        set(shopLocation, "No shared operations folder specified yet");
        set(manVersionButtonsHide, "1"); //default MCREW man version radio buttons and text to hidden (hidden = 1 and visible = 0)
        set(noCat, "1");
        set(NRCSRunLen, "50"); /* default NRCS run length */
        set(cycleRunLen, "50"); /* default Cycle run length */
        set(auxRefresh, "1");
        set(Units, Util.SIUnits);
        set(OutputFreq, "2");
        set(windDecNum, "5");
        set(OPHydroDet, "0");
        set(OPSoilDet, "0");
        set(OPManageDet, "0");
        set(OPCropDet, "0");
        set(OPDecompDet, "0");
        set(OPErosionDet, "0");
        set(OPHydroDbg, "0");
        set(OPSoilDbg, "0");
        set(OPManageDbg, "0");
        set(OPCropDbg, "0");
        set(OPDecompDbg, "0");
        set(OPErosionDbg, "0");
        set(WinExe, "");
        set(WinCmd, "");
        set(WinData, "No Windgen record data (.wdb) file specified yet");
        set(WindgenInterpolationBoundaryFile, "No Windgen interpolation boundary (.pol) file specified yet");
        set(CliExe, "");
        set(CliCmd, "");
        set(CliData, "No Cligen record data (.par) file specified yet");
        set(WepsExe, "");
        set(WepsCmd, "");
        set(CalWepsCmd, "");
        set(EmailSender, "");
        set(MantisMaxSize, "10");
        set(AddDirectoryButton, "0");
        set(CommentAddr, "");
        set(BugsAddr, "");
        set(ManTemp, "");
        set(LocalManDB, "");
        set(SharedManDB, "");
        set(ManSkel, "");
        set(SystemCropDB, "No crop records folder specified yet");
        set(SystemOpDB, "");
        set(LocalOpDB, "");
        set(LocalCropDB, "");
        set(SoilDB, "");
        set(SoilOrganicFile, "$No substitute organic soil file (.ifc) specified yet");
        set(SoilMaxOrganicDepth, "101.6");
        set(SoilTestOrganic, "1");
        set(cachedCsipSoilDir_CB, "1");
        set(csipSoilService_CB, "1");
        set(ProjDir, "");
        set(CurrentProj,"");
        set(MCREW, "mcrew_cfg");
        set(McrewDataConfigFile, "dataconfig.xml");
        set(TimeSteps, "24");
        set(WindFlag, "0");
        set(ClimateFlag, "0");
        set(SubDailyFlag, "0");
        set(DefaultRunMode, ConfigData.Cycle);
        set(TTInit, "2000");		// set to more reasonable defaults if not
        set(TTDismiss, "2000");		// set in cfg file (or cfg file is missing)
        set(ReadonlySlope, "1");
        set(ReadonlyRockFragments, "1");
        set(FormatOperationDate, "MMM dd, y");
        set(PurgeRunFilesFlg, "0");
        set(PurgeRunFiles, "win_gen.win:cli_gen.cli");
        set(DefaultRunsLocationTemplate, "${project.directory}/Runs");
        set(SkelTranslationFile, "No Translation file specified yet");
        set(DoNotWarnAboutSystemLocale, "0");
        set(DoNotEstimateMissingSurgoValues, "0");
//        set(UseDefaultMailClient, "0");
        set(DoNotReviewWarningsWhenRestoringRun, "0");
        set(SoilEstimate, "1");
        set(SoilAverageStratifiedLayers, "0");
        set(OMFractionThreshold, "0.20");
        set(DetailTableFilterFile, "tables/detail_filters.xml");
        set(HideProjectFileButtons, "0");
        set(HideTemplatetFileButtons, "0");
        set(DaysToKeepLogs, "2");
        //reports 
        set(ReportsView, "run");
        set(ReportsGeneratePDF, "run,management,cropdetail,cropsummary,cropintervalsum,stir,covercropsummary,covercropdetail,ci");
        set(ReportsDirectory, "reports/");
        set(ReportsCustomized, null);
        set(ReportsConfidenceIntervalEnabled, "1");
        set(ReportsCropIntPerDetEnabled, "1");
        set(AllowScriptCreation, "1");
        set(CompatibilityNRCS, "0");
        set(BarriersFile, "$No wind barriers data file specified yet");
        set(BarriersReadonly, "0");
        set(ShowWeatheFilerCheckboxes, "1");
        set(WindgenAllowedModes, "choice,nearest,file,gis,interpolated,nrcs");
        set(WindgenEnabledModes, "choice,nearest,file,gis,interpolated,nrcs");
        set(CligenAllowedModes, "choice,nearest,file,gis,nrcs");
        set(CligenEnabledModes, "choice,nearest,file,gis,nrcs");
        setData(SoilAverageStratifiedLayers, "0");
        setData(SoilSkipOrganicSurfaceLayers, "0");
        setData(SiteChooserShowCountry, "0");
        setData(CollapseOp, "0");
        set(GISData, "db/gis");
        set(SoilReadonly, "0");
        set(FormatsXML, "0");
        set(FuelDatabase, "No fuel database file specified yet");
        set(FuelDefault, "diesel");
        set(NRMVMode, "off");
        set(MCREWAutosort, "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");
        set(LocalSDMDB, Util.parse("${weps.databases}/db/soil/SDM Archive"));
        set(geotoolsTileExtentCount, "128");
    }

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

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

    public Quantity<Length> getSoilMaxOrganicDepth() {
        String valueString = getData(SoilMaxOrganicDepth);
        try {
            double depthMiliMeters = Double.valueOf(valueString);
            Unit<Length> millimetre = MetricPrefix.MILLI(SI.METRE);
            return Quantities.getQuantity(depthMiliMeters, millimetre);
        } catch (NumberFormatException e) {
            LOGGER.warn("Unable to parse the soil max organic depth. Defaulting to 4 inches." + valueString);
            return Quantities.getQuantity(4, USCustomary.INCH);
        }
    }

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

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

    public Quantity<Length> getCligenSearchRadius() {
        String valueString = getData(ClimateRadius);
        try {
            double distanceMeters = Double.parseDouble(valueString.trim());
            Unit<?> units = ConfigData.getDefault().getMeasureableUnit(ClimateRadius);
            return Quantities.getQuantity(distanceMeters, MetricPrefix.KILO(SI.METRE));
        } catch (NumberFormatException e) {
            LOGGER.warn("Unable to parse the cligen radius limit. " + valueString);
            return null;
        }
    }

    public int getCligenSearchLimit() {
        return 50;
    }

    public Quantity<Length> getWindgenSearchRadius() {
        String valueString = getData(WindRadius);
        try {
            double distanceMeters = Double.parseDouble(valueString.trim());
            return Quantities.getQuantity(distanceMeters, MetricPrefix.KILO(SI.METRE));
        } catch (NumberFormatException e) {
            LOGGER.warn("Unable to parse the windgen radius limit. " + valueString);
            return null;
        }
    }

    public int getWindgenSearchLimit() {
        return 50;
    }

    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 {

        String units();
        String alternativeUnits();
        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;
    }

    public MeasureableConfigurationOption getMeasureableConfigurationOption(String propertyName) {
        return measureableOptions().get(propertyName);
    }

    public <Q extends Quantity<Q>> Unit<Q> getMeasureableUnit(String propertyName) {
        MeasureableConfigurationOption option = getMeasureableConfigurationOption(propertyName);

        if (option != null) {
            @SuppressWarnings("unchecked")
            Unit<Q> units = (Unit<Q>) org.geotools.measure.Units.parseUnit(option.units());
            return units;
        }
        return null;
    }

    public <Q extends Quantity<Q>> Unit<Q> getMeasureableAlternativeUnit(String propertyName) {
        MeasureableConfigurationOption option = getMeasureableConfigurationOption(propertyName);

        if (option != null) {
            @SuppressWarnings("unchecked")
            Unit<Q> units = (Unit<Q>) org.geotools.measure.Units.parseUnit(option.alternativeUnits());
            return units;
        }
        return null;
    }

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

        boolean required() default true;
        String parentOption() default "";
        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;
    }

    public boolean isFileConfigurationOption(String propertyName) {
        return fileOptions().containsKey(propertyName);
    }

    public FileConfigurationOption getFileConfigurationOption(String propertyName) {
        return fileOptions().get(propertyName);
    }

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

    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);
                    // MEH: need to use canonical or absolute path (not relative) in order for System.setProperty("user.dir" .... to function
                    // need this so the -working option works, so we can function in s WebStart scenario
                    if (path != null) {
                        if (parentFile != null) {
                            path = parentFile.getPath() + "/" + path;
                        }
                        path = new TFile(path).getCanOrAbsPath();
                    }
                    
                    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) + "\n"
                                + "     or when parsed/expanded: " + path + "\n");
                    }
                }
            }
        }
        return failed;
    }

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

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

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

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

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

    public boolean isReportCropIntPerDetEnabled() {
        return "1".equals(getData(ReportsCropIntPerDetEnabled));
    }
    public boolean isEnabledEmptyManagementFilePopupMessage(){
    
        return "1".equals(getData(EnableEmptyManagementFilePopupMessage));
    }
    
    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;
    }

    public static Unit<Dimensionless> getCurrency() {
        return USD;
    }

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

    public static boolean checkParmValue (String parm, String value) {
        String cacheParm = getDefault().getData(parm);
        return (cacheParm != null && cacheParm.contentEquals(value));
    }

    public static int getIntParm (String parm, int defValue) {
        String cacheParm = getDefault().getData(parm);
        return (cacheParm != null) ? Integer.valueOf(cacheParm) : defValue;
    }

    public static long getLongParm (String parm, long defValue) {
        String cacheParm = getDefault().getData(parm);
        return (cacheParm != null) ? Long.valueOf(cacheParm) : defValue;
    }

    public static float getFloatParm (String parm, float defValue) {
        String cacheParm = getDefault().getData(parm);
        return (cacheParm != null) ? Float.valueOf(cacheParm) : defValue;
    }

    public static String getStringParm (String parm, String defValue) {
        String cacheParm = getDefault().getData(parm);
        return (cacheParm != null) ? cacheParm : defValue;
    }
    
    public static boolean isWepsIntegrated () {
        return ConfigData.getDefault().getData(cdKeyWepsBase+cdKeyAddEndpoint).contains("wepsIntegrated");
    }

}
