Crop.java [src/java/m/wqm/nutappmgtscores] Revision: b8599413dc61e5fbe13f982678cad73e7c05a7a6  Date: Thu Aug 13 12:36:50 MDT 2015
package m.wqm.nutappmgtscores;

/**
 *
 * @author RUMPAL SIDHU
 * @author Shaun Case
 */
import csip.utils.Dates;
import csip.utils.JSONUtils;

import java.util.Date;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

public class Crop {

    
    
    //  Begin Crop Class
    public static final int N_APP_RATE_SCORE = 0;
    public static final int P_APP_RATE_SCORE = 1;
    public static final int N_APP_TIMING_SCORE = 0;
    public static final int P_APP_TIMING_SCORE = 1;
    
    private int mgtCropId;
    private String cropPlantDate;
    private Date plantDate;
    private double cropYield;
    private String cropYieldUnits;
    private ArrayList<Crop.NutrientApplication> nutrientApplicationList;
    private m.wqm.nutappmgtscores.Result calc_result;
    private Statement statement;
    private String cropType = "";
    private String error_msg = "";
    private Boolean multipleNutrientApplication = false;
    private int n_app_timing_score = 100;
    private int p_app_timing_score = 100;
    private int app_method_score = 0;
    private int[] appRateScore = new int[2];
    private int[] appNutrientTimingScore = new int[2];
    private String pSoilTestResult = "None";
  
    public Crop(int mgtCropId,String cropPlantDate, double cropYield,
                String cropYieldUnits, JSONArray applicationList, String pSoilTestResult, Statement statement){
        this.mgtCropId = mgtCropId;
        this.cropPlantDate = cropPlantDate;
        this.cropYield = cropYield;
        this.cropYieldUnits = cropYieldUnits;
        this.statement = statement;
        this.pSoilTestResult = pSoilTestResult;
     
        nutrientApplicationList = new ArrayList<>();
        
        try{
            plantDate = getCropPlantDate();
            for (int j = 0; j < applicationList.length(); j++) {
                Map<String, JSONObject> application = JSONUtils.preprocess(applicationList.getJSONArray(j));
                nutrientApplicationList.add(new Crop.NutrientApplication(JSONUtils.getStringParam(application, "nutrient_application_date", "err"), JSONUtils.getStringParam(application, "incorporated", ""), JSONUtils.getJSONArrayParam(application, "application")));              
            }            
        }
        catch( Exception ex){
           error_msg += "Cannot process list of applied nutrients.  " + ex.getMessage(); 
        }

        multipleNutrientApplication = (nutrientApplicationList.size() > 1);           
        this.appRateScore[N_APP_RATE_SCORE] = this.appRateScore[P_APP_RATE_SCORE] = -1;
        this.appNutrientTimingScore[N_APP_TIMING_SCORE] = this.appNutrientTimingScore[P_APP_TIMING_SCORE] = -1;
    }

    //Get methods
    public int getMgtCropId() {
        return this.mgtCropId;
    }

    public String getErrorMsg(){
        for ( Crop.NutrientApplication nutrientApplied: this.nutrientApplicationList ){
            error_msg += nutrientApplied.getErrorMsg();
        }
        return error_msg;
    }
    
    public String getCropType(){
        if ( (cropType.isEmpty()) && (error_msg.isEmpty()) ){
            ResultSet resultSet;                   
            String query = "SELECT wqm_crop_type FROM wqm_crops WHERE wqm_crop_id=" + mgtCropId + " AND wqm_crop_units='" + cropYieldUnits + "';";
            try{
                resultSet = statement.executeQuery(query);
                if (!resultSet.first()){
                    error_msg += "That crop, " + mgtCropId + ", and crop yield units, "+ cropYieldUnits + ", was not found in the database";
                }
                else{                        
                    cropType = resultSet.getString("wqm_crop_type");   
                }
            }
            catch( SQLException ex ) {
                error_msg += ex.getMessage();
            }
        }
        return cropType;
    }
    public final Date getCropPlantDate() throws Exception {
        String[] parse = this.cropPlantDate.split("-");
        Calendar date = new GregorianCalendar(Integer.parseInt(parse[0]),
                Integer.parseInt(parse[1]) - 1, Integer.parseInt(parse[2]));
        return date.getTime();
    }

    public double getCropYield() {
        return this.cropYield;
    }

    public String getCropYieldUnits() {
        return this.cropYieldUnits;
    }

    public ArrayList getNutrientApplicationList() {
        return this.nutrientApplicationList;
    }
    
    public int[] getNutrientApplicationRateScores(){
        if ( ( appRateScore[N_APP_RATE_SCORE] <0 ) || ( appRateScore[P_APP_RATE_SCORE] <0 ) ) {
            try{
                if ( nutrientApplicationList.isEmpty() ){
                    ResultSet resultSet;                   

                    appRateScore[N_APP_RATE_SCORE] = appRateScore[P_APP_RATE_SCORE] = 0;            
                    String queryNitrogen = "SELECT app_mgt_score FROM wqm_nutrient_application_mgt_scores WHERE nutrient='Nitrogen' AND app_mgt_kind='Rate' AND app_mgt_factor='none';";
                    String queryPhosporous = "SELECT app_mgt_score FROM wqm_nutrient_application_mgt_scores WHERE nutrient='Phosphorus' AND app_mgt_kind='Rate' AND app_mgt_factor='none' AND soil_test_result='" + pSoilTestResult + "';";

                    resultSet = statement.executeQuery(queryNitrogen);
                    if ( resultSet.first() ) {
                        appRateScore[N_APP_RATE_SCORE] = resultSet.getInt("app_mgt_score");
                    }

                    resultSet = statement.executeQuery(queryPhosporous);
                    if ( resultSet.first() ) {
                        appRateScore[P_APP_RATE_SCORE] = resultSet.getInt("app_mgt_score");  
                    }  
                }
                else{
                    ResultSet resultSet;
                    String query;
                    
                    double nrate = 0.0;
                    double prate = 0.0;

                    double wqm_crop_pct_dmat = 0.0;
                    double wqm_pct_nitrogen = 0.0;
                    double wqm_pct_phosphorus = 0.0;
                    double wqm_crop_yield = 0.0;
                                        
                    int ncrop_app_rate_score = 0;
                    int pcrop_app_rate_score = 0;                       
                    
                    for (Crop.NutrientApplication nutrient : nutrientApplicationList) {
                        ArrayList<Crop.NutrientApplication.Nutrient> nutrientAppliedList = nutrient.getNutrientList();
                        for (Crop.NutrientApplication.Nutrient nApplied : nutrientAppliedList) {
                            switch(nApplied.getNutrientApplied()){  //Note:  This switch requires JDK 1.7 or above
                                case "Nitrogen":
                                    nrate += nApplied.getApplicationRate();
                                    break;
                                case "Phosphorus":
                                    prate += nApplied.getApplicationRate();
                                    break;                                                                                                 
                            }
                        }
                    }
                    
                    query = "SELECT wqm_crop_pct_dmat, wqm_pct_nitrogen, wqm_pct_phosphorus, wqm_crop_yield FROM wqm_crops WHERE wqm_crop_id=" + mgtCropId + ";";
                    resultSet = statement.executeQuery(query);
                    if (resultSet.first()) {
                        wqm_crop_pct_dmat = resultSet.getDouble("wqm_crop_pct_dmat");
                        wqm_pct_nitrogen = resultSet.getDouble("wqm_pct_nitrogen");
                        wqm_pct_phosphorus = resultSet.getDouble("wqm_pct_phosphorus");
                        wqm_crop_yield = resultSet.getDouble("wqm_crop_yield");
                        
                        //  Added wqm_crop_yield to the calc per bug #462839
                        double n_growout = cropYield * wqm_crop_yield * wqm_crop_pct_dmat * wqm_pct_nitrogen;
                        double p_growout = cropYield * wqm_crop_yield * wqm_crop_pct_dmat * wqm_pct_phosphorus;

                        double n_remove_ratio = nrate / n_growout;
                        double p_remove_ratio = prate / p_growout;      

                        query = "SELECT app_mgt_score FROM wqm_nutrient_application_mgt_scores WHERE nutrient='" + "Nitrogen" + "' AND app_mgt_kind='" + "Rate" + "' AND app_mgt_factor=";

                        switch( cropType ){
                            case "Small Grain":  //Note the case conflicts here...
                                query += "'small grain'";
                                break;

                            default:  
                                query += "'none'";
                        }   
                        query += " AND remove_ratio_1 <= " + n_remove_ratio + "AND remove_ratio_2 > " + n_remove_ratio + ";";
                        
                        resultSet = statement.executeQuery(query);
                        if (resultSet.first()) {
                            ncrop_app_rate_score = resultSet.getInt("app_mgt_score");
                        }
                        else{
                            appRateScore[N_APP_RATE_SCORE] = appRateScore[P_APP_RATE_SCORE] = 0;
                        }
                            
                        pcrop_app_rate_score = -1;
                        //#Compute P application management rate scores based on removal ratio and soil test result
                        switch (pSoilTestResult){
                            case "High":
                               if (p_remove_ratio >= 1.2) {
                                    pcrop_app_rate_score = 0;
                                }
                                break;
                            case "Medium": case "Low":
                                if (p_remove_ratio >= 1.6) {
                                    pcrop_app_rate_score = 0;
                                }
                                break;
                            case "None":
                                if (p_remove_ratio >= 1.2) {
                                    pcrop_app_rate_score = 0;
                                }
                                break;                                                                                                                                                                                     
                        }

                        if ( pcrop_app_rate_score == -1 ){
                            query = "SELECT app_mgt_score FROM wqm_nutrient_application_mgt_scores WHERE nutrient='Phosphorus' AND app_mgt_kind='Rate' AND app_mgt_factor='app' AND soil_test_result='" + pSoilTestResult +"' AND remove_ratio_1 <= " + p_remove_ratio + " AND remove_ratio_2 > " + p_remove_ratio + ";";
                            resultSet = statement.executeQuery(query);
                            if (resultSet.first()) {
                                 pcrop_app_rate_score = resultSet.getInt("app_mgt_score");
                             }                                   
                        }

                        if (pcrop_app_rate_score != -1){
                        //#Update N and P application management rate scores for the AoA
                        appRateScore[N_APP_RATE_SCORE] = ncrop_app_rate_score;
                        appRateScore[P_APP_RATE_SCORE] = pcrop_app_rate_score;                                                                                                
                        }
                        else{//We have a problem...no validity in any following computations if we proceed since the last query failed to find any data.  What would you like to do?
                            appRateScore[N_APP_RATE_SCORE] = appRateScore[P_APP_RATE_SCORE] = 0;
                        }                                                                                                                                                                                                                                                                                                                                                                              
                    }
                    else{ //We have a problem...no validity in any following computations if we proceed.  What would you like to do?
                        appRateScore[N_APP_RATE_SCORE] = appRateScore[P_APP_RATE_SCORE] = 0;
                    }                                                       
                }
            }
            catch (SQLException ex) {
                error_msg += ex.getMessage();
            }                   
        }
       
        return this.appRateScore;
    }
    
    public int[] getNutrientApplicationTimingScores(){
        if ( ( appNutrientTimingScore[N_APP_TIMING_SCORE]  <0 ) || ( appNutrientTimingScore[P_APP_TIMING_SCORE]  <0 ) ) {
            int tempNScore = 100;
            int tempPScore = 100;
            
            for ( Crop.NutrientApplication nutrientApplication : this.nutrientApplicationList ){
                //  Call each application compare its results to the last and adjust main score if necessary.
                int[] tempAppTimingScores = nutrientApplication.getNutrientTimingScores( this.multipleNutrientApplication );
                
                if ( tempAppTimingScores[N_APP_TIMING_SCORE] < tempNScore ) {
                    tempNScore = tempAppTimingScores[N_APP_TIMING_SCORE];
                }
                
                if ( tempAppTimingScores[P_APP_TIMING_SCORE] < tempPScore ) {
                    tempPScore = tempAppTimingScores[P_APP_TIMING_SCORE];                               
                }                               
            }
            
            appNutrientTimingScore[P_APP_TIMING_SCORE] = tempPScore;
            appNutrientTimingScore[N_APP_TIMING_SCORE] = tempNScore;
        }
                
        return this.appNutrientTimingScore;                        
    }
            
    public Boolean allNutrientsIncorporated(){
        Boolean ret_val = true;
        for( Crop.NutrientApplication nutrientApplication : this.nutrientApplicationList ){
            if ( !nutrientApplication.allNutrientsIncorporated() ){
                ret_val = false;
                break;
            }
        }
        
        return ret_val;
    }
    
    public String processCropData(){
        String ret_val = "";
        
        return ret_val;
    }
    
    public Boolean validate(){          
        for( Crop.NutrientApplication tNutrientApplication : nutrientApplicationList ) {
            if (!tNutrientApplication.validate()) {
                error_msg += "; " + tNutrientApplication.getErrorMsg();                
            }
        }                

        
        return ( error_msg.isEmpty() );
    }

    //  Inner Classes
    class NutrientApplication {

        class Nutrient {

            private String error_msg = "";
            private final String nutrientApplied;
            private final double applicationRate;

            Nutrient(String nutrientApplied, double applicationRate) {
                this.nutrientApplied = nutrientApplied;
                this.applicationRate = applicationRate;
                
                if ( !nutrientApplied.equals("Nitrogen") && !nutrientApplied.equals("Phosphorus") ) {
                    this.error_msg += "Invalid input data.  Bad nutrient name";
                }
            }

            //Get Methods
            public String getNutrientApplied() {
                return this.nutrientApplied;
            }

            public double getApplicationRate() {
                return this.applicationRate;
            }

            public String getErrorMsg(){
                return this.error_msg;
            }

            public Boolean validate(){
                return (this.error_msg.isEmpty());
            }
        }
        private String error_msg = "";
        private String applicationDate;
        private boolean incorporated;
        private ArrayList<Crop.NutrientApplication.Nutrient> nutrientList;
        private int appMethodScore = 0;
        private int[] nutrientTimingScores = new int[2];

        NutrientApplication(String applicationDate, String incorporated, JSONArray applications) {
            this.applicationDate = applicationDate;
            nutrientTimingScores[N_APP_TIMING_SCORE] = nutrientTimingScores[P_APP_TIMING_SCORE] = -1;
            if (( !"true".equalsIgnoreCase(incorporated) ) && ( !"false".equalsIgnoreCase(incorporated) )) {
                this.error_msg += "Invalid incorporation value for nutrient application " + applicationDate;
            } else {
                this.incorporated = Boolean.parseBoolean(incorporated);
                nutrientList = new ArrayList<>();
                try {
                    for (int k = 0; k < applications.length(); k++) {
                        Map<String, JSONObject> nutrient = JSONUtils.preprocess(applications.getJSONArray(k));
                        nutrientList.add(new Crop.NutrientApplication.Nutrient(JSONUtils.getStringParam(nutrient, "nutrient_applied", "err"), JSONUtils.getDoubleParam(nutrient, "application_rate", 0)));
                    }
                } catch (JSONException ex) {
                    this.error_msg += "Cannot process applied nutrient list.  " + ex.getMessage();
                }
            }
        }

        //Get Methods
        public Date getApplicationDate() throws Exception {
            String[] parse = this.applicationDate.split("-");
            //Some say should set to use UTC first...do we wanna do that?
            Calendar date = new GregorianCalendar(Integer.parseInt(parse[0]),
                    Integer.parseInt(parse[1]) - 1, Integer.parseInt(parse[2]));
            return date.getTime();
        }
        
        public boolean isIncorporated() {
            return this.incorporated;
        }
        
        public ArrayList getNutrientList() {
            return this.nutrientList;
        }

        public String getErrorMsg(){
            for (Crop.NutrientApplication.Nutrient nutrient : this.nutrientList ){
                this.error_msg += nutrient.getErrorMsg();
            }
            return this.error_msg;
        }

        public Boolean validate() {
            for (Crop.NutrientApplication.Nutrient nutrient : nutrientList) {
                if (!nutrient.validate()) {
                    this.error_msg += "; " + nutrient.getErrorMsg();
                }
            }
            return (this.error_msg.isEmpty());
        }

        public int[] getNutrientTimingScores(Boolean split) {
            if (( nutrientTimingScores[N_APP_TIMING_SCORE] < 0 ) || ( nutrientTimingScores[P_APP_TIMING_SCORE] < 0 )) {
                int tempNScore = 100;
                int tempPScore = 100;
                ResultSet resultSet;
                for (Crop.NutrientApplication.Nutrient nutrient : nutrientList) {
                    int app_timing_score;
                    try {
                        long app_day_diff = Dates.diffInMillis( getCropPlantDate(), getApplicationDate() );
                        app_day_diff = TimeUnit.MILLISECONDS.toDays(app_day_diff);
                        String query = "SELECT app_mgt_score FROM wqm_nutrient_application_mgt_scores WHERE nutrient='";
                        query += nutrient.getNutrientApplied() + "' ";
                        query += "AND app_mgt_kind='Timing' AND app_mgt_factor='" + (split? "split":"nosplit") + "'AND days_fr_plant_1 <= " + app_day_diff + "AND days_fr_plant_2 > " + app_day_diff + ";";
                        resultSet = statement.executeQuery(query);
                        if ( !resultSet.first() ) {
                            app_timing_score = -1;
                        }
                        else {
                            app_timing_score = resultSet.getInt("app_mgt_score");
                        }
                        switch (nutrient.getNutrientApplied()) {
                            case "Nitrogen":
                                if (app_timing_score < tempNScore) {
                                    tempNScore = app_timing_score;
                                }
                                break;
                            case "Phosphorus":
                                if (app_timing_score < tempPScore) {
                                    tempPScore = app_timing_score;
                                }
                                break;
                            default:
                                this.error_msg += "Invalid nutrient name specified";
                        }
                    }catch (Exception ex){
                        this.error_msg += "Cannot calculate nutrient application timing scores: " + ex.getMessage();
                    }
                    if ( this.error_msg.isEmpty() ){
                        nutrientTimingScores[N_APP_TIMING_SCORE] = tempNScore;
                        nutrientTimingScores[P_APP_TIMING_SCORE] = tempPScore;
                    }
                }
            }
            return nutrientTimingScores;
        }

        public Boolean allNutrientsIncorporated(){
            return this.incorporated;
        }
    }
}