V2_0.java [src/java/m/wepp] Revision:   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 m.wepp;

import csip.Config;
import csip.api.server.Executable;
import csip.ModelDataService;
import csip.api.client.ModelDataServiceCall;
import csip.api.server.ServiceException;
import csip.annotations.*;
import static csip.annotations.ResourceType.EXECUTABLE;
import static csip.annotations.ResourceType.OUTPUT;
import static csip.annotations.State.RELEASED;
import csip.utils.Numeric;
import csip.utils.TextParser;
import java.io.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.ws.rs.Path;
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;
import static util.WeppConstants.CONFIG_CLIGEN_PRISM_URL;
import static util.WeppConstants.CONFIG_CONTOURS_URL;
import static util.WeppConstants.CONFIG_CROPS_URL;
import static util.WeppConstants.CONFIG_MANAGEMENTS_URL;
import static util.WeppConstants.CONFIG_OPERATIONS_URL;
import static util.WeppConstants.CONFIG_RESIDUES_URL;
import static util.WeppConstants.CONFIG_SOIL_URL;
import static util.WeppConstants.CONFIG_STRIPS_URL;
import static util.WeppConstants.buildConfigKey;

/**
 *
 * <p>
 * WEPP This implements the CSIP WEPP service. To build the input files for WEPP
 * several external c++ and FORTRAN programs are used:</p>
 * <ul>
 * <li>wepphillslopeserv.exe - a c++ program that takes a high-level description
 * of the WEPP run and creates WEPP input files. It handles getting the input
 * files into multiple OFE format if required.
 * <li>wepp.exe - The FORTRAN WEPP model setup and run by wepphillslopeserv.exe
 * </ul>
 * <p>
 * The data resources referenced include:</p>
 * <ul>
 * <li>defaultInitCond.txt - WEPP managements require a set of initial
 * conditions parameters. This is one supplied for all runs.
 * <li>defaultInitCondCrop.txt - This is the set of crop parameters that go with
 * the initial condition.
 * <li>defaultPereInitCond.txt - This initial condition is used for perennial
 * crops.
 * <li>defaultPereInitCondCrop.txt - This is the initial perennial crop that is
 * referenced by the initial condition.
 * </ul>
 *
 * @author jf, od
 */
@Name("wepp2018")
@Description("WEPP model CSIP REST service CRLMOD version - 8/1/2018")
@State(RELEASED)
@Path("m/wepp/2.0")
@Polling(first = 2000, next = 2000)

// Executables & File parameter
@Resource(file = "/bin/${arch}/wepphillslopeserv.exe", id = "weppserv", type = EXECUTABLE)
@Resource(file = "/bin/${arch}/wepp_2018.exe", id = "wepp", type = EXECUTABLE)

//  TODO:  Pull this data out and put it into classes...stop reading small data from the filesystem, this is a waste of resources and time.
// Files.  All of this data should be a static define in WEPPManagement.java...
//@Resource(file = "/data/defaultInitCond.txt", id = "defInitCond")
@Resource(file = "/data/defaultInitCondCrop.txt", id = "defInitCondCrop")
//@Resource(file = "/data/defaultPereInitCond.txt", id = "defPereInitCond")
@Resource(file = "/data/defaultPereInitCondCrop.txt", id = "defPereInitCondCrop")
//@Resource(file = "/data/defaultFallowInitCond.txt", id = "defFallowInitCond")
@Resource(file = "/data/woodyini.json", id = "woodyInitCondSettings")

// Output to capture
@Resource(file = "*stdout.txt *stderr.txt", type = OUTPUT)
public class V2_0 extends ModelDataService {

  //////////////////////////////////////////////////////
  //  EVERY subclass should redifine these values
  //
  //  This value "modelVersion" determines a string value to be inserted into
  // the key value to search for in the configuration file for a specific setting
  // that is unique to this model version of csip-wepp/m/wepp
  protected String modelVersion = "2";

  //  Removed setting these to defaults so as to avoid confusion if data returned causes a subsequent model run to return bad data, if the wrong
  //  version of another service was called to get data needed by the model.
  // service URL defaults, these can be overriden by passing differnt URL's as part
  // of the request
  protected String soilServiceURL = null;
  protected String climateServiceURL;
  protected String contourServiceURL = null;
  protected String manServiceURL = null;
  protected String stripsServiceURL = null;
  protected String cropsURL = null;
  protected String operationsURL = null;
  protected String residueURL = null;
  /**
   *
   */
  static final int MAX_SEGMENTS = 50;
  static final String WEPP_KEY_MGMTS = "managementPtr";
  static final String WEPP_KEY_MGMTS_DATA = "managements";
  static final String WEPP_KEY_SOILS = "soilPtr";
  static final String WEPP_KEY_CONTOURS = "contour";
  static final String WEPP_KEY_STRIPS = "strip_barrier";
  static final String WEPP_KEY_TOPOSTEEPNESS = "topo_steepness";
  static final String WEPP_KEY_TOPOLENGTHS = "topo_length";
  static final String WEPP_KEY_SOIL_LENGTHS = "soil_length";
  static final String WEPP_KEY_MGMT_LENGTHS = "mgmt_length";
  static final String WEPP_KEY_RUN_YEARS = "run_years";
  static final String WEPP_KEY_WIDTH = "width";
  static final String WEPP_KEY_ASPECT = "aspect";
  static final String WEPP_KEY_SLOPE_LEN = "length";
  static final String WEPP_KEY_SLOPE_TYPE = "slope_type";
  static final String WEPP_KEY_STEEPNESS = "slope_steepness";
  static final String WEPP_KEY_GRAPH_OUTPUT = "graphics_output";
  static final String WEPP_KEY_PLOT_OUTPUT = "plot_output";
  static final String WEPP_KEY_YIELD_OUTPUT = "yield_output";
  static final String WEPP_KEY_WATER_OUTPUT = "waterBalance_output";
  static final String WEPP_KEY_DAILY_SOIL_WATER = "dailySoilWater";
  static final String WEPP_KEY_EVENT_OUTPUT = "event_output";
  static final String WEPP_KEY_WINTER_OUTPUT = "winter_output";
  static final String WEPP_KEY_PLANT_OUTPUT = "plant_output";
  static final String WEPP_KEY_SOIL_OUTPUT = "soil_output";
  static final String WEPP_KEY_OFE_OUTPUT = "ofeSummary_output";
  static final String WEPP_KEY_BRIEF_OUTPUT = "briefSummary_output";
  static final String WEPP_KEY_RETURN_OUTPUT = "returnPeriod_output";
  static final String WEPP_KEY_CLIMATE_DATA_VERSION = "climateDataVersion";
  static final String WEPP_KEY_CLIMATE_USE_COUNTIES = "climateCounties";
  static final String WEPP_TRANSLATE = "useR2Names";
  static final String WEPP_PRISM = "usePRISM";
  static final String SOIL_URL = "soilService";
  static final String CLIMATE_URL = "climateService";
  static final String CONTOUR_URL = "contourService";
  static final String MAN_URL = "managementService";
  static final String STRIPS_URL = "stripsService";
  static final String PUBLIC_SSURGO = "public_ssurgo";
  static final String WEPP_REPORT_OUTPUT = "report_output";
  static final String WIND_EROSION = "wind_erosion";
  static final String DEBUGGING = "debugging";
  static final String WEPP_KEY_MGMT_OFFSETS = "mgmt_offsets";
  static final String SOIL_NAME = "soilName";
  static final String STATION_NAME = "stationName";
  static final String STATE_ABBR = "stateAbbr";
  static final String CRLMOD31_FORMAT = "crlmod";
  static final String USE_IETFORMAT = "ietcrlmod";
  static final String IET_ROTATIONS = "rotations";
  static final String IET_ROTATIONS3 = "rotationFiles";
  static final String EXPANDED_REPORT = "expandedReport";
  static final String KEY_PARAM_FILES = "param_files";
  static final String WEPP_KEY_VERSION = "dataversion";
  static final String WEPP_KEY_CAL_YEARS = "calYears";
  static final String WEPP_KEY_CAL_TOL = "calTol";
  static final String ADJUSTPWW_PWD = "adjustPWW_PWD";
  static final String WEPP_RAND_SEED = "randSeed";
  static final String WEPP_ONLY_MANAGMENTS = "onlyConvertManagements";


  protected void setURLs() throws ServiceException {
    soilServiceURL = getConfigString(buildConfigKey(modelVersion, CONFIG_SOIL_URL));
    climateServiceURL = getConfigString(buildConfigKey(modelVersion, CONFIG_CLIGEN_PRISM_URL));
    //contourServiceURL = getConfigString(buildConfigKey(modelVersion, CONFIG_CONTOURS_URL));
    manServiceURL = getConfigString(buildConfigKey(modelVersion, CONFIG_MANAGEMENTS_URL));
    //stripsServiceURL = getConfigString(buildConfigKey(modelVersion, CONFIG_STRIPS_URL));
    cropsURL = getConfigString(buildConfigKey(modelVersion, CONFIG_CROPS_URL));
    operationsURL = getConfigString(buildConfigKey(modelVersion, CONFIG_OPERATIONS_URL));
    residueURL = getConfigString(buildConfigKey(modelVersion, CONFIG_RESIDUES_URL));
  }


  protected static String getConfigString(String key) throws ServiceException {
    String ret_val = Config.getString(key);
    if (null == ret_val) {
      throw new ServiceException("Configuration parameter, '" + key + "', is missing.  Cannot continue.");
    }
    return ret_val;
  }

  String sessionWorkDir;

  protected final WEPPModel weppmodel = new WEPPModel(LOG);

  /**
   * latitude location, used by CLIGEN
   */
  double latitude;
  /**
   * longitude location, used by CLIGEN
   */
  double longitude;

  /**
   * slope profile length (meters)
   */
  double slopeLength;
  /**
   * slope facing direction (degrees)
   */
  double slopeAspect;
  /**
   * slope width (meters)
   */
  double slopeWidth;

  /**
   * when slope is described as segments, steepness for each segment
   */
  double[] topo_steepness;
  /**
   * when slope is described as segments, length for each segment in feet
   */
  double[] topo_length;
  /**
   * when slope is described as segments, length for each segment (meters)
   */
  double[] topo_length_meters;
  /**
   * slope steepness for single type slope (uniform, convex, concave, s-shape
   */
  double slope_steepness;
  /**
   * slope type convex, concave, uniform, s-shape for single type slopes.
   */
  String slope_type;

  // soils related data
  String[] soils;
  double[] soils_length;
  double[] soils_length_meters;
  String shortSoilName;

  // management related data
  String[] managements;
  double[] managements_length;
  double[] managements_length_meters;
  int[] management_offsets;
  String[] managementFileNames;
  String[] managementNames;
  JSONArray managementsWithData;
  JSONObject contourData;
  JSONObject stripData;
  JSONArray managementReports;
  String contourName;
  String stripName;
  int finalManagementCount;

  // run options
  int run_years;
  boolean graphicsOutput;
  boolean plotOutput;
  boolean yieldOutput;
  boolean waterBalOutput;
  boolean eventOutput;
  boolean winterOutput;
  boolean plantOutput;
  boolean soilOutput;
  boolean ofeSummaryOutput;
  boolean briefSummaryOutput;
  boolean returnPeriodOutput;
  boolean reportOutput;
  boolean dailySoilWaterOutput;
  int climateDataVersion;
  boolean climateCounties;
  boolean useR2Names;
  boolean usePRISM;
  boolean publicSSURGO;
  boolean use_CRLMOD;
  boolean use_IETFormat;
  boolean paramFiles;
  boolean onlyManagements;

  String soilName;
  String state;
  String county;
  boolean expandedReport;

  double windErosion;

  String debugData;
  JSONObject debugDataJSON;

  String soilFileName;

  String allVegsCals = ""; // updated version

  String runErrorsToUser = "";

  String dataversion = "";
  int calYears;
  int calTol;
  boolean adjustPWW_PWD;
  int randomSeedVal;


  /**
   * Initialize WEPP workspace. This requires creating a couple subdirectories.
   * In addition the connections to the sqlite databases are verified.
   *
   * @throws ServiceException message to return in failed CSIP response.
   * @return true if initialization was ok
   */
  protected boolean init() throws ServiceException, Exception {
    setURLs();

    weppmodel.setServiceURLS(contourServiceURL, manServiceURL, stripsServiceURL);

    LOG.info("Initializing WEPP Service...\n");
    sessionWorkDir = workspace().getDir().toString();

    File file = workspace().getFile("runs");
    if (!file.exists()) {
      file.mkdir();
    }
    if (!file.exists()) {
      LOG.severe("Could not create runs directory");
    }
    file = workspace().getFile("output");
    if (!file.exists()) {
      file.mkdir();
    }
    if (!file.exists()) {
      LOG.severe("Could not create output directory");
    }

    // set all optional request parameters
    climateCounties = false; // Is this ever used?
    managementReports = new JSONArray();

    return true;
  }


  /**
   * Calls the CSIP service to run CLIGEN and return a climate file with
   * optional PRISM adjustments.
   *
   * @throws JSONException parsing JSON errors
   * @throws ServiceException CSIP message to return if failed.
   * @throws Exception file writing errors
   */
  void getClimateFile() throws Exception {
    boolean isCligenPrism = climateServiceURL.endsWith("cligen_prism/wepp/1.0");

    ModelDataServiceCall req = new ModelDataServiceCall()
        .put("outputFile", "wepp.cli")
        .put("returnParFiles", "true")
        .put("adjustPWW_PWD", adjustPWW_PWD)
        .put("usePRISM", usePRISM)
        .put(isCligenPrism ? "climateDataVersion" : "dataVersion", climateDataVersion)
        .put(isCligenPrism ? WEPP_KEY_RUN_YEARS : "duration", run_years)
        .put("input_zone_features", new JSONObject(
            "{  'type': 'FeatureCollection',"
            + "   'features': [{"
            + "      'type': 'Feature',"
            + "      'properties': {"
            + "        'name': 'pt one',"
            + "        'gid': 1"
            + "       },"
            + "      'geometry': {"
            + "        'type': 'Point',"
            + "        'coordinates': ["
            + "          " + longitude + ","
            + "          " + latitude
            + "         ]"
            + "       }"
            + "   }]"
            + "}"))
        .withDefaultLogger()
        .url(climateServiceURL);
    if (randomSeedVal > -1) {
      req.put("randSeed", randomSeedVal);
    }

    ModelDataServiceCall r = req.call();

    if (r.serviceFinished()) {
      r.download("wepp.cli", workspace().getFile("wepp.cli"));
      if (usePRISM) {
        URI parUri = new URI(r.getString("cligenRecordPrism.par", ""));
        if (parUri.getHost() != null) {
          r.download("cligenRecordPrism.par", workspace().getFile("wepp.par"));
        } else {
          parUri = new URI(r.getString("cligenRecord.par", ""));
          if (parUri.getHost() != null) {
            r.download("cligenRecord.par", workspace().getFile("wepp.par"));
          } else {
            throw new ServiceException("CligenRecord.par missing from cligen response.");
          }
        }
      } else {
        r.download(isCligenPrism ? "base.par" : "cligenRecord.par", workspace().getFile("wepp.par"));
      }
    } else {
      throw new ServiceException("Climate service error: " + r.getError());
    }
  }


  /**
   * Get the topo segments from the request and build a WEPP format single
   * OFE slope file. For multiple OFE simulations the c++ code will take of
   * splitting the slope file.
   *
   * @param slopeFile Name of the file where slope data is written.
   * @throws Exception any problems writing slope file
   */
  protected void buildSlopeInput(File slopeFile) throws Exception {

    if (slopeLength <= 0) {
      throw new ServiceException("Slope length not set in request.");
    }

    double defaultTransition = 20.0;
    int defaultTopCondition = 0;
    int defaultBotCondition = 0;

    StringBuilder output = new StringBuilder();
    output.append("97.5\n#\n");
    output.append("#\n1\n");
    output.append(slopeAspect);
    output.append(' ');
    output.append(slopeWidth);
    output.append('\n');
    output.append('0');
    output.append(' ');
    output.append(slopeLength);
    output.append('\n');
    output.append('\n'); // line for list of points is empty
    output.append("Segments = ");
    output.append(topo_length.length);
    output.append(' ');
    output.append(defaultTransition);
    output.append(' ');
    output.append(defaultTopCondition);
    output.append(' ');
    output.append(0.0f);
    output.append(' ');
    output.append(defaultBotCondition);
    output.append(' ');
    output.append(0.0f);
    output.append('\n');

    int j = 0;
    for (int i = 0; i < topo_length.length; i++) {
      output.append(++j);
      output.append(' ');
      output.append(topo_length_meters[i]);
      output.append(' ');
      output.append(topo_steepness[i]);
      output.append('\n');
    }
    FileUtils.writeStringToFile(slopeFile, output.toString(), "UTF-8");
  }


  /**
   * Get the curve characteristics from the request and build a WEPP format
   * single OFE slope file. For multiple OFE simulations the c++ code will take
   * of splitting the slope file.
   *
   * @param slopeFile Name of the file where slope data is written.
   * @throws Exception any problems writing slope file.
   */
  protected void buildSlopeInputPoints(File slopeFile) throws Exception {

    StringBuilder output = new StringBuilder();
    output.append("97.5\n#\n");
    output.append("#\n1\n");
    output.append(slopeAspect);
    output.append(' ');
    output.append(slopeWidth);
    output.append('\n');
    switch (slope_type.toLowerCase()) {
      case "uniform":
      case "convex":
      case "concave":
        output.append('2');
        break;
      default:
        output.append('3');
        break;
    }
    output.append(' ');
    output.append(slopeLength);
    output.append('\n');
    switch (slope_type.toLowerCase()) {
      case "uniform":
        output.append(0.0);
        output.append(' ');
        output.append(slope_steepness / 100.0f);
        output.append(", ");
        output.append(1.0);
        output.append(' ');
        output.append(slope_steepness / 100.0f);
        break;
      case "convex":
        output.append(0.0);
        output.append(' ');
        //output.append(0.0);
        output.append((slope_steepness / 100.0f) * 0.5f);
        output.append(", ");
        output.append(1.0);
        output.append(' ');
        output.append((slope_steepness / 100.0f) * 1.5f);
        break;
      case "concave":
        output.append(0.0);
        output.append(' ');
        output.append((slope_steepness / 100.0f) * 1.5f);
        output.append(", ");
        output.append(1.0);
        output.append(' ');
        //output.append(0.0);
        output.append((slope_steepness / 100.0f) * 0.5f);
        break;
      case "s-shape":
      case "sshape":
      case "s-shaped":
        output.append(0.0);
        output.append(' ');
        //output.append(0.0);
        output.append((slope_steepness / 100.0f) * 0.5f);
        output.append(", ");
        output.append(0.5);
        output.append(' ');
        output.append((slope_steepness / 100.0f) * 1.5f);
        output.append(", ");
        output.append(1.0);
        output.append(' ');
        //output.append(0.0);
        output.append((slope_steepness / 100.0f) * 0.5f);
        break;
    }
    FileUtils.writeStringToFile(slopeFile, output.toString(), "UTF-8");
  }


  /**
   * This calls functions to build the WEPP soil files. This uses the NRCS
   * SSURGO web services so it needs to be updated to use the local CSIP
   * database, or the soil files prebuilt. The CSIP cokey field may not match
   * the NRCS SSURGO database cokey field.
   *
   * @param file Name of soil file where output is written
   * @param soilid SSURGO soil ID in the format mukey:cokey or cokey
   * @throws JSONException JSON
   * @throws Exception any errors parsing numbers.
   *
   */
  protected void buildSoilInput(String file, String soilid) throws JSONException, Exception {
    // Need to use the soil id from the request to create a soil file with
    // WeppSoil
    int mukey, cokey;

    String[] parts = soilid.split(":");
    if (parts.length == 2) {
      cokey = Integer.parseInt(parts[1]);
      mukey = mukey = Integer.parseInt(parts[0]);
    } else {
      mukey = -1;
      cokey = Integer.parseInt(parts[0]);
    }
    mukey = Integer.parseInt(parts[0]);

    WeppSoil theSoil = new WeppSoil(LOG, soilServiceURL);
    if (theSoil.getSoilCSIP( cokey, sessionWorkDir, file) == false) {
      // throw new ServiceException( "The cokey provided does not exist in the Soils Data Mart." );

      // This means there was some problem getting the WEPP soil for this SSURGO cokey
      // one option is to check if the soil name is found under a different cokey this can
      // happen if the project was saved and later loaded while during that time the SSURGO database
      // has been updated with new cokeys.
      /*
****************************
  Uncomment line below this if we really, really want to try to lookup the soil based on its name and county...
  not sure that is advisable since there may be many with similar names in the same county and we wouldn't
  know which one is actually meant, and it should be selected by which mapunit and soil survey area it is located in, not county.
****************************
       */
      // Yes - we want to do this otherwise any project saved will probably not run after NRCS updates
      // COKEYS which occurs each year. However... this logic (or some other intelligent verifying) should really reside in whatever code
      // is calling the service to be sure stale COKEYS are not being passed.
      // Until that happens leave in the service.  
      //String url = UriBuilder.fromUri(request().getURL()).replacePath(request().getContext()).toString();
      //theSoil.retryGetSoilCSIP(cokey, sessionWorkDir, file, soilName, county, state, url);
      // Updated - push the error up by throwing an exception, let the calling code
      // or user interface decide how to handle this condition.
      String msg = "The cokey provided does not exist in the Soils Data Mart.<br>";
      msg = msg + "Soil identifier information in project may not be up to date.<br><br>";
      msg = msg + "Data returned from the WEPPSoilInput service did not contain the expected results. ";
      throw new ServiceException(msg);
    }

    //  Validate this assumption with NRCS before deploying this service:
    //   Not ALL horizons need to have a CEC or ECEC value....
    // This is no longer needed, the soil service should be doing this check. 
    //if ( false )//TODO: HACK: make sure this is valid.  theSoil.checkForValidCEC( file ) == false )
    //{
    //    LOG.severe( "One or more soil layers have no CEC value." );
    //    throw new ServiceException( "Error: One or more soil layers are missing CEC data, can not be used as a WEPP soil input." );
    //}
    shortSoilName = theSoil.getSoilName();
  }


  /**
   * Builds the WEPP rotation files. The low level WEPP management files are
   * built by the c++ program since the format is so unreadable when multiple
   * OFE's are involved.
   *
   * @param file Where the management data is to be written in WEPP ROT format
   * @param contents The JSON data from and LMOD request or an editor
   * @param index What section this is in the profile (1..N)
   * @param locUseR2Names if any translation need to be done.
   * @param useCRLMOD if data comes from CRLMOD
   * @return management intermediate file for this OFE that gets passed to c++.
   */
  protected String buildManagementInput(File file, String contents, int index, boolean locUseR2Names, boolean useCRLMOD) {
    String calVegs = null;
    try {
      File prj = workspace().getFile("lmodman_" + String.valueOf(index) + ".txt");
      FileUtils.writeStringToFile(prj, contents, "UTF-8");
    } catch (IOException ex) {
      throw new RuntimeException(ex);
    }

    try {
      WEPPManagement man = new WEPPManagement(weppmodel, LOG, debugDataJSON, useCRLMOD, cropsURL, operationsURL, residueURL, dataversion);
      man.allInitial(resources().getFile("defInitCondCrop"), resources().getFile("defPereInitCondCrop"), resources().getFile("woodyInitCondSettings"));
      File saveFile = workspace().getFile("lmodmanconv_" + String.valueOf(index) + ".json");
      man.convertLMOD(contents, contourData, (float) slopeWidth, locUseR2Names, useCRLMOD, use_IETFormat, index, saveFile);
      if (!man.getErrors().isEmpty()) {
        runErrorsToUser = man.getErrors();
        LOG.severe(runErrorsToUser);
        throw new ServiceException("Error creating WEPP management: \n" + runErrorsToUser);
      } else {
        LOG.info(man.getErrors());
      }
      LOG.info("Did LMOD conversion.");
      man.getNativeFile(file);
      //JSONObject manSum = man.createJSONSummary("management_" + String.valueOf(index))
      JSONObject manSum = man.createJSONSummary(man.getName(), Float.parseFloat(modelVersion));
      managementReports.put(manSum);

      LOG.info("Created WEPP management file.");
      calVegs = man.getVegDBS();
    } catch (Exception e) {
      LOG.severe("Failed management conversion: " + e.getMessage());
      throw new RuntimeException(e);
    }
    return calVegs;
  }


  /**
   * From a strip/barrier name adjust the array of managements.
   *
   * @deprecated The javascript interface now handles building the strips so the
   * management sequences are already established when sent to the service.
   *
   * @return new number of OFE/sections
   */
  protected int insertStrips() {
    int sections;
    if (stripData == null) {
      // no strips, just create the file name array
      for (int i = 0; i < managements_length.length; i++) {
        String s = "lmod";
        s = s.concat("_" + String.valueOf(i + 1));
        s = s.concat(".rot");
        managementFileNames[i] = s;
      }
      sections = managements_length.length;
    } else {
      if (managements_length.length > 1) {
        LOG.severe("Only one management allowed before strips are added.");
        return 0;
      }
      sections = 1;
      String basename = "lmod_1.rot";
      String stripName = "lmod_str.rot";
      // set defaults, as defined by R2 if parameters are absent
      int atBotVal = 1;
      int wq2x = 1;
      String barwidtype = "STRIP_BARRIER_SPEC_WIDTH_ABS";
      String bartype = "STRIP_BARRIER_TYPE_FILTER";
      double widthabs = 9.144;  // 30' as meters
      double stripLen = 9.144;  // 30' as meters

      int numstrips = stripData.optInt("BarrierNumStrips");
      String wabs = stripData.optString("BarrierWidthAbs");
      String wq = stripData.optString("BarrierForWQ");
      String btype = stripData.optString("BarrierType");
      String widtype = stripData.optString("BarrierSpecWidth");
      String atBot = stripData.optString("BarrierAtBottom");

      if (wq.equals("0")) {
        wq2x = 0;
      } else if (wq.equals("1")) {
        wq2x = 1;
      } else {
        if (btype.equals("")) {
          wq2x = 0;
        }
      }

      if (atBot.equals("0")) {
        atBotVal = 0;
      }
      if (widtype.equals("") == false) {
        barwidtype = widtype;
      }
      if (btype.equals("") == false) {
        bartype = btype;
      }
      if (wabs.equals("") == false) {
        widthabs = Double.parseDouble(wabs);
        widthabs = widthabs * 0.3048;
      }

      stripLen = widthabs;

      if (barwidtype.equals("STRIP_BARRIER_SPEC_WIDTH_ABS") == false) {
        // percentage of slope length, not supported
        LOG.info("Strip/Barrier ignored, only absolute lengths for strips allowed");
        return 1;
      }
      managementFileNames[0] = basename;
      managementFileNames[1] = stripName;
      managementFileNames[2] = basename;
      managementFileNames[3] = stripName;
      managementFileNames[4] = basename;
      managementFileNames[5] = stripName;
      managementFileNames[6] = basename;
      if ((atBotVal == 1) && (numstrips == 1)) {
        // strip at bottom
        // |--|*|   (* indicates a strip, -- base management)
        if (wq2x == 1) {
          stripLen = stripLen * 2;
        }
        // check if slope is long enough
        if (stripLen < slopeLength) {
          managements_length_meters[0] = slopeLength - stripLen;
          managements_length_meters[1] = stripLen;
          sections = 2;
        } else {
          // error, need to log
          LOG.info("Strip/Barrier ignored, strip too long for slope");
        }
      } else if ((atBotVal == 0) && (numstrips == 1)) {
        // single strip in middle of slope
        // |--|*|--|
        if (stripLen < slopeLength) {
          managements_length_meters[0] = (slopeLength - stripLen) / 2;
          managements_length_meters[1] = stripLen;
          managements_length_meters[2] = (slopeLength - stripLen) / 2;
          sections = 3;
        } else {
          LOG.info("Strip/Barrier ignored, strip too long for slope");
        }
      } else if ((atBotVal == 1) && (numstrips == 2)) {
        // two strips, one at bottom
        // |--|*|--|*|  (* indicates a strip, -- base management)
        // if water quality modifier then double last strip length
        // |--|*|--|**| (* indicates a strip, -- base management)
        double extra = 0;
        double segLen = 0;
        if (wq2x == 1) {
          extra = stripLen;
          segLen = (slopeLength - ((stripLen * 2) + extra)) / 2;
        } else {
          segLen = (slopeLength - (stripLen * 2)) / 2;
        }
        if ((stripLen * 2) + extra < slopeLength) {
          managements_length_meters[0] = segLen;
          managements_length_meters[1] = stripLen;
          managements_length_meters[2] = segLen;
          managements_length_meters[3] = stripLen + extra;
          sections = 4;
        } else {
          LOG.info("Strip/Barrier ignored, strip too long for slope");
        }
      } else if ((atBotVal == 0) && (numstrips == 2)) {
        // two strips
        // |--|*|--|*|--|   (* indicates a strip, -- base management)
        double segLen = (slopeLength - (stripLen * 2)) / 3;
        if (stripLen * 2 < slopeLength) {
          managements_length_meters[0] = segLen;
          managements_length_meters[1] = stripLen;
          managements_length_meters[2] = segLen;
          managements_length_meters[3] = stripLen;
          managements_length_meters[4] = segLen;
          sections = 5;
        } else {
          LOG.info("Strip/Barrier ignored, strip too long for slope");
        }
      } else if ((atBotVal == 1) && (numstrips == 3)) {
        // three strips, one at bottom
        // |--|*|--|*|--|*|  (* indicates a strip, -- base management)
        // if water quality modifier then double last strip length
        // |--|*|--|*|--|**|  (* indicates a strip, -- base management)
        double extra = 0;
        double segLen = 0;
        if (wq2x == 1) {
          extra = stripLen;
          segLen = (slopeLength - ((stripLen * 3) + extra)) / 3;
        } else {
          segLen = (slopeLength - (stripLen * 3)) / 3;
        }
        if ((stripLen * 3) + extra < slopeLength) {
          managements_length_meters[0] = segLen;
          managements_length_meters[1] = stripLen;
          managements_length_meters[2] = segLen;
          managements_length_meters[3] = stripLen;
          managements_length_meters[4] = segLen;
          managements_length_meters[5] = stripLen + extra;
          sections = 6;

        } else {
          LOG.info("Strip/Barrier ignored, strip too long for slope");
        }
      } else if ((atBotVal == 0) && (numstrips == 3)) {
        // three strips
        // |--|*|--|*|--|*|--|  (* indicates a strip, -- base management)
        double segLen = (slopeLength - (stripLen * 3)) / 4;
        if (stripLen * 3 < slopeLength) {
          managements_length_meters[0] = segLen;
          managements_length_meters[1] = stripLen;
          managements_length_meters[2] = segLen;
          managements_length_meters[3] = stripLen;
          managements_length_meters[4] = segLen;
          managements_length_meters[5] = stripLen;
          managements_length_meters[4] = segLen;
          sections = 7;
        } else {
          LOG.info("Strip/Barrier ignored, strip too long for slope");
        }
      } else {
        // not supported
        sections = 1;
        LOG.info("Strip/Barrier ignored, unknown strip configuration");
      }
    }
    return sections;
  }


  /**
   * This creates the wepp project file that lists all the input files and
   * options for the simulation. The c++ wepphillsopeserv program uses this file
   * to build all the low level input files for WEPP and handle OFE formatting.
   *
   * @param prjFile Name of the file where WEPP project will be written
   * @param years number of years to make the WEPP run.
   * @throws Exception any problems writing files for c++ program.
   */
  protected void buildProjectInput(File prjFile, int years) throws Exception {
    StringBuilder output = new StringBuilder();
    output.append("Version = 98.6\n");
    output.append("Name = csip-wepp\n");
    output.append("Comments {\n}\n");
    output.append("Units = English\n");
    output.append("Landuse = 1\n");
    output.append("Length = ");
    output.append(slopeLength);
    output.append("\n");
    output.append("Profile {\n");
    output.append("  File = \"wepp.slp\"\n");
    output.append("}\n");
    output.append("Climate {\n");
    output.append("  File = \"wepp.cli\"\n");
    output.append("}\n");
    output.append("Soil {\n");
    int breaks = soils.length - 1;
    output.append("  Breaks = ");
    output.append(breaks);
    output.append("\n");
    for (int i = 0; i < soils.length; i++) {
      output.append("  Soil-");
      output.append(i);
      output.append(" {\n");
      output.append("      Distance = ");
      output.append(soils_length_meters[i]);
      output.append("\n");
      String s = soils[i];
      s = s.replace(":", "_");
      s = s.concat("_" + String.valueOf(i + 1));
      s = s.concat(".sol");
      output.append("      File = \"");
      output.append(s);
      output.append("\"\n");
      output.append("  }\n");
    }

    output.append("}\n");

    int numMan = insertStrips();
    finalManagementCount = numMan;

    output.append("Management {\n");
    breaks = numMan - 1;
    output.append("  Breaks = ");
    output.append(breaks);
    output.append("\n");
    for (int i = 0; i < numMan; i++) {
      output.append("  Management-");
      output.append(i);
      output.append(" {\n");
      output.append("      Distance = ");
      output.append(managements_length_meters[i]);
      output.append("\n");
      output.append("      File = \"");
      //output.append(s);
      output.append(managementFileNames[i]);
      output.append("\"\n");
      output.append("  }\n");
    }
    output.append("}\n");
    output.append("RunOptions {\n");
    output.append("WindErosion = ");
    output.append(windErosion);
    output.append("\n");
    output.append("  Version = 1\n");
    output.append("  SoilLossOutputType = 1\n");
    output.append("  SoilLossOutputFile = AutoName\n");
    if (waterBalOutput) {
      output.append("  WaterBalanceFile = AutoName\n");
    }
    if (graphicsOutput) {
      output.append("  GraphFile = AutoName\n");
    }
    if (yieldOutput) {
      output.append("  YieldFile = AutoName\n");
    }
    if (plotOutput) {
      output.append("  PlotFile = AutoName\n");
    }
    if (eventOutput) {
      output.append("  EventFile = AutoName\n");
    }
    if (winterOutput) {
      output.append("  WinterFile = AutoName\n");
    }
    if (plantOutput) {
      output.append("  CropFile = AutoName\n");
    }
    if (soilOutput) {
      output.append("  SoilFile = AutoName\n");
    }
    if (ofeSummaryOutput) {
      output.append("  ElementFile = AutoName\n");
    }
    if (briefSummaryOutput) {
      output.append("   FinalSummaryFile = AutoName\n");
    }
    if (dailySoilWaterOutput) {
      output.append("   DailySoilWaterPlot = 1\n");
    }
    if ((returnPeriodOutput) && (ofeSummaryOutput == false)) {
      output.append("  ElementFile = AutoName\n");
    }

    output.append("  SimulationYears = ");
    output.append(years);
    output.append("\n");
    output.append("  Model = weppsm_2012\n");
    output.append("  SmallEventByPass = 1\n");
    output.append("}");

    FileUtils.writeStringToFile(prjFile, output.toString(), "UTF-8");
  }


  /**
   * Get input parameters from WEPP request. This is the first call done by
   * the CSIP framework to setup any parameters from the request JSON.
   *
   * @throws Exception any problems getting JSON parameters.
   */
  @Override
  protected void preProcess() throws Exception {

    double soilsLength = 0;
    double mansLength = 0;
    String u;

    useR2Names = parameter().getBoolean(WEPP_TRANSLATE, false);
    if (useR2Names == true) {
        throw new ServiceException("The RUSLE2 translation parameter (useR2Names) is not supported. Operation and crop names should be based on the CRLMOD database..");
    }
    use_CRLMOD = true; // only format supported 
    //if (parameter().has(USE_CRLMOD)) {
    //  use_CRLMOD = true;
    //}
    
    use_IETFormat = parameter().getBoolean(USE_IETFORMAT, true);

    //The user should not be responsible for all these urls.  They can be changed with the config file
    // override if in the request
    if (parameter().has(CLIMATE_URL)) {
      climateServiceURL = parameter().getString(CLIMATE_URL);
    }
    init();

    //TODO
    //  FIX ME:  These are later used in such a way that more than MAX_SEGMENTS could be attempted...
    //           "ArrayIndexOutOfBounds" Exceptions WILL occur until this is fixed!
    managements_length_meters = new double[MAX_SEGMENTS];
    managementFileNames = new String[MAX_SEGMENTS];
    management_offsets = new int[MAX_SEGMENTS];
    managementNames = new String[MAX_SEGMENTS];

    usePRISM = parameter().getBoolean(WEPP_PRISM, false);
    adjustPWW_PWD = parameter().getBoolean(ADJUSTPWW_PWD, false);

    // get run options
    run_years = parameter().getInt(WEPP_KEY_RUN_YEARS, 100);
    graphicsOutput = parameter().getBoolean(WEPP_KEY_GRAPH_OUTPUT, true);
    plotOutput = parameter().getBoolean(WEPP_KEY_PLOT_OUTPUT, true);
    yieldOutput = parameter().getBoolean(WEPP_KEY_YIELD_OUTPUT, false);
    waterBalOutput = parameter().getBoolean(WEPP_KEY_WATER_OUTPUT, false);
    dailySoilWaterOutput = parameter().getBoolean(WEPP_KEY_DAILY_SOIL_WATER, false);
    eventOutput = parameter().getBoolean(WEPP_KEY_EVENT_OUTPUT, false);
    winterOutput = parameter().getBoolean(WEPP_KEY_WINTER_OUTPUT, false);
    plantOutput = parameter().getBoolean(WEPP_KEY_PLANT_OUTPUT, false);
    soilOutput = parameter().getBoolean(WEPP_KEY_SOIL_OUTPUT, false);
    ofeSummaryOutput = parameter().getBoolean(WEPP_KEY_OFE_OUTPUT, false);
    briefSummaryOutput = parameter().getBoolean(WEPP_KEY_BRIEF_OUTPUT, false);
    returnPeriodOutput = parameter().getBoolean(WEPP_KEY_RETURN_OUTPUT, false);
    climateDataVersion = parameter().getInt(WEPP_KEY_CLIMATE_DATA_VERSION, 1992);
    reportOutput = parameter().getBoolean(WEPP_REPORT_OUTPUT, false);
    publicSSURGO = parameter().getBoolean(PUBLIC_SSURGO, false);
    county = parameter().getString(STATION_NAME, "");
    state = parameter().getString(STATE_ABBR, "");
    soilName = parameter().getString(SOIL_NAME, "");
    expandedReport = parameter().getBoolean(EXPANDED_REPORT, false);
    dataversion = parameter().getString(WEPP_KEY_VERSION, "");
    calYears = parameter().getInt(WEPP_KEY_CAL_YEARS, 15);
    calTol = parameter().getInt(WEPP_KEY_CAL_TOL, 10);
    randomSeedVal = parameter().getInt(WEPP_RAND_SEED, -1);
    onlyManagements = parameter().getBoolean(WEPP_ONLY_MANAGMENTS, false);

    // will use the NSERL soil service that requests data using the public SSURGO
    // service - removed 11/13/2017
    //if (publicSSURGO == true) {
    //    soilServiceURL = soilServiceNSERLURL;
    //}
    // This is going to to up being 50 meters or 15.24 meters depending on if someone put the units meters or ft. I think this could be cleaned up
    slopeWidth = parameter().getDouble(WEPP_KEY_WIDTH, 50);
    try {
      u = parameter().getUnit(WEPP_KEY_WIDTH);
      if ("ft".equals(u)) {
        slopeWidth = slopeWidth * WeppConstants.CONV_FT_TO_M;
      }
    } catch (Exception e) {
      slopeWidth = slopeWidth * WeppConstants.CONV_FT_TO_M; // assume ft
    }

    slope_type = parameter().getString(WEPP_KEY_SLOPE_TYPE, "topo");
    slopeAspect = parameter().getDouble(WEPP_KEY_ASPECT, 180);
    slope_steepness = parameter().getDouble(WEPP_KEY_STEEPNESS, 5.0);
    latitude = parameter().getDouble("latitude");
    longitude = parameter().getDouble("longitude");

    contourData = null;

    soils = parameter().getStringArray(WEPP_KEY_SOILS);

    debugDataJSON = null;
    if (parameter().has(DEBUGGING)) {
      debugData = parameter().getString(DEBUGGING);
      debugDataJSON = new JSONObject(debugData);
    }

    if (parameter().has(WEPP_KEY_MGMTS)) {
      managements = parameter().getStringArray(WEPP_KEY_MGMTS);
    }

    if (use_IETFormat) {
      // Updated CRLMOD formatting, pure JSON
      JSONArray rots = null;
      boolean v31 = false;
      if (parameter().has(CRLMOD31_FORMAT)) {
        // only in 3.1, slightly different management section formating
        // format used in CRLMOD database when a maangement template is requested.
        JSONObject crlmodobj = parameter().getJSON(CRLMOD31_FORMAT);
        rots = crlmodobj.getJSONArray(IET_ROTATIONS3);
        v31 = true;
      } else {
        // v2 and v3, used by IET generated WEPP reqquests.
        if (parameter().has(IET_ROTATIONS)) {
          rots = parameter().getJSONArray(IET_ROTATIONS);
        }
      }
      if (rots != null) {
        managementsWithData = new JSONArray();
        managements_length = new double[rots.length()];
        for (int i = 0; i < rots.length(); i++) {
          JSONObject chk1;
          chk1 = rots.getJSONObject(i);
          if (v31) {
            chk1 = chk1.getJSONObject("rotation");
          }
          if (onlyManagements == false) {
            managements_length[i] = chk1.getDouble("length");
          } else {
            managements_length[i] = 0;
          }
          // assume these are in feet - but no units given
          managements_length_meters[i] = managements_length[i] * WeppConstants.CONV_FT_TO_M;
          // No offsetting option in IET
          management_offsets[i] = 0;
          managementNames[i] = chk1.getString("name");
          JSONArray parts = chk1.getJSONArray("managements");
          JSONArray newchunks = new JSONArray();
          for (int j = 0; j < parts.length(); j++) {
            JSONObject mparts = parts.getJSONObject(j);
            JSONArray evts = mparts.getJSONArray("events");
            for (int k = 0; k < evts.length(); k++) {
              newchunks.put(evts.get(k));
            }
          }
          JSONObject obn = new JSONObject();
          obn.append("events", newchunks);
          managementsWithData.put(obn);
        }
        //LOG.info("Management Chunk: " + managementsWithData.toString());
      }
    } else {
      // Formatting similar to original RUSLE2, at present used by 
      // WEPP web interface
      if (parameter().has(WEPP_KEY_MGMTS_DATA)) {
        managementsWithData = parameter().getJSONArray(WEPP_KEY_MGMTS_DATA);
      }
    }

    if (parameter().has(WEPP_KEY_CONTOURS)) {
      contourName = parameter().getString(WEPP_KEY_CONTOURS);
      if ((contourName.equals("a. rows up-and-down hill") == false)
          && (contourName.equals("(none)") == false)) {
        LOG.info("Read contour name:" + contourName);
        if (contourName != null) {
          contourData = weppmodel.getContour(contourName);
          if (contourData != null) {
            LOG.info("Contour Data:" + contourData.toString());
          } else {
            LOG.info("Error: No contour data returned from service.");
          }
        }
      }
    }

    if (parameter().has(WEPP_KEY_STRIPS)) {
      stripName = parameter().getString(WEPP_KEY_STRIPS);
      if (stripName != null) {
        if (stripName.equals("(none)")) {
          stripData = null;
        } else {
          // only apply strips/barriers to single OFE hillslope, if there are
          // already segments that means the strips/barriers were already inserted
          // in the user interface
          if (managementsWithData.length() == 1) {
            stripData = weppmodel.getStripBarrier(stripName);
          } else {
            stripName = null;
          }
        }
      }
    }
    // compute total slope length
    double slopeConvFactor = 1;
    try {
      u = parameter().getUnit(WEPP_KEY_TOPOLENGTHS);
      if ("ft".equals(u)) {
        slopeConvFactor = WeppConstants.CONV_FT_TO_M;
      }
    } catch (Exception e) {
      // no unit specified - assume feet
      slopeConvFactor = WeppConstants.CONV_FT_TO_M;
    }
    if (slope_type.equals("topo")) {
      if (onlyManagements == false) {
        topo_steepness = parameter().getDoubleArray(WEPP_KEY_TOPOSTEEPNESS);
        topo_length = parameter().getDoubleArray(WEPP_KEY_TOPOLENGTHS);

        if (topo_steepness.length != topo_length.length) {
          LOG.info("The number of topo length and steepness are not the same.");
        }

        slopeLength = 0;
        topo_length_meters = new double[topo_length.length];
        for (int i = 0; i < topo_length.length; i++) {
          double len = topo_length[i];
          len = len * slopeConvFactor;
          slopeLength = slopeLength + len;
          topo_length_meters[i] = len;
        }
      }
    } else {
      // it is possible that there are no topo segments, so get the slope length
      // from the 'length' parameter
      slopeLength = parameter().getDouble(WEPP_KEY_SLOPE_LEN);
      try {
        u = parameter().getUnit(WEPP_KEY_SLOPE_LEN);
        if ("ft".equals(u)) {
          slopeConvFactor = WeppConstants.CONV_FT_TO_M;
        }
      } catch (Exception e) {
        slopeConvFactor = WeppConstants.CONV_FT_TO_M;
      }
      slopeLength = slopeLength * slopeConvFactor;
    }

    // Check if soils and management length keys exist, if not assume entire length
    if (parameter().has(WEPP_KEY_SOIL_LENGTHS)) {
      soils_length = parameter().getDoubleArray(WEPP_KEY_SOIL_LENGTHS);
      double convFactor = 1;
      u = parameter().getUnit(WEPP_KEY_SOIL_LENGTHS);
      if ("ft".equals(u)) {
        convFactor = WeppConstants.CONV_FT_TO_M;
      }
      soils_length_meters = new double[soils_length.length];
      for (int i = 0; i < soils_length.length; i++) {
        double len = soils_length[i];
        len = len * convFactor;
        soils_length_meters[i] = len;
        soilsLength = soilsLength + len;
      }
    } else {
      soils_length = new double[]{slopeLength};
      soils_length_meters = new double[]{slopeLength};
      soilsLength = slopeLength;
    }

    if (parameter().has(WEPP_KEY_MGMT_LENGTHS)) {
      managements_length = parameter().getDoubleArray(WEPP_KEY_MGMT_LENGTHS);
      double convFactor = 1;
      try {
        u = parameter().getUnit(WEPP_KEY_MGMT_LENGTHS);
        if ("ft".equals(u)) {
          convFactor = WeppConstants.CONV_FT_TO_M;
        }
      } catch (Exception e) {
        // no unit specified - assume feet
        convFactor = WeppConstants.CONV_FT_TO_M;
      }
      for (int i = 0; i < managements_length.length; i++) {
        double len = managements_length[i];
        len = len * convFactor;
        managements_length_meters[i] = len;
        mansLength = mansLength + len;
      }
    } else {
      // IET_ROTATIONS have length included in structure, don't overwrite
      if (use_IETFormat == false) {
        if ((parameter().has(IET_ROTATIONS) == false) && (parameter().has(IET_ROTATIONS3) == false)) {
          // slopeLength is already in units of (m)
          managements_length = new double[]{
            slopeLength
          };
          managements_length_meters[0] = slopeLength;
          mansLength = slopeLength;
        }
      }
    }

    if (parameter().has(WEPP_KEY_MGMT_OFFSETS)) {
      management_offsets = parameter().getIntArray(WEPP_KEY_MGMT_OFFSETS);
    }

    // Check that all the total lengths are the same for the slope, soils
    // and managements.
    if (((slopeLength < (mansLength - 0.001)) || (slopeLength > (mansLength + 0.001)))
        || ((slopeLength < (soilsLength - 0.001)) || (slopeLength > (soilsLength + 0.001)))) {
      LOG.info("The total length of the slope profile, soil segment lengths and management segment are not equal.");
    }

    windErosion = parameter().getDouble(WIND_EROSION, 0);
    paramFiles = false;

    if (metainfo().hasName(KEY_PARAM_FILES)) {
      paramFiles = metainfo().getBoolean(KEY_PARAM_FILES);
    }
  }


  /**
   * Calls functions to build the WEPP input files and then run WEPP. This is
   * the main starting point after the call to preprocess.
   *
   * @throws Exception Any problem building WEPP input files or making the run.
   */
  @Override
  protected void doProcess() throws Exception {

    if (onlyManagements == true) {
      // for use by watershed wepp service to create manaagement files, no need to run
      for (int i = 0; i < managementsWithData.length(); i++) {
        JSONObject temp = managementsWithData.getJSONObject(i);
        String mname = (i + 1) + "_" + managementNames[i];
        String s = mname + ".rot";
        allVegsCals = allVegsCals + buildManagementInput(workspace().getFile(s),
            temp.toString(), i + 1, useR2Names, use_CRLMOD);
      }
      return;
    }

    getClimateFile();

    if (slope_type.equals("topo")) {
      buildSlopeInput(workspace().getFile("wepp.slp"));
    } else {
      buildSlopeInputPoints(workspace().getFile("wepp.slp"));
    }

    LOG.info("Build slope complete.");

    for (int i = 0; i < soils.length; i++) {
      // temporary to test with R2
      // soils[i] = "366916:12340959";
      String s = soils[i];
      String soilid = s;
      s = s.replace(":", "_");
      s = s.concat("_" + (i + 1));
      s = s.concat(".sol");
      buildSoilInput(s, soilid);
    }

    // This will build management files based on the LMOD key, this would be data
    // directly out of LMOD with no editting.
    //String allVegsCals = "";
    if (managements != null && managements.length > 0) {
      // This will use file pointers and get data from LMOD
      for (int i = 0; i < managements.length; i++) {
        String fkey = managements[i];
        String s = "lmod";
        s = s.concat("_" + (i + 1));
        s = s.concat(".rot");
        String contents = weppmodel.getManagement(fkey);
        allVegsCals = allVegsCals + buildManagementInput(workspace().getFile(s), contents, i + 1, useR2Names, use_CRLMOD);
      }
    } else {
      // This will build management files based on the LMOD data in the request, this would be data
      // that may have been editted from the original LMOD data.
      for (int i = 0; i < managementsWithData.length(); i++) {
        String s = "lmod_" + (i + 1) + ".rot";
        JSONObject temp = managementsWithData.getJSONObject(i);
        //LOG.info("MAN To Build: " + temp.toString());
        allVegsCals = allVegsCals + buildManagementInput(workspace().getFile(s), temp.toString(), i + 1, useR2Names, use_CRLMOD);
      }
    }

    if (stripData != null) {
      throw new ServiceException("This should not be called.");
//      buildStripInput(sessionWorkDir + "/lmod_str.rot");
    }

    int calibrationYears = calYears;

    try {
      buildProjectInput(workspace().getFile("wepp.prj"), calibrationYears);
    } catch (Exception e) {
      throw new ServiceException("Crashed in project run file builder (calibration).");
    }

    File weppmodelexe = resources().getFile("wepp");
    Executable weppserv = resources().getExe("weppserv");

    // Run an external program to create all the model inputs and run wepp
    // turn on calibration flag
    weppserv.setArguments(workspace().getDir(),
        workspace().getFile("wepp.prj"),
        workspace().getFile("output/weppout.txt"),
        weppmodelexe.toString(), "1", calTol);

    int ret = weppserv.exec();
    if (ret != 0) {
      throw new ServiceException("WEPP Calibration run error: " + ret);
    }

    // if you reuse the weppservobject, you need to reinit the
    // output streams, or call getRessourceExe again.
    weppserv.redirectDefaults();

    // Set project for correct number of years
    try {
      buildProjectInput(workspace().getFile("wepp.prj"), run_years);
    } catch (Exception E) {
      throw new ServiceException("Crashed in project run file builder.", E);
    }

    // make final run - no calibration flag, this should use final
    // beinpadj factors from calibration (file is still there).
    // don't modify the beinps in the input ROT files.
    //
    weppserv.setArguments(workspace().getDir(),
        workspace().getFile("wepp.prj"),
        workspace().getFile("output/weppout.txt"),
        weppmodelexe.toString(), "0");

    ret = weppserv.exec();
    if (ret != 0) {
      throw new ServiceException("WEPP run error :" + ret);
    }
  }


  /**
   * Insert IET specific names into the response
   */
  protected void addRUSLE2Names() {
    double dummyTValue = 0;
    results().put("#RD:SOIL_COND_INDEX_PTR:SOIL_COND_INDEX_RESULT", String.valueOf(weppmodel.getSCI()));
    results().put("SLOPE_DELIVERY", String.valueOf(weppmodel.getLoss()));
    results().put("SLOPE_T_VALUE", String.valueOf(dummyTValue));
    results().put("SLOPE_DEGRAD", String.valueOf(weppmodel.getLoss()));
    results().put("SLOPE_EQUIV_DIESEL_USE_PER_AREA", String.valueOf(weppmodel.getFuel()));

    double[] dummyArr2 = new double[managements_length.length];
    String[] soilPtrs = new String[managements_length.length];
    String[] manPtrs = new String[managements_length.length];
    for (int i = 0; i < managements_length.length; i++) {
      dummyArr2[i] = 0;
      String s = "lmod";
      s = s.concat("_" + (i + 1));
      s = s.concat(".rot");
      //soilPtrs[i] = soilFileName;
      soilPtrs[i] = soils[0];
      manPtrs[i] = s;
    }
    results().put("SEG_SOIL_LOSS", dummyArr2);
    results().put("climatePtr", "wepp.cli", "IET Use - Climate");
    results().put("soilPtr", soilPtrs, "IET Use - Soil");
    results().put("mgmtPtr", manPtrs, "IET Use - Managements");
  }


  /**
   * convert sedimentYield from (t/ac/yr) unit to (t/yr) unit.
   *
   * @return sediment yield in t/yr
   */
  protected double convertSedYield() {
    // fomula: (tons/acre/year x 50 ft x profile length / (43560 ft^2/acre) = tons/year
    return (weppmodel.getSedyield() * 50 * slopeLength * WeppConstants.CONV_M_TO_FT) / 43560;  // self.slopeWith is always 50 ft.
  }


  /**
   * Get the vegetation name associated with a key.
   *
   * @param vegKey vegetation key to search for
   * @return crop name or null
   */
  protected String getVegName(String vegKey) {
    String[] lines = allVegsCals.split("\n");  // each line is in the formate of "vegKey\tvegName\n"
    for (int i = 0; i < lines.length; i++) {
      String[] parts = lines[i].split("\t");
      if (parts[0].equals(vegKey)) {
        return parts[1];
      }
    }
    return null;
  }


  /**
   * check whether calibration factor--BEINP(factor) is out of range. Out of
   * range means the factor is less than 0.5 or greater than 2).
   *
   * @return calibration warning message if out of range. Otherwise, return
   * empty string.
   */
  protected String checkCalibResults() {
    // read calibrationResults.txt
    Float modVer = Float.parseFloat(modelVersion);
    String res = "";
    try {
      FileInputStream fin = new FileInputStream(workspace().getFile("runs/calibrationResults.txt"));
      BufferedReader br = new BufferedReader(new InputStreamReader(fin));
      try {
        for (int i = 0; i < managementReports.length(); i++) {
          JSONArray yields = managementReports.getJSONObject(i).getJSONArray("yields");
          for (int j = 0; j < yields.length(); j++) {
            JSONObject yield = yields.getJSONObject(j);
            String vegKey = yield.getString("vegkey");
            String line = br.readLine(); // skip the title line
            line = br.readLine();
            // get "target yield" and "real yield" from managementReports.
            while (line != null) {

              // check whether BEINP(factor) value is within the reasonable range 0.5 <= factor <= 2
              String[] parts = line.split("\t+(\\s)+|(\\s)+");
              //String vegName = getVegName(parts[1]);

              if (parts[1].equals(vegKey)) {

                //String msg = vegName + "\t" + parts[9] + "\t" + parts[12] + "\t" + yield.getDouble("targetyld") + "\t";   //  do not work in case of :"veg": "Alfalfa-Bromegrass, forage", "vegkey": "L_12_Alfalfa". vegName equals null. so we replace it with field.getString("veg").
                String msg;
                if (modVer < 4) {
                  msg = yield.getString("veg") + "\t" + parts[9] + "\t" + parts[12] + "\t" + yield.getDouble("targetyld") + "\t"; //parts[9] is "calibration factor" and parts[12] is "calibrited"
                } else {
                  msg = yield.getString("veg") + " [" + yield.getString("altdate") + "]\t" + parts[9] + "\t" + parts[12] + "\t" + yield.getDouble("targetyld") + "\t";
                }
                Integer iter = Integer.parseInt(parts[0]);
                String att;
                if (iter > 0) {
                  att = "Yes";
                } else {
                  att = "No";
                }
                if (yield.getString("yld").equals("N/A")) {
                  msg += "N/A" + "\t" + att + "\n";
                } else {
                  msg += yield.getDouble("yld") + "\t" + att + "\n";
                }
                res = res + msg;
                // reset reading from the beginning of the file
                fin.getChannel().position(0);
                br = new BufferedReader(new InputStreamReader(fin));
                break;
              }
              line = br.readLine();
            }
          }
        }
      } finally {
        br.close();
        fin.close();
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    return res;
  }


  /**
   * Extract some WEPP outputs and include links to model input and output
   * files.
   *
   * @throws Exception reading any result files.
   */
  @Override
  protected void postProcess() throws Exception {
    LOG.info("Starting postProcess...");

    if (onlyManagements == true) {
      for (int i = 0; i < managements_length.length; i++) {
        String s = (i + 1) + "_" + managementNames[i];
        s = s.concat(".rot");
        putExpectedFileResult(s, "WEPPServ Management " + (i + 1) + " Input", false);
      }
      return;
    }

    weppmodel.parseOutputs(workspace().getFile("output/weppout.txt"));

    //runErrorsToUser = runErrorsToUser + checkCalibResults();
    results().put("Messages", runErrorsToUser);
    //results().put("Calibration",checkCalibResults());

//    results().put("WEPPVersion", weppmodel.getWEPPVersion(), "WEPP FORTRAN Model Version");
//    results().put("PreprocessorVersion", weppmodel.getPreprocversion(), "WEPP Preprocessor version");
    metainfo().put("WEPPVersion", weppmodel.getWEPPVersion());
    metainfo().put("PreprocessorVersion", weppmodel.getPreprocversion());

    results().put("Precipitation", weppmodel.getPrecip(), "Average Annual Preciptation", "in");
    results().put("SoilLoss", weppmodel.getLoss(), "Average Annual Soil Loss", "ton/ac/yr");
    results().put("Runoff", weppmodel.getRunoff(), "Average Annual Runoff", "in");
    results().put("SedimentYield", weppmodel.getSedyield(), "Average Annual Sediment Yield", "ton/ac/yr");
    results().put("Fuel", weppmodel.getFuel(), "Fuel used (gal/A)");
    results().put("STIR", weppmodel.getSTIR(), "STIR");
    results().put("SCI", weppmodel.getSCI(), "SCI");
    results().put("OM", weppmodel.getOM(), "OM Factor of SCI");
    results().put("FO", weppmodel.getFO(), "FO Factor of SCI");
    results().put("ER", weppmodel.getER(), "ER Factor of SCI");
    results().put("ContoursHeld", weppmodel.getContoursHeld(), "Contours Held");
    results().put("ContoursFailed", weppmodel.getContoursFailed(), "Contours Failed");

    results().put("NRCSSoilLoss", weppmodel.getNRCSLoss(), "NRCS Planning Soil Loss", "ton/ac/yr");
    results().put("Deposition", weppmodel.getDeposition(), "Average Annual Deposition", "ton/ac/yr");
    results().put("Irrigation", weppmodel.getIrrigation(), "Average Annual Irrigation", "in");

    results().put("SedimentYield2", WEPPUtils.round(convertSedYield(), 2), "Average Annual Sediment Yield", "ton/yr"); // convert sedYield from (ton/ac/yr) unit to (ton/yr) unit.

    results().put("ManagementSegments", weppmodel.getSegments(), "Management Segments");

    // These are RUSLE2 names so that IET may recognize
    addRUSLE2Names();

    File weppLoss = workspace().getFile("output/loss_0.txt");
    if (weppLoss.exists()) {
      results().put(weppLoss, "Main WEPP Output");
      LOG.info("Added main WEPP output to results list.");
    } else {
      LOG.info("Missing loss_0.txt");
    }

    if (plotOutput) {
      putExpectedFileResult("output/plot_0.txt", "WEPP Profile Soil Loss Output", false);
    }
    if (waterBalOutput) {
      putExpectedFileResult("output/watr_0.txt", "WEPP Water Balance output", false);
    }

    results().put("numOFEs", finalManagementCount, "Number of OFEs");

    if (graphicsOutput) {
      if (run_years <= 10) {
        putExpectedFileResult("output/grph_0.txt", "WEPP Graphics Tabular Data Output", false);
      }
      putExpectedFileResult("output/precip_plot_0.txt", "Precipitation Plot Data", false);
      putExpectedFileResult("output/avgloss_plot_0.txt", "Avg Loss Plot Data", false);

      // add statistics file into response.json
      putExpectedFileResult("output/precip_yearly.txt", "Precipitation Data Yearly", false);
      putExpectedFileResult("output/sed_yearly.txt", "Sediment Delivery Data Yearly", false);
      putExpectedFileResult("output/avgloss_yearly.txt", "Avg Loss Data Yearly", false);

      // The following are by OFE
      for (int i = 0; i < finalManagementCount; i++) {
        putExpectedFileResult(String.format("output/biomass_plot_%d.txt", i), "Biomass Plot Data", false);
        putExpectedFileResult(String.format("output/intrcover_plot_%d.txt", i), "Interrill Cover Plot Data", false);
        putExpectedFileResult(String.format("output/cancover_plot_%d.txt", i), "Canopy Cover Plot Data", false);
        putExpectedFileResult(String.format("output/canhgt_plot_%d.txt", i), "Canopy Height Plot Data", false);
        putExpectedFileResult(String.format("output/rescover_daily_plot_%d.txt", i), "Daily Interrill Cover Plot Data", false);
        putExpectedFileResult(String.format("output/rootmass_plot_%d.txt", i), "Root mass Plot Data", false);
        putExpectedFileResult(String.format("output/resmass_plot_%d.txt", i), "Flat Residue mass Plot Data", false);
        putExpectedFileResult(String.format("output/buriedresmass_plot_%d.txt", i), "Buried Residue mass Plot Data", false);
        putExpectedFileResult(String.format("output/deadrootmass_plot_%d.txt", i), "Dead Root mass Plot Data", false);
        putExpectedFileResult(String.format("output/standmass_plot_%d.txt", i), "Standing Residue mass Plot Data", false);
        putExpectedFileResult(String.format("output/ofe_%d.txt", i), "OFE Statistics Yearly Data", false);
        if (dailySoilWaterOutput) {
          putExpectedFileResult(String.format("output/soil_water_daily_plot_%d.txt", i), "Soil Water Daily Plot Data", false);
          putExpectedFileResult(String.format("output/soil_water_daily_plot_all_%d.txt", i), "Soil Water Daily Plot All Years Data", false);
        }
      }
    }

    if (reportOutput) {
      //parseYields(getWorkspaceFile("output/yld_0.txt"));
      createReportJSON(workspace().getFile("output/reportData.json"));
      putExpectedFileResult("output/reportData.json", "WEPP Report Data", false);
      results().put("Calibration", checkCalibResults());
    }

    if (yieldOutput) {
      putExpectedFileResult("output/yld_0.txt", "WEPP Yield Output", false);
    }
    if ((eventOutput) || (returnPeriodOutput)) {
      putExpectedFileResult("output/evbe_0.txt", "WEPP Event Summary Output", false);
    }
    if (winterOutput) {
      putExpectedFileResult("output/wntr_0.txt", "WEPP Winter Output", false);
    }
    if (plantOutput) {
      putExpectedFileResult("output/crop_0.txt", "WEPP Plant Output", false);
    }
    if (soilOutput) {
      putExpectedFileResult("output/soil_0.txt", "WEPP Soil Output", false);
    }
    if (ofeSummaryOutput) {
      putExpectedFileResult("output/ofe_0.txt", "WEPP OFE Output", false);
    }
    if (briefSummaryOutput) {
      putExpectedFileResult("output/finl_0.txt", "WEPP OFE Output", false);
    }

    putExpectedFileResult("wepp.prj", "WEPPServ Project Input", false);
    putExpectedFileResult("wepp.slp", "WEPPServ Slope Input", false);

    for (int i = 0; i < managements_length.length; i++) {
      String s = "lmod";
      s = s.concat("_" + (i + 1));
      s = s.concat(".rot");
      putExpectedFileResult(s, "WEPPServ Management " + (i + 1) + " Input", false);
    }

    for (int i = 0; i < soils.length; i++) {
      String s = soils[i];
      s = s.replace(":", "_");
      s = s.concat("_" + (i + 1));
      s = s.concat(".sol");
      putExpectedFileResult(s, "WEPP soil " + (i + 1) + " Input", false);
    }

    putExpectedFileResult("wepp.cli", "WEPP Climate Input", false);
    putExpectedFileResult("runs/p0.man", "WEPP FORTRAN Management Input", false);
    putExpectedFileResult("runs/p0.sol", "WEPP FORTRAN Soil Input", false);
    putExpectedFileResult("runs/p0.slp", "WEPP FORTRAN Slope Input", false);
    putExpectedFileResult("runs/p0.run", "WEPP FORTRAN Run Input", false);
    putExpectedFileResult("runs/p0a.irr", "WEPP FORTRAN Irrigation Input", true);
    putExpectedFileResult("wepp.par", "CLIGEN PAR Input", false);
    putExpectedFileResult("calibrationAttempts.txt", "Crop Calibration", false);

    if (paramFiles) {
      File paramZip = zipParams();
      results().put(paramZip);
    }

    if (metainfo().hasName("out")) {

      String[] o = metainfo().getStringArray("out");
      for (String s : o) {
        switch (s) {
          case "sol": {
            File soilFile = workspace().getFile(shortSoilName);
            try (TextParser e = new TextParser(soilFile)
                .autoClose(false)
                .nextLine(4)) {

              double[] line4 = e.rightOfLast("'").tokens().asDoubleArray();

              int layer_count = (int) line4[0];
              results().put("sol_albedo", line4[1]);
              results().put("sol_profile_porosity", line4[2]);
              results().put("sol_interill_erodibility", line4[3]);
              results().put("sol_rill_erodibility", line4[4]);
              results().put("sol_critical_shear", line4[5]);
              results().put("sol_surface_conductivity", line4[6]);

//      System.out.println(surface_conductivity);
              List<double[]> l = new ArrayList<>();
//      System.out.println(Arrays.toString(line4));
              for (int i = 0; i < layer_count; i++) {
                l.add(e.nextLine().tokens().asDoubleArray());
              }
              results().put("sol_wave_sand", weightedAverageSol(l, 1));
              results().put("sol_wave_clay", weightedAverageSol(l, 2));
              results().put("sol_wave_om", weightedAverageSol(l, 3));
              results().put("sol_wave_cation", weightedAverageSol(l, 4));
              results().put("sol_wave_rock", weightedAverageSol(l, 5));
            }
          }
          break;
          case "avePrecipDur": {
            try (TextParser e = new TextParser(workspace().getFile("wepp.cli"))
                .autoClose(false)
                .nextLine(5)) {

              // Years simulated
              int years = Integer.parseInt(e.getWsTokenAt(5));
              e.toLineContaining("da mo year").nextLine().nextLine();
              double dur = 0.0;
              while (e.nextLineSkipEmpty().notEOF()) {
                dur += Double.parseDouble(e.getWsTokenAt(4));
              }
              dur /= years;
              results().put("avgPrecipDur", Numeric.round(dur, 3));
            }
          }
          break;
          case "avgPrecip": {
            File cliFile = workspace().getFile("wepp.cli");
            double avp = new TextParser(cliFile)
                .toLineContaining("ave precipitation").nextLine().tokens().sum();
            results().put("avgPrecip", avp);
            String avpm[] = new TextParser(cliFile)
                .toLineContaining("ave precipitation").nextLine().tokens().asStringArray();
            for (int i = 0; i < avpm.length; i++) {
              results().put("avgPrecipMo_" + (i + 1), avpm[i]);
            }
          }
          break;
        }
      }
    }
  }


  public static void main(String[] args) throws Exception {
    try (TextParser e = new TextParser(new File("/tmp/csip/work/29/13/ae8b01d5-90c3-11eb-ac79-2b71fe1625d1/wepp.cli"))
        .autoClose(false)
        .nextLine(5)) {

      // Years simulated
      int years = Integer.parseInt(e.getWsTokenAt(5));
      e.toLineContaining("da mo year").nextLine().nextLine();
      double dur = 0.0;
      while (e.nextLineSkipEmpty().notEOF()) {
        System.out.println(e.getWsTokenAt(4));
        dur += Double.parseDouble(e.getWsTokenAt(4));
      }
      System.out.println(Numeric.round(dur, 3));
      dur /= years;
      System.out.println(dur);
      System.out.println(Numeric.round(dur, 3));
    }
  }


  static double weightedAverageSol(List<double[]> layer, int idx) {
    double acc = 0.0;
    double total_weight = 0.0;
    for (double[] prop : layer) {
      total_weight += prop[0];
      acc += prop[idx] * prop[0];
    }
    return Numeric.round(acc / total_weight, 3);
  }


  protected void putExpectedFileResult(String filename, String description, boolean skip) throws IOException, ServiceException {
    try {
      results().put(workspace().getFile(filename), description);
    } catch (IllegalArgumentException ex) {
      if (!skip) {
        throw new ServiceException("An expected model ouptut file, " + filename + ", is not accessible or was never written by the model run.  Cannot proceed with copying model output for retrieval.", ex);
      } //  Else:  We don't care that it was unavailable...skip this file.
      else {
        LOG.info("Skipping copy of output file, " + filename + ", by parameter instruction.   Can't get file:  " + ex.getMessage());
      }
    }
  }


  /**
   * Create the report for CSIP. This is used by IET.
   *
   * @return array of all elements in the report
   *
   * @throws Exception if any
   */
  @Override
  protected void doReport() throws Exception {
    if (onlyManagements == true) {
      return;
    }
    LOG.info("in createreport..");
    JSONArray dim = new JSONArray();
    JSONObject val = new JSONObject();
    JSONArray ja_val;
    JSONArray segVals = new JSONArray();
    String dates;
    String dates2wk;
    String[] dateArr;
    JSONArray dummyArr = new JSONArray();
    List<PlotData> pfiledata;
    List<Double> yfiledata;
    String plotFile;

    dateArr = new String[managements_length.length];

    // read dates for each segment and also canopy cover values
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/cancover_plot_%d.txt", j);
      pfiledata = readPlotFile(workspace().getFile(plotFile));

      dates = "";
      for (int i = 0; i < pfiledata.size(); i++) {
        if (dates != "") {
          dates = dates + ",\"" + pfiledata.get(i).date + "\"";
        } else {
          dates = "\"" + pfiledata.get(i).date + "\"";
        }
      }
      dateArr[j] = dates;
      dummyArr = new JSONArray();
      for (int i = 0; i < pfiledata.size(); i++) {
        Float per = pfiledata.get(i).val * 100;
        dummyArr.put(per.toString());
      }
      segVals.put(dummyArr);
    }

    dim = new JSONArray();
    dim.put("SEGMENT");
    ja_val = new JSONArray();
    for (int i = 0; i < managements_length.length; i++) {
      ja_val.put(dateArr[i]);
    }
    report().put("DAY_IN_SIM", ja_val, "Simulation day", "date");
    report().putMetaInfo("DAY_IN_SIM", "dim", dim);

    dim = new JSONArray();
    dim.put("SEGMENT");
    report().put("SEGMENT", managements_length.length, "(Index)");
    report().putMetaInfo("SEGMENT", "dim", dim);

    dim = new JSONArray();
    dim.put("STATTYPES");
    report().put("STATTYPES", 6, "Statistics, min,max,mean,median,stdev,coefvar");
    report().putMetaInfo("STATTYPES", "dim", dim);

    // Canopy cover, this is the canopy cover for each segment at 2 week intervals with additional dates when operations occur
    dim = new JSONArray();
    dim.put("SEGMENT");
    dim.put("DAY_IN_SIM");
    report().put("CANOPY_COVER", segVals, "Canopy Cover", "percent");
    report().putMetaInfo("CANOPY_COVER", "dim", dim);

    // --- Live biomass, this is the live biomass for each segment at 2 week intervals with additional dates when operations occur
    segVals = new JSONArray();
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/biomass_plot_%d.txt", j);
      pfiledata = readPlotFile(workspace().getFile(plotFile));

      dummyArr = new JSONArray();
      for (int i = 0; i < pfiledata.size(); i++) {
        Float lbs = pfiledata.get(i).val * 2000;
        dummyArr.put(lbs.toString());
      }
      segVals.put(dummyArr);
    }

    report().put("LIVE_BIOMASS", segVals, "Live biomass", "pound/acre");
    report().putMetaInfo("LIVE_BIOMASS", "dim", dim);

    // --- Live biomass, this is the live biomass for each segment at 2 week intervals with additional dates when operations occur
    segVals = new JSONArray();
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/deadrootmass_plot_%d.txt", j);
      pfiledata = readPlotFile(workspace().getFile(plotFile));

      dummyArr = new JSONArray();
      for (int i = 0; i < pfiledata.size(); i++) {
        Float lbs = pfiledata.get(i).val * 2000; //TODO: verify - is this value in tons?
        dummyArr.put(lbs.toString());
      }
      segVals.put(dummyArr);
    }

    report().put("DEAD_ROOT_BIOMASS", segVals, "Dead root biomass", "pound/acre");
    report().putMetaInfo("DEAD_ROOT_BIOMASS", "dim", dim);

    // --- Live biomass, this is the live biomass for each segment at 2 week intervals with additional dates when operations occur
    segVals = new JSONArray();
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/rootmass_plot_%d.txt", j);
      pfiledata = readPlotFile(workspace().getFile(plotFile));

      dummyArr = new JSONArray();
      for (int i = 0; i < pfiledata.size(); i++) {
        Float lbs = pfiledata.get(i).val * 2000; //TODO: verify - is this value in tons?
        dummyArr.put(lbs.toString());
      }
      segVals.put(dummyArr);
    }

    report().put("ROOT_BIOMASS", segVals, "root biomass", "pound/acre");
    report().putMetaInfo("ROOT_BIOMASS", "dim", dim);

    // --- Soil loss, this is for the whole hillslope at each day.
    //     Need to also read the dates since they are for each day and
    //     not be the same as the segment dates which are at 2 week intervals and
    //     will include operations.
    pfiledata = readPlotFile(workspace().getFile("output/avgloss_daily_plot_0.txt"));
    dummyArr = new JSONArray();
    dates = "";
    for (int i = 0; i < pfiledata.size(); i++) {
      if (dates != "") {
        dates = dates + ",\"" + pfiledata.get(i).date + "\"";
      } else {
        dates = "\"" + pfiledata.get(i).date + "\"";
      }

      Float tons = pfiledata.get(i).val;
      dummyArr.put(tons.toString());
    }
    report().put("DAY_IN_SIM_DAILY", dates, "Simulation day daily", "date");

    report().put("SOIL_LOSS", dummyArr, "Soil loss Daily values", "ton/acre/year");
    report().putMetaInfo("SOIL_LOSS", "dim", "DAY_IN_SIM_DAILY");

    // -- Soil loss at two week intervals.
    pfiledata = readPlotFile(workspace().getFile("output/avgloss_plot_0.txt"));
    dummyArr = new JSONArray();
    dates2wk = "";
    for (int i = 0; i < pfiledata.size(); i++) {
      if (!dates2wk.equals("")) {
        dates2wk = dates2wk + ",\"" + pfiledata.get(i).date + "\"";
      } else {
        dates2wk = "\"" + pfiledata.get(i).date + "\"";
      }

      Float tons = pfiledata.get(i).val;
      dummyArr.put(tons.toString());
    }

    report().put("TWO_WEEK_INTERVALS", dates2wk, "Simulation dates at 1 and 16 day of each month, 24 total", "date");
    report().put("SOIL_LOSS_2_WEEK_INTERVALS", dummyArr, "Soil loss averaged at 2 week intervals", "ton/acre");
    report().putMetaInfo("SOIL_LOSS_2_WEEK_INTERVALS", "dim", "TWO_WEEK_INTERVALS");

    // --- Flat residue, this is the flat residue for each segment at 2 week intervals with additional dates when operations occur
    segVals = new JSONArray();
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/resmass_plot_%d.txt", j);
      pfiledata = readPlotFile(workspace().getFile(plotFile));

      dummyArr = new JSONArray();
      for (int i = 0; i < pfiledata.size(); i++) {
        Float lbs = pfiledata.get(i).val * 2000;
        dummyArr.put(lbs.toString());
      }
      segVals.put(dummyArr);
    }

    report().put("FLAT_RESIDUE", segVals, "Flat residue mass", "pound/year");
    report().putMetaInfo("FLAT_RESIDUE", "dim", dim);

    // --- Interrill cover, this is the interrill cover for each segment at 2 week intervals with additional dates when operations occur
    segVals = new JSONArray();
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/intrcover_plot_%d.txt", j);
      pfiledata = readPlotFile(workspace().getFile(plotFile));

      dummyArr = new JSONArray();
      for (int i = 0; i < pfiledata.size(); i++) {
        Float lbs = pfiledata.get(i).val;
        dummyArr.put(lbs.toString());
      }
      segVals.put(dummyArr);
    }

    report().put("INTERRILL_COVER", segVals, "Interrill surface cover", "fraction");
    report().putMetaInfo("INTERRILL_COVER", "dim", dim);

    // --- Standing residue, this is the standing residue for each segment at 2 week intervals with additional dates when operations occur
    segVals = new JSONArray();
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/standmass_plot_%d.txt", j);
      pfiledata = readPlotFile(workspace().getFile(plotFile));

      dummyArr = new JSONArray();
      for (int i = 0; i < pfiledata.size(); i++) {
        Float lbs = pfiledata.get(i).val * 2000;
        dummyArr.put(lbs.toString());
      }
      segVals.put(dummyArr);
    }

    report().put("STANDING_RESIDUE", segVals, "Standing residue mass", "pound/acre");
    report().putMetaInfo("STANDING_RESIDUE", "dim", dim);

    dim = new JSONArray();
    dim.put("YEARS");
    report().put("YEARS", run_years, "Number of years in simulation");
    report().putMetaInfo("YEARS", "dim", dim);

    val = new JSONObject();
    dim = new JSONArray();
    dim.put("SEGMENT");
    dim.put("YEARS");

    JSONArray dimSeg = new JSONArray();
    dimSeg.put("SEGMENT");
    dimSeg.put("STATTYPES");

    // --- Yearly average transpiration
    segVals = new JSONArray();
    JSONObject stats;
    JSONArray statVals = new JSONArray();
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/ofe_%d.txt", j);
      yfiledata = readYearlyFile(workspace().getFile(plotFile), 3);

      dummyArr = new JSONArray();
      for (int i = 0; i < yfiledata.size(); i++) {
        Double dval = yfiledata.get(i);
        dummyArr.put(dval);
      }
      segVals.put(dummyArr);
      Double[] items = yfiledata.toArray(new Double[yfiledata.size()]);
      stats = WEPPUtils.doStats(items);
      statVals.put(stats);
    }
    if (expandedReport == true) {
      report().put("Plant Transpiration, yearly", segVals, "Yearly plant transpiration", "in/yr");
      report().putMetaInfo("Plant Transpiration, yearly", "dim", dim);
    }
    report().put("Plant Transpiration, statistics", statVals, "Plant transpiration, min,max,mean,median,stddev,coefvar", "in/yr");
    report().putMetaInfo("Plant Transpiration, statistics", "dim", dimSeg);

    // --- Yearly average soil evaporation
    segVals = new JSONArray();
    statVals = new JSONArray();
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/ofe_%d.txt", j);
      yfiledata = readYearlyFile(workspace().getFile(plotFile), 4);

      dummyArr = new JSONArray();
      for (int i = 0; i < yfiledata.size(); i++) {
        Double dval = yfiledata.get(i);
        dummyArr.put(dval);
      }
      segVals.put(dummyArr);
      Double[] items = yfiledata.toArray(new Double[yfiledata.size()]);
      stats = WEPPUtils.doStats(items);
      statVals.put(stats);
    }
    if (expandedReport == true) {
      report().put("Soil Evaporation, yearly", segVals, "Yearly soil evaporation", "in/yr");
      report().putMetaInfo("Soil Evaporation, yearly", "dim", dim);
    }

    report().put("Soil Evaporation, statistics", statVals, "Soil evaportation, min,max,mean,median,stddev,coefvar", "in/yr");
    report().putMetaInfo("Soil Evaporation, statistics", "dim", dimSeg);

    // --- Yearly average runoff
    segVals = new JSONArray();
    statVals = new JSONArray();
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/ofe_%d.txt", j);
      yfiledata = readYearlyFile(workspace().getFile(plotFile), 2);

      dummyArr = new JSONArray();
      for (int i = 0; i < yfiledata.size(); i++) {
        Double dval = yfiledata.get(i);
        dummyArr.put(dval);
      }
      segVals.put(dummyArr);
      Double[] items = yfiledata.toArray(new Double[yfiledata.size()]);
      stats = WEPPUtils.doStats(items);
      statVals.put(stats);
    }
    if (expandedReport == true) {
      report().put("Runoff, yearly", segVals, "Yearly runoff", "in/yr");
      report().putMetaInfo("Runoff, yearly", "dim", dim);
    }

    report().put("Runoff, statistics", statVals, "Runoff, min,max,mean,median,stddev,coefvar", "in/yr");
    report().putMetaInfo("Runoff, statistics", "dim", dimSeg);

    // --- Yearly average irrigation, one for each OFE/segment
    segVals = new JSONArray();
    statVals = new JSONArray();
    for (int j = 0; j < managements_length.length; j++) {
      plotFile = String.format("output/ofe_%d.txt", j);
      yfiledata = readYearlyFile(workspace().getFile(plotFile), 1);

      dummyArr = new JSONArray();
      for (int i = 0; i < yfiledata.size(); i++) {
        Double dval = yfiledata.get(i);
        dummyArr.put(dval);
      }
      segVals.put(dummyArr);
      Double[] items = yfiledata.toArray(new Double[yfiledata.size()]);
      stats = WEPPUtils.doStats(items);
      statVals.put(stats);
    }
    if (expandedReport == true) {
      report().put("Irrigation, yearly", segVals, "Yearly irrigation", "in/yr");
      report().putMetaInfo("Runoff, statistics", "dim", dim);
    }
    report().put("Irrigation, statistics", statVals, "Irrigation, min,max,mean,median,stddev,coefvar", "in/yr");
    report().putMetaInfo("Irrigation, statistics", "dim", dimSeg);

    // --- Yearly preciptation, one set for hillslope
    plotFile = String.format("output/precip_yearly.txt");
    yfiledata = readYearlyFile(workspace().getFile(plotFile), 1);

    dummyArr = new JSONArray();
    for (int i = 0; i < yfiledata.size(); i++) {
      Double dval = yfiledata.get(i);
      dummyArr.put(dval);
    }
    Double[] items = yfiledata.toArray(new Double[yfiledata.size()]);
    stats = WEPPUtils.doStats(items);
    JSONArray starr = new JSONArray();
    starr.put(stats);

    if (expandedReport == true) {
      report().put("Precipitation, yearly", dummyArr, "Yearly preciptation", "in/yr");
      report().putMetaInfo("Precipitation, yearly", "dim", "YEARS");
    }

    report().put("Precipitation, statistics", starr, "Precipitation, min,max,mean,median,stddev,coefvar", "in/yr");
    report().putMetaInfo("Precipitation, statistics", "dim", "STATTYPES");

    // --- Yearly soil loss, one set for hillslope
    plotFile = String.format("output/avgloss_yearly.txt");
    yfiledata = readYearlyFile(workspace().getFile(plotFile), 1);

    dummyArr = new JSONArray();
    for (int i = 0; i < yfiledata.size(); i++) {
      Double dval = yfiledata.get(i);
      dummyArr.put(dval);
    }
    items = yfiledata.toArray(new Double[yfiledata.size()]);
    stats = WEPPUtils.doStats(items);
    starr = new JSONArray();
    starr.put(stats);

    if (expandedReport == true) {
      report().put("Soil Loss, yearly", dummyArr, "Yearly soil loss", "ton/ac/yr");
      report().putMetaInfo("Soil Loss, yearly", "dim", "YEARS");
    }

    report().put("Soil Loss, statistics", starr, "Soil Loss, min,max,mean,median,stddev,coefvar", "ton/ac/yr");
    report().putMetaInfo("Soil Loss, statistics", "dim", "STATTYPES");

    // --- Yearly sediment delivery, one set for hillslope
    plotFile = String.format("output/sed_yearly.txt");
    yfiledata = readYearlyFile(workspace().getFile(plotFile), 1);

    dummyArr = new JSONArray();
    for (int i = 0; i < yfiledata.size(); i++) {
      Double dval = yfiledata.get(i);
      dummyArr.put(dval);
    }
    items = yfiledata.toArray(new Double[yfiledata.size()]);
    for (int i = 0; i < items.length; i++) {
      items[i] = (items[i] * 50) / 2000.0;  // 50ft width and convert to tons.
    }
    stats = WEPPUtils.doStats(items);
    starr = new JSONArray();
    starr.put(stats);

    if (expandedReport == true) {
      report().put("Sediment delivery, yearly", dummyArr, "Yearly sediment delivery per foot of slope width", "lbs/ft/yr");
      report().putMetaInfo("Sediment delivery, yearly", "dim", "YEARS");
    }

    report().put("Sediment delivery, statistics", starr, "Sediment delivery from 50ft width, min,max,mean,median,stddev,coefvar", "ton/yr");
    report().putMetaInfo("Sediment delivery, statistics", "dim", "STATTYPES");
  }


  /**
   * Read the WEPP plot file output.
   *
   * @param filename WEPP output file name to read
   *
   * @return array of dates and values
   */
  protected List<PlotData> readPlotFile(File filename) {
    List<PlotData> vals = new ArrayList();
    try {
      BufferedReader br = new BufferedReader(new FileReader(filename));
      try {
        String line = br.readLine();
        while (line != null) {
          String parts[] = line.split(" ");
          PlotData obj = new PlotData();
          obj.date = parts[0].replace("-", "/");
          obj.val = Float.parseFloat(parts[1]);
          vals.add(obj);
          line = br.readLine();
        }
      } finally {
        br.close();
      }
    } catch (Exception e) {
      vals = null;
      throw new RuntimeException(e);
    }
    return vals;
  }


  /**
   * Read the WEPP plot file output.
   *
   * @param filename WEPP output file name to read
   *
   * @return array of dates and values
   */
  protected List<Double> readYearlyFile(File filename, int col) {
    List<Double> vals = new ArrayList();
    try {
      BufferedReader br = new BufferedReader(new FileReader(filename));
      try {
        String line = br.readLine();
        while (line != null) {
          String parts[] = line.split("\\s+");
          Double val;
          if (parts.length > col) {
//                        try {
            val = Double.parseDouble(parts[col]);
//                        } catch (NumberFormatException E) {
//                            if (!parts[col].contains("nan")) {
//                              throw E;
//                            }
//                            val = -999.0;
//                            LOG.severe( "nan in file :" + filename);
//                        }
          } else {
            LOG.severe("Error reading values:" + filename);
            val = -999.0;
          }
          vals.add(val);
          line = br.readLine();
        }
      } finally {
        br.close();
      }
    } catch (Exception e) {
      vals = null;
      LOG.severe("Error reading " + filename);
      throw new RuntimeException(e);
    }
    return vals;
  }


  /**
   * Create a file with JSON data that will be the basis for the PDF template.
   *
   * @param fp where to write data to
   *
   * @return JSON written to file
   *
   * @throws Exception if any
   */
  protected JSONObject createReportJSON(File fp) throws Exception {
    JSONObject rep = new JSONObject();
    JSONObject res = new JSONObject();
    JSONObject loc = new JSONObject();
    JSONObject soil = new JSONObject();
    JSONObject slp = new JSONObject();
    JSONObject man = new JSONObject();
    JSONObject prac = new JSONObject();
    JSONObject info = new JSONObject();

    res.put("Precipitation", weppmodel.getPrecip());
    res.put("SoilLoss", weppmodel.getLoss());
    res.put("Runoff", weppmodel.getRunoff());
    res.put("SedimentYield", weppmodel.getSedyield());
    res.put("NRCSSoilLoss", weppmodel.getNRCSLoss());
    res.put("Fuel", weppmodel.getFuel());
    res.put("STIR", WEPPUtils.round(weppmodel.getSTIR(), 2));
    res.put("SCI", WEPPUtils.round(weppmodel.getSCI(), 2));
    res.put("OM", WEPPUtils.round(weppmodel.getOM(), 2));
    res.put("FO", WEPPUtils.round(weppmodel.getFO(), 2));
    res.put("ER", WEPPUtils.round(weppmodel.getER(), 2));
    res.put("Irrigation", WEPPUtils.round(weppmodel.getIrrigation(), 2));
    res.put("SedimentYield2", WEPPUtils.round(convertSedYield(), 2)); // convert sedYield from (ton/ac/yr) unit to (ton/yr) unit.

    loc.put("Latitude", latitude);
    loc.put("Longitude", longitude);

    soil.put("COKEY", soils[0]);

    slp.put("Length", slopeLength * WeppConstants.CONV_M_TO_FT);
    slp.put("Steepness", slope_steepness);
    slp.put("Shape", slope_type);
    slp.put("Aspect", slopeAspect);

    if (contourName == null) {
      contourName = "None";
    }
    if (stripName == null) {
      stripName = "None";
    }
    prac.put("Contours", contourName);
    prac.put("Strips", stripName);

    updateManagementReport();
    man.put("numstrips", managementReports.length());

    String stripLens = "";
    String offsets = "";
    String slen;
    for (int i = 0; i < managementReports.length(); i++) {
      slen = String.format("%.0f", managements_length_meters[i] * 3.28084);
      if (i > 0) {
        stripLens = stripLens + "-" + slen;
      } else {
        stripLens = slen;
      }
      slen = String.format("%d", management_offsets[i]);
      if (i > 0) {
        offsets = offsets + "-" + slen;
      } else {
        offsets = slen;
      }
    }
    man.put("striplengths", stripLens);
    man.put("offsets", offsets);
    man.put("managements", managementReports);

    rep.put("Info", info);
    rep.put("Results", res);
    rep.put("Climate", loc);
    rep.put("Soil", soil);
    rep.put("Slope", slp);
    rep.put("managements", man);
    rep.put("Practices", prac);

    FileUtils.writeStringToFile(fp, rep.toString(), "UTF-8");
    return rep;
  }


  /**
   * Go through the PDF JSON and fill in the data from the management that
   * is not known until after the run.
   *
   * @throws Exception if any
   */
  protected void updateManagementReport() throws Exception {
    // go through the report JSON and fill in the residue cover fields for
    // the corresponding dates.
    JSONObject jo, op, yld, cropint;
    String odate, namestr, lastDate;
    JSONArray ops;
    JSONArray ylds;
    JSONArray ints;
    String cov, harv, oyld, istart, iend, resamtStr, ddate, resdiffStr;
    double covfact, yldConv, moisture, cyld, target, eff, fldLoss;
    double resamt, resdiff, resamtPrev;
    double[] sloss;
    double[] slossPrev;
    int years, idxstart, idxend, lossPts;
    double marea, carea;
    double sumLen, prevLoss;
    int lossPtsPrev;
    double netLoss, prevArea;
    int prevYearLimit;

    sloss = null;

    File fp = workspace().getFile("output/reportDataPre.json");
    FileUtils.writeStringToFile(fp, managementReports.toString(), "UTF-8");

    Map<String, String> hmap = new HashMap<>();
    Map<String, String> yldmap = new HashMap<>();
    Map<String, Integer> lossmap = new HashMap<>();
    Map<String, String> resaddedmap = new HashMap<>();

    String line;
    String harveg;
    int idx = 0;
    sumLen = 0;
    prevLoss = 0;
    lossPtsPrev = 0;
    slossPrev = null;
    carea = 0;
    netLoss = 0;
    prevArea = 0;
    prevYearLimit = 0;
    years = 1;

    BufferedReader br = new BufferedReader(new FileReader(workspace().getFile("output/yields.txt")));
    while ((line = br.readLine()) != null) {
      String arr[] = line.trim().split("\\s+");
      if (arr.length == 2) {
        yldmap.put(arr[0], arr[1]);
      }
    }
    br.close();

    // this needs to account for all OFE's - currently only works for one
    for (int i = 0; i < managementReports.length(); i++) {
      jo = managementReports.getJSONObject(i);
      prevYearLimit = years;
      years = jo.getInt("years");
      int totDays = years * 366;

      // need to keep track of the previous OFE values.
      if (i == 0) {
        slossPrev = new double[totDays];
        for (int j = 0; j < totDays; j++) {
          slossPrev[j] = 0;
          lossPtsPrev = totDays;
        }
        prevYearLimit = years;
      } else {
        if (sloss != null) {
          slossPrev = new double[sloss.length];
          System.arraycopy(sloss, 0, slossPrev, 0, sloss.length);
          lossPtsPrev = slossPrev.length;
        }
      }
      marea = managements_length_meters[i] * slopeWidth; // area of current OFE m^2
      sumLen = sumLen + managements_length_meters[i];  // cumulative length of OFE + previous OFE's
      carea = sumLen * slopeWidth;
      sloss = new double[totDays];

      String resFile = String.format("output/rescover_daily_plot_%d.txt", i);
      br = new BufferedReader(new FileReader(workspace().getFile(resFile)));
      while ((line = br.readLine()) != null) {
        String arr[] = line.trim().split("\\s+");
        if (arr.length == 2) {
          hmap.put(arr[0], arr[1]);
        }
      }
      br.close();

      String resAmountFile = String.format("output/resmassadded_daily_plot_%d.txt", i);
      br = new BufferedReader(new FileReader(workspace().getFile(resAmountFile)));
      while ((line = br.readLine()) != null) {
        String arr[] = line.trim().split("\\s+");
        if (arr.length == 2) {
          resaddedmap.put(arr[0], arr[1]);
        }
      }
      br.close();

      String lossFile = String.format("output/sed_daily_plot_%d.txt", i);
      br = new BufferedReader(new FileReader(workspace().getFile(lossFile)));
      idx = 0;
      while ((line = br.readLine()) != null) {
        String arr[] = line.trim().split("\\s+");
        if (arr.length == 2) {
          lossmap.put(arr[0], idx);
          sloss[idx] = Double.parseDouble(arr[1]);
          idx++;
        }
      }
      br.close();

      lossPts = idx;
      if (years != prevYearLimit) {
        slossPrev = WEPPUtils.buildLoss(sloss, slossPrev, years, prevYearLimit);
      }

      lossPtsPrev = lossPts;
      prevYearLimit = years;
      namestr = jo.getString("name");

      ylds = jo.getJSONArray("yields");
      for (int j = 0; j < ylds.length(); j++) {
        yld = ylds.getJSONObject(j);
        harv = yld.getString("vegkey");
        oyld = yldmap.get(harv);
        if (oyld != null) {
          double dryYield = Double.parseDouble(oyld);
          if (dryYield > 0.0001) {
            yldConv = yld.getDouble("yldConv");
            moisture = yld.getDouble("moisture");
            cyld = yldConv * dryYield;  // this is dry yield
            cyld = cyld / (1.0 - (moisture / 100.0));
            yld.put("yld", cyld);
            target = yld.getDouble("targetyld");
            eff = (cyld / target) * 100.0;
            yld.put("eff", eff);
          } else {
            yld.put("yld", "N/A");
            yld.put("eff", "N/A");
          }
        } else {
          yld.put("yld", "N/A");
          yld.put("eff", "N/A");
        }
      }

      ints = jo.getJSONArray("intervals");
      for (int j = 0; j < ints.length(); j++) {
        cropint = ints.getJSONObject(j);
        istart = cropint.getString("start");
        iend = cropint.getString("end");
        idxstart = lossmap.get(istart);
        idxend = lossmap.get(iend);

        fldLoss = WEPPUtils.getLoss(idxstart, idxend, sloss, lossPts);
        double fldlosso = fldLoss;

        fldLoss = fldLoss * WeppConstants.TPAKGM * carea;
        if (i == 0) {
          prevLoss = 0;
        } else {
          if (slossPrev != null) {
            prevLoss = WEPPUtils.getLoss(idxstart, idxend, slossPrev, lossPtsPrev);
            prevLoss = prevLoss * WeppConstants.TPAKGM * prevArea;
          }
        }
        netLoss = ((fldLoss - prevLoss) / marea) * WeppConstants.KGMTPA;
        LOG.info("Interval " + j + " fldloss=" + fldLoss + " prevLoss=" + prevLoss + " marea=" + marea + " carea=" + carea + " startIDX=" + idxstart + " endIDX=" + idxend + " getLoss=" + fldlosso);
        //netLoss = (((fldLoss * carea) - (prevLoss)) / marea) * KGMTPA;
        prevArea = carea;
        cropint.put("loss", netLoss + 0.0001);
      }

      ops = jo.getJSONArray("operations");
      lastDate = "";
      JSONObject opnext;
      String nextdate;

      for (int j = 0; j < ops.length(); j++) {
        op = ops.getJSONObject(j);
        odate = op.getString("date");
        cov = hmap.get(odate);
        covfact = Float.parseFloat(cov) * 100;
        covfact = Math.round(covfact * 100.0) / 100.0;
        op.put("cover", covfact);
        harveg = op.optString("harVeg", "");
        if (!harveg.equals("")) {
          //
        }
        resamtStr = resaddedmap.get(odate);
        String resamtStr1 = resamtStr;
        resamt = Float.parseFloat(resamtStr) * 2000;
        resamt = Math.round(resamt * 100.0) / 100.0;   // This is the residue amount on the operation day
        // need to get the amount on the previous day to report the
        // amount added.
        ddate = WEPPUtils.decDay(odate);
        resamtStr = resaddedmap.get(ddate);
        if (resamtStr != null) {
          resamtPrev = Float.parseFloat(resamtStr) * 2000;
          resamtPrev = Math.round(resamtPrev * 100.0) / 100.0;
          resdiff = resamt - resamtPrev;
          if (resdiff > 10) {
            resdiffStr = String.format("%.0f", resdiff);
          } else {
            resdiffStr = "";
          }
          // there could be several operations on the same day, try to determine
          // what one it applies to. There may be several contributing to the residue addition.
          // The appoach is to attach the amout to the last operation of the day
          if (j != (ops.length() - 1)) {
            opnext = ops.getJSONObject(j + 1);
            nextdate = opnext.getString("date");
            if (nextdate.equals(odate)) {
              // same date, set this one to blank and defer to last one
              resdiffStr = "";
            }
          }
          op.put("resamt", resdiffStr);
        } else {
          op.put("resamt", odate + "/" + ddate);
        }
        lastDate = odate;
      }
    }
  }


  /**
   * Return the name of the contour selection.
   *
   * @return name of contour or null if none
   */
  public String getContourName() {
    if (contourData != null) {
      try {
        return contourData.getString("name");
      } catch (Exception e) {
        return null;
      }
    } else {
      return null;
    }
  }


  /**
   * Check if service has requested contouring.
   *
   * @return false if default 'no contour' name, true otherwise
   */
  public boolean hasContours() {
    if (contourData == null) {
      return false;
    } else {
      try {
        if (contourData.getString("name").equals("a. rows up-and-down hill")) {
          return false;
        } else {
          return true;
        }
      } catch (Exception e) {
        return false;
      }
    }
  }


  protected File zipParams() throws IOException, JSONException {
    File paramZip;
    File source, dest;
    FileOutputStream fos;
    ZipOutputStream weppParams;
    String rname, mpath, mname;
    String[] paths;
    String[] names;

    boolean has_run_years, has_length, has_steepness, has_state, has_station, has_soil, has_cokey, has_user, has_field;

    has_run_years = has_length = has_steepness = has_state = has_station = has_soil = has_cokey = has_user = has_field = false;

    paramZip = workspace().getFile("wepp_project.zip");
    fos = new FileOutputStream(paramZip);
    weppParams = new ZipOutputStream(fos);

    JSONObject req = request().getRequest();
    JSONObject meta = req.getJSONObject("metainfo");
    JSONObject newData = new JSONObject();
    newData = newData.put("metainfo", meta);
    JSONArray parms = req.getJSONArray("parameter");
    JSONArray parmsnew = new JSONArray();
    for (int i = 0; i < parms.length(); ++i) {
      JSONObject rec = parms.getJSONObject(i);
      rname = rec.getString("name");
      if (!rname.startsWith("rotation")) {
        parmsnew.put(rec);
        if (rname.equals("run_years")) {
          has_run_years = true;
        } else if (rname.equals("length")) {
          has_length = true;
        } else if (rname.equals("slope_steepness")) {
          has_steepness = true;
        } else if (rname.equals("state")) {
          has_state = true;
        } else if (rname.equals("stationName")) {
          has_station = true;
        } else if (rname.equals("soilName")) {
          has_soil = true;
        } else if (rname.equals("soilCokey")) {
          has_cokey = true;
        } else if (rname.equals("userName")) {
          has_user = true;
        } else if (rname.equals("fieldName")) {
          has_field = true;
        }
      }
    }
    JSONObject ob = new JSONObject();
    ob.put("name", "ietcrlmod");
    ob.put("value", false);
    parmsnew = parmsnew.put(ob);

    ob = new JSONObject();
    ob = ob.put("name", "report_output");
    ob = ob.put("value", true);
    parmsnew = parmsnew.put(ob);

    ob = new JSONObject();
    ob.put("name", "crlmod");
    ob.put("value", true);
    parmsnew = parmsnew.put(ob);

    if (has_run_years == false) {
      ob = new JSONObject();
      ob.put("name", "run_years");
      ob.put("value", run_years);
      parmsnew = parmsnew.put(ob);
    }

    if (has_length == false) {
      ob = new JSONObject();
      ob.put("name", "length");
      int slopeLengthFt = (int) (slopeLength * WeppConstants.CONV_M_TO_FT);
      ob.put("value", slopeLengthFt);
      parmsnew = parmsnew.put(ob);
    }

    if (has_steepness == false) {
      ob = new JSONObject();
      ob.put("name", "slope_steepness");
      ob.put("value", slope_steepness);
      parmsnew = parmsnew.put(ob);
    }

    if (has_state == false) {
      ob = new JSONObject();
      ob.put("name", "state");
      ob.put("value", "Iowa");
      parmsnew = parmsnew.put(ob);
    }

    if (has_station == false) {
      ob = new JSONObject();
      ob.put("name", "stationName");
      ob.put("value", "ADAIR COUNTY");
      parmsnew = parmsnew.put(ob);
    }

    if (has_soil == false) {
      ob = new JSONObject();
      ob.put("name", "soilName");
      String sname = "0|" + shortSoilName + "|X(100%, 1T)";
      ob.put("value", sname);
      parmsnew = parmsnew.put(ob);
    }

    if (has_cokey == false) {
      ob = new JSONObject();
      ob.put("name", "soilCokey");
      ob.put("value", soils[0]);
      parmsnew = parmsnew.put(ob);
    }

    if (has_user == false) {
      ob = new JSONObject();
      ob.put("name", "userName");
      ob.put("value", "iet2");
      parmsnew = parmsnew.put(ob);
    }

    if (has_field == false) {
      ob = new JSONObject();
      ob.put("name", "fieldName");
      ob.put("value", "field 1");
      parmsnew = parmsnew.put(ob);
    }

    JSONObject oba = new JSONObject();
    JSONArray mfa = new JSONArray();

    paths = new String[managements_length.length];
    names = new String[managements_length.length];
    ob = new JSONObject();
    ob.put("name", "mgmt_length");

    for (int i = 0; i < managements_length.length; i++) {
      mfa = mfa.put(managements_length[i]);
    }
    ob.put("value", mfa);
    parmsnew = parmsnew.put(ob);

    ob = new JSONObject();
    mfa = new JSONArray();
    ob.put("name", "mgmt_offsets");

    for (int i = 0; i < managements_length.length; i++) {
      mfa = mfa.put(management_offsets[i]);
    }
    ob.put("value", mfa);
    parmsnew = parmsnew.put(ob);

    mfa = new JSONArray();
    for (int i = 0; i < managements_length.length; i++) {
      String s = "lmodmanconv";
      s = s.concat("_" + (i + 1));
      s = s.concat(".json");
      String mfile = FileUtils.readFileToString(workspace().getFile(s), "UTF-8");
      JSONObject mfilepart = new JSONObject(mfile);
      JSONObject mf = mfilepart.getJSONObject("lmodData");
      mfa.put(mf);
    }
    oba = oba.put("name", "managements");
    oba = oba.put("value", mfa);
    parmsnew = parmsnew.put(oba);

    ob = new JSONObject();
    mfa = new JSONArray();
    ob.put("name", "mgmt_names");

    for (int i = 0; i < managements_length.length; i++) {
      mfa = mfa.put(managementNames[i]);
    }
    ob.put("value", mfa);
    parmsnew = parmsnew.put(ob);
    newData.put("parameter", parmsnew);

    FileUtils.writeStringToFile(workspace().getFile("wepp_proj.json"), newData.toString(), "UTF-8");
    addToZipFile(workspace().getFile("wepp_proj.json"), weppParams);

    weppParams.close();
    return paramZip;
  }


  protected void addToZipFile(File file, ZipOutputStream zos) throws FileNotFoundException, IOException {
    LOG.info("Adding to zip file: " + file);
    FileInputStream fis = new FileInputStream(file);
    ZipEntry zipEntry = new ZipEntry(file.getName());
    zos.putNextEntry(zipEntry);
    byte[] bytes = new byte[1024];
    int length;
    while ((length = fis.read(bytes)) >= 0) {
      zos.write(bytes, 0, length);
    }
    zos.closeEntry();
    fis.close();
  }

  static class PlotData {

    String date;
    float val;
  }
}