WEPPManagement.java [src/java/m/wepp] Revision:   Date:
package m.wepp;

import csip.SessionLogger;
import csip.utils.Parallel;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import util.WEPPUtils;
import util.WeppConstants;

/**
 * This class represents one management section on a hillslope. The management
 * is composed of a list of operations (LMODOperation class). The output is the
 * .rot files that are read by the c++ wepphillslopeserv program.
 *
 * @author jrf
 */
public class WEPPManagement {

  public static final String DEFAULT_NAME = "wepp";
  public static final String DEFAULT_EXT = ".rot";
  public static final int MAX_LINE_LENGTH = 256;
  public static final double LITER_TO_GAL = 0.264172;
  public static final double HECTA_TO_ACRE = 2.47105;

  float fuelTotal = 0;
  float stirTotal = 0;

  int rotYears = 0;

  String description;
  String name;

  LMODWeppData dbs;

  String initialCondFile;
  String initialCondPlant;
  String initialKey = "Default";
  String version;
  File defAnnualCrop;
  File defPerennialCrop;
  File defWoodySettings;
  boolean crlmod31_format;
  boolean hasIntervalsDefined = false;

  String tranErrors = "";

  WEPPModel wm;

  boolean use_CRLMOD;

  SessionLogger LOG;

  List<LMODOperation> lmodOperations;

  JSONObject debugData;

  String initialConditionCrop;


  /**
   * Create a WEPP management instance.
   *
   * @param wm lower level WEPP functions.
   * @param log access to CSIP LOG file
   * @param debugData string of overrides
   * @param use_CRLMOD whether CRLMOD data should be assumed.
   */
  WEPPManagement(WEPPModel wm, SessionLogger log, JSONObject debugData,
      boolean use_CRLMOD, String cropsURL, String operationsURL, String residueURL, String version) {
    this.LOG = log;
    this.wm = wm;
    this.use_CRLMOD = use_CRLMOD;
    this.debugData = debugData;
    this.version = version;
    lmodOperations = new ArrayList<>();
    description = "WEPP file translated from LMOD.";
    initialConditionCrop = "???";
    dbs = new LMODWeppData(debugData, LOG, cropsURL, operationsURL, residueURL, version);

    crlmod31_format = true;

    if (cropsURL.endsWith("/2.0") || cropsURL.endsWith("/3.0")) {
      crlmod31_format = false;
    }
  }


  /**
   * A general validation function, called on the Rotation before saving and
   * exporting it to JSON. This function should check whether pointer names are
   * correct, and dates are all valid and in-order.
   */
  public void validate() {
  }


  /**
   * Get any errors generated from this management.
   *
   * @return string of error messages
   */
  public String getErrors() {
    return tranErrors;
  }


  /**
   * Creates the WEPP .rot file.
   *
   * @param rotFile Where to write data
   * @return ROT file
   * @throws IOException any errors creating file.
   */
  public File getNativeFile(File rotFile) throws IOException {
    writeROT(rotFile);
    return rotFile;
  }


  /**
   * Check all operations for processes that could be used in a crop calibration
   * and mark the associated vegetation.
   *
   */
  private void markVegsToCal() {
    for (LMODOperation op : lmodOperations) {
      // check if operation has a process for harvest
      if (op.hasCalRelatedProcess()) {
        // get veg key
        String harvPlant = op.getHarVegKey();
        if (harvPlant != null) {
          dbs.setVegHarvest(harvPlant, true);
        }
      }
    }
  }


  /**
   * Write the crop section of the .rot file. This is a list of crops with their
   * parameters.
   *
   * @param writer where to write data to
   */
  private void writeCropDB(PrintWriter writer) {
    markVegsToCal();
    String recs = dbs.printVegs();
    writer.print(recs);
  }


  /**
   * Write the operation section of the .rot file. This is a list of operations
   * with their parameters.
   *
   * @param writer where to write data to
   */
  private void writeOpDB(PrintWriter writer) {
    String recs = dbs.printOps();
    writer.print(recs);
  }


  /**
   * Write the contour section of the .rot file. This is a list of contouring
   * records and the parameters.
   *
   * @param writer where to write the data to
   */
  private void writeContourDB(PrintWriter writer) {
    String recs = dbs.printContours();
    writer.print(recs);
    writer.print("\n");
  }


  /**
   * Write the residue section of the .rot file. This is a list of the residues
   * used by this management and the parameters.
   *
   * @param writer where to write the data to.
   */
  private void writeResidueDB(PrintWriter writer) {
    String recs = dbs.printResidues();
    writer.print(recs);
    writer.print("\n");
  }


  /**
   * Writes out initial condition section of the .rot file. The data written is
   * a string of initial condition lines that was read from a template file.
   *
   * @param writer where to write the data to.
   */
  private void writeInitialCondDB(PrintWriter writer) {
    writer.print(initialCondFile);
    writer.print("\n");
  }


  /**
   * Write out the irrigation records if this management has irrigation.
   *
   * @param writer where to write the data to
   * @param ty type of irrigation.
   */
  private void writeIrrDB(PrintWriter writer, int ty) {
    int count = dbs.getIrrCount(ty);
    if (count > 0) {
      String recs = dbs.printIrrs(ty);
      writer.print(recs);
    }
  }


  /**
   * Adds contouring to the output .rot file
   *
   * @param writer where to write data to
   * @param year year that contouring applies to
   */
  private void insertContour(PrintWriter writer, int year) {
    int mon = 1;
    int day = 2;
    String key = "CONT1";
    String stl = String.format("%4d  %2d  %2d  1 Start-Contour\tContourDef.%s  {}\n", mon, day, year, key);
    writer.print(stl);
  }


  /**
   * Adds irrigation to the output .rot file
   *
   * @param writer where to write data to
   * @param year year this irrigation applies to
   * @param key irrigation record key
   */
  private void insertIrrigation(PrintWriter writer, int year, String key, int endday, int endyear) {
    int mon = 1;
    int day = 2;
    String stl = String.format("%4d  %2d  %2d  1 Start-Irrigation\tIrrDef.%s  {%d, %d}\n", mon, day, year, key, endday, endyear);
    writer.print(stl);
  }


  /**
   *
   * Writes out the rotation file that the WEPP file builder program can
   * process. This contains the final WEPP operation sequences along with all
   * parameters.
   *
   * @param out output
   * @throws IOException if content could not write out successfully.
   *
   */
  public void writeROT(File out) throws IOException {
    validate();

    try (PrintWriter writer = new PrintWriter(out)) {
      writer.println("#");
      Date date = new Date();
      SimpleDateFormat formatter = new SimpleDateFormat("EEE MMM dd HH:mm:ss aa yyyy");
      writer.println("# WEPP rotation saved on: " + formatter.format(date));
      writer.println("#");
      writer.println("# File created by Java WEPP libraries");
      writer.println("#");
      //writer.println("# --- Original LMOD sequence ---");
      writer.println("# --- Translated LMOD sequence ---");

      int lastYear = 1;
      fuelTotal = 0;
      stirTotal = 0;
      String curCrop = markGrowingPeriods();
      markSinglePlant();
      for (int i = 0; i < lmodOperations.size(); i++) {
        LMODOperation lm = lmodOperations.get(i);
        String st = lm.toString();
        st = st.replace("\n", "\n# ");
        writer.println("# " + st);
        fuelTotal = fuelTotal + lm.getFuel();
        stirTotal = stirTotal + lm.getSTIR();
        lastYear = lm.getYear();
        writer.printf("#    Fuel=%f  STIR=%f%n", (lm.getFuel() * LITER_TO_GAL) / (HECTA_TO_ACRE), lm.getSTIR());
      }
      writer.printf("# Total Fuel = %f%n", (fuelTotal * LITER_TO_GAL) / (HECTA_TO_ACRE));
      writer.printf("# Total STIR = %f%n", stirTotal);

      fuelTotal = fuelTotal / (float) lastYear;
      stirTotal = stirTotal / (float) lastYear;

      writer.printf("# Management Years = %d%n", lastYear);
      writer.printf("Version = 2014.1%n");
      writer.printf("Name = %s%n", name);
      writer.println("Description {");
      writer.println(description);
      writer.println("}");
      writer.printf("Color = 255 0 0%n");
      writer.printf("LandUse = 1%n");
      writer.printf("FuelUsage = %f%n", (fuelTotal * LITER_TO_GAL) / HECTA_TO_ACRE);
      writer.printf("STIR = %f%n", stirTotal);
      if (dbs.getVegCount() > 0) {
        writer.println("CropDB {");
        writeCropDB(writer);
        writer.println("}");
      }
      writer.println();
      writer.println("OperationsDB {");
      writeOpDB(writer);
      writer.println("}");
      writer.println();
      if (dbs.getResCount() > 0) {
        writer.println("ResidueDB {");
        writeResidueDB(writer);
        writer.println("}");
      }
      if (dbs.getContCount() > 0) {
        writer.println("ContourDB {");
        writeContourDB(writer);
        writer.println("}");
        writer.println();
      }
      if (dbs.getIrrCount(0) > 0) {
        writer.println("IrrigationDB {");
        writeIrrDB(writer, 0);
        writer.println("}");
      }
      if (dbs.getIrrCount(1) > 0) {
        writer.println("DailyIrrDB {");
        writeIrrDB(writer, 1);
        writer.println("}");
      }
      writer.println("InitialConditionsDB {");
      writeInitialCondDB(writer);
      writer.println("}");
      writer.println();
      writer.println();
      if (version.equals("2") || version.equals("3") || version.equals("3.1")) {
        writer.printf("InitialConditions = IniCropDef.%s", initialKey);
      } else {
        int seneday;
        String initCropKey;
        seneday = dbs.getSenescenceDay(initialConditionCrop);
        if (seneday > 0) {
          writer.printf("InitialConditions = IniCropDef.%s {0.0 , %d}", initialKey, seneday);
        } else {
          writer.printf("InitialConditions = IniCropDef.%s", initialKey);
        }
      }
      writer.println();
      writer.println("Operations {");
      int curYear = -1;
      boolean firstTime = true;
      boolean irrigationActive = false;
      String irrigationKey = "";
      int lastIrrYear = curYear;
      int currentIndex = 0;

      for (LMODOperation op : lmodOperations) {
        if ((curYear != op.getYear() || firstTime) && ((dbs.getContCount() > 0))) {
          if (firstTime) {
            firstTime = false;
          }
          if (dbs.getContCount() > 0) {
            insertContour(writer, op.getYear());
          }
        }
        
        /*
        if ((curYear != op.getYear()) && (irrigationActive)) {
          if (lastIrrYear != -1) {
              for (int k=lastIrrYear+1;k<=op.getYear();k++) {
                  insertIrrigation(writer, k, irrigationKey);
              }
          } else {
            insertIrrigation(writer, op.getYear(), irrigationKey);
          }
          lastIrrYear = op.getYear();
        }
        */
        curCrop = op.getCurCrop(curCrop);
        if (op.hasProcess("irrigation")) {
            // depletion irrigation, look for when this ends
            irrigationActive = true;
            irrigationKey = op.getIrrigationKey();
            int endday,endyear;
            endday = endyear = 0;
            int lastday, lastyear;
            lastday = lastyear = 0;
            LMODOperation op2;
            for (int k=currentIndex;k<lmodOperations.size();k++) {
                op2 = lmodOperations.get(k);
                if (op2.hasProcess("irrigation-stop")) {
                    endday = op2.getJulianDay();
                    endyear = op2.getYear();
                    break;
                } else {
                    lastday = op2.getJulianDay();
                    lastyear = op2.getYear();
                }
            }
            if ((endday == 0) && (endyear == 0)) {
                // not ending stop irrigation, use date of last operation
                endday = lastday;
                endyear = lastyear;
            }
            insertIrrigation(writer, op.getYear(), irrigationKey, endday, endyear);
        } else {
            String oline = op.toOpString(curCrop);
            if (oline.isEmpty() == false) {
              if (version.equals("2") || version.equals("3") || version.equals("3.1")) {
                writer.print(oline);
              } else {
                if (!op.isPerennialMaturePlant()) {
                  writer.print(oline);
                }
              }
            }
        }

        /*
        if (op.hasProcess("irrigation")) {
          irrigationActive = true;
          irrigationKey = op.getIrrigationKey();
        }
        
        if (op.hasProcess("irrigation-stop")) {
          irrigationActive = false;
          irrigationKey = "";
        }
        */
        if (curYear != op.getYear()) {
          lastIrrYear = curYear;  
          curYear = op.getYear();
        }
        currentIndex++;
      }
      writer.println("}");
    }
  }


  /**
   * TBD
   *
   */
  private void markSinglePlant() {
    List<LMODOperation> remlist = new ArrayList<>();
    String vkey = null;
    for (LMODOperation op : lmodOperations) {
      boolean startGrowth = op.hasProcess("plant");
      if (startGrowth) {
        vkey = op.getVegKey();
        if (dbs.isPerennial(vkey)) {
          // need plant operation to pass senescence date along, the 
          // default is 0 so not needed and can be removed.
          if (dbs.getSenescenceDay(vkey) > 0) {
            return;
          }
          for (int i = 0; i < remlist.size(); i++) {
            if (!vkey.equals(remlist.get(i).getVegKey())) {
              // this means there are different perennial plants, so keep
              return;
            }
          }
          remlist.add(op);
        }
      }
      if (op.hasStopGrowthProcess(vkey)) {
        return;
      }
    }
    for (int i = 0; i < remlist.size(); i++) {
      remlist.get(i).deletePlantProcess = true;
    }
  }


  /**
   * Go through all the operations and indicate where crop growth starts and
   * ends.
   *
   * @return current crop growing at end of rotation
   */
  private String markGrowingPeriods() {
    boolean cropGrowing = false;  // assume no crop growing.
    // need to do this twice in case there is a winter annual that is planted
    // at the end so growth wraps around to beginning.
    String curCrop = "";
    for (int i = 0; i < 2; i++) {
      for (LMODOperation op : lmodOperations) {
        boolean startGrowth = op.hasProcess("plant");
        boolean endGrowth = op.hasStopGrowthProcess(curCrop);
        //curCrop = op.getCurCrop(curCrop);
        if ((startGrowth == false) && (cropGrowing == false)) {
          op.setCropGrowing(false);
        } else if ((startGrowth == true) && (endGrowth == true)) {
          cropGrowing = true;
          op.setCropGrowing(true);
          op.setIncludeKill(true);  // this assumes there is a kill-crop followed by
                                    // a next-day followed by a planting
        } else if ((startGrowth == true) && (endGrowth == false)) {
          cropGrowing = true;
          op.setCropGrowing(true);
        } else if (endGrowth == true) {
          if (cropGrowing) {
            op.setIncludeKill(true);
          }
          op.setCropGrowing(false);
          cropGrowing = false;
        }
        if (cropGrowing == true) {
          op.setCropGrowing(true);
        }
        if (op.hasCalRelatedProcess()) {
          op.setVegIfEmpty(curCrop);
        }
        curCrop = op.getCurCrop(curCrop);
      }
    }
    return curCrop;
  }


  /**
   * Converts CRLMOD JSON blob into an internal structure.
   *
   * @param baseCRLMODObject starting CRLMOD object
   * @return new representation as a string
   * @throws IOException if any
   * @throws ParseException if any
   * @throws JSONException if any
   */
  protected String parseDataCSIPCRLMOD(JSONObject baseCRLMODObject) throws
      IOException, ParseException, JSONException {
    String newdata = "";
    String name;
    try {
      JSONArray ev0 = baseCRLMODObject.getJSONArray("events");
      JSONArray events = ev0.getJSONArray(0);
      List<String> dates = new ArrayList<>();
      List<String> ids = new ArrayList<>();
      List<String> ops = new ArrayList<>();
      List<String> vegs = new ArrayList<>();
      List<Integer> veg_ids = new ArrayList<>();
      List<Integer> yields = new ArrayList<>();
      List<String> yieldUnits = new ArrayList<>();
      List<String> residues = new ArrayList<>();
      List<Integer> res_ids = new ArrayList<>();
      List<Integer> res_amts = new ArrayList<>();
      List<Boolean> intervals = new ArrayList<>();
      //LOG.info("Looking at events:\n");
      int yearBase = 1;
      for (int i = 0; i < events.length(); i++) {
        JSONObject ev = events.optJSONObject(i);
        //LOG.info("Event Object: " + ev.toString());
        String datestr = ev.getString("date");
        String parts[] = datestr.split("-");
        int yr = Integer.parseInt(parts[0]);
        // WEPP expects years 1..N If we see a very large number it probably means that
        // actual years are being sent, like 1986 2017. In this case adjust the years
        // so they start at year 1. Note: that in a regular WEPP year numbers (1..N) the beginning
        // years can be fallow, when real year values are sent no beginning fallow can be specified.
        if (yr > 1000) {
          if (i == 0) {
            yearBase = yr;
          }
          yr = (yr - yearBase) + 1;
          datestr = String.format("%04d", yr) + "-" + parts[1] + "-" + parts[2];
        }
        dates.add(datestr);

        JSONObject op = ev.getJSONObject("operation");
        String idstr = op.getString("id");
        ids.add(idstr);
        String namestr = op.getString("name");
        ops.add(namestr);

        double yld = 0;
        double resa = 0;
        if (!crlmod31_format) {
          yld = ev.optDouble("yield");
          resa = ev.optDouble("res_added");
        }
        JSONObject crop = ev.optJSONObject("crop");
        if (crop != null) {
          String id = crop.getString("id");
          if (id.equals("")) {
            vegs.add(null);
            veg_ids.add(null);
            yields.add(null);
            yieldUnits.add(null);
          } else {
            if (crlmod31_format) {
              yld = crop.optDouble("yield");
            }
            vegs.add(crop.getString("name"));
            veg_ids.add(Integer.parseInt(crop.getString("id")));
            String yunit = crop.getString("yieldUnit");
            if (yunit == null) {
              yieldUnits.add("???");
            } else {
              yieldUnits.add(yunit);
            }
            if (Double.isNaN(yld)) {
              try {
                int cyld = crop.getInt("defaultYield");
                yields.add(cyld);
              } catch (Exception e) {
                // there is no yield or default value specified -
                LOG.severe("Yield missing on: " + datestr);
              }
            } else {
              yields.add((int) yld);
            }
          }
        } else {
          vegs.add(null);
          veg_ids.add(null);
          yields.add(null);
          yieldUnits.add(null);
        }

        JSONObject res = ev.optJSONObject("residue");
        if (res != null) {
          String id = res.getString("id");
          if (id.equals("")) {
            res_ids.add(null);
            residues.add(null);
            res_amts.add(null);
          } else {
            if (crlmod31_format) {
              resa = res.optDouble("res_added");
            }
            residues.add(res.getString("name"));
            res_ids.add(Integer.parseInt(res.getString("id")));
            res_amts.add((int) resa);
          }
        } else {
          res_ids.add(null);
          residues.add(null);
          res_amts.add(null);
        }
      }

      // build the new object
      JSONObject newobj = new JSONObject();
      JSONObject lmod_file = new JSONObject();
      JSONObject lmod_data = new JSONObject();
      JSONObject params = new JSONObject();
      JSONArray param = new JSONArray();

      lmod_file.put("path", "path");
      lmod_file.put("name", "name");

      // dates
      JSONObject op_date = new JSONObject();
      op_date.put("name", "OP_DATE");
      JSONArray op_dates = new JSONArray();
      for (int i = 0; i < dates.size(); i++) {
        op_dates.put(dates.get(i));
      }
      op_date.put("data", op_dates);
      param.put(op_date);

      // yields
      JSONObject harv_yields = new JSONObject();
      harv_yields.put("name", "MAN_OP_VEG_NUM_HARV_UNITS");
      JSONArray unitarr = new JSONArray();
      for (int i = 0; i < yields.size(); i++) {
        unitarr.put(yields.get(i));
      }
      harv_yields.put("data", unitarr);
      param.put(harv_yields);

      // operations
      JSONObject my_ops = new JSONObject();
      my_ops.put("name", "OP_PTR");
      JSONArray oparr = new JSONArray();
      for (int i = 0; i < ops.size(); i++) {
        JSONObject oneop = new JSONObject();
        if (ids.get(i) == null) {
          oneop.put("file_key", ops.get(i));
        } else {
          oneop.put("file_key", ids.get(i).toString());
          oneop.put("value", "operations\\" + ops.get(i));
        }
        oparr.put(oneop);
      }
      my_ops.put("data", oparr);
      param.put(my_ops);

      // crops
      JSONObject my_vegs = new JSONObject();
      my_vegs.put("name", "VEG_PTR");
      JSONArray vegarr = new JSONArray();
      for (int i = 0; i < vegs.size(); i++) {
        JSONObject oneveg = new JSONObject();
        if (vegs.get(i) == null) {
          oneveg.put("file_key", JSONObject.NULL);
        } else {
          oneveg.put("file_key", veg_ids.get(i).toString());
          oneveg.put("value", "vegetations\\" + vegs.get(i));
        }
        vegarr.put(oneveg);
      }
      my_vegs.put("data", vegarr);
      param.put(my_vegs);

      // residue amounts
      JSONObject resobj = new JSONObject();
      resobj.put("name", "RES_ADDED");
      JSONArray reso = new JSONArray();
      for (int i = 0; i < res_amts.size(); i++) {
        reso.put(res_amts.get(i));
      }
      resobj.put("data", reso);
      param.put(resobj);

      // residue names
      JSONObject my_ress = new JSONObject();
      my_ress.put("name", "EXT_RES_PTR");
      JSONArray resarr = new JSONArray();
      for (int i = 0; i < residues.size(); i++) {
        JSONObject oneres = new JSONObject();
        if (residues.get(i) == null) {
          oneres.put("file_key", JSONObject.NULL);
        } else {
          oneres.put("file_key", res_ids.get(i).toString());
          oneres.put("value", "residues\\" + residues.get(i));
        }
        resarr.put(oneres);
      }
      my_ress.put("data", resarr);
      param.put(my_ress);

      params.put("param", param);
      lmod_file.put("params", params);
      lmod_data.put("lmod_file", lmod_file);
      newobj.put("lmodData", lmod_data);
      newdata = newobj.toString();
    } catch (Exception e) {
      LOG.info("translate2WEPP_CRLMOD error: could not convert management" + baseCRLMODObject.toString());
      newdata = "";
    }
    LOG.info("converted from CRLMOD: " + newdata);
    return newdata;
  }


  /**
   * Parses the LMOD section of the request.
   *
   * @param jsonInput JSON string in LMOD format to parse
   * @param errors Error messages if problems.
   * @param useR2Names If the names in the management in JSON request are RUSLE2
   * LMOD (true) or have been translated to WEPP in GUI
   * @return boolean if there is no lmod_file in jsonInput, return false
   * @throws IOException if any
   * @throws ParseException if any
   * @throws JSONException if any
   *
   */
  protected boolean parseDataCSIPLMOD(String jsonInput, List<String> errors, boolean useR2Names) throws IOException, ParseException, JSONException {
    JSONObject baseLMODObject = new JSONObject(jsonInput);
    JSONObject lma = null;
    JSONObject lmf = null;
    if (!baseLMODObject.has("lmodData")) {
      if (baseLMODObject.has("lmod_file")) {
        lmf = baseLMODObject.getJSONObject("lmod_file");
      } else {
        return false;
      }
    } else {
      lma = new JSONObject(baseLMODObject.getString("lmodData"));
      lmf = lma.getJSONObject("lmod_file");
    }

    if (lmf == null) {
      return false;
    }

    String mname = lmf.optString("name");
    if (mname != null) {
      name = mname;
    } else {
      name = "???";
    }

    JSONObject plist = lmf.getJSONObject("params");
    JSONArray params = plist.getJSONArray("param");

    //JSONArray params = baseLMODObject.getJSONArray("params");
    JSONArray extResidueType = null;
    JSONArray dates = null;
    JSONArray resAdded = null;
    JSONArray yields = null;
    JSONArray operations = null;
    JSONArray vegetations = null;
    JSONArray intervals = null;
    for (int i = 0; i < params.length(); i++) {
      JSONObject param = params.optJSONObject(i);
      if (param.optString("name").equals("VEG_PTR")) {
        vegetations = param.optJSONArray("data");
        for (int j = 0; j < vegetations.length(); j++) {
          JSONObject oobj = vegetations.getJSONObject(j);
          String ostring = oobj.optString("file_key");
          if (ostring.equals("null")) {
            vegetations.put(j, (String) null);
          } else {
            ostring = oobj.optString("value");
            if (ostring.startsWith("vegetations\\")) {
              String s = ostring.replace("vegetations\\", "").trim();
              if (s.startsWith("\\")) {
                s = s.substring(1);
              }
              vegetations.put(j, s.trim());
            }
          }
        }
      }
      if (param.optString("name").equals("EXT_RES_PTR")) {
        extResidueType = param.optJSONArray("data");
        for (int j = 0; j < extResidueType.length(); j++) {
          JSONObject oobj = extResidueType.getJSONObject(j);
          String ostring = oobj.optString("file_key");
          if (ostring.equals("null")) {
            extResidueType.put(j, (String) null);
          } else {
            ostring = oobj.optString("value");
            if (ostring.startsWith("residues\\")) {
              String s = ostring.replace("residues\\", "").trim();
              if (s.startsWith("\\")) {
                s = s.substring(1);
              }
              extResidueType.put(j, s.trim());
            }
          }
        }
      }
      if (param.optString("name").equals("OP_DATE")) {
        dates = param.optJSONArray("data");
      }
      if (param.optString("name").equals("RES_ADDED")) {
        resAdded = param.optJSONArray("data");
      }
      if (param.optString("name").equals("MAN_OP_VEG_NUM_HARV_UNITS")) {
        yields = param.optJSONArray("data");
      }
      if( param.optString( "name" ).equals( "INTERVAL" ) ) {
        intervals = param.optJSONArray( "data" );
      }
      if (param.optString("name").equals("OP_PTR")) {
        operations = param.optJSONArray("data");
        for (int j = 0; j < operations.length(); j++) {
          JSONObject oobj = operations.getJSONObject(j);
          String ostring = oobj.optString("value");
          if (ostring.startsWith("operations\\")) {
            String s = ostring.replace("operations\\", "").trim();
            if (s.startsWith("\\")) {
              s = s.substring(1);
            }
            operations.put(j, s.trim());
          }
        }
      }
    }

    // create the LMOD operations records
    List<String> allResidues = new ArrayList<>();
    List<String> allCrops = new ArrayList<>();
    List<String> allOperations = new ArrayList<>();
    for (int i = 0; i < dates.length(); i++) {
      if (extResidueType != null) {
        String resExtStr = extResidueType.optString(i);
        if (!resExtStr.isEmpty()) {
          allResidues.add(resExtStr);
        }
      }
      // **** only add if the name is not already present ****
      allOperations.add(operations.optString(i));
      if (!vegetations.optString(i).isEmpty()) {
        allCrops.add(vegetations.optString(i));
      }
    }
    if (use_CRLMOD) {
      try {
        Parallel.run(
            () -> { dbs.getOpBlobsCRLMOD(allOperations); },
            () -> { dbs.getVegBlobsCRLMOD(allCrops); },
            () -> { dbs.getResBlobsCRLMOD(allResidues); }
        );
      } catch (Exception ex) {
        throw new RuntimeException(ex);
      }
    }
    for (int i = 0; i < dates.length(); i++) {
      String resStr = (resAdded != null) ? resAdded.optString(i) : "";
      String resExtStr = (extResidueType != null) ? extResidueType.optString(i) : "";
      String yldStr = (yields != null) ? yields.optString(i) : "";

       boolean isintv = false;
       if (intervals == null) {
           if (operations.optString(i).startsWith("Harvest")) {
                isintv = true;
           }
           hasIntervalsDefined = false;
        } else {
            isintv = intervals.optBoolean( i );
            hasIntervalsDefined = true;
        }
      LMODOperation lo = new LMODOperation(wm, dates.optString(i),
          operations.optString(i), vegetations.optString(i), resExtStr,
          resStr, yldStr, dbs, useR2Names, use_CRLMOD, isintv);
      if (lo.getErrors() != null) {
        tranErrors = tranErrors + lo.getErrors();
      }
      lmodOperations.add(lo);
    }

    // check if we need to move anything around
    adjustYear0();
    adjustFallTillage();
    rotYears = getLastYear();

    return true;
  }


  /**
   * Get the year of the last operation in the rotation.
   *
   * @return last year used in management
   */
  private int getLastYear() {
    int sz = lmodOperations.size();
    LMODOperation lmlast = lmodOperations.get(sz - 1);
    return lmlast.getYear();
  }


  /**
   * adjustFallTillage()
   *
   * Check date sequences to see it initial fall operations can be moved to the
   * end of the rotation.
   *
   */
  private void adjustFallTillage() {
    int year = 0;
    boolean rotChanged = false;
    LMODOperation lm = lmodOperations.get(0);
    int firstYear = lm.getYear();
    int sz = lmodOperations.size();
    LMODOperation lmlast = lmodOperations.get(sz - 1);
    int lastYear = lmlast.getYear();
    int lastMon = lmlast.getMonth();
    int lastDay = lmlast.getDay();

    // first check if there are any plants in year 1, if there are do not
    // adjust
    ListIterator<LMODOperation> lo = lmodOperations.listIterator();
    while (lo.hasNext()) {
      lm = lo.next();
      if (lm.getYear() == firstYear) {
        if (lm.hasPlant() == true) {
          // has a plant in first year, do not adjust
          return;
        }
      }
    }

    lo = lmodOperations.listIterator();
    while (lo.hasNext()) {
      lm = lo.next();
      int curMon = lm.getMonth();
      int curDay = lm.getDay();
      if (lm.getYear() == firstYear) {
        if (curMon > lastMon) {
          lm.changeDate(lastYear, curMon, curDay);
          rotChanged = true;
        } else if ((curMon == lastMon) && (curDay > lastDay)) {
          lm.changeDate(lastYear, curMon, curDay);
          rotChanged = true;
        } else {
          break;
        }
      } else {
        break;
      }
    }

    // sort list by date in case anything changed.
    Collections.sort(lmodOperations);

    // make sure the dates start at year 1
    lm = lmodOperations.get(0);
    firstYear = lm.getYear();

    int chgAmt = 0;

    if (firstYear != 1) {
      if (firstYear > 0) {
        chgAmt = firstYear - 1;
      } else if (firstYear == 0) {
        chgAmt = -1;
      }
    }

    // only do this for year 0 errors. For other rotations that do not start at
    // year 1 this may be correct.
    if ((chgAmt == -1) || (rotChanged)) {
      lo = lmodOperations.listIterator();
      while (lo.hasNext()) {
        lm = lo.next();
        year = lm.getYear() - chgAmt;
        int curMon = lm.getMonth();
        int curDay = lm.getDay();
        lm.changeDate(year, curMon, curDay);
      }
    }
  }


  /**
   * adjustYear0()
   *
   * Some RUSLE2 templates have a starting year of 0, adjust so it will always
   * start at year 1.
   */
  private void adjustYear0() {
    LMODOperation lm = lmodOperations.get(0);
    if (lm.getYear() == 0) {
      for (int i = 0; i < lmodOperations.size(); i++) {
        lm = lmodOperations.get(i);
        lm.adjustYears();
      }
    }
  }


  /**
   * Get references to all the default initial conditions files.
   *
   * @param defAnnualCrop default annual crop used in initial condition
   * @param defPerennialCrop default perennial crop used in initial condition
   * @param defWoodySettings
   */
  public void allInitial(File defAnnualCrop, File defPerennialCrop, File defWoodySettings) {
    this.defAnnualCrop = defAnnualCrop;
    this.defPerennialCrop = defPerennialCrop;
    this.defWoodySettings = defWoodySettings;
  }


  /**
   * Turns a csip-lmod object into a WEPPManagement directly by calling other
   * functions.
   *
   * @param lmodObject The LMOD object, as a String object containing JSON
   * @param contourObject JSON object contains contour
   * @param slopeWidth slope width
   * @param useR2Names If the names in the management in JSON request are RUSLE2
   * LMOD (true) or have been translated to WEPP in GUI
   * @param use_CRLMOD boolean value for whether to use crlmod
   * @param use_IETFormat boolean value
   * @param index index
   * @return The WEPPManagement object created
   * @throws SQLException if any
   * @throws IOException if any
   * @throws ParseException if any
   * @throws JSONException if any
   */
  public boolean convertLMOD(String lmodObject, JSONObject contourObject,
      float slopeWidth, boolean useR2Names, boolean use_CRLMOD, boolean use_IETFormat,
      int index, File saveFile) throws Exception {

    boolean rc = false;
    List<String> errors = new ArrayList<>();
    if (use_IETFormat == true) {
      //LOG.info("ORIGINAL CRLMOD: " + lmodObject);
      lmodObject = parseDataCSIPCRLMOD(new JSONObject(lmodObject));
    }
    FileUtils.writeStringToFile(saveFile, lmodObject, "UTF-8");

    if (parseDataCSIPLMOD(lmodObject, errors, useR2Names) == false) {
      LOG.info("Error: Failed in parseDataCSIPLMOD\n");
    } 

    // decide what intial condition to use
    LMODOperation lastPlant = null;
    boolean isFallow = true;
    for (int i = 0; i < lmodOperations.size(); i++) {
      LMODOperation lm = lmodOperations.get(i);
      if (lm.hasProcess("plant")) {
        lastPlant = lm;
      }
      if (lm.hasCalRelatedProcess()) {
        isFallow = false;
      }
      if (lm.hasProcess( "initialplant" ) ) {
        // use this, don't consider anything else
        lastPlant = lm;
        break;
      }
    }
    if (lastPlant != null) {
      String vegkey = lastPlant.getVegKey();
      if (dbs.isPerennial(vegkey)) {
        // perennial
        // check if it is a woody veg, need to treat these different
        try {
          JSONObject wr = dbs.getWoodyVeg(lastPlant.vegName, defWoodySettings);
          LOG.info("Vegetation for initial test:" + lastPlant.vegName + ":");
          if (wr != null) {
            LOG.info("Vegetation for initial test FOUND:" + lastPlant.vegName + ":");
            LOG.info(wr.toString());
            // defWoody - initial condition template
            double cancov = wr.getDouble("cancov");
            LOG.info("found cancov");
            double inrcov = wr.getDouble("inrcov");
            LOG.info("found inrcov");
            double rilcov = wr.getDouble("rilcov");
            LOG.info("found rilcov");
            double usrril = wr.getDouble("usrril");
            LOG.info("found usrril");
            double usrintr = wr.getDouble("usrintr");
            LOG.info("found usrintr");
            addWoodyInitialCondition(WeppConstants.InitialConditions.WOODY, vegkey, cancov, inrcov, rilcov, usrril, usrintr, index);
          } else {
            addInitialCondition(WeppConstants.InitialConditions.PERENNIAL, vegkey, defPerennialCrop, 2, index);
          }
        } catch (Exception e) {
          // extra woody params missing, default to a perennial
          addInitialCondition(WeppConstants.InitialConditions.PERENNIAL, vegkey, defPerennialCrop, 2, index);
        }
      } else {
        // annual crop
        addInitialCondition(WeppConstants.InitialConditions.ANNUAL, vegkey, defAnnualCrop, 1, index);
      }
    } else {
      // plant process, could be all fallow or assumed only some harvests
      if (isFallow) {
        addInitialCondition(WeppConstants.InitialConditions.FALLOW, null, defAnnualCrop, 3, index);
      } else {
        // assume some type of perennial but don't know what
        addInitialCondition(WeppConstants.InitialConditions.PERENNIAL, null, defPerennialCrop, 2, index);
      }
    }
    parseContours(contourObject, slopeWidth);

    //rc = toRotation(ldata, initialCondition, initialConditionCrop,contouring,errors);
    return rc;
  }


  /**
   * Extract any contouring information.
   *
   * @param contourObj contour object
   * @param len really width of slope
   */
  private void parseContours(JSONObject contourObj, float len) {
    try {
      double grade = contourObj.getDouble("abs");
      dbs.addContour((float) grade, len);
    } catch (Exception e) {
      e.printStackTrace(System.err);
//            throw new RuntimeException(e);
    }
    return;
  }


  /**
   * Update the initial condition template with the last crop growing in the
   * rotation if possible.
   *
   * @param iniFile template initial condition file to use
   * @param vegkey last vegetation growing
   * @param iniCropFile template initial crop to use
   * @param type general type of last crop growing
   * @param index sequence of this management rotation
   */
  private void addInitialCondition(WeppConstants.InitialConditions iniType,
      String vegkey, File iniCropFile, int type, int index) {
    try {
      initialCondFile = WeppConstants.getInitialCondition(iniType);
      if (vegkey == null) {
        // no crop defined, use a generic code residue for initial conditions
        String ifile = iniCropFile.getAbsolutePath();
        byte[] data = Files.readAllBytes(Paths.get(ifile));
        String blob = new String(data, Charset.defaultCharset());
        // replace __CROP__ placeholder in intial conditions
        if (type == 1) {
          initialCondFile = initialCondFile.replace("__CROP__", "Corn");
          initialConditionCrop = "Corn";
        } else if (type == 2) {
          initialCondFile = initialCondFile.replace("__CROP__", "Grass");
          initialConditionCrop = "Grass";
        } else if (type == 3) {
          initialCondFile = initialCondFile.replace("__CROP__", "Corn");
          initialConditionCrop = "Corn";
        }
        // crop to list
        dbs.getVegs(blob);
      } else {
        // use the last crop planted as the initial crop
        initialCondFile = initialCondFile.replace("__CROP__", vegkey);
        initialConditionCrop = vegkey;
      }
    } catch (Exception e) {
      initialCondFile = "???";
    }
    initialKey = "Default" + String.valueOf(index);
    initialCondFile = initialCondFile.replace("__DEFAULT__", initialKey);
  }

  // addWoodyInitialCondition(defWoody,vegkey,cancov,inrcov,rilcov,2,index);

  private void addWoodyInitialCondition(WeppConstants.InitialConditions iniType,
      String vegkey, double cancov, double inrcov, double rilcov, double usrril, double usrintr, int index) {
    try {
      initialCondFile = WeppConstants.getInitialCondition(iniType);
      initialCondFile = initialCondFile.replace("__CROP__", vegkey);
      initialConditionCrop = vegkey;
    } catch (Exception e) {
      initialCondFile = "???";
    }
    initialKey = "Default" + String.valueOf(index);
    initialCondFile = initialCondFile.replace("__DEFAULT__", initialKey);
    initialCondFile = initialCondFile.replace("__CANOPYCOV__", String.valueOf(cancov));
    initialCondFile = initialCondFile.replace("__INTERRILLCOV__", String.valueOf(inrcov));
    initialCondFile = initialCondFile.replace("__RILLCOV__", String.valueOf(rilcov));
    initialCondFile = initialCondFile.replace("__USRRIL__", String.valueOf(usrril));
    initialCondFile = initialCondFile.replace("__USRINTR__", String.valueOf(usrintr));

    // check if we are debugging and want to overwrite any of these
    // parameters
    if (debugData != null) {
      initialCondFile = applyTempWoodyDatabase(initialCondFile);
    }
    LOG.info(initialCondFile);
  }


  /**
   * Get a list of vegetations that need calibration.
   *
   * @return list of vegetation keys and names
   */
  public String getVegDBS() {
    return dbs.getVegsToCal();
  }


  /**
   * Get name of this rotation
   *
   * @return string name of rotation
   */
  public String getName() {
    return name;
  }


  /**
   * Generate a JSON structure that can be passed to the PDF report processor.
   *
   * @param name of this management
   * @return JSON objec that can be added to report inputs
   * @throws Exception anything
   */
  public JSONObject createJSONSummary(String name, float modelversion) throws Exception {
    JSONArray manSum = new JSONArray();
    JSONArray yields = new JSONArray();
    JSONArray intervals = new JSONArray();
    JSONObject manAll = new JSONObject();
    String[] monstrs = {
      "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };

    LMODOperation lm;
    int mon, day, yr, idays;
    String sdate, adate, startDate, endDate;
    double fuelr, stirr;
    boolean hasInterval = false;
    String vegkeyName;

    // newly added code for adding STIR sum value :    STIRSum = sum of (STIR of startDate, STIR of endDate].  (start adding after start date)
    JSONObject firstInterval = null;
    boolean firstIntervalFlag = false;  // a flag to indicate firstInterval
    double firstSTIRSum = 0;  // save the first part of STIRSum for the first interval.
    double STIRSum = 0;
    // adding fuel sum value: same as STIR sum.
    double firstFuelSum = 0;
    double fuelSum = 0;
    boolean hasintv;

    for (int i = 0; i < lmodOperations.size(); i++) {

      lm = lmodOperations.get(i);
      STIRSum += lm.getSTIR();
      double fuel = (lm.getFuel() * LITER_TO_GAL) / (HECTA_TO_ACRE);
      fuel = Math.round(fuel * 100.0) / 100.0;
      fuelSum += fuel;

      if (lm.hasCalRelatedProcess()) {
        JSONObject na = new JSONObject();
        na.put("name", name);
        na.put("veg", lm.getHarVegName());
        vegkeyName = lm.getHarVegKey();
        if (vegkeyName == null) {
          na.put("vegkey", "");
          na.put("yldUnits", "");
          na.put("targetyld", 0.0);
          na.put("targetyldraw", 0.0);
          na.put("yld", 0.0);
          na.put("yldConv", 0.0);
          na.put("moisture", 0.0);
          na.put("calibrated", "No");
          na.put( "date", lm.getDate());
          na.put( "altdate", lm.getAltDate());
        } else {
          na.put("vegkey", lm.getHarVegKey());
          na.put("yldUnits", lm.getYldUnits());
          na.put("targetyld", lm.getTargetYieldConv());
          na.put("targetyldraw", lm.getTargetYield());
          na.put("yld", lm.getYield());
          na.put("yldConv", lm.getYieldConvFactor());
          na.put("moisture", lm.getMoisture() + 0.00001);
          na.put("calibrated", dbs.getVegCalibStatus(vegkeyName));
          na.put( "date", lm.getDate());
          na.put( "altdate", lm.getAltDate());
        }
        yields.put(na);
      }
      if ((hasIntervalsDefined == false) || (modelversion < 4.0)) {
        hasintv = lm.hasIntervalProcess();
      } else {
        hasintv = lm.interval;
      }
      if( hasintv ) {
        JSONObject na = new JSONObject();
        if ((hasIntervalsDefined == false) || (modelversion < 4.0)) {
            startDate = findPreviousHarvest( i );
        } else {
            startDate = findPreviousInterval( i );
        }
 
        na.put("start", WEPPUtils.swapDate(startDate));

        endDate = lm.getDate();
        na.put("end", WEPPUtils.swapDate(endDate));
        na.put("altstart", WEPPUtils.getAltDate(startDate));
        na.put("altend", WEPPUtils.getAltDate(endDate));
        //if (lm.tranVegName != null) {
        //  na.put("veg", lm.tranVegName);
        //} else {
          na.put("veg", lm.getHarVegName());
        //}
        na.put("loss", 0.0001);
        idays = WEPPUtils.daysDiff(endDate, startDate, rotYears, getYears());
        na.put("days", idays);

        // code for STIRSum
        if (!firstIntervalFlag) { // which means the current JSONObject na is the first benchmark interval
          firstInterval = na;
          firstSTIRSum = STIRSum;
          firstIntervalFlag = true;
          firstFuelSum = fuelSum;
        } else {
          na.put("STIRSum", STIRSum);
          na.put("fuelSum", fuelSum);

        }
        // reset STIRSum value for next interval
        STIRSum = 0;
        fuelSum = 0;

        intervals.put(na);
        hasInterval = true;
      }
      // combine two STIRSums for the first interval
      if (i == lmodOperations.size() - 1 && (firstInterval != null)) {
        firstInterval.put("STIRSum", firstSTIRSum + STIRSum);
        firstInterval.put("fuelSum", firstFuelSum + fuelSum);
      }
    }
    if (hasInterval == false) {
      // no crop intervals in this management, make a placeholder
      JSONObject na = new JSONObject();
      String lastCrop = "Unknown";
      for (int i = 0; i < lmodOperations.size(); i++) {
        lm = lmodOperations.get(i);
        if (lm.hasPlant()) {
          if (lm.tranVegName != null) {
            lastCrop = lm.tranVegName;
          } else {
            lastCrop = lm.getVegName();
          }
        }
      }
      if (lastCrop == null) {
        lastCrop = "Unknown";
      }
      lm = lmodOperations.get(0);
      startDate = lm.getDate();
      na.put("start", WEPPUtils.swapDate(startDate));

      int last = lmodOperations.size() - 1;
      lm = lmodOperations.get(last);
      endDate = lm.getDate();
      na.put("end", WEPPUtils.swapDate(endDate));

      na.put("altstart", WEPPUtils.getAltDate(startDate));
      na.put("altend", WEPPUtils.getAltDate(endDate));
      na.put("veg", lastCrop);   // search for a begin-growth/plant name? what if no crop?
      na.put("loss", 0.0);
      idays = WEPPUtils.daysDiff(endDate, startDate, rotYears, getYears());
      na.put("days", idays);
      na.put("STIRSum", STIRSum);
      na.put("fuelSum", fuelSum);
      intervals.put(na);
    }
    int lastyr = 1;
    for (int i = 0; i < lmodOperations.size(); i++) {
      lm = lmodOperations.get(i);
      yr = lm.getYear();
      mon = lm.getMonth();
      day = lm.getDay();
      sdate = String.format("%02d-%02d-%04d", mon, day, yr);
      adate = String.format("%s %02d, %02d", monstrs[mon - 1], day, yr);
      JSONObject oneOp = new JSONObject();
      oneOp.put("date", sdate);
      oneOp.put("altdate", adate);
      oneOp.put("opName", lm.opName);
      if (lm.tranVegName != null) {
        oneOp.put("vegName", lm.tranVegName);
      } else {
        oneOp.put("vegName", lm.vegName);
      }
      oneOp.put("resname", lm.resName);
      stirr = lm.getSTIR();
      stirr = Math.round(stirr * 100.0) / 100.0;
      oneOp.put("STIR", stirr);
      fuelr = (lm.getFuel() * LITER_TO_GAL) / (HECTA_TO_ACRE);
      fuelr = Math.round(fuelr * 100.0) / 100.0;
      oneOp.put("Fuel", fuelr);
      oneOp.put("cover", 0);
      oneOp.put("resamt", 0);

      manSum.put(oneOp);
      lastyr = yr;
    }
    manAll.put("name", name);
    manAll.put("years", lastyr);
    manAll.put("yields", yields);
    manAll.put("intervals", intervals);
    manAll.put("operations", manSum);
    //
    //LOG.info("MANAGEMENT SUMMARY: " + manAll.toString());
    return manAll;
  }


  /**
   * Starting from a specific operation find the previous harvest.
   *
   * @param st starting index of operation
   * @return date of harvest.
   */
  String findPreviousHarvest(int st) {
    LMODOperation lm;
    for (int i = st - 1; i >= 0; i--) {
      lm = lmodOperations.get(i);
      if (lm.hasIntervalProcess()) {
        return lm.incDayString();
      }
    }

    // if we get continue from last operation
    for (int i = lmodOperations.size() - 1; i > st; i--) {
      lm = lmodOperations.get(i);
      if (lm.hasIntervalProcess()) {
        // found it
        return lm.incDayString();
      }
    }
    // if we get here it means there was only one harvest
    // use date of this operation
    lm = lmodOperations.get(st);
    return lm.getDate();
  }

/**
     * Starting from a specific operation find the previous interval.
     *
     * @param st starting index of operation
     * @return date of interval.
     */
    String findPreviousInterval( int st )
    {
        LMODOperation lm;

        for ( int i = st - 1; i >= 0; i-- )
        {
            lm = lmodOperations.get( i );
            if( lm.interval )
            {
                return lm.incDayString();
            }
        }

        // if we get continue from last operation
        for ( int i = lmodOperations.size() - 1; i > st; i-- )
        {
            lm = lmodOperations.get( i );
            if( lm.interval )
            {
                // found it
                return lm.incDayString();
            }
        }

        // if we get here it means there was only one interval
        // use date of this operation
        lm = lmodOperations.get( st );
        return lm.getDate();
    }
    
  /**
   * Get number of years in management
   *
   * @return number of years in rotation
   */
  private int getYears() {
    int idx = lmodOperations.size() - 1;
    LMODOperation lm = lmodOperations.get(idx);
    lm.getYear();
    return lm.getYear();
  }


  /**
   * Use the debug JSON to modify any woody initial condition records.
   *
   */
  public String applyTempWoodyDatabase(String iniCond) {
    double fval;
    int ival;
    String updatedIniCond = iniCond;
    LOG.info("Applying debug setting to woody initial conditions.");
    try {
      JSONObject rec = debugData.getJSONObject("woodyinicond");
      for (int j = 0; j < rec.names().length(); j++) {
        String ckey = rec.names().getString(j);
        LOG.info("Woody parm:" + ckey);
        switch (ckey) {
          case "bdtill":
            fval = rec.getDouble("bdtill");
            updatedIniCond = changeIniVal(updatedIniCond, 7, 0, String.valueOf(fval));
            break;
          case "cancov":
            fval = rec.getDouble("cancov");
            updatedIniCond = changeIniVal(updatedIniCond, 7, 1, String.valueOf(fval));
            break;
          case "daydis":
            ival = rec.getInt("daydis");
            updatedIniCond = changeIniVal(updatedIniCond, 7, 2, String.valueOf(ival));
            break;
          case "dsharv":
            ival = rec.getInt("daydis");
            updatedIniCond = changeIniVal(updatedIniCond, 7, 3, String.valueOf(ival));
            break;
          case "frdp":
            fval = rec.getDouble("frdp");
            updatedIniCond = changeIniVal(updatedIniCond, 7, 4, String.valueOf(fval));
            break;
          case "inrcov":
            fval = rec.getDouble("inrcov");
            updatedIniCond = changeIniVal(updatedIniCond, 7, 5, String.valueOf(fval));
            break;
          case "imngmt":
            fval = rec.getDouble("imngmt");
            updatedIniCond = changeIniVal(updatedIniCond, 8, 0, String.valueOf(fval));
            break;
          case "rfcum":
            fval = rec.getDouble("rfcum");
            updatedIniCond = changeIniVal(updatedIniCond, 9, 0, String.valueOf(fval));
            break;
          case "rhinit":
            fval = rec.getDouble("rhinit");
            updatedIniCond = changeIniVal(updatedIniCond, 9, 1, String.valueOf(fval));
            break;
          case "rilcov":
            fval = rec.getDouble("rilcov");
            updatedIniCond = changeIniVal(updatedIniCond, 9, 2, String.valueOf(fval));
            break;
          case "rrinit":
            fval = rec.getDouble("rrinit");
            updatedIniCond = changeIniVal(updatedIniCond, 9, 3, String.valueOf(fval));
            break;
          case "rspace":
            fval = rec.getDouble("rspace");
            updatedIniCond = changeIniVal(updatedIniCond, 9, 4, String.valueOf(fval));
            break;
          case "snodpy":
            fval = rec.getDouble("snodpy");
            updatedIniCond = changeIniVal(updatedIniCond, 11, 0, String.valueOf(fval));
            break;
          case "thdp":
            fval = rec.getDouble("thdp");
            updatedIniCond = changeIniVal(updatedIniCond, 11, 1, String.valueOf(fval));
            break;
          case "tillay1":
            fval = rec.getDouble("tillay1");
            updatedIniCond = changeIniVal(updatedIniCond, 11, 2, String.valueOf(fval));
            break;
          case "tillay2":
            fval = rec.getDouble("tillay2");
            updatedIniCond = changeIniVal(updatedIniCond, 11, 3, String.valueOf(fval));
            break;
          case "width":
            fval = rec.getDouble("width");
            updatedIniCond = changeIniVal(updatedIniCond, 11, 4, String.valueOf(fval));
            break;
          case "sumrtm":
            fval = rec.getDouble("sumrtm");
            updatedIniCond = changeIniVal(updatedIniCond, 12, 0, String.valueOf(fval));
            break;
          case "sumsrm":
            fval = rec.getDouble("sumsrm");
            updatedIniCond = changeIniVal(updatedIniCond, 12, 1, String.valueOf(fval));
            break;
          case "usrintr":
            fval = rec.getDouble("usrintr");
            updatedIniCond = changeIniVal(updatedIniCond, 12, 2, String.valueOf(fval));
            break;
          case "usrril":
            fval = rec.getDouble("usrril");
            updatedIniCond = changeIniVal(updatedIniCond, 12, 3, String.valueOf(fval));
            break;
        }
      }
    } catch (Exception e) {
      e.printStackTrace(System.err);
      // problem with debug syntax or keyword not found
    }
    return updatedIniCond;
  }


  public String changeIniVal(String src, int row, int col, String val) {
    String newstr = src;
    String[] lines = src.split("\n");

    if (row < lines.length) {
      String oneline = lines[row];
      String[] vals = oneline.split(" ");
      if (col < vals.length) {
        vals[col] = val;
        lines[row] = String.join(" ", vals);
        newstr = String.join("\n", lines);
      }
    }
    return newstr;
  }
}