V1_0.java [src/java/m/wqm/wqm05_nutrientslp] Revision:   Date:
package m.wqm.wqm05_nutrientslp;

import csip.ModelDataService;
import csip.api.server.ServiceException;
import java.util.ArrayList;
import javax.ws.rs.Path;
import csip.annotations.Description;
import csip.annotations.Name;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import csip.utils.JSONUtils;
import java.util.Map;
import org.codehaus.jettison.json.JSONException;

/**
 *
 * @author Srinivas
 * @author Rumpal Sidhu
 * @author Shaun Case
 */
@Name("WQM-05: Nutrient Soil Leaching Potential (NutrientSLP)")
@Description("This service computes nutrient soil leaching potential for soil "
        + "components in an area of analysis, and then computes nutrient soil "
        + "leaching potential representing the area of analysis (AoA). The service "
        + "primarily will consume data from the WQM-02 WQMSoilAttributes service "
        + "to compute nutrient soil leaching potentials used later by the WQM-13 "
        + "service to compute threshold treatment level scores.")
@Path("m/nutrient_slp/1.0")
@Deprecated
public class V1_0 extends ModelDataService {

    private final String[] nslpList = new String[]{"LOW", "MODERATE", "MODERATELY HIGH", "HIGH"};
    //Request
    private int aoaId;
    private ArrayList<SoilComponent> soilComponentList = new ArrayList<>();
    //Result
    private String aoaNslp;

    @Override
    protected void preProcess() throws ServiceException, JSONException {
        aoaId = parameter().getInt("AoAId");
    }

    @Override
    protected void doProcess() throws ServiceException, JSONException {
        JSONArray groups = parameter().getJSONArray("soilcomponents");
        for (int i = 0; i < groups.length(); i++) {
            Map<String, JSONObject> group = JSONUtils.preprocess(groups.getJSONArray(i));
            SoilComponent input = new SoilComponent(group);
            soilComponentList.add(input);
        }
        computeNSLP();
        computeAoaNSLP();
    }

    @Override
    protected void postProcess() throws Exception {
        results().put("aoa_id", aoaId, "Area of analysis identifier");
        results().put("aoa_nslp", aoaNslp, "Soil leaching potential of the area of analysis");
        JSONArray resultArr = new JSONArray();
        for (SoilComponent sc : soilComponentList) {
            JSONArray array = new JSONArray();
            array.put(JSONUtils.dataDesc("cokey", sc.getCokey(), "Soil component key"));
            array.put(JSONUtils.dataDesc("comp_nslp", sc.getNslp(), "Soil leaching potential of the soil component"));
            resultArr.put(JSONUtils.dataDesc("soil_component", array, "Soil Component"));
        }
        results().put("soil_components", resultArr, "List of Soil Components");
    }

    private void computeNSLP() throws ServiceException {
        for (SoilComponent ip : soilComponentList) {
            int comp_nslp_number = -2;  //Preset to error condition of invalid combinations.

            //  If Histosols OR ( Apparent and wtbl_top_min <= 76cm )
//            if (ip.getTaxorder().equals("Histosols") || ((ip.getWtbl().equals("Apparent")
//                    || ip.getWtbl().equals("Perched")) && (ip.getWtblTopMin() <= 76))) {
            if (ip.getTaxorder().equals("Histosols") || ((ip.getWtblTopMin() <= 76))) {
                comp_nslp_number = 3;
            } else {
                //  Otherwise we need to look at slope and/or coarse fragmentation for this component's hydrologic group.
                switch (ip.getHsg()) {
                    case "A":
                        comp_nslp_number = computeNSLPHsgA(ip);
                        break;
                    case "B":
                        comp_nslp_number = computeNSLPHsgB(ip);
                        break;
                    case "C":
                        comp_nslp_number = computeNSLPHsgC(ip);
                        break;
                    case "D":
                        comp_nslp_number = computeNSLPHsgD(ip);
                        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 (ip.getCoarseFrag() > 30) {
                        comp_nslp_number += 2;
                        if (comp_nslp_number > 3) {
                            comp_nslp_number = 3;
                        }
                    } else if (ip.getCoarseFrag() > 10) {
                        comp_nslp_number += 1;
                        if (comp_nslp_number > 3) {
                            comp_nslp_number = 3;
                        }
                    } // Otherwise just leave it alone, no adjustments are needed.

                    ip.setNslpNumber(comp_nslp_number);
                    ip.setNslp(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: " + ip.getHsg() + ".  Cannot continue with NSLP estimation for component key: " + ip.getCokey() + ". ");
            } else if (comp_nslp_number == -2) {  //Failed the above switch statement
                throw new ServiceException("Invalid Hsg name for this soil component key: " + ip.getCokey()
                        + " 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.
        }
    }

    private int computeNSLPHsgA(SoilComponent ip) {
        int nslpNumber = 3;
        if (ip.getSlope() > 12) {
            nslpNumber = 2;
        }
        return nslpNumber;
    }

    private int computeNSLPHsgB(SoilComponent ip) {
        int nslpNumber = -1;
        if ((ip.getSlope() <= 12 && ip.getKfact() >= 0.24) || (ip.getSlope() > 12)) {
            nslpNumber = 1;
        } else if ((ip.getSlope() >= 3) && (ip.getSlope() <= 12) && (ip.getKfact() < 0.24)) {
            nslpNumber = 2;
        } else if ((ip.getSlope() < 3) && (ip.getKfact() < 0.24)) {
            nslpNumber = 3;
        }
        return nslpNumber;
    }

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

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

    //Compute weighted average nutrient soil leaching potential for the AoA
    private void computeAoaNSLP() {
        double cumNslpProduct = 0;
        double aoaArea = 0;

        for (SoilComponent sc : soilComponentList) {
            cumNslpProduct += (sc.getNslpNumber() * sc.getArea());
            aoaArea += sc.getArea();
        }

        double aoaNslpFract = ((aoaArea != 0.0) ? (cumNslpProduct / aoaArea) : 0.0);
        if (aoaNslpFract <= 0.50) {
            aoaNslp = "LOW";
        } else if (aoaNslpFract > 0.50 && aoaNslpFract <= 1.50) {
            aoaNslp = "MODERATE";
        } else if (aoaNslpFract > 1.50 && aoaNslpFract <= 2.50) {
            aoaNslp = "MODERATELY HIGH";
        } else {
            aoaNslp = "HIGH";
        }
    }

    public class SoilComponent {

        private String cokey;
        private double area;
        private String hsg;
        private String taxorder;
        private double kfact;
        private double slope;
        private double coarseFrag;
        private boolean drained = false;
        private String wtbl;
        private double wtbl_top_min;

        private int nslpNumber;
        private String nslp;

        private String[] requiredSoilInputs = new String[]{
            "cokey",
            "aoa_comp_area",
            "aoa_comp_hsg",
            "aoa_comp_taxorder",
            "aoa_comp_kfact",
            "aoa_comp_slope",
            "aoa_comp_coarse_frag",
            "aoa_comp_wtbl",
            "aoa_comp_wtbl_top_min"
        };

        public SoilComponent(Map<String, JSONObject> soilGroup) throws ServiceException, JSONException {

            checkSoilComponentInputParameters(soilGroup);

            cokey = soilGroup.get("cokey").getString("value");
            area = soilGroup.get("aoa_comp_area").getDouble("value");
            hsg = soilGroup.get("aoa_comp_hsg").getString("value");
            taxorder = soilGroup.get("aoa_comp_taxorder").getString("value");
            kfact = soilGroup.get("aoa_comp_kfact").getDouble("value");
            slope = soilGroup.get("aoa_comp_slope").getDouble("value");
            coarseFrag = soilGroup.get("aoa_comp_coarse_frag").getDouble("value");
            wtbl = soilGroup.get("aoa_comp_wtbl").getString("value");
            String wtbl_top_min = soilGroup.get("aoa_comp_wtbl_top_min").getString("value");
            if ((null == wtbl_top_min) || wtbl_top_min.equalsIgnoreCase("none") || wtbl_top_min.isEmpty()) {
                this.wtbl_top_min = 99999.99;
            } else {
                this.wtbl_top_min = Double.parseDouble(wtbl_top_min);
            }
        }

        private void checkSoilComponentInputParameters(Map<String, JSONObject> soilGroup) throws ServiceException {
            for (String requiredSoilInput : requiredSoilInputs) {
                if (!soilGroup.containsKey(requiredSoilInput) || soilGroup.get(requiredSoilInput).optString("value", "error").equalsIgnoreCase("error")) {
                    throw new ServiceException("The input JSON is missing the required soil component value for: " + requiredSoilInput);
                }
            }
        }

        //Getter Methods
        public String getCokey() {
            return cokey;
        }

        public double getArea() {
            return area;
        }

        public String getHsg() {
            return hsg;
        }

        public String getTaxorder() {
            return taxorder;
        }

        public double getKfact() {
            return kfact;
        }

        public double getSlope() {
            return slope;
        }

        public double getCoarseFrag() {
            return coarseFrag;
        }

        public String getWtbl() {
            return wtbl;
        }

        public double getWtblTopMin() {
            return wtbl_top_min;
        }

        public int getNslpNumber() {
            return nslpNumber;
        }

        public String getNslp() {
            return nslp;
        }

        //Setter Methods
        public void setNslpNumber(int number) {
            nslpNumber = number;
        }

        public void setNslp(String value) {
            nslp = value;
        }

    }

}