/**
   * Add multiple new nodes (SpeciesZoneType objects) to a manipulation and then submit. HJR
   *
   * @param manipSpeciesMap - species being added
   * @param fullSpeciesMap - full list; for predator/prey info
   * @param timestep
   * @param isFirstManipulation
   * @param networkOrManipulationId
   * @return manipulation ID (String)
   * @throws SimulationException
   */
  public String addMultipleSpeciesType(
      HashMap<Integer, SpeciesZoneType> manipSpeciesMap,
      HashMap<Integer, SpeciesZoneType> fullSpeciesMap,
      int timestep,
      boolean isFirstManipulation,
      String networkOrManipulationId) {

    // job.setNode_Config("5,
    // [5],2000,1.000,1,K=9431.818,0,
    // [14],1751,20.000,1,X=0.273,0,
    // [31],1415,0.008,1,X=1.000,0,
    // [42],240,0.205,1,X=0.437,0,
    // [70],2494,13.000,1,X=0.155,0");

    //		  		  In addMultipleSpeciesType: node [70], biomass 2494, K = -1, R = -1.0000, X = 0.1233
    //				  In addMultipleSpeciesType: node [5], biomass 2000, K = 10000, R = 1.0000, X = 0.5000
    //				  In addMultipleSpeciesType: node [42], biomass 240, K = -1, R = -1.0000, X = 0.3478
    //				  In addMultipleSpeciesType: node [31], biomass 1415, K = -1, R = -1.0000, X = 0.7953
    //				  In addMultipleSpeciesType: node [14], biomass 1752, K = -1, R = -1.0000, X = 0.0010
    StringBuilder builder = new StringBuilder();
    builder.append(fullSpeciesMap.size()).append(",");
    for (SpeciesZoneType species : fullSpeciesMap.values()) {
      System.out.printf(
          "In addMultipleSpeciesType: node [%d], " + "biomass %d, K = %d, R = %6.4f, X = %6.4f\n",
          species.getNodeIndex(),
          +(int) species.getCurrentBiomass(),
          (int) species.getParamK(),
          species.getParamR(),
          species.getParamX());

      builder.append("[").append(species.getNodeIndex()).append("]").append(",");
      builder.append((int) species.getCurrentBiomass()).append(",");
      builder.append(roundToThreeDigits(species.getPerSpeciesBiomass())).append(",");

      String systemParam = this.setSystemParameters(species, fullSpeciesMap, timestep);
      builder.append(systemParam);
      System.out.println(builder);
    }
    String node_config = builder.substring(0, builder.length() - 1);
    // call processsim job here
    return node_config;
  }
  /*5/5/14, JTC, added persistent species data for players; system parameter masterSpeciesList,
  replaces mSpecies.
  Get previous timestep biomass for all species from web service*/
  public HashMap<Integer, SpeciesZoneType> getPrediction(
      String networkOrManipulationId,
      int startTimestep,
      int runTimestep,
      Map<Integer, Integer> addSpeciesNodeList,
      ZoneNodes zoneNodes)
      throws SimulationException {
    long milliseconds = System.currentTimeMillis();

    Log.printf("\nPrediction at %d\n", startTimestep);

    // Get previous timestep biomass for all species from web service
    // JTC, use new HashMap containing all current settings from zoneNodes, masterSpeciesList
    // HJR changing to make a deep copy here , I am getting a null while iterating
    HashMap<Integer, SpeciesZoneType> masterSpeciesList =
        new HashMap<Integer, SpeciesZoneType>(zoneNodes.getNodes());

    HashMap<Integer, SpeciesZoneType> mNewSpecies = new HashMap<Integer, SpeciesZoneType>();
    // JTC, mUpdateBiomass renamed from mUpdateSpecies
    HashMap<Integer, SpeciesZoneType> mUpdateBiomass = new HashMap<Integer, SpeciesZoneType>();
    // JTC, added new update type, mUpdateParams
    HashMap<Integer, SpeciesZoneType> mUpdateParams = new HashMap<Integer, SpeciesZoneType>();

    SpeciesZoneType szt;
    String nodeConfig = null;
    SimJob job = new SimJob();
    // {70=2494, 5=2000, 42=240, 14=1752, 31=1415}
    for (int node_id : addSpeciesNodeList.keySet()) {
      int addedBiomass = addSpeciesNodeList.get(node_id);

      if (!masterSpeciesList.containsKey(node_id)) {
        szt = createSpeciesZoneType(node_id, addedBiomass);
        mNewSpecies.put(node_id, szt);
        // jtc - 04/19/15
        masterSpeciesList.put(node_id, szt);
      } else {
        szt = masterSpeciesList.get(node_id);

        szt.setCurrentBiomass(Math.max(0, szt.getCurrentBiomass() + addedBiomass));
        szt.setBiomassUpdated(true);
      }
    }

    //      //JTC, separated this to capture biomass updates made to ZoneNodes that
    //      //are not received through addSpeciesNodeList (biomass and param updates)
    //      for (SpeciesZoneType species : masterSpeciesList.values()) {
    //          //param update also updates biomass, so insert into that list
    //          //preferentially; o/w use biomass update list
    //          if (species.paramUpdated) {
    //              mUpdateParams.put(species.getNodeIndex(), species);
    //              species.setParamUpdated(false);
    //          } else if (species.biomassUpdated) {
    //              mUpdateBiomass.put(species.getNodeIndex(), species);
    //              species.setBiomassUpdated(false);
    //          }
    //      }

    // Insert new species using web services
    if (!mNewSpecies.isEmpty()) {
      zoneNodes.addNodes(mNewSpecies);
    }
    try {
      nodeConfig =
          addMultipleSpeciesType(
              mNewSpecies, masterSpeciesList, startTimestep, false, networkOrManipulationId);
    } catch (Exception ex) {
      Log.println_e(ex.getMessage());
    }
    //      // Update biomass changes to existing species using web services
    //      if (!mUpdateBiomass.isEmpty()) {
    //          List<NodeBiomass> lNodeBiomass = new ArrayList<NodeBiomass>();
    //          for (SpeciesZoneType s : mUpdateBiomass.values()) {
    //              Log.printf("Updating Biomass: [%d] %s %f\n", s.getNodeIndex(), s.getName(),
    //                      s.getCurrentBiomass() / Constants.BIOMASS_SCALE);
    //              lNodeBiomass.add(new NodeBiomass(
    //                      s.getCurrentBiomass() / Constants.BIOMASS_SCALE, s.getNodeIndex()));
    //          }
    //          try {
    ////              updateBiomass(networkOrManipulationId, lNodeBiomass, startTimestep);
    //          } catch (Exception ex) {
    //              Log.println_e(ex.getMessage());
    //          }
    //      }

    //      // JTC Update changes to existing species parameters using web services (also
    //      // resubmits biomass, but couldn't find a way to do params w/o biomass
    //      if (!mUpdateParams.isEmpty()) {
    //          try {
    ////              increaseMultipleSpeciesType(
    ////                      mUpdateBiomass,
    ////                      masterSpeciesList,
    ////                      startTimestep,
    ////                      false,
    ////                      networkOrManipulationId
    ////              );
    //          } catch (Exception ex) {
    //              Log.println_e(ex.getMessage());
    //          }
    //      }

    //      run(startTimestep, runTimestep, networkOrManipulationId);

    // get new predicted biomass
    try {
      // JTC - changed variable from "mSpecies = " to "mUpdateBiomass = "
      // mUpdateBiomass = getBiomass(networkOrManipulationId, 0, startTimestep + runTimestep);
      if (!masterSpeciesList.isEmpty() || !mNewSpecies.isEmpty()) {
        mUpdateBiomass =
            submitManipRequest("ATN", nodeConfig, startTimestep + runTimestep, false, null);
      }
    } catch (Exception ex) {
      Log.println_e(ex.getMessage());
      return null;
    }
    //      getBiomassInfo(networkOrManipulationId);

    // JTC - add loop to update persistent player species biomass information
    SpeciesZoneType updS;
    for (SpeciesZoneType priorS : masterSpeciesList.values()) {
      System.out.println("priorS.nodeIndex " + priorS.nodeIndex);
      updS = mUpdateBiomass.get(priorS.nodeIndex);
      if (updS != null && updS.currentBiomass != 0) {
        masterSpeciesList
            .get(priorS.nodeIndex)
            .setCurrentBiomass(Math.ceil(updS.getCurrentBiomass()));
      }
      //          else {
      //              zoneNodes.removeNode(priorS.nodeIndex);
      //          }
    }

    Log.printf(
        "Total Time (Get Prediction): %.2f seconds",
        Math.round((System.currentTimeMillis() - milliseconds) / 10.0) / 100.0);

    return (HashMap) zoneNodes.getNodes();
  }