Component.java [src/soils] Revision: default  Date:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package soils;

import csip.api.server.ServiceException;
import csip.utils.JSONUtils;
import csip.utils.Validation;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import static soils.db.SOILS_DATA.BAD_KFFACT_LAYERS;
import static soils.db.SOILS_DATA.BAD_SAND_LAYERS;
import soils.db.SOILS_DATA.FilterResults;
import soils.db.tables.TableCocropyld;
import soils.db.tables.TableCoecoclass;
import soils.db.tables.TableColumn;
import soils.db.tables.TableComponent;
import soils.db.tables.TableComponentCalculations;
import soils.db.tables.TableExcluded;
import soils.exceptions.SDMException;
import soils.exceptions.WEPPException;
import soils.exceptions.WEPP_WEPSException;
import soils.exceptions.WEPSException;
import soils.utils.EvalResult;
import soils.utils.GenericIFCData;
import soils.utils.IFCRanges;
import soils.utils.SoilUtils;
import static soils.utils.SoilUtils.formatIFCArray;
import static soils.utils.SoilUtils.formatIFCValue;
import static soils.utils.SoilUtils.getCationExchangeCapacity;
import static soils.utils.SoilUtils.getRockFrags;
import static soils.utils.SoilUtils.getSoilpH;
import static soils.utils.SoilUtils.metricConversion;
import soils.utils.StratifiedTextures;

/**
 *
 * @author <a href="mailto:shaun.case@colostate.edu">Shaun Case</a>
 */
public class Component {

  private static HashMap<String, Integer> pondRatings = new HashMap<>();
  private static HashMap<String, Integer> floodRatings = new HashMap<>();
  private static ArrayList<String> floodRatingNames = new ArrayList<>(Arrays.asList("None", "Very Rare", "Rare", "Occaisional", "Frequent", "Very Frequent"));
  private static ArrayList<String> pondRatingNames = new ArrayList<>(Arrays.asList("None", "Rare", "Occaisional", "Frequent"));

  protected static final StratifiedTextures STRATIFIED_TEXTURES = StratifiedTextures.getInstance();

  private static double CM_TO_INCH = 0.393701;
  private static final String COMP_PSLP_STRING[] = {"", "VERY LOW", "LOW", "INTERMEDIATE", "HIGH"};
  private static final String COMP_PSSRP[] = {"", "LOW", "INTERMEDIATE", "HIGH"};
  private static double INCH_TO_CM = 2.54;
  private static final int PSLP_HIGH = 4;
  private static final int PSLP_INTERMEDIATE = 3;
  private static final int PSLP_LOW = 2;
  private static final int PSLP_VERY_LOW = 1;
  private static final String LONG_NAME = "comp_long_name";

  protected static ArrayList<String> componentRequiredInputs = new ArrayList<>();
  protected static ArrayList<String> componentUsedColumns = new ArrayList<>();

  static {
    for (int i = 0; i < floodRatingNames.size(); i++) {
      floodRatings.put(floodRatingNames.get(i).toLowerCase(), i);
    }

    for (int i = 0; i < pondRatingNames.size(); i++) {
      pondRatings.put(pondRatingNames.get(i).toLowerCase(), i);
    }
  }

  /**
   *
   * @param usedColumns
   */
  public synchronized static void setDefaultUsedColumns(List<String> usedColumns) {
    componentUsedColumns.clear();
    if (null != usedColumns) {
      componentUsedColumns.addAll(usedColumns);
    }
  }

  public synchronized static void setRequiredInputs(List<String> componentInputs) {
    componentRequiredInputs.clear();
    if (null != componentInputs) {
      componentRequiredInputs.addAll(componentInputs);
      componentUsedColumns.addAll(componentInputs);
    }
  }

  public synchronized static ArrayList<String> getDefaultUsedColumns() {
    return ((null != componentUsedColumns) ? ((ArrayList<String>) componentUsedColumns.clone()) : null);
  }

  public synchronized static ArrayList<String> getRequiedInputs() {
    return ((null != componentRequiredInputs) ? ((ArrayList<String>) componentRequiredInputs.clone()) : null);
  }
  ////////////////////////////////////////////////////////////////////////////////////
  // Default Organic layer data to be used for substituted layer lines in .sol file.
  ////////////////////////////////////////////////////////////////////////////////////
  //  Thk   : (Use existing layer thickness)
  //  Sand %: 50
  //  Clay %: 10
  //  OM   %: 77
  //  CEC   : 40
  //  rfg  %: 0
  ////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////      
  //  Generic Organic Soil SOL File
  protected String genericOrganicSoilSol = "2006.2\n"
      + "wepp organic soil file\n"
      + "1 1\n"
      + "'organic' 'SIL' 2 0.230000 0.750000 3112200 0.00257 3.204 10.28756\n"
      + "   400 50.0 10.0 77.000 40.0 0.0\n"
      + "   1500 13.0 5.0 81.000 45.0 0.0\n"
      + "0 0.000000 0";
  ////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////      
  //  Generic Organic Soil IFC File
  protected String genericOrganicSoilIfc = "Version: 1.0\n"
      + "#\n"
      + "# Soil ID\n"
      + "NA-NA-Organic Soil Mod-100-MUCK-NA-NA-NA\n"
      + "#\n"
      + "# Local Phase\n"
      + "NA\n"
      + "# Soil Order\n"
      + "NA\n"
      + "# Soil Loss Tolerance (tons/acre/year)\n"
      + "3\n"
      + "# Dry soil albedo (fraction)\n"
      + "0.090\n"
      + "# Slope gradient (fraction)\n"
      + "0.000\n"
      + "# Surface fragment cover or surface layer fragments (area fraction)\n"
      + "0.000\n"
      + "#\n"
      + "# Depth to bedrock (mm)\n"
      + "99990\n"
      + "# Depth to root restricting layer (mm)\n"
      + "99990\n"
      + "#\n"
      + "# Number of layers\n"
      + "2\n"
      + "# Layer thickness (mm)\n"
      + "400     1100     \n"
      + "#\n"
      + "# Sand fraction\n"
      + "0.500     0.130     \n"
      + "# Silt fraction\n"
      + "0.400     0.820     \n"
      + "# Clay fraction\n"
      + "0.100     0.050     \n"
      + "# Rock fragments\n"
      + "0.000     0.000     \n"
      + "# Sand fraction very coarse\n"
      + "0.000     0.000     \n"
      + "# Sand fraction coarse\n"
      + "0.020     0.003     \n"
      + "# Sand fraction medium\n"
      + "0.128     0.023     \n"
      + "# Sand fraction fine\n"
      + "0.332     0.065     \n"
      + "# Sand fraction very fine\n"
      + "0.020     0.039     \n"
      + "#\n"
      + "# Bulk Density (1/3 bar)(Mg/m^3)\n"
      + "0.390     0.220     \n"
      + "# Organic matter (kg/kg)\n"
      + "0.7700     0.8100     \n"
      + "# Soil PH (0-14)\n"
      + "5.00     5.00     \n"
      + "# Calcium carbonate equivalent (CaCO3)\n"
      + "0.00     0.00     \n"
      + "# Cation exchange capacity (CEC) (meq/100g)\n"
      + "40.00     45.00     \n"
      + "# Linear extensibility\n"
      + "0.000     0.000     \n"
      + "#\n"
      + "# Aggregate geometric mean diameter (mm)\n"
      + "5.000     5.000     \n"
      + "# Aggregate geometric standard deviation\n"
      + "5.000     5.000     \n"
      + "# Maximum aggregate size (mm)\n"
      + "30.000     30.000     \n"
      + "# Minimum aggregate size (mm)\n"
      + "0.010     0.010     \n"
      + "# Aggregate density (Mg/m^3)\n"
      + "0.700     0.700     \n"
      + "# Aggregate stability (ln(J/m^2))\n"
      + "4.000     4.000     \n"
      + "#\n"
      + "# Crust thickness (mm)\n"
      + "0.010\n"
      + "# Crust density (Mg/m^3)\n"
      + "0.600\n"
      + "# Crust stability (ln(J/m^2))\n"
      + "4.00\n"
      + "# Crust surface fraction (m^2/m^2)\n"
      + "0.00\n"
      + "# Mass of loose material on crust (kg/m^2)\n"
      + "0.00\n"
      + "# Fraction of loose material on crust (m^2/m^2)\n"
      + "0.00\n"
      + "#\n"
      + "# Random roughness (mm)\n"
      + "4.00\n"
      + "# Ridge orientation (deg)\n"
      + "0.00\n"
      + "# Ridge height (mm)\n"
      + "0.00\n"
      + "# Spacing between ridge tops (mm)\n"
      + "10.00\n"
      + "# Ridge width (mm)\n"
      + "10.00\n"
      + "#\n"
      + "# Initial Bulk Density (1/3 bar)(Mg/m^3)\n"
      + "0.390     0.220     \n"
      + "# Initial soil water content (m^3/m^3)\n"
      + "0.379     0.379     \n"
      + "# Saturation soil water content (m^3/m^3)\n"
      + "0.550     0.550     \n"
      + "# Field capacity water content (m^3/m^3)\n"
      + "0.335     0.335     \n"
      + "# Wilting point water content (m^3/m^3)\n"
      + "0.240     0.240     \n"
      + "#\n"
      + "# Soil CB value (exponent to Campbell's SWRC)\n"
      + "6.831     6.831     \n"
      + "# Air entry potential (J/kg)\n"
      + "-4.379     -4.379     \n"
      + "# Saturated hydraulic conductivity (m/s)\n"
      + "5.0E-5     2.5E-5     \n"
      + "#";
  ////////////////////////////////////////////////////////////////////////////////////

  private LinkedHashMap<String, Cocropyld> cropYlds = new LinkedHashMap<>();
  private LinkedHashMap<String, Coecoclass> ecoClasses = new LinkedHashMap<>();
  private final String[] nslpList = new String[]{"LOW", "MODERATE", "MODERATELY HIGH", "HIGH"};
  private final String[] srpList = new String[]{"LOW", "MODERATE", "MODERATELY HIGH", "HIGH"};

  private final TableComponent tableComponent = new TableComponent();
  private final TableComponentCalculations tableComponentCalculatoins = new TableComponentCalculations();
  private final TableExcluded tableExcluded = new TableExcluded();

  private int restrict = 0;
  private int restrictType = 0;
  private double aniso = 0;
  private double uksat = 0;
  private Horizon surfaceHorizon = null;
  private Horizon restrictingHorizon = null;
  private boolean isOrganic = false;

  public LinkedHashMap<String, Horizon> horizons;
  public LinkedHashMap<String, Comonth> comonths;

  /**
   *
   * @throws ServiceException
   */
  public Component() throws ServiceException {
    horizons = new LinkedHashMap<>();
    comonths = new LinkedHashMap<>();
    tableComponent.setRequiredColumns(componentRequiredInputs);
    tableComponentCalculatoins.setRequiredColumns(componentRequiredInputs);
    tableComponent.setUsedColumns(componentUsedColumns);
    tableComponentCalculatoins.setUsedColumns(componentUsedColumns);
  }

  /**
   *
   * @param dataJSON
   * @throws ServiceException
   * @throws JSONException
   */
  public Component(JSONArray dataJSON) throws ServiceException, JSONException {
    if ((null != dataJSON) && (dataJSON.length() > 0)) {
      Map<String, JSONObject> componentDataArray = JSONUtils.preprocess(dataJSON);
      JSONArray horizonsArray;
      tableComponent.setRequiredColumns(componentRequiredInputs);
      tableComponentCalculatoins.setRequiredColumns(componentRequiredInputs);
      tableComponent.setUsedColumns(componentUsedColumns);
      tableComponentCalculatoins.setUsedColumns(componentUsedColumns);

      tableComponent.readValuesFromJSON(componentDataArray);
      tableComponentCalculatoins.readValuesFromJSON(componentDataArray);

      tableComponentCalculatoins.area_pct(tableComponentCalculatoins.area_pct() / 100.0);

      horizons = new LinkedHashMap<>();
      comonths = new LinkedHashMap<>();

      //TODO:  Look for all horizon list types, i.e. wind, water, or just horizons here...
      setHorizons(JSONUtils.getJSONArrayParam(componentDataArray, "horizons"));
    } else {
      throw new ServiceException("No component JSON specified.  A NULL or empty value was passed to Component()");
    }
  }

  public Component(ResultSet results) throws ServiceException {
    tableComponent.setUsedColumns(componentUsedColumns);
    tableComponentCalculatoins.setUsedColumns(componentUsedColumns);
    tableComponent.setRequiredColumns(componentRequiredInputs);
    tableComponentCalculatoins.setRequiredColumns(componentRequiredInputs);

    tableComponent.readValuesFromSQL(results);
    tableComponentCalculatoins.readValuesFromSQL(results);

    horizons = new LinkedHashMap<>();
    comonths = new LinkedHashMap<>();
  }

  public boolean hasTableColumn(String columnName) {
    return tableComponent.getColumnList().contains(columnName);
  }

  public Object getTableColumnValue(String columnName) {
    if (hasTableColumn(columnName)) {
      return tableComponent.getColumns().get(columnName).getValue();
    }
    return null;
  }

  public TableColumn getTableColumn(String columnName) {
    if (hasTableColumn(columnName)) {
      return tableComponent.getColumns().get(columnName);
    }
    return null;
  }

  public TableComponent getTableColumns() {
    return tableComponent;
  }

  public boolean isEqual(Component tComp) throws ServiceException {
    boolean ret_val = (tableComponent.isEqual(tComp.getTableComponent())
        && tableComponentCalculatoins.isEqual(tComp.getTableComponentCalculations()));

    //  If sizes don't match, no need to scan each list, something will not match.
    ret_val &= (cropYlds.size() == tComp.cropYlds().size()
        && ecoClasses.size() == tComp.ecoClasses().size()
        && horizons.size() == tComp.horizons().size()
        && comonths.size() == tComp.comonths().size());

    for (Cocropyld tcropYld : cropYlds.values()) {
      if (ret_val && tComp.cropYlds().containsKey(tcropYld.cocropyldkey())) {
        ret_val &= tcropYld.isEqual(tComp.cropYlds().get(tcropYld.cocropyldkey()));
      }
    }

    for (Coecoclass tecoClass : ecoClasses.values()) {
      if (ret_val && tComp.ecoClasses().containsKey(String.valueOf(tecoClass.coecoclasskey()))) {
        ret_val &= tecoClass.isEqual(tComp.ecoClasses().get(String.valueOf(tecoClass.coecoclasskey())));
      }
    }

    for (Horizon thorizon : horizons.values()) {
      if (ret_val && tComp.horizons().containsKey(thorizon.chkey())) {
        ret_val &= thorizon.isEqual(tComp.horizons().get(thorizon.chkey()));
      }
    }

    for (Comonth tcomonth : comonths.values()) {
      if (ret_val && tComp.comonths.containsKey(tcomonth.comonthkey())) {
        ret_val &= tcomonth.isEqual(tComp.comonths.get(tcomonth.comonthkey()));
      }
    }

    return ret_val;
  }

  public void addComonth(Comonth comonth) {

    if (null != comonths) {
      if (!comonths.containsKey(comonth.comonthkey())) {
        comonths.put(comonth.comonthkey(), comonth);
      } else {
        //  Should we ignore this or throw an exception??
      }
    }
  }

  public void adjustForWEPS(MapUnit mapUnit, boolean averageStratified, boolean organicChecks, boolean estimateNulls) throws WEPSException {
    try {
      getWEPPSurfaceHorizon();
    } catch (SDMException ex) {
      throw new WEPSException(ex);
    }

    averageMissingWEPSValues();

    if (averageStratified) {
      SoilUtils.averageStratifiedLayers(this, STRATIFIED_TEXTURES);
    }

    adjustWEPSData(mapUnit, estimateNulls); // have to do this before converting units, I think it should be after but this is how WEPS does it

    SoilUtils.convertUnits(mapUnit, this);
    try {
      if (organicChecks) {
        checkWEPSOrganicSurface();
      }

      removeBadWEPSLayers();
    } catch (SDMException ex) {
      throw new WEPSException(ex);
    }
  }

  // TODO:  Examine this logic...does this still make sense for the latest WEPS filtering??
  protected void removeBadWEPSLayers() throws SDMException {
    Iterator<Horizon> it = horizons.values().iterator();
    Horizon h;
    boolean bad = false; // delete everything after bad found

    if (it.hasNext()) {
      it.next(); // skip the first horizon but I don't think the WEPS code is right

      while (it.hasNext()) {
        h = it.next();
        if (bad) {
          it.remove();
          continue;
        }
        // stop if layer is identified as rock, peat, etc.
        if (h.chkWEPSStopTexture()) {
          it.remove();
          bad = true;
          continue;
        }

        // stop if both sand and clay are missing or zero
        // Should already be removed because of the query
        if ((EvalResult.testDefaultDouble(h.claytotal_r()) || h.claytotal_r() == 0.0)
            && (EvalResult.testDefaultDouble(h.sandtotal_r()) || h.sandtotal_r() == 0.0)) {
          it.remove();
          bad = true;
          continue;
        }
        // stop if layer does not have wet bulk density
        if (EvalResult.testDefaultDouble(h.dbthirdbar_r()) || h.dbthirdbar_r() == 0.0) {
          it.remove();
          bad = true;
        }
      }
    } else {
      throw new SDMException("No suitable list of horizons can be found for this component.  Cannot continue WEPS model preparations.");
    }
  }

  public void adjustWEPSData(MapUnit mapUnit, boolean estimateNulls) {
    double calculatedSiltTotal;

    if (compname() == null || compname().length() == 0) {
      compname("unknown");
    }
    // WEPS check comp_pct here but as a string? wait until it becomes a problem
    if (taxorder() == null || taxorder().length() == 0) {
      taxorder("unkown");
    }
    if (localphase() == null || localphase().length() == 0) {
      localphase("unkown");
    }

    tfact(SoilUtils.testNumericSoilElement(5, 1, tfact(), -1));
    slope_r(SoilUtils.testNumericSoilElement(999, 0, slope_r(), -1));

    mapUnit.brockdepmin(SoilUtils.testNumericSoilElement(9999, 0, mapUnit.brockdepmin(), 9999));

    calculated_resdeptmin_r(SoilUtils.testNumericSoilElement(9999, 0, calculated_resdeptmin_r(), 9999));

    for (Horizon h : horizons.values()) {
      h.setWEPSDefaults(estimateNulls);
    }

  }

  protected void setOrganicWEPS() throws SDMException {

    if (taxorder().equalsIgnoreCase("Histosols")) {
      isOrganic = true;
    }
    // assuming the first horizon is the top because the sql orders by deptht_r
    if (surfaceHorizon.isOrganic()) {
      isOrganic = true;
    }

  }

  protected void checkWEPSOrganicSurface() throws SDMException, WEPSException {
    double maxOrganicDepth = 100;  //mm
    int i = 0;
    Iterator<Horizon> it = horizons.values().iterator();
    Horizon h;

    while (it.hasNext()) {
      h = it.next();
      if (h.isOrganic()) {
        if (h.hzdepb_r() <= maxOrganicDepth) {
          it.remove(); // save remove
        } else if (i == 0) {
          throw new WEPSException("The organic surface layer depth exceeded the maximum ignorable depth.\n"
              + "hzdepb_r=" + h.hzdepb_r() + " max organic depth set to " + maxOrganicDepth);
        } else {
          throw new WEPSException("The soil layer below the ignored organic layer is still organic.");
        }
        i++;
      } else {
        break; // We found a mineral layer
      }
    }
  }

  /**
   * Averages the "_l" and "_h" values of component columns in SDM if the "_r"
   * column was missing data, and sets the average as the value normally taken
   * from the "_r" suffixed column. This function has questionable validity as
   * the "_l" and "_h" suffixed columns in SDM are not meant to be interpreted
   * this way necessarily; They represent the lowest and highest values
   * encountered during a soil survey for this component at this location, but
   * do not mean that these are distributed equally across the location, as the
   * "_r" value is supposed to be used for that. This function, therefore, was
   * created for WEPS in order to allow the WEPS model to run when the SDM
   * database was missing data. Ideally, one should report the missing data, as
   * is done in other functions, and have an ARS SME update the database.
   */
  protected void averageMissingWEPSValues() {
    slope_r(SoilUtils.averageMissingValue(slope_h(), slope_l(), slope_r()));
    albedodry_r(SoilUtils.averageMissingValue(albedodry_h(), albedodry_l(), albedodry_r()));
    for (Horizon h : horizons.values()) {
      h.hzdept_r(SoilUtils.averageMissingValue(h.hzdept_h(), h.hzdept_l(), h.hzdept_r()));
      h.hzdepb_r(SoilUtils.averageMissingValue(h.hzdepb_h(), h.hzdepb_l(), h.hzdepb_r()));
      h.claytotal_r(SoilUtils.averageMissingValue(h.claytotal_h(), h.claytotal_l(), h.claytotal_r()));
      h.sandtotal_r(SoilUtils.averageMissingValue(h.sandtotal_h(), h.sandtotal_l(), h.sandtotal_r())); // may be useless because the query removes null sandtotals
      h.silttotal_r(SoilUtils.averageMissingValue(h.silttotal_h(), h.silttotal_l(), h.silttotal_r()));
      h.sandvc_r(SoilUtils.averageMissingValue(h.sandvc_h(), h.sandvc_l(), h.sandvc_r(), 0));
      h.sandco_r(SoilUtils.averageMissingValue(h.sandco_h(), h.sandco_l(), h.sandco_r(), 0));
      h.sandmed_r(SoilUtils.averageMissingValue(h.sandmed_h(), h.sandmed_l(), h.sandmed_r(), 0));
      h.sandfine_r(SoilUtils.averageMissingValue(h.sandfine_h(), h.sandfine_l(), h.sandfine_r(), 0));
      h.sandvf_r(SoilUtils.averageMissingValue(h.sandvf_h(), h.sandvf_l(), h.sandvf_r()));
      h.dbthirdbar_r(SoilUtils.averageMissingValue(h.dbthirdbar_h(), h.dbthirdbar_l(), h.dbthirdbar_r()));
      h.wthirdbar_r(SoilUtils.averageMissingValue(h.wthirdbar_h(), h.wthirdbar_l(), h.wthirdbar_r()));
      h.wfifteenbar_r(SoilUtils.averageMissingValue(h.wfifteenbar_h(), h.wfifteenbar_l(), h.wfifteenbar_r()));
      h.ksat_r(SoilUtils.averageMissingValue(h.ksat_h(), h.ksat_l(), h.ksat_r()));
      h.cec7_r(SoilUtils.averageMissingValue(h.cec7_h(), h.cec7_l(), h.cec7_r()));
      h.ecec_r(SoilUtils.averageMissingValue(h.ecec_h(), h.ecec_l(), h.ecec_r()));
      h.om_r(SoilUtils.averageMissingValue(h.om_h(), h.om_l(), h.om_r()));
      h.caco3_r(SoilUtils.averageMissingValue(h.caco3_h(), h.caco3_l(), h.caco3_r()));
      h.ph1to1h2o_r(SoilUtils.averageMissingValue(h.ph1to1h2o_h(), h.ph1to1h2o_l(), h.ph1to1h2o_r()));
      h.ph01mcacl2_r(SoilUtils.averageMissingValue(h.ph01mcacl2_h(), h.ph01mcacl2_l(), h.ph01mcacl2_r()));
      h.lep_r(SoilUtils.averageMissingValue(h.lep_h(), h.lep_l(), h.lep_r()));

    }
  }

  /**
   * This function checks to see if this component can be represented by the
   * generic organic .sol file. Added the check for "histosol" to prevent
   * returning the default organic .sol file for components that have complex
   * layers and are not histosols.
   *
   * @return Returns true if the soil is a histosol, or if the "organic" member
   * variable has already been set to true.
   * @throws ServiceException
   */
  protected boolean isDefaultOrganicSolFileComponent() throws SDMException {
    return ((isOrganicSoil() && (0 == taxorder().compareToIgnoreCase("histosols"))) || (allHorizonsOrganic()));
  }

  public void adjustForWEPP_WEPS(String testModel) throws ServiceException, SDMException, WEPP_WEPSException, WEPPException, WEPSException {
    ArrayList<String> skippedHorizons = new ArrayList<>();
    boolean skipTheRest = false;
    String msgWeppWepsParams;

    //  Do this for all models???
    if (testModel.equalsIgnoreCase("weps")) {
      slope_r(SoilUtils.averageMissingValue(slope_h(), slope_l(), slope_r()));
      albedodry_r(SoilUtils.averageMissingValue(albedodry_h(), albedodry_l(), albedodry_r()));
    }

    for (Horizon tHorizon : horizons.values()) {
      if (!skipTheRest) {
        tHorizon.evaluateMineral();
        tHorizon.evaluateForRestrictingHorizon();  //  Sets the horizon restricting classification using ARS logic.

        if (0 == tHorizon.hzdept_r()) {
          if (tHorizon.hzdepb_r() >= 10) {
            if (tHorizon.isOrganic()) {
              surfaceHorizon(tHorizon);
              tHorizon.useGenericHz1Values(true);  //TODO:  Also set component to use organic .sol file line-4 values...                        
            } else {
              if ((msgWeppWepsParams = tHorizon.hasWeppWepsParams(testModel)).isEmpty()) {
                //  Okay move on and use SDM values for this horizon
                surfaceHorizon(tHorizon);
                tHorizon.useGenericHz1Values(false);
                tHorizon.useGenericHz2Values(false);
              } else {
                if (tHorizon.isRestricting() && tHorizon.isPermiable()) {  // non-cemented restricting
                  skippedHorizons.add(tHorizon.chkey());
                } else {
                  throw new WEPP_WEPSException("Horizon, " + tHorizon.chkey() + ", does not meet model parameter requirements: " + msgWeppWepsParams);
                }
              }
            }
          } else {
            if (tHorizon.isOrganic()) {
              skippedHorizons.add(tHorizon.chkey());
            } else {
              if (tHorizon.hasWeppWepsParams(testModel).isEmpty()) {
                //  Okay move on and use SDM values for this horizon
                surfaceHorizon(tHorizon);
                tHorizon.useGenericHz1Values(false);
                tHorizon.useGenericHz2Values(false);
              } else {
                skippedHorizons.add(tHorizon.chkey());
              }
            }
          }
        } else {
          if (tHorizon.hzdepb_r() < 10) {
            if (tHorizon.isOrganic()) {
              skippedHorizons.add(tHorizon.chkey());
            } else {
              if (tHorizon.hasWeppWepsParams(testModel).isEmpty()) {
                //  Okay move on and use SDM values for this horizon
                if (null == surfaceHorizon()) {
                  surfaceHorizon(tHorizon);
                }
                tHorizon.useGenericHz1Values(false);
                tHorizon.useGenericHz2Values(false);
              } else {
                skippedHorizons.add(tHorizon.chkey());
              }
            }
          } else {
            if (tHorizon.isOrganic()) {
              if (tHorizon.hzdepb_r() < 40) {
                if (null == surfaceHorizon()) {
                  surfaceHorizon(tHorizon);
                }
                tHorizon.useGenericHz1Values(true);
              } else {
                //  TODO:  What to do if we got all the way here and have not yet assigned a surfaceHorizon value??!
                //  TODO when writing this new horizon function be sure to toggle hz1 and hz2 together...they are mutually exclusive.
                tHorizon.useGenericHz2Values(true);
              }
            } else {
              if (tHorizon.isRestricting() && !tHorizon.isPermiable()) {
                restrictingHorizon(tHorizon);
                restrictingHorizon = tHorizon;
                skipTheRest = true;
                //  ??  How to do this ?? What does this mean???  Is this the "restricting layer" from which we calculate those values
                //  stop and populate n+1 line in .sol file or depth to bedrock parameter in .ifc file
              } else {
                if (tHorizon.isRestricting() && tHorizon.isPermiable()) {
                  if (tHorizon.hasWeppWepsParams(testModel).isEmpty()) {
                    //  Okay move on and use SDM values for this horizon
                    if (null == surfaceHorizon()) {
                      surfaceHorizon(tHorizon);
                    }
                    tHorizon.useGenericHz1Values(false);
                    tHorizon.useGenericHz2Values(false);
                  } else {
                    skippedHorizons.add(tHorizon.chkey());
                  }
                } else {
                  if ((msgWeppWepsParams = tHorizon.hasWeppWepsParams(testModel)).isEmpty()) {
                    //  Okay move on and use SDM values for this horizon
                    if (null == surfaceHorizon()) {
                      surfaceHorizon(tHorizon);
                    }
                    tHorizon.useGenericHz1Values(false);
                    tHorizon.useGenericHz2Values(false);
                  } else {
                    throw new WEPP_WEPSException("Horizon, " + tHorizon.chkey() + ", does not meet model parameter requirements: " + msgWeppWepsParams);
                  }
                }
              }
            }
          }
        }
      } else {
        skippedHorizons.add(tHorizon.chkey());
      }
    }

    if (null == surfaceHorizon()) {
      throw new WEPP_WEPSException("Cannot continue with model parameter development, no surface horizon found that meets model requirements");
    }

    for (int i = 0; i < skippedHorizons.size(); i++) {
      horizons.remove(skippedHorizons.get(i));
    }

    if (testModel.equalsIgnoreCase("wepp") || !testModel.equalsIgnoreCase("weps")) {
      try {
        if (!surfaceHorizon.useGenericHz1Values()) {
          surfaceHorizon.computeErodibility();
        }
      } catch (WEPPException ex) {
        throw new WEPP_WEPSException(ex);
      }
      setBedrockData(restrictingHorizon);
    }

  }

  /**
   * Adjusts the component and its subsequent member horizons data to meet WEPP
   * filtering requirements. Finds the surface horizon, the restricting horizon,
   * removes horizons that ARS wants to ignore, sets default horizon values, and
   * computes the erodibility values. If any of the WEPP filtering requirements
   * cannot be met, this function will throw a ServiceException, with an
   * explanation of what is missing.
   *
   * @throws ServiceException
   */
  public void adjustForWEPP() throws WEPPException {
    try {
      getWEPPSurfaceHorizon();
      removeIgnoredHorizons();
      getWEPPRestrictingHorizon();  //  Also sets all the bedrock data for WEPP...    

      //  Surface horizon needs to be set, but some components may not have a restricting layer, so don't error on that not being set.
      if (null != surfaceHorizon) {
        //    If we're going to just return the generic organic sol file, 
        //  no need to calculate erodibility, or set default values.
        if (!isDefaultOrganicSolFileComponent()) {
          if (!surfaceHorizon.useGenericHz1Values()) {
            //  Set all default horizon values needed for WEPP before attempting to compute erodability
            for (Horizon tH : horizons.values()) {
              tH.setWEPPDefaults();
            }
            surfaceHorizon.computeErodibility();
          }
        }
      } else {
        throw new WEPPException("No suitable surface horizon was found for this component.  Cannot continue WEPP model preparations.  Please contact your SDM data steward to have this corrected for this component: " + cokey());
      }

    } catch (SDMException ex) {  // Wrap into a WEPP Exception so that services can choose to error-out or list errors but continue for WEPP filtering.
      throw new WEPPException(ex);
    }

  }

  /**
   * Finds the restricting layer (horizon) for this component by calling the
   * setBedRockData() function.
   *
   * @return Returns the restricting horizon if found, or throws an exception on
   * error.
   * @throws ServiceException
   */
  protected Horizon getWEPPRestrictingHorizon() throws SDMException {
    if (null == restrictingHorizon) {
      if ((null != horizons) && (horizons.size() > 0)) {
        //  This function finds the restricting horizon and sets the internal restrictingHorizon variable as well along with WEPP specific bedrock data.
        setBedrockData();
      }
    }

    return restrictingHorizon;
  }

  /**
   * Locates a suitable surface layer if one is present for this component. If
   * multiple organic layers are stacked it adds them up until the total depth
   * reaches the required ARS depth for an organic layer. ARS requires WEPP
   * surface horizons to be organic.
   *
   * @return Returns the surface horizon if found.
   * @throws ServiceException if an error is encountered during review of
   * horizon data.
   */
  protected Horizon getWEPPSurfaceHorizon() throws SDMException {
    if (null == surfaceHorizon) {
      if ((null != horizons) && (horizons.size() > 0)) {
        Horizon nextHorizon = null;
        double organicDepthTotal = 0;

        ArrayList<Horizon> horizonArray = new ArrayList<>(horizons.values());

        for (int i = 0; i < horizonArray.size(); i++) {
          Horizon tHorizon = horizonArray.get(i);
          boolean mineralHorizon = tHorizon.isMineral();

          nextHorizon = (((i + 1) < horizonArray.size()) ? horizonArray.get(i + 1) : null);

          if (tHorizon.isOrganic()) {
            organicDepthTotal += (tHorizon.hzdepb_r() - tHorizon.hzdept_r());
          } else {
            //stop adding horizons, we have a non organic one.
            nextHorizon = tHorizon;
            break;
          }
          if (organicDepthTotal >= 10) {
            //  Not really needed but keeps the rest of the filter funcitons happy to set this...
            surfaceHorizon = horizonArray.get(0);
            surfaceHorizon.setSurface(true);
            break;
          }
        }
        if (organicDepthTotal >= 10) {
          isOrganic = true;
          if (null != nextHorizon) {
            if (nextHorizon.isMineral()) {
              //  Set all horizons above the mineral one to use generic hz1 values from generic organic .sol file.
              for (int i = 0; i < horizonArray.size(); i++) {
                Horizon tHorizon = horizonArray.get(i);
                if (tHorizon == nextHorizon) {
                  break;
                }
                tHorizon.useGenericSolParams(true);
              }
            }
          }
        } else {
          surfaceHorizon = nextHorizon;
          if (null != surfaceHorizon) {
            surfaceHorizon.setSurface(true);

            for (int i = 0; i < horizonArray.size(); i++) {
              if (horizonArray.get(i) == surfaceHorizon) {
                break;
              }
              //  Remove skipped upper horizons from horizon list
              horizons.remove(horizonArray.get(i));
            }

          }
        }
      }
    }
    return surfaceHorizon;
  }

  /**
   * Removes horizons that ARS feels should be ignored for WEPP filtering.
   * Horizon is removed entirely from the component's current list of horizons
   * if it is deemed "ignored".
   *
   * @throws ServiceException
   */
  public void removeIgnoredHorizons() throws SDMException {
    ArrayList<String> badKeys = new ArrayList<>();
    for (String hKey : horizons.keySet()) {
      Horizon tHorizon = horizons.get(hKey);
      if (tHorizon.isIgnored() && ((null != surfaceHorizon) && (tHorizon != surfaceHorizon))) {
        badKeys.add(hKey);
      }
    }

    for (int i = 0; i < badKeys.size(); i++) {
      horizons.remove(badKeys.get(i));
    }
  }

  public void computeSalinityAndSodicity() {
    double totalSalinity = 0.0;
    double totalSodicity = 0.0;
    double totalDepth = 0.0;
    boolean allValuesPresent = true;

    for (Horizon tHorizon : horizons.values()) {
      if ((!EvalResult.testDefaultDouble(tHorizon.hzdepb_r()))
          && (!EvalResult.testDefaultDouble(tHorizon.hzdept_r()))) {
        if (tHorizon.hzdept_r() <= 60.0) {
          double thickness = (tHorizon.hzdepb_r() - tHorizon.hzdept_r());

          if (EvalResult.testDefaultDouble(tHorizon.ec_r()) || EvalResult.testDefaultDouble(tHorizon.sar_r())) {
            allValuesPresent = false;
            break;  //  No point in continuing; cannot calculate weighted averages if missing data...
          }

          double wAvgSal = thickness * tHorizon.ec_r();
          double wAvgSod = thickness * tHorizon.sar_r();

          if (tHorizon.hzdepb_r() <= 60.0) {
            totalSalinity += wAvgSal;
            totalSodicity += wAvgSod;
            totalDepth += thickness;
          } else {
            double diff = 60.0 - tHorizon.hzdept_r();
            wAvgSal *= (diff / thickness);
            wAvgSod *= (diff / thickness);
            totalSalinity += wAvgSal;
            totalSodicity += wAvgSod;
            totalDepth += diff;
          }
        }
      }
    }

    if ((totalDepth > 0.0) && (allValuesPresent)) {
      calculated_salinity(totalSalinity / totalDepth);
      calculated_sodicity(totalSodicity / totalDepth);
    }
  }

  public void computeTillageLayerClayTotal() {
    double totalClay = 0.0;
    double totalDepth = 0.0;

    for (Horizon tHorizon : horizons.values()) {
      if ((!EvalResult.testDefaultDouble(tHorizon.hzdepb_r()))
          && (!EvalResult.testDefaultDouble(tHorizon.hzdept_r()))
          && (!EvalResult.testDefaultDouble(tHorizon.claytotal_r()))) {
        if (tHorizon.hzdept_r() <= 15.24) {
          double thickness = (tHorizon.hzdepb_r() - tHorizon.hzdept_r());
          double wAvg = thickness * tHorizon.claytotal_r();
          if (tHorizon.hzdepb_r() <= 15.24) {
            totalClay += wAvg;
            totalDepth += thickness;
          } else {
            double diff = 15.24 - tHorizon.hzdept_r();
            wAvg *= (diff / thickness);
            totalClay += wAvg;
            totalDepth += diff;
          }
        }
      }
    }

    if (totalDepth > 0.0) {
      calculated_tl_claytotal(totalClay / totalDepth);
    }
  }

  private int computeNSLPHsgA() {
    int nslpNumber = 3;
    if (slope_r() > 12) {
      nslpNumber = 2;
    }
    return nslpNumber;
  }

  private int computeNSLPHsgB() {
    int nslpNumber = -1;
    double slope = slope_r();
    double kffact = calculated_kffact();

    if ((slope <= 12 && kffact >= 0.24) || (slope > 12)) {
      nslpNumber = 1;
    } else if ((slope >= 3) && (slope <= 12) && (kffact < 0.24)) {
      nslpNumber = 2;
    } else if ((slope < 3) && (kffact < 0.24)) {
      nslpNumber = 3;
    }
    return nslpNumber;
  }

  private int computeNSLPHsgC() {
    int nslpNumber = 1;
    return nslpNumber;
  }

  private int computeNSLPHsgD() {
    int nslpNumber = 0;
    return nslpNumber;
  }

  private int computeSRPHsgB() {
    int srp_number = -1;
    if (slope_r() < 4) {
      srp_number = 0;
    } else if (slope_r() >= 4 && slope_r() <= 6 && calculated_kffact() < 0.32) {
      srp_number = 1;
    } else if (slope_r() >= 4 && slope_r() <= 6 && calculated_kffact() >= 0.32) {
      srp_number = 2;
    } else if (slope_r() > 6) {
      srp_number = 3;
    }
    return srp_number;
  }

  private int computeSRPHsgC() {
    int srp_number = -1;
    if (slope_r() < 2) {
      srp_number = 0;
    } else if (slope_r() >= 2 && slope_r() <= 6 && calculated_kffact() < 0.28) {
      srp_number = 1;
    } else if (slope_r() >= 2 && slope_r() <= 6 && calculated_kffact() >= 0.28) {
      srp_number = 2;
    } else if (slope_r() > 6) {
      srp_number = 3;
    }
    return srp_number;
  }

  private int computeSRPHsgD() {
    int srp_number = -1;
    if (slope_r() < 2 && calculated_kffact() < 0.28) {
      srp_number = 0;
    } else if (slope_r() < 2 && calculated_kffact() >= 0.28) {
      srp_number = 1;
    } else if (slope_r() >= 2 && slope_r() <= 4) {
      srp_number = 2;
    }//NOTE:  For the else below:  The isDrained should already be checked 
    //        before calling this fucntion therefore no need to use it in the hwt_lt_24 comparison. 
    else if ((slope_r() > 4) || ((calculated_wtkind().equals("Perched")
        || calculated_wtkind().equals("Apparent")) || this.calculated_hwt_lt_24())) {
      srp_number = 3;
    }
    return srp_number;
  }

  /**
   * Traverses the horizons of this component and looks for the layer that meets
   * ARS conditions for being a restricting layer. If one is found, it is
   * marked, and the permeable or non-permeable restriction values are set.
   *
   * @throws ServiceException
   */
  public void setBedrockData() throws SDMException {
    //char hzChar;

    restrict = 0;
    restrictType = 0;
    aniso = 0;
    uksat = 0;

    if (null != horizons) {
      for (Horizon tHorizon : horizons.values()) {
        if (tHorizon.evaluateForRestrictingHorizon()) {
          if (tHorizon.isPermiable()) {
            restrict = 1;
            restrictType = 2;
            aniso = 25.000000;
            uksat = 3.6;
            restrictingHorizon = tHorizon;
            restrictingHorizon.setRestricting(true);
          } else {
            restrict = 1;
            restrictType = 2;
            aniso = 25.000000;
            uksat = 0.0036;
            restrictingHorizon = tHorizon;
            restrictingHorizon.setRestricting(true);
          }
          break;
        }
      }
    }
  }

  public void setBedrockData(Horizon tHorizon) throws SDMException {
    //char hzChar;

    restrict = 0;
    restrictType = 0;
    aniso = 0;
    uksat = 0;

    if (null != tHorizon) {
      if (tHorizon.evaluateForRestrictingHorizon()) {
        if (tHorizon.isPermiable()) {
          restrict = 1;
          restrictType = 2;
          aniso = 25.000000;
          uksat = 3.6;
          restrictingHorizon = tHorizon;
          restrictingHorizon.setRestricting(true);
        } else {
          restrict = 1;
          restrictType = 2;
          aniso = 25.000000;
          uksat = 0.0036;
          restrictingHorizon = tHorizon;
          restrictingHorizon.setRestricting(true);
        }
      }
    }
  }

  private void setHorizons(JSONArray dataJSON) throws ServiceException, JSONException {
    if ((null != dataJSON) && (dataJSON.length() > 0)) {
      for (int i = 0; i < dataJSON.length(); i++) {
        Horizon horizon = new Horizon(dataJSON.getJSONArray(i));

        if (!horizons.containsKey(horizon.chkey())) {
          horizons.put(horizon.chkey(), horizon);
        } else {
          throw new ServiceException("Duplicate component passed to Component.setHorizons() from input JSON.  Horizon keys listed by component should be unique.");
        }
      }
      tableComponentCalculatoins.long_name(comp_long_name());
    }
  }

  private JSONArray groupBySelectionType(String type, boolean onlySelectedHorizons) throws JSONException {
    JSONArray horizonArray = new JSONArray();

    for (String chkey : horizons.keySet()) {
      Horizon tHorizon = horizons.get(chkey);
      if ((tHorizon.selected() && tHorizon.hasSelectedReason(type) && onlySelectedHorizons) || (!onlySelectedHorizons)) {
        horizonArray.put(tHorizon.toJSON());
      }
    }

    return horizonArray;
  }

  private JSONArray groupBySelectionTypeBasicJSON(String type, boolean onlySelectedHorizons) throws JSONException {
    JSONArray horizonArray = new JSONArray();

    for (String chkey : horizons.keySet()) {
      Horizon tHorizon = horizons.get(chkey);
      if ((tHorizon.selected() && tHorizon.hasSelectedReason(type) && onlySelectedHorizons) || (!onlySelectedHorizons)) {
        horizonArray.put(tHorizon.toBasicJSON());
      }
    }

    return horizonArray;
  }

  public final void hydricrating(boolean value) {
    tableComponent.hydricrating(value);
  }

  public final boolean hydricrating() {
    return tableComponent.hydricrating();
  }

  public final void albedodry_h(double value) {
    tableComponent.albedodry_h(value);
  }

  public final double albedodry_h() {
    return tableComponent.albedodry_h();
  }

  public final void albedodry_l(double value) {
    tableComponent.albedodry_l(value);
  }

  public final double albedodry_l() {
    return tableComponent.albedodry_l();
  }

  public final void albedodry_r(double value) {
    tableComponent.albedodry_r(value);
  }

  public final double albedodry_r() {
    return tableComponent.albedodry_r();
  }

  /**
   *
   * @return
   */
  public final double calculated_water_ep() {
    return tableComponentCalculatoins.calculated_water_ep();
  }

  /**
   *
   * @param water_ep
   */
  public final void calculated_water_ep(double water_ep) {
    tableComponentCalculatoins.calculated_water_ep(water_ep);
  }

  /**
   *
   * @return
   */
  public final double calculated_wind_ep() {
    return tableComponentCalculatoins.calculated_wind_ep();
  }

  /**
   *
   * @param wind_ep
   */
  public final void calculated_wind_ep(double wind_ep) {
    tableComponentCalculatoins.calculated_wind_ep(wind_ep);
  }

  /**
   *
   * @return
   */
  public final double area_pct() {
    return tableComponentCalculatoins.area_pct();
  }

  /**
   *
   * @param area_pct
   */
  public final void area_pct(double area_pct) {
    tableComponentCalculatoins.area_pct(area_pct);
  }

  /**
   *
   * @return
   */
  public double calculated_area() {
    return tableComponentCalculatoins.area();
  }

  /**
   *
   * @param value
   */
  public final void drainagecl(String value) {
    tableComponent.drainagecl(value);
  }

  /**
   *
   * @return
   */
  public String drainagecl() {
    return tableComponent.drainagecl();
  }

  /**
   *
   * @param area
   */
  public void calculated_area(double area) {
    tableComponentCalculatoins.area(area);
  }

  public String erocl() {
    return tableComponent.erocl();
  }

  public void erocl(String value) {
    tableComponent.erocl(value);
  }

  /**
   *
   * @return
   */
  public final double calculated_claytotal_r() {
    return tableComponentCalculatoins.claytotal_r();
  }

  public final void calculated_claytotal_r(double value) {
    tableComponentCalculatoins.claytotal_r(value);
  }

  public final double calculated_tl_claytotal() {
    return tableComponentCalculatoins.tl_claytotal();
  }

  public final void calculated_tl_claytotal(double value) {
    tableComponentCalculatoins.tl_claytotal(value);
  }

  public final void calculated_salinity(double value) {
    tableComponentCalculatoins.calculated_salinity(value);
  }

  public final void calculated_sodicity(double value) {
    tableComponentCalculatoins.calculated_sodicity(value);
  }

  public final double calculated_salinity() {
    return tableComponentCalculatoins.calculated_salinity();
  }

  public final double calculated_sodicity() {
    return tableComponentCalculatoins.calculated_sodicity();
  }

  /**
   *
   * @param calculated_coarse_frag
   */
  public final void calculated_coarse_frag(double calculated_coarse_frag) {
    tableComponentCalculatoins.calculated_coarse_frag(calculated_coarse_frag);
  }

  /**
   *
   * @return
   */
  public final double calculated_coarse_frag() {
    return tableComponentCalculatoins.calculated_coarse_frag();
  }

  /**
   *
   * @param calculated_coarse_frag_vol_total
   */
  public final void calculated_coarse_frag_vol_total(double calculated_coarse_frag_vol_total) {
    tableComponentCalculatoins.calculated_coarse_frag_vol_total(calculated_coarse_frag_vol_total);
  }

  /**
   *
   * @return
   */
  public final double calculated_coarse_frag_vol_total() {
    return tableComponentCalculatoins.calculated_coarse_frag_vol_total();
  }

  /**
   *
   * @param calculated_hwt_lt_24
   */
  public final void calculated_hwt_lt_24(boolean calculated_hwt_lt_24) {
    tableComponentCalculatoins.calculated_hwt_lt_24(calculated_hwt_lt_24);
  }

  /**
   *
   * @param value
   */
  public final void calculated_ec(double value) {
    tableComponentCalculatoins.calculated_ec(value);
  }

  /**
   *
   * @return
   */
  public final double calculated_ec() {
    return tableComponentCalculatoins.calculated_ec();
  }

  /**
   *
   * @param value
   */
  public final void calculated_ecec(double value) {
    tableComponentCalculatoins.calculated_ecec(value);
  }

  /**
   *
   * @return
   */
  public final double calculated_ecec() {
    return tableComponentCalculatoins.calculated_ecec();
  }

  /**
   *
   * @param value
   */
  public final void calculated_cec7(double value) {
    tableComponentCalculatoins.calculated_cec7(value);
  }

  /**
   *
   * @return
   */
  public final double calculated_cec7() {
    return tableComponentCalculatoins.calculated_cec7();
  }

  /**
   *
   * @return
   */
  public final boolean calculated_hwt_lt_24() {
    return tableComponentCalculatoins.calculated_hwt_lt_24();
  }

  /**
   *
   * @param calculated_hzdepb_r
   */
  public final void calculated_hzdepb_r(double calculated_hzdepb_r) {
    tableComponentCalculatoins.calculated_hzdepb_r(calculated_hzdepb_r);
  }

  /**
   *
   * @return
   */
  public final double calculated_hzdepb_r() {
    return tableComponentCalculatoins.calculated_hzdepb_r();
  }

  /**
   *
   * @param kffact
   */
  public final void calculated_kffact(double kffact) {
    tableComponentCalculatoins.calculated_kffact(kffact);
  }

  /**
   *
   * @return
   */
  public final double calculated_kffact() {
    return tableComponentCalculatoins.calculated_kffact();
  }

  /**
   *
   * @param lsfact
   */
  public final void calculated_lsfact(double lsfact) {
    tableComponentCalculatoins.calculated_lsfact(lsfact);
  }

  public final double calculated_lsfact() {
    return tableComponentCalculatoins.calculated_lsfact();
  }

  /**
   *
   * @param nslp
   */
  public void calculated_nslp(String nslp) {
    tableComponentCalculatoins.calculated_nslp(nslp);
  }

  /**
   *
   * @return
   */
  public String calculated_nslp() {
    return tableComponentCalculatoins.calculated_nslp();
  }

  /**
   *
   * @param nslp_number
   */
  public void calculated_nslp_number(double nslp_number) {
    tableComponentCalculatoins.nslp_number(nslp_number);
  }

  /**
   *
   * @return
   */
  public double calculated_nslp_number() {
    return tableComponentCalculatoins.nslp_number();
  }

  /**
   *
   * @param calculated_om_r
   */
  public final void calculated_om_r(double calculated_om_r) {
    tableComponentCalculatoins.calculated_om_r(calculated_om_r);
  }

  /**
   *
   * @return
   */
  public final double calculated_om_r() {
    return tableComponentCalculatoins.calculated_om_r();
  }

  /**
   * @param calculated_pH
   */
  public final void calculated_pH(double calculated_pH) {
    tableComponentCalculatoins.calculated_pH(calculated_pH);
  }

  /**
   *
   * @return
   */
  public final double calculated_pH() {
    return tableComponentCalculatoins.calculated_pH();
  }

  public final void calculated_resdeptmin_r(double value) {
    tableComponentCalculatoins.resdeptmin_r(value);
  }

  public final double calculated_resdeptmin_r() {
    return tableComponentCalculatoins.resdeptmin_r();
  }

  /**
   *
   * @param calculated_sandtotal_r
   */
  public final void calculated_sandtotal_r(double calculated_sandtotal_r) {
    tableComponentCalculatoins.calculated_sandtotal_r(calculated_sandtotal_r);
  }

  /**
   *
   * @return
   */
  public final double calculated_sandtotal_r() {
    return tableComponentCalculatoins.calculated_sandtotal_r();
  }

  /**
   *
   * @return
   */
  public final double calculated_silttotal_r() {
    return tableComponentCalculatoins.silttotal_r();
  }

  public final void calculated_silttotal_r(double value) {
    tableComponentCalculatoins.silttotal_r(value);
  }

  /**
   *
   * @param srp
   */
  public void calculated_srp(String srp) {
    tableComponentCalculatoins.calculated_srp(srp);
  }

  /**
   *
   * @return
   */
  public String calculated_srp() {
    return tableComponentCalculatoins.calculated_srp();
  }

  /**
   *
   * @param srp_number
   */
  public void calculated_srp_number(double srp_number) {
    tableComponentCalculatoins.calculated_srp_number(srp_number);
  }

  /**
   *
   * @return
   */
  public double calculated_srp_number() {
    return tableComponentCalculatoins.calculated_srp_number();
  }

  /**
   *
   * @param wtbl_top_min
   */
  public final void calculated_wtbl_top_min(double wtbl_top_min) {
    tableComponentCalculatoins.calculated_wtbl_top_min(wtbl_top_min);
  }

  /**
   *
   * @return
   */
  public final double calculated_wtbl_top_min() {
    return tableComponentCalculatoins.calculated_wtbl_top_min();
  }

  /**
   *
   * @param wtkind
   */
  public final void calculated_wtkind(String wtkind) {
    tableComponentCalculatoins.calculated_wtbl(wtkind);
  }

  /**
   *
   * @return
   */
  public final String calculated_wtkind() {
    return tableComponentCalculatoins.calculated_wtbl();
  }

//    /**
//     *
//     * @return
//     */
//    public final String cokey() {
//        return tableComponent.cokey();
//    }
//
//    /**
//     *
//     * @param cokey
//     */
//    public final void cokey(String cokey) {
//        tableComponent.cokey(cokey);
//    }
  public final void cokey(int value) {
    Validation.checkCokey(value);
    tableComponent.cokey(value);
  }

  public final void cokey(String value) {
    Validation.checkCokey(value);
    tableComponent.cokey(Integer.parseInt(value));
  }

  public final String cokey() {
    return Integer.toString(tableComponent.cokey());
  }

  /**
   *
   * @return
   */
  public final String compkind() {
    return tableComponent.compkind();
  }

  /**
   *
   * @param compkind
   */
  public final void compkind(String compkind) {
    tableComponent.compkind(compkind);
  }

  /**
   *
   * @return
   */
  public final String compname() {
    return tableComponent.compname();
  }

  /**
   *
   * @param compname
   */
  public final void compname(String compname) {
    tableComponent.compname(compname);
  }

  /**
   *
   * @return
   */
  public final double comppct_r() {
    return tableComponent.comppct_r();
  }

  /**
   *
   * @param comppct_r
   */
  public final void comppct_r(double comppct_r) {
    tableComponent.comppct_r(comppct_r);
  }

  //  NOTE:  HSG is hydgrp
  public void computeNSLP() throws ServiceException {

    int comp_nslp_number = -2;  //Preset to error condition of invalid combinations.

    //  OLD Logic---
    //  If Histosols OR ( Apparent and wtbl_top_min <= 76cm )
    // if (ip.getTaxorder().isEqual("Histosols") || ((ip.getWtbl().isEqual("Apparent")
    // || ip.getWtbl().isEqual("Perched")) && (ip.getWtblTopMin() <= 76))) {
    //  END OLD Logic
    if (taxorder().equals("Histosols") || ((calculated_wtkind().equalsIgnoreCase("Apparent")) && (!EvalResult.testDefaultDouble(calculated_wtbl_top_min())) && (calculated_wtbl_top_min() <= 76))) {
      comp_nslp_number = 3;
    } else {
      //  Otherwise we need to look at slope and/or coarse fragmentation for this component's hydrologic group.
      switch (hydgrp()) {
        case "A":
          comp_nslp_number = computeNSLPHsgA();
          break;
        case "B":
          comp_nslp_number = computeNSLPHsgB();
          break;
        case "C":
          comp_nslp_number = computeNSLPHsgC();
          break;
        case "D":
          comp_nslp_number = computeNSLPHsgD();
          break;

        //  Any dual/combined hydrologic group is "HIGH"
        case "A/D":
        case "B/D":
        case "C/D":
          comp_nslp_number = 3;
          break;

        //NOTE:  There is no default section here because the comp_nslp_number variable is
        //      being used to mark specific error conditions.  If this switch fails through
        //      then comp_nslp_number will remain set to -2, which is checked below.  This should NEVER happen, but
        //      the check is here just in case...
      }
    }

    //Check for error conditions, and if none, then set the NSLP number for this component.
    if (comp_nslp_number != -1 && comp_nslp_number != -2) { // These represent error conditions in this function.
      if ((comp_nslp_number >= 0) && (comp_nslp_number <= 3)) {
        //Adjust final number by coarseFrag values logic
        if (calculated_coarse_frag() > 30) {
          comp_nslp_number += 2;
          if (comp_nslp_number > 3) {
            comp_nslp_number = 3;
          }
        } else if (calculated_coarse_frag() > 10) {
          comp_nslp_number += 1;
          if (comp_nslp_number > 3) {
            comp_nslp_number = 3;
          }
        } // Otherwise just leave it alone, no adjustments are needed.

        calculated_nslp_number(comp_nslp_number);
        calculated_nslp(nslpList[comp_nslp_number]);
      } else {
        throw new ServiceException("An invalid Soil Leaching Potential value was calculated:  " + comp_nslp_number + ". Cannot continue. ");
      }
    } else if (comp_nslp_number == -1) { //  Failed in the embedded Hsg computational functions
      throw new ServiceException("Invalid Hsg calculation for this soil for: " + hydgrp() + ".  Cannot continue with NSLP estimation for component key: " + cokey() + ". ");
    } else if (comp_nslp_number == -2) {  //Failed the above switch statement
      throw new ServiceException("Invalid Hsg name for this soil component key: " + cokey()
          + " which is neither Histosols nor (Apparent AND <= 76cm) in Water Table Type.  Cannot continue with the NSLP estimations.");
    } //  No else here because it cannot happen...unless someone edits this code and adds a new "error condition" number to use for
    //  comp_nslp_number...if you do, then PLEASE add a check for it here and keep to the convention of using a negative integer.
  }

  public void computePestSARP(boolean isDrained) {
    String hydgrp = hydgrp();
    switch (hydgrp) {
      case "A/D":
      case "B/D":
      case "C/D":
        if (!isDrained) {
          hydgrp = "D";
        } else {
          hydgrp = hydgrp.substring(0, 1);
        }
        break;
    }

    psarp_number(2);

    if ((hydgrp.equals("C") && calculated_kffact() >= 0.21)
        || (hydgrp.equals("D") && calculated_kffact() >= 0.10)) {
      psarp_number(3);
    } else if (hydgrp.equals("A")
        || (hydgrp.equals("B") && calculated_kffact() <= 0.10)
        || (hydgrp.equals("C") && calculated_kffact() <= 0.07)
        || (hydgrp.equals("D") && calculated_kffact() <= 0.02)) {
      psarp_number(1);
    }

    if (this.slopegr15()) {
      if (psarp_number() < 3) {
        psarp_number(psarp_number() + 1);
      }
    }

  }

  public void computePestSLP(boolean isDrained) {
    boolean aoa_comp_cracksgr24 = false;

    if (calculated_hwt_lt_24() && !isDrained) {
      pslp_number(PSLP_HIGH);
    } else if (((hydgrp().equals("A") || hydgrp().equals("A/D")) && ((calculated_hzdepb_r() * calculated_om_r()) <= (30 * INCH_TO_CM)))
        || ((hydgrp().equals("B") || hydgrp().equals("B/D")) && ((calculated_hzdepb_r() * calculated_om_r()) <= (9 * INCH_TO_CM)) && (calculated_kffact() <= 0.48))
        || ((hydgrp().equals("B") || hydgrp().equals("B/D")) && ((calculated_hzdepb_r() * calculated_om_r()) <= (15 * INCH_TO_CM)) && (calculated_kffact() <= 0.26))) {
      pslp_number(PSLP_HIGH);
    } else if (((hydgrp().equals("B") || hydgrp().equals("B/D")) && ((calculated_hzdepb_r() * calculated_om_r()) >= (35 * INCH_TO_CM)) && calculated_kffact() >= 0.40)
        || ((hydgrp().equals("B") || hydgrp().equals("B/D")) && ((calculated_hzdepb_r() * calculated_om_r()) >= (45 * INCH_TO_CM)) && calculated_kffact() >= 0.20)
        || ((hydgrp().equals("C") || hydgrp().equals("C/D")) && ((calculated_hzdepb_r() * calculated_om_r()) <= (10 * INCH_TO_CM)) && calculated_kffact() <= 0.28)
        || ((hydgrp().equals("C") || hydgrp().equals("C/D")) && ((calculated_hzdepb_r() * calculated_om_r()) >= (10 * INCH_TO_CM)))) {
      if (!aoa_comp_cracksgr24) {
        pslp_number(PSLP_LOW);
      } else {
        pslp_number(PSLP_INTERMEDIATE);
      }
    } else if (hydgrp().equals("D")) {
      if (!aoa_comp_cracksgr24) {
        pslp_number(PSLP_VERY_LOW);
      } else {
        pslp_number(PSLP_LOW);
      }
    } else if (!aoa_comp_cracksgr24) {
      pslp_number(PSLP_INTERMEDIATE);
    } else {
      pslp_number(PSLP_HIGH);
    }

    if (pslp_number() < COMP_PSLP_STRING.length) {
      pslp(COMP_PSLP_STRING[pslp_number()]);
    } else {
      pslp(COMP_PSLP_STRING[0]);
    }

  }

  public void computePestSSRP(boolean isDrained) {

    switch (hydgrp()) {
      case "C":
      case "D":
      case "C/D":
        pssrp_number(3);
        break;
      case "A":
        pssrp_number(1);
        break;
      case "A/D":
        if (isDrained) {
          pssrp_number(1);
        } else {
          pssrp_number(3);
        }
        break;
      case "B":
        pssrp_number(2);
        break;
      case "B/D":
        if (isDrained) {
          pssrp_number(2);
        } else if (!isDrained) {
          pssrp_number(3);
        }
        break;
      default:
        pssrp_number(0);
    }
    pssrp(COMP_PSSRP[pssrp_number()]);
  }

  public void computeSedNutSRP(boolean isDrained) throws ServiceException {
    int comp_srp_number = -2;

    switch (hydgrp()) {
      case "A":
        comp_srp_number = 0;
        break;
      case "B":
        comp_srp_number = computeSRPHsgB();
        break;
      case "C":
        comp_srp_number = computeSRPHsgC();
        break;
      case "D":
        if ((calculated_wtkind().equals("Perched")
            || calculated_wtkind().equals("Apparent")) || (calculated_hwt_lt_24() && !isDrained)) {
          comp_srp_number = 3;
        } else if (slope_r() < 2 && calculated_kffact() < 0.28) {
          comp_srp_number = 0;
        } else if (slope_r() < 2 && calculated_kffact() >= 0.28) {
          comp_srp_number = 1;
        } else if (slope_r() >= 2 && slope_r() <= 4) {
          comp_srp_number = 2;
        } else if (slope_r() > 4) {
          comp_srp_number = 3;
        }
        break;
      case "A/D":
        if (isDrained) {
          comp_srp_number = 0;
        } else {
          comp_srp_number = computeSRPHsgD();
        }
        break;
      case "B/D":
        if (isDrained) {
          comp_srp_number = computeSRPHsgB();
        } else {
          comp_srp_number = computeSRPHsgD();
        }
        break;
      case "C/D":
        if (isDrained) {
          comp_srp_number = computeSRPHsgC();
        } else {
          comp_srp_number = computeSRPHsgD();
        }
        break;
    }
    if (comp_srp_number != -1 && comp_srp_number != -2) {
      calculated_srp_number(comp_srp_number);
      calculated_srp(srpList[comp_srp_number]);
    } else {
      throw new ServiceException("Error in calculating SedNutSRP:  Invalid hydgrp value, (" + hydgrp() + "), for this component, " + cokey() + ", found in the database.  Please contact your NRCS data steward for assistance in corecting the database.");
    }
  }

  public LinkedHashMap<String, Cocropyld> cropYlds() {
    return cropYlds;
  }

  public Component deepCopy() throws ServiceException {
    Component tComponent = new Component();

    deepCopy(tComponent);

    return tComponent;
  }

  public synchronized void deepCopy(Component newComponent) {
    if (null != newComponent) {
      tableComponent.deepCopy(newComponent.tableComponent);
      tableComponentCalculatoins.deepCopy(newComponent.tableComponentCalculatoins);
      tableExcluded.deepCopy(newComponent.tableExcluded);

      LinkedHashMap<String, Horizon> tHorizons = new LinkedHashMap<>();
      for (Horizon tHorizon : horizons.values()) {
        Horizon newHorizon = tHorizon.deepCopy();
        if (null != newHorizon) {
          tHorizons.put(newHorizon.chkey(), newHorizon);
        }
      }

      if (tHorizons.size() > 0) {
        newComponent.setHorizons(tHorizons);
      }

      newComponent.restrict(restrict());
      newComponent.restrictType(restrictType());
      newComponent.aniso(aniso());
      newComponent.uksat(uksat());
      newComponent.surfaceHorizon(surfaceHorizon());
      newComponent.restrictingHorizon(restrictingHorizon());
      newComponent.isOrganic(isOrganic());
    }
  }

  public void setHorizons(LinkedHashMap<String, Horizon> tHorizons) {
    if ((null != tHorizons) && (tHorizons.size() > 0)) {
      horizons.clear();

      for (Horizon tHorizon : tHorizons.values()) {
        if (!horizons.containsKey(tHorizon.chkey())) {
          horizons.put(tHorizon.chkey(), tHorizon);
        }
      }
    }
  }

  public int restrict() {
    return restrict;
  }

  public int restrictType() {
    return restrictType;
  }

  public double aniso() {
    return aniso;
  }

  public double uksat() {
    return uksat;
  }

  public Horizon surfaceHorizon() {
    return surfaceHorizon;
  }

  public Horizon restrictingHorizon() {
    return restrictingHorizon;
  }

  public boolean isOrganic() {
    return isOrganic;
  }

  public void restrict(int value) {
    restrict = value;
  }

  public void restrictType(int value) {
    restrictType = value;
  }

  public void aniso(double value) {
    aniso = value;
  }

  public void uksat(double value) {
    uksat = value;
  }

  /**
   * updateCalculations goes through and updates any table calculations locally
   * and at the horizon, texture fragment levels in order to fill in missing
   * values.
   */
  public void updateCalculations() {
    double profile_thk = 0.0;
    boolean haveHzDepth = false;
    boolean haveOM_R = false;
    double comp_product = 0.0;
    int counter = 0;

    if (horizons.size() > 0) {
      double pH = 0.0;
      double pH_thickness = 0.0;

      if (EvalResult.testDefaultDouble(calculated_coarse_frag_vol_total())) {
        calculated_coarse_frag_vol_total(0.0);
      }

      for (Horizon horizon : horizons.values()) {
        double horizonThickness = 0.0;
        double horizonProduct = 0.0;
        double pH_l = horizon.ph1to1h2o_l();
        double pH_r = horizon.ph1to1h2o_r();
        double pH_h = horizon.ph1to1h2o_h();

//                    if (0 == counter) {
//                        calculated_om_r(horizon.om_r());
//                    }
        counter++;

        horizonThickness = horizon.hzthk_r();

        if ((!haveOM_R) && (!haveHzDepth) && (horizon.selected())) {
          calculated_om_r(horizon.om_r());
          calculated_hzdepb_r(horizonThickness);
          haveHzDepth = true;
          haveOM_R = true;
        }

        if (EvalResult.testDefaultDouble(pH_r)) {
          if (!EvalResult.testDefaultDouble(pH_h) && !EvalResult.testDefaultDouble(pH_l)) {
            pH_r = (pH_h + pH_l) / 2.0;
            pH_thickness += horizonThickness;
            pH += pH_r * horizonThickness;
          }//Else ignore this layer in the calculation...don't need to do anything else in this case...

        } else {
          pH_thickness += horizonThickness;
          pH += pH_r * horizonThickness;
        }

        calculated_coarse_frag_vol_total(calculated_coarse_frag_vol_total() + (EvalResult.testDefaultDouble(horizon.fragvol_r()) ? 0 : horizon.fragvol_r()));

        profile_thk += horizonThickness;
        horizonProduct = horizonThickness * (EvalResult.testDefaultDouble(horizon.fragvol_r()) ? 0 : horizon.fragvol_r());
        comp_product += horizonProduct;
      }

      if (profile_thk > 0.0) {
        calculated_coarse_frag(comp_product / profile_thk);
      }
      if (pH_thickness > 0.0) {
        calculated_pH(pH / pH_thickness);
      }
      profileDepth(profile_thk);
    }

//        double totalFragVol = 0.0, calculatedFragVol = 0.0;
//        double totalProfileDepth = 0.0;
//
//        for (Horizon tHorizon : horizons.values()) {
//            totalProfileDepth += tHorizon.hzthk_r();
//            //fragvol_r auto fills itself from the underlying fragment records, if it hasn't been set yet.
//            //calculatedFragVol += (tHorizon.hzthk_r() / totalProfileDepth) * tHorizon.fragvol_r();
//            calculatedFragVol = calculatedFragVol + (tHorizon.hzthk_r() * tHorizon.fragvol_r());
//            totalFragVol += tHorizon.fragvol_r();
//        }
//
//        profileDepth(totalProfileDepth);
//
//        if (totalProfileDepth > 0.0) {
//            calculated_coarse_frag(calculatedFragVol / totalProfileDepth);
//        }
//
//        calculated_coarse_frag_vol_total(totalFragVol);
  }

  public void surfaceHorizon(Horizon value) {
    surfaceHorizon = value;
  }

  public void restrictingHorizon(Horizon value) {
    restrictingHorizon = value;
  }

  public void isOrganic(boolean value) {
    isOrganic = value;
  }

  public LinkedHashMap<String, Coecoclass> ecoClasses() {
    return ecoClasses;
  }

  public void exclude() {
    tableExcluded.exclude();
  }

  public void exclude(boolean value) {
    tableExcluded.exclude(value);
  }

  public void setExcludedReason(String reason) {
    exclude();
    tableExcluded.setExcludedReason(reason);
  }

  public boolean applyHorizonFilter(String filter) throws ServiceException {
    boolean ret_val = false;
    FilterResults filterResults = new FilterResults();

    switch (filter.toLowerCase()) {
      case "water":
        filterResults.foundKffact = false;

        for (Horizon tHorizon : horizons.values()) {
          if (!filterResults.foundKffact) {
            if (EvalResult.testDefaultDouble(tHorizon.kffact())) {
              if (EvalResult.testDefaultDouble(tHorizon.kwfact())) {
                if (taxorder().equalsIgnoreCase("histosols")) {
                  tHorizon.kffact(0.02);
                  filterResults.foundKffact = true;
                  tHorizon.selected(true);
                  tHorizon.selectedReason("WATER");
                  calculated_kffact(tHorizon.kffact());
                  calculated_lsfact(tHorizon.lsfact());
                  calculated_claytotal_r(tHorizon.claytotal_r());
                  ret_val = true;
                }
              } else {
                filterResults.foundKffact = true;
                tHorizon.selected(true);
                tHorizon.selectedReason("WATER");
                calculated_kffact(tHorizon.kffact());
                calculated_lsfact(tHorizon.lsfact());
                calculated_claytotal_r(tHorizon.claytotal_r());
                ret_val = true;
              }
            } else {
              filterResults.foundKffact = true;
              tHorizon.selected(true);
              tHorizon.selectedReason("WATER");
              calculated_kffact(tHorizon.kffact());
              calculated_lsfact(tHorizon.lsfact());
              calculated_claytotal_r(tHorizon.claytotal_r());
              ret_val = true;
            }
          } else {
            break;
          }
        }
        if (!filterResults.foundKffact) {
          setExclude(true, BAD_KFFACT_LAYERS);
        }
        break;

      case "wind":
        filterResults.foundSandLayer = false;

        for (Horizon tHorizon : horizons.values()) {
          if (!filterResults.foundSandLayer) {
            if (taxorder().equalsIgnoreCase("histosols") || (!taxorder().equalsIgnoreCase("histosols") && (tHorizon.hzdepb_r() > 10) && (tHorizon.om_r() > 15))) {
              tHorizon.sandtotal_r(50);
            }
            if (tHorizon.sandtotal_r() >= 0.0) {
              calculated_sandtotal_r(tHorizon.sandtotal_r());
              tHorizon.selected(true);
              tHorizon.selectedReason("WIND");
              calculated_om_r(tHorizon.om_r());
              calculated_hzdepb_r(tHorizon.hzdepb_r());
              calculated_claytotal_r(tHorizon.claytotal_r());
              filterResults.foundSandLayer = true;
              ret_val = true;
            }
          } else {
            break;
          }
        }
        if (!filterResults.foundSandLayer) {
          setExclude(true, BAD_SAND_LAYERS);
        }
        break;

      default:
        throw new ServiceException("Invalid filter specified.");
    }

    return ret_val;
  }

  public String getExcludedReason() {
    return tableExcluded.getExcludedReason();
  }

  /**
   *
   * @param usedList
   */
  public void setHorizonOutputColumns(List<String> usedList) {
    for (String key : horizons.keySet()) {
      horizons.get(key).setOutputColumns(usedList);
    }
  }

  /**
   *
   * @param usedList
   */
  public void setHorizonOutputColumnOrdering(List<String> usedList) {
    for (String key : horizons.keySet()) {
      horizons.get(key).setOutputColumnOrdering(usedList);
    }
  }

  /**
   *
   * @param usedList
   */
  public void setTextureGroupOutputColumns(List<String> usedList) {
    for (Horizon tHorizon : horizons.values()) {
      for (TextureGroup tGroup : tHorizon.textureGroups.values()) {
        tGroup.setOutputColumns(usedList);
      }
    }
  }

  /**
   *
   * @param usedList
   */
  public void setTextureOutputColumns(List<String> usedList) {
    for (Horizon tHorizon : horizons.values()) {
      for (TextureGroup tGroup : tHorizon.textureGroups.values()) {
        for (Texture tTexture : tGroup.textures.values()) {
          tTexture.setOutputColumns(usedList);
        }
      }
    }
  }

  /**
   *
   * @param usedList
   */
  public void setFragmentOutputColumns(List<String> usedList) {
    for (Horizon tHorizon : horizons.values()) {
      for (Fragments tFragments : tHorizon.fragments.values()) {
        tFragments.setOutputColumns(usedList);
      }
    }
  }

  /**
   *
   * @param usedList
   */
  public void setFragmentOutputColumnOrdering(List<String> usedList) {
    for (Horizon tHorizon : horizons.values()) {
      for (Fragments tFragments : tHorizon.fragments.values()) {
        tFragments.setOutputColumnOrdering(usedList);
      }
    }
  }

  /**
   *
   * @param usedList
   */
  public void setTextureGroupOutputColumnOrdering(List<String> usedList) {
    for (Horizon tHorizon : horizons.values()) {
      for (TextureGroup tGroup : tHorizon.textureGroups.values()) {
        tGroup.setOutputColumnOrdering(usedList);
      }
    }
  }

  /**
   *
   * @param usedList
   */
  public void setTextureOutputColumnOrdering(List<String> usedList) {
    for (Horizon tHorizon : horizons.values()) {
      for (TextureGroup tGroup : tHorizon.textureGroups.values()) {
        for (Texture tTexture : tGroup.textures.values()) {
          tTexture.setOutputColumnOrdering(usedList);
        }
      }
    }
  }

  /**
   *
   * @param usedList
   */
  public void setHorizonUsedColumns(List<String> usedList) {
    for (String key : horizons.keySet()) {
      horizons.get(key).setUsedColumns(usedList);
    }
  }

  /**
   *
   * @param usedList
   */
  public void setOutputColumns(List<String> usedList) {
    tableComponent.setOutputColumns(usedList);
    tableComponentCalculatoins.setOutputColumns(usedList);
  }

  /**
   *
   * @param usedList
   */
  public void setOutputColumnOrdering(List<String> usedList) {
    tableComponent.setOutputColumnOrdering(usedList);
    tableComponentCalculatoins.setOutputColumnOrdering(usedList);
  }

  //TODO:  double-check that this is the same as is done for WEPS
  public double getWEPPSurfaceAlbedo() throws WEPPException {

    if (null != surfaceHorizon) {
      if (albedodry_r() == 0.0 || EvalResult.testDefaultDouble(albedodry_r()) || albedodry_r() < 0 || albedodry_r() > 1) {
        albedodry_r(0.6 / (Math.exp(0.4 * surfaceHorizon.om_r() * 100.0)));
      }
    } else {
      throw new WEPPException("Cannot compute WEPP surface albedo.  Missing surface horizon layer.");
    }

    return albedodry_r();
  }

  public TableComponent getTableComponent() {
    return tableComponent;
  }

  public TableComponentCalculations getTableComponentCalculations() {
    return tableComponentCalculatoins;
  }

  /**
   *
   * @param usedList
   */
  public void setUsedColumns(List<String> usedList) {
    tableComponent.setUsedColumns(usedList);
    tableComponentCalculatoins.setUsedColumns(usedList);
  }

  public LinkedHashMap<String, Horizon> horizons() {
    return this.horizons;
  }

  public LinkedHashMap<String, Comonth> comonths() {
    return this.comonths;
  }

  /**
   *
   * @return
   */
  public final String hydgrp() {
    return tableComponent.hydgrp();
  }

  /**
   *
   * @param hydgrp
   */
  public final void hydgrp(String hydgrp) {
    tableComponent.hydgrp(hydgrp);
  }

  public boolean isExcluded() {
    return tableExcluded.isExcluded();
  }

  public boolean allHorizonsOrganic() throws SDMException {
    int organicCount = 0;
    for (Horizon tHorizon : horizons.values()) {
      if (tHorizon.isOrganic()) {
        organicCount++;
      }
    }
    if (organicCount == horizons.size()) {
      return true;
    }
    return false;
  }

  public boolean isOrganicSoil() throws SDMException {
    //  This may get set to true while searching for the surface horizon in 
    // the beginning if multiple organic layers were found next to each other 
    // that all added up to more than 10cm in depth.
    if (isOrganic) {
      return true;
    } else if (null != surfaceHorizon) {

      //  Is the taxorder of this soil a Histosol?
      if (0 == taxorder().compareToIgnoreCase("histosols")) {
        return true;
      }

      //  Is the surface Horizon organic and sufficiently deep?
      if ((surfaceHorizon.hzdepb_r() - surfaceHorizon.hzdept_r()) > 10) {
        if (surfaceHorizon.om_r() >= 15) {
          return true;
        }
      }

      // Are all horizons organic?  Then soil is organic
      int organicCount = 0;
      for (Horizon tHorizon : horizons.values()) {
        if (tHorizon.isOrganic()) {
          organicCount++;
        }
      }
      if (organicCount == horizons.size()) {
        return true;
      }

    }

    return false;
  }

  /**
   *
   * @return
   */
  public final double length_r() {
    return tableComponentCalculatoins.length_r();
  }

  /**
   *
   * @param length_r
   */
  public final void length_r(double length_r) {
    tableComponentCalculatoins.length_r(length_r);
  }

  /**
   *
   * @return
   */
  public final String localphase() {
    return tableComponent.localphase();
  }

  /**
   *
   * @param localphase
   */
  public final void localphase(String localphase) {
    tableComponent.localphase(localphase);
  }

  public String comp_long_name() {
    String ret_val = compname() + "_" + mukey() + "_";
    Double tPct = comppct_r();

    ret_val += tPct.intValue();
    if (null != horizons) {
      Horizon topH = topSelectedHorizon();
      if (null != topH) {
        for (TextureGroup tGroup : topH.textureGroups.values()) {
          if (tGroup.rvindicator()) {
            ret_val += "_" + tGroup.texture();
          }
        }
      }
    }

    return ret_val;
  }

  public void merge(Component mergeThisUnit) throws ServiceException {
    this.tableComponent.merge(mergeThisUnit.getTableComponent());
    this.tableComponentCalculatoins.merge(mergeThisUnit.getTableComponentCalculations());
    for (String key : mergeThisUnit.horizons.keySet()) {
      if (horizons.containsKey(key)) {
        horizons.get(key).merge(mergeThisUnit.horizons.get(key));
      }
    }
  }

  public final void mukey(String value) {
    Validation.checkMukey(value);
    tableComponent.mukey(Integer.parseInt(value));
  }

  public final void mukey(int value) {
    Validation.checkMukey(value);
    tableComponent.mukey(value);
  }

  public final String mukey() {
    return Integer.toString(tableComponent.mukey());
  }

  public final boolean majorComponent() {
    return tableComponent.majorComponent();
  }

  /**
   *
   * @return
   */
  public final String otherph() {
    return tableComponent.otherph();
  }

  /**
   *
   * @param otherph
   */
  public final void otherph(String otherph) {
    tableComponent.otherph(otherph);
  }

  public final double profileDepth() {
    return tableComponentCalculatoins.calculated_profile_depth();
  }

  public final void profileDepth(double value) {
    tableComponentCalculatoins.calculated_profile_depth(value);
  }

  public final int psarp_number() {
    return tableComponentCalculatoins.psarp_number();
  }

  public final void psarp_number(int psarp_number) {
    tableComponentCalculatoins.psarp_number(psarp_number);
  }

  /**
   *
   * @return
   */
  public final String pslp() {
    return tableComponentCalculatoins.pslp();
  }

  /**
   *
   * @param pslp
   */
  public final void pslp(String pslp) {
    tableComponentCalculatoins.pslp(pslp);
  }

  /**
   *
   * @return
   */
  public final int pslp_number() {
    return tableComponentCalculatoins.pslp_number();
  }

  /**
   *
   * @param pslp_number
   */
  public final void pslp_number(int pslp_number) {
    tableComponentCalculatoins.pslp_number(pslp_number);
  }

  /**
   *
   * @return
   */
  public final String pssrp() {
    return tableComponentCalculatoins.pssrp();
  }

  /**
   *
   * @param pssrp
   */
  public final void pssrp(String pssrp) {
    tableComponentCalculatoins.pssrp(pssrp);
  }

  /**
   *
   * @return
   */
  public final int pssrp_number() {
    return tableComponentCalculatoins.pssrp_number();
  }

  /**
   *
   * @param pssrp_number
   */
  public final void pssrp_number(int pssrp_number) {
    tableComponentCalculatoins.pssrp_number(pssrp_number);
  }

  public void readCropYldFromSQL(ResultSet results) throws ServiceException {
    String cropYldKey;
    try {
      cropYldKey = results.getString(TableCocropyld.COCROPYLDKEY);
    } catch (SQLException ex) {
      throw new ServiceException("Cannot find the cocropyldkey value in the returned database resultset in readCropYldFromSQL(): " + ex.getMessage(), ex);
    }

    if (null != cropYldKey) {
      Cocropyld tCropYld = cropYlds.get(cropYldKey);
      if (null == tCropYld) {
        tCropYld = new Cocropyld(results);
        cropYlds.put(cropYldKey, tCropYld);
      } else {
        throw new ServiceException("Duplicate cocropyldkey found in results returned from database querey in readCropYldFromSQL().  Results should have unidque cocropyldkey.");
      }

    }
  }

  public void readEcoClassFromSQL(ResultSet results) throws ServiceException {
    String ecoClassKey;
    try {
      ecoClassKey = results.getString(TableCoecoclass.COECOCLASSKEY);
    } catch (SQLException ex) {
      throw new ServiceException("Cannot find the coecoclasskey value in the returned database resultset in readEcoClassFromSQL(): " + ex.getMessage(), ex);
    }

    if (null != ecoClassKey) {
      Coecoclass tEcoClass = ecoClasses.get(ecoClassKey);
      if (null == tEcoClass) {
        tEcoClass = new Coecoclass(results);
        ecoClasses.put(ecoClassKey, tEcoClass);
      } else {
        throw new ServiceException("Duplicate coecoclasskey found in results returned from database querey in readEcoClassFromSQL().  Results should have unidque coecoclasskeys.");
      }

    }
  }

  public void readFromSQL(ResultSet results) throws ServiceException {
    tableComponent.readValuesFromSQL(results);
    tableComponentCalculatoins.readValuesFromSQL(results);
  }

  public void setExclude(boolean value, String reason) {
    tableExcluded.setExcluded(value, reason);
  }

  public final void rsprod_h(int value) {
    tableComponent.rsprod_h(value);
  }

  public final int rsprod_h() {
    return tableComponent.rsprod_h();
  }

  public final void rsprod_l(int value) {
    tableComponent.rsprod_l(value);
  }

  public final int rsprod_l() {
    return tableComponent.rsprod_l();
  }

  /**
   *
   * @return
   */
  public final int rsprod_r() {
    return tableComponent.rsprod_r();
  }

  /**
   *
   * @param rsprod_r
   */
  public final void rsprod_r(int rsprod_r) {
    tableComponent.rsprod_r(rsprod_r);
  }

  public final void slope_h(double value) {
    tableComponent.slope_h(value);
  }

  public final double slope_h() {
    return tableComponent.slope_h();
  }

  public final void slope_l(double value) {
    tableComponent.slope_l(value);
  }

  public final double slope_l() {
    return tableComponent.slope_l();
  }

  /**
   *
   * @return
   */
  public final double slope_r() {
    return tableComponent.slope_r();
  }

  /**
   *
   * @param slope_r
   */
  public final void slope_r(double slope_r) {
    tableComponent.slope_r(slope_r);
  }

  /**
   *
   * @param slopegr15
   */
  public void slopegr15(boolean slopegr15) {
    tableComponentCalculatoins.calculated_slopegr15(slopegr15);
  }

  /**
   *
   * @return
   */
  public boolean slopegr15() {
    return tableComponentCalculatoins.calculated_slopegr15();
  }

  public final void slopelenusle_h(double value) {
    tableComponent.slopelenusle_h(value);
  }

  public final double slopelenusle_h() {
    return tableComponent.slopelenusle_h();
  }

  public final void slopelenusle_l(double value) {
    tableComponent.slopelenusle_l(value);
  }

  public final double slopelenusle_l() {
    return tableComponent.slopelenusle_l();
  }

  public final void slopelenusle_r(double value) {
    tableComponent.slopelenusle_r(value);
  }

  public final double slopelenusle_r() {
    return tableComponent.slopelenusle_r();
  }

  /**
   *
   * @param outArray
   * @throws JSONException
   */
  public void tableColumnsToJSON(JSONArray outArray) throws JSONException {
    tableComponent.toJSON(outArray);
    tableComponentCalculatoins.toJSON(outArray);
  }

  /**
   *
   * @return
   */
  public final String taxorder() {
    return tableComponent.taxorder();
  }

  /**
   *
   * @param taxorder
   */
  public final void taxorder(String taxorder) {
    tableComponent.taxorder(taxorder);
  }

  public final void taxsubgrp(String value) {
    tableComponent.taxsubgrp(value);
  }

  public final String taxsubgrp() {
    return tableComponent.taxsubgrp();
  }

  /**
   *
   * @return
   */
  public final String taxpartsize() {
    return tableComponent.taxpartsize();
  }

  /**
   *
   * @param taxpartsize
   */
  public final void taxpartsize(String taxpartsize) {
    tableComponent.taxpartsize(taxpartsize);
  }

  /**
   *
   * @return
   */
  public final double tfact() {
    return tableComponent.tfact();
  }

  /**
   *
   * @param tfact
   */
  public final void tfact(double tfact) {
    tableComponent.tfact(tfact);
  }

  public void usesGenericIFCorSOLValues(boolean value) {
    if (null != surfaceHorizon) {
      surfaceHorizon.useGenericHz1Values(value);
      surfaceHorizon.useGenericHz2Values(value);
    }

    if (null != horizons) {
      for (Horizon horizon : horizons.values()) {
        horizon.useGenericHz1Values(value);
        horizon.useGenericHz2Values(value);
      }
    }
  }

  public boolean usesGenericIFCorSOLValues() {
    if ((null != surfaceHorizon) && (surfaceHorizon.useGenericHz1Values() || surfaceHorizon.useGenericHz2Values())) {
      return true;
    }

    if (null != horizons) {
      for (Horizon horizon : horizons.values()) {
        if (horizon.useGenericHz1Values() || horizon.useGenericHz2Values()) {
          return true;
        }
      }
    }
    return false;
  }

  public String toIfc(double brockdepmin) throws WEPSException {
    if (null == surfaceHorizon) {
      throw new WEPSException("Cannot build IFC file data.  Missing a designated surface horizon.");
    }

    //Remove the restricting horizon.  WEPS doesn't want it in the IFC file.
    if (null != restrictingHorizon) {
      horizons.remove(restrictingHorizon.chkey());
    }

//        try {
//            if (isOrganicSoil()) {
//                return genericOrganicSoilIfc;
//            }
//        } catch (SDMException ex) {
//            throw new WEPSException(ex);
//        }
    horizons.values().forEach((h) -> {
      h.hzthk_r(metricConversion(h.hzthk_r(), SoilUtils.Metric.CM, SoilUtils.Metric.MM));
    });

    String ifcString = "";

    ifcString += formatIFCValue(GenericIFCData.LOCAL_PHASE, ((EvalResult.testDefaultEmptyString(localphase()) || (localphase().isEmpty()) ? "unknown" : localphase())));  // local phase
    ifcString += formatIFCValue(GenericIFCData.TAX_ORDER, taxorder());  // tax order

    ifcString += formatIFCValue(GenericIFCData.SOIL_LOSS_TOLERANCE, ((!surfaceHorizon.useGenericHz1Values()) ? (int) tfact() : GenericIFCData.soilLossTolerance));
    try {
      ifcString += formatIFCValue(GenericIFCData.DRY_SOIL_ALBEDO, ((!surfaceHorizon.useGenericHz1Values()) ? getWEPPSurfaceAlbedo() : GenericIFCData.drySoilAlbedo), 3);
    } catch (WEPPException ex) {
      throw new WEPSException(ex);
    }
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.SLOPE_GRADIENT, ((!surfaceHorizon.useGenericHz1Values()) ? slope_r() : GenericIFCData.slopeGradient), 3, false, 1.0);
    //ifcString += writeValue(IFCRanges.surfaceList[14].getTitle(), Defaults to "0" in WEPS //SoilCalc.getSurfaceFragmentCover(), 3);        
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.SURFACE_FRAGMENT_COVER, ((!surfaceHorizon.useGenericHz1Values()) ? surfaceHorizon.fragvol_r() : GenericIFCData.surfFragCover), 3, false); //  WAS:  0.0, 3 );  

    ifcString += soils.utils.SoilUtils.println("#");
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.DEPTH_TO_BEDROCK, ((!surfaceHorizon.useGenericHz1Values()) ? ((EvalResult.testDefaultDouble(brockdepmin)) ? GenericIFCData.depthToBedrock : brockdepmin) : GenericIFCData.depthToBedrock), 0, false);
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.DEPTH_TO_ROOT_RESTRICTING, ((!surfaceHorizon.useGenericHz1Values()) ? ((EvalResult.testDefaultDouble(calculated_resdeptmin_r())) ? GenericIFCData.depthToRootRestrictingLayer : calculated_resdeptmin_r()) : GenericIFCData.depthToRootRestrictingLayer), 0, false);
    ifcString += soils.utils.SoilUtils.println("#");

    ifcString += formatIFCValue(GenericIFCData.NUM_LAYERS, horizons.size());  // number of layers

    ifcString += formatIFCArray(GenericIFCData.LAYER_THICKNESS, this, 0, (Horizon h) -> h.hzthk_r());  // soil characteristics by layer
    ifcString += soils.utils.SoilUtils.println("#");

    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SAND_FRACTION, GenericIFCData.sandFraction, true, restrictingHorizon, 3, horizons, (Horizon h) -> h.sandtotal_r());			// soil fractions
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SILT_FRACTION, GenericIFCData.siltFraction, false, restrictingHorizon, 3, horizons, (Horizon h) -> h.silttotal_r());
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.CLAY_FRACTION, GenericIFCData.clayFraction, true, restrictingHorizon, 3, horizons, (Horizon h) -> h.claytotal_r());
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.ROCK_FRAGMENTS, GenericIFCData.rockFragments, false, restrictingHorizon, 3, horizons, (Horizon h) -> getRockFrags(h));
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SAND_FRACTION_VC, GenericIFCData.sandFractionVC, false, restrictingHorizon, 3, horizons, (Horizon h) -> h.sandvc_r());
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SAND_FRACTION_C, GenericIFCData.sandFractionC, false, restrictingHorizon, 3, horizons, (Horizon h) -> h.sandco_r());
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SAND_FRACTION_M, GenericIFCData.sandFractionM, false, restrictingHorizon, 3, horizons, (Horizon h) -> h.sandmed_r());
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SAND_FRACTION_F, GenericIFCData.sandFractionF, false, restrictingHorizon, 3, horizons, (Horizon h) -> h.sandfine_r());
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SAND_FRACTION_VF, GenericIFCData.sandFractionVF, true, restrictingHorizon, 3, horizons, (Horizon h) -> h.sandvf_r());
    ifcString += soils.utils.SoilUtils.println("#");

    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.BUILK_DENSITY, GenericIFCData.bulkDensity, true, restrictingHorizon, 3, horizons, (Horizon h) -> h.dbthirdbar_r());
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.ORGANIC_MATTER, GenericIFCData.organicMatter, true, restrictingHorizon, 4, horizons, (Horizon h) -> h.om_r());				// soil chemical properties
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SOIL_PH, GenericIFCData.soilPh, true, restrictingHorizon, 2, horizons, (Horizon h) -> getSoilpH(h));
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.CACO3, GenericIFCData.CaCO3, true, restrictingHorizon, 2, horizons, (Horizon h) -> h.caco3_r());
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.CATION_EXCHANGE_CAPACITY, GenericIFCData.CEC, true, restrictingHorizon, 2, horizons, (Horizon h) -> getCationExchangeCapacity(h));
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.LINEAR_EXTENSIBILITY, GenericIFCData.linearExtensibility, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getLinearExtensibility(h));
    ifcString += soils.utils.SoilUtils.println("#");

    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.AGG_GMD, GenericIFCData.aggGMD, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getAggregateMeanDiameter(h));	// aggregate characteristics
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.AGG_GSD, GenericIFCData.aggGSD, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getAggregateStdDeviation(h));
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.MAX_AGG_SIZE, GenericIFCData.maxAggSize, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getMaxAggregateSize(h));
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.MIN_AGG_SIZE, GenericIFCData.minAggSize, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getMinAggregateSize());
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.AGG_DENSITY, GenericIFCData.aggDensity, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getAggregateDensity(h));
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.AGG_STABILITY, GenericIFCData.aggStability, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getAggregateStability(h));
    ifcString += soils.utils.SoilUtils.println("#");

    ifcString += GenericIFCData.formatIFCValue(IFCRanges.surfaceList[1].getTitle(), GenericIFCData.crustThk, 3, false); //SoilUtils.getCrustThickness(), 3);  // crust and surface characteristics     
    //  TODO:  Check this crustDensity stuff....SoilUtils just always returns 1.8 regardless of soil properties while the default value is always 0.6...
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.CRUST_DENSITY, ((!surfaceHorizon.useGenericHz1Values()) ? SoilUtils.getCrustDensity(surfaceHorizon) : GenericIFCData.crustDensity), 3, false);
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.CRUST_STABILITY, ((!surfaceHorizon.useGenericHz1Values()) ? SoilUtils.getCrustStability(surfaceHorizon) : GenericIFCData.crustStability), 2, false);
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.CRUST_SURFACE_FRACTION, ((!surfaceHorizon.useGenericHz1Values()) ? SoilUtils.getCrustFraction() : GenericIFCData.crustSurfaceFraction), 2, false);
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.MASS_LOOSE_MAT_CRUST, ((!surfaceHorizon.useGenericHz1Values()) ? SoilUtils.getCrustLooseMaterialMass() : GenericIFCData.massLooseMatCrust), 2, false);
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.FRACTION_LOOSE_MAT_CRUST, ((!surfaceHorizon.useGenericHz1Values()) ? SoilUtils.getCrustLooseMaterialFraction() : GenericIFCData.fractionLooseMatCrust), 2, false);
    ifcString += soils.utils.SoilUtils.println("#");

    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.RANDOM_ROUGHNESS, ((!surfaceHorizon.useGenericHz1Values()) ? SoilUtils.getRandomRoughness() : GenericIFCData.randomRoughness), 2, false);
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.RIDGE_ORIENTATION, ((!surfaceHorizon.useGenericHz1Values()) ? SoilUtils.getRoughnessOrientation() : GenericIFCData.ridgeOrientation), 2, false);
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.RIDGE_HEIGHT, ((!surfaceHorizon.useGenericHz1Values()) ? SoilUtils.getRoughnessHeight() : GenericIFCData.ridgeHeight), 2, false);
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.RIDGE_TOP_SPACING, ((!surfaceHorizon.useGenericHz1Values()) ? SoilUtils.getRoughnessSpacing() : GenericIFCData.ridgeTopSpacing), 2, false);
    ifcString += GenericIFCData.formatIFCValue(GenericIFCData.RIDGE_WIDTH, ((!surfaceHorizon.useGenericHz1Values()) ? SoilUtils.getRoughnessWidth() : GenericIFCData.ridgeWidth), 2, false);
    ifcString += soils.utils.SoilUtils.println("#");

    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.INIT_BULK_DENSITY, GenericIFCData.initBulkDensity, false, restrictingHorizon, 3, horizons, (Horizon h) -> h.dbthirdbar_r());  // hydrology by layers
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.INIT_SOIL_WATER_CONTENT, GenericIFCData.initSoilWaterContent, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getInitialSWC(h));  // hydrology by layers
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SAT_SOIL_WATER_CONTENT, GenericIFCData.satSoilWaterContent, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getSaturatedSWC(h));
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.FIELD_CAPACITY_WATER_CONTENT, GenericIFCData.fieldCapWaterContent, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getFieldCapacitySWC(h));
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.WILTING_POINT_WATER_CONTENT, GenericIFCData.wiltingPointWaterContent, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getWiltingPointSWC(h));
    ifcString += soils.utils.SoilUtils.println("#");

    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SOIL_CB_VALUE, GenericIFCData.soilCBValue, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getSoilCB(h));
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.AIR_ENTRY_POTENTIAL, GenericIFCData.airEntryPotential, false, restrictingHorizon, 3, horizons, (Horizon h) -> SoilUtils.getAirEntryPotential(h));
    ifcString += GenericIFCData.formatIFCArray(GenericIFCData.SAT_HYDRAULIC_CONDUCTIVITY, GenericIFCData.satHydraulicConductivity, false, restrictingHorizon, 10, horizons, (Horizon h) -> SoilUtils.getSaturatedHydraulicConductivity(h));
    ifcString += soils.utils.SoilUtils.println("#");

    return ifcString;
  }

// Sol file description
  // Line 1:  version control number (95.7) - real (datver)
  // Line 2:  a) User comment line - character*80, (solcom)
  // Line 3:  a) number of overland flow elements(OFE's) or channels integer (ntemp)
  //          b) flag to to use internal hydraulic conductivity adjustments - integer (ksflag)
  //              0 - do not use adjustments (conductivity will be held constant)
  //              1 - use internal adjustments
  //      Lines 4 & 5 are repeated for the number of OFE's or channels on Line 3a.
  // Line 4:  a) soil name for current OFE or channel - character (slid)
  //          b) soil texture for current OFE or channel - character (texid)
  //          c) number of soil layers for current OFE or channel - integer (nsl)
  //          d) albedo of the bare dry surface soil on the current OFE or channel - real (salb)
  //          e) initial saturation level of the soil profile porosity (m/m) - real (sat)
  //          f) baseline interrill erodibility parameter (kg*s/m4) - real (ki)
  //          g) baseline rill erodibility parameter (s/m) - real (kr)
  //          h) baseline critical shear parameter (N/m2) - real (shcrit)
  //          i) effective hydraulic conductivity of surface soil (mm/h) - real (avke)
  // Line 5: (repeated for the number of soil layers indicated on Line 4c.)
  //          a) depth from soil surface to bottom of soil layer (mm) - real (solthk)
  //          b) percentage of sand in the layer (%) - real (sand)
  //          c) percentage of clay in the layer (%) - real (clay)
  //          d) percentage of organic matter (volume) in the layer (%) - real (orgmat)
  //          e) cation exchange capacity in the layer (meq/100 g of soil) - real (cec)
  //          f) percentage of rock fragments by volume in the layer (%) - real (rfg)
  // Line 6: Bedrock restricting layer info (Most of the soils are not going to have any bedrock layer defined so this line ends up being all 0's)
  //          a) flag to indicate if present
  //          b) type
  //          c) anisotropy ratio
  //          d) ksat
  // May want to break this into methods if it grows    
  public String toSol() throws WEPPException {
    if (null == surfaceHorizon) {
      throw new WEPPException("Cannot build sol file data.  Missing a designated surface horizon.");
    }

//        try {
//            if (isDefaultOrganicSolFileComponent()) {
//                return genericOrganicSoilSol;
//            }
//        } catch (SDMException ex) {
//            throw new WEPPException(ex);
//        }
    String solStr = "";

    // Line 1:
    solStr = "2006.2\n";
    //Line 2:
    solStr += "Comments: sol file generated by csip-soils.\n";

    //Line 3:
    // only one soil is used
    // always use hydraulic conductivity adjustments
    solStr += "1 1\n";
    //Line 4:
    // append some soil info here
    boolean useGenericHz1Values = surfaceHorizon.useGenericHz1Values();
    boolean useGenericHz2Values;

    solStr += "'" + compname() + "' ";
    try {
      solStr += "'" + surfaceHorizon.getRepresentativeTextureName() + "' ";
    } catch (SDMException ex) {
      throw new WEPPException(ex);
    }

    solStr += ((null == restrictingHorizon) ? horizons.size() : horizons.size() - 1) + " ";
    solStr += String.format("%.6f", ((useGenericHz1Values) ? 0.230000 : getWEPPSurfaceAlbedo())) + " ";
    solStr += "0.750000 ";
    solStr += String.format("%.6f", ((useGenericHz1Values) ? 3112200 : surfaceHorizon.getInterrill())) + " ";
    solStr += String.format("%.6f", ((useGenericHz1Values) ? 0.00257 : surfaceHorizon.getRill())) + " ";
    solStr += String.format("%.6f", ((useGenericHz1Values) ? 3.204 : surfaceHorizon.getShear())) + " ";
    solStr += String.format("%.6f", ((useGenericHz1Values) ? 10.28756 : surfaceHorizon.getConductivity())) + "\n";

    //Line 5:
    for (Horizon horizon : horizons.values()) {
      if (horizon.isRestricting()) {
        break;
      }
      useGenericHz1Values = horizon.useGenericHz1Values();
      useGenericHz2Values = horizon.useGenericHz2Values();

      solStr += "  " + String.format("%.6f", horizon.hzdepb_r() * 10) + " "; // convert from cm to mm
      solStr += String.format("%.6f", ((useGenericHz1Values) ? 50.0 : ((useGenericHz2Values) ? 13.0 : horizon.sandtotal_r()))) + " ";
      solStr += String.format("%.6f", ((useGenericHz1Values) ? 10.0 : ((useGenericHz2Values) ? 5.0 : horizon.claytotal_r()))) + " ";
      solStr += String.format("%.6f", ((useGenericHz1Values) ? 77.000 : ((useGenericHz2Values) ? 81.0 : horizon.om_r()))) + " ";
      solStr += String.format("%.6f", ((useGenericHz1Values) ? 40.0 : ((useGenericHz2Values) ? 45.0 : horizon.cec7_r()))) + " ";
      solStr += String.format("%.6f", ((useGenericHz1Values) ? 0.0 : ((useGenericHz2Values) ? 0.0 : horizon.getWEPPRockFragments()) * 100)); // put rock frag here. should be in percent
      solStr += "\n";
    }

    //Line 6:
    // add some bedrock data
    solStr += restrict + " ";
    solStr += restrictType + " ";
    solStr += String.format("%.6f", aniso) + " ";
    solStr += String.format("%.6f", uksat);
    solStr += "\n";

    return solStr;
  }

  /**
   *
   * @param onlySelectedHorizons
   * @return
   * @throws JSONException
   */
  public JSONArray toJSON(boolean onlySelectedHorizons) throws JSONException {
    return toJSON(onlySelectedHorizons, null);
  }

  /**
   *
   * @param onlySelectedHorizons
   * @param selectedReasons
   * @return
   * @throws JSONException
   */
  public JSONArray toJSON(boolean onlySelectedHorizons, ArrayList<String> selectedReasons) throws JSONException {
    JSONArray ret_val = new JSONArray();
    JSONArray horizonsArray;
    tableExcluded.toJSON(ret_val);
    tableComponent.toJSON(ret_val);
    tableComponentCalculatoins.long_name(comp_long_name());

    //Calculated or interpolated values
    tableComponentCalculatoins.toJSON(ret_val);

    if (!isExcluded()) {
      if ((null != selectedReasons) && !selectedReasons.isEmpty()) {
        for (String type : selectedReasons) {
          horizonsArray = groupBySelectionType(type, onlySelectedHorizons);
          if (horizonsArray.length() > 0) {
            ret_val.put(JSONUtils.data(type.toLowerCase() + " horizons", horizonsArray));
          }
        }
      } else {
        horizonsArray = groupBySelectionType("ANY", onlySelectedHorizons);
        if (horizonsArray.length() > 0) {
          ret_val.put(JSONUtils.data("horizons", horizonsArray));
        }
      }
    }

    return ret_val;
  }

  /**
   *
   * @param onlySelectedHorizons
   * @param selectedReasons
   * @return
   * @throws JSONException
   */
  public JSONObject toBasicJSON(boolean onlySelectedHorizons, List<String> selectedReasons) throws JSONException {
    JSONObject ret_val = new JSONObject();

    JSONArray horizonsArray;
    tableExcluded.toBasicJSON(ret_val);
    tableComponent.toBasicJSON(ret_val);
    tableComponentCalculatoins.long_name(comp_long_name());

    //Calculated or interpolated values
    tableComponentCalculatoins.toBasicJSON(ret_val);

    if (!isExcluded()) {
      if ((null != selectedReasons) && !selectedReasons.isEmpty()) {
        for (String type : selectedReasons) {
          horizonsArray = groupBySelectionTypeBasicJSON(type, onlySelectedHorizons);
          if (horizonsArray.length() > 0) {
            ret_val.put(type.toLowerCase() + " horizons", horizonsArray);
          }
        }
      } else {
        horizonsArray = groupBySelectionTypeBasicJSON("ANY", onlySelectedHorizons);
        if (horizonsArray.length() > 0) {
          ret_val.put("horizons", horizonsArray);
        }
      }
    }

    return ret_val;
  }

  public Horizon topHorizon() {
    Horizon tHorizon = null;

    if (null != horizons) {
      for (Horizon horizon : horizons.values()) {
        if (null != tHorizon) {
          if (horizon.hzdept_r() < tHorizon.hzdept_r()) {
            tHorizon = horizon;
          }
        } else {
          tHorizon = horizon;
        }
      }
    }
    return tHorizon;
  }

  public Horizon topSelectedHorizon() {
    Horizon tHorizon = null;

    if (null != horizons) {
      for (Horizon horizon : horizons.values()) {
        if (null != tHorizon) {
          if ((horizon.hzdept_r() < tHorizon.hzdept_r()) && (horizon.selected())) {
            tHorizon = horizon;
          }
        } else {
          if (horizon.selected()) {
            tHorizon = horizon;
          }
        }
      }
    }
    return tHorizon;
  }

  /**
   * Finds highest/maximum Flooding rating in the monthly rating values and uses
   * that as the general rating for this component. If no ratings are found
   * "None" is assumed as the rating value. Also, if any monthly value is
   * missing or NULL, then "None" is assume for that month's value during
   * comparisons.
   *
   * @return
   * @throws ServiceException
   */
  public void maxFloodFreq() throws ServiceException {
    int tComonthRate = floodRatings.get("none");

    if (null != comonths) {
      for (Comonth comonth : comonths.values()) {
        if (null != comonth) {
          if (floodRatings.containsKey(comonth.flodfreqcl().toLowerCase())) {
            int newRating = floodRatings.get(comonth.flodfreqcl().toLowerCase());

            if (tComonthRate < newRating) {
              tComonthRate = newRating;
            }
          }
        }
      }
    }

    if ((tComonthRate >= 0) && (tComonthRate < floodRatingNames.size())) {  //this should always be true, but just in case...
      tableComponentCalculatoins.calculated_flod_freq_cl(floodRatingNames.get(tComonthRate));
    } else {
      tableComponentCalculatoins.calculated_flod_freq_cl("None");
    }
  }

  /**
   * Finds highest/maximum Ponding rating in the monthly rating values and uses
   * that as the general rating for this component. If no ratings are found
   * "None" is assumed as the rating value. Also, if any monthly value is
   * missing or NULL, then "None" is assume for that month's value during
   * comparisons.
   *
   * @return
   * @throws ServiceException
   */
  public void maxPoolFreq() throws ServiceException {
    int tComonthRate = pondRatings.get("none");

    if (null != comonths) {
      for (Comonth comonth : comonths.values()) {
        if (null != comonth) {
          if (pondRatings.containsKey(comonth.pondfreqcl().toLowerCase())) {
            int newRating = pondRatings.get(comonth.pondfreqcl().toLowerCase());

            if (tComonthRate < newRating) {
              tComonthRate = newRating;
            }
          }
        }
      }
    }

    if ((tComonthRate >= 0) && (tComonthRate < pondRatingNames.size())) {  //this should always be true, but just in case...
      tableComponentCalculatoins.calculated_pond_freq_cl(pondRatingNames.get(tComonthRate));
    } else {
      tableComponentCalculatoins.calculated_pond_freq_cl("None");
    }
  }

  /**
   *
   * @return
   */
  public final double wei() {
    return tableComponent.wei();
  }

  /**
   *
   * @param wei
   */
  public final void wei(double wei) {
    tableComponent.wei(wei);
  }
}