Management.java [src/nodes] Revision: default  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 nodes;

import static csip.ModelDataServiceConstants.ERROR;
import static csip.ModelDataServiceConstants.KEY_METAINFO;
import static csip.ModelDataServiceConstants.KEY_PARAMETER;
import static csip.ModelDataServiceConstants.KEY_RESULT;
import static csip.ModelDataServiceConstants.KEY_STATUS;
import static csip.ModelDataServiceConstants.KEY_VALUE;
import static csip.ModelDataServiceConstants.VALUE;
import csip.api.client.ModelDataServiceCall;
import csip.api.server.ServiceException;
import csip.utils.Client;
import csip.utils.Parallel;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
import java.time.LocalDate;
import static java.time.temporal.ChronoUnit.DAYS;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.xml.sax.SAXException;
import translators.Translator.FORMAT;
import static utils.Constants.*;
import utils.TranslatorException;
import utils.Urls;
import utils.Util;

/**
 *
 * @author Brad
 */
public abstract class Management {

  protected String name;
  protected int rotationYears;
  protected final List<Event> eventData;
  protected final Urls urls;

  public Management(Urls urls) {
    eventData = new CopyOnWriteArrayList<>();
    this.urls = urls;
  }

  public int getRotationYears() {
    return rotationYears;
  }

  public List<Event> getManagementData() {
    return eventData;
  }

  public void readJSONData(JSONObject data, FORMAT type) throws JSONException, TranslatorException {
    JSONArray rotations;
    JSONObject managementTemplate;
    JSONArray events;
    JSONObject jdatum;
    Event datum;
    int yearoffset = 0;
    LocalDate latest = null;

    rotationYears = data.getInt(DURATION);
    rotations = data.getJSONArray(MANAGEMENTS);

    for (int i = 0; i < rotations.length(); i++) {
      managementTemplate = rotations.getJSONObject(i);
      events = managementTemplate.getJSONArray(EVENTS);

      for (int j = 0; j < events.length(); j++) {
        jdatum = events.getJSONObject(j);
        datum = new Event();
        datum.readJSONData(jdatum, type);
        eventData.add(datum);
        if (eventData.size() == 1) {
          yearoffset = eventData.get(0).getDate().getYear() - 1;
        }
        datum.setDate(datum.getDate().minusYears(yearoffset));

        // This event should be before the last event
        if (eventData.size() > 2 && datum.getDate().isBefore(latest)) {
          throw new TranslatorException("Management Error: management " + i + " has an event out of order at position " + (j));
        }

        latest = datum.getDate();
      }
    }
  }

  public void fillLMODData(FORMAT type) throws SQLException, XMLStreamException, ParserConfigurationException, SAXException, IOException, FileNotFoundException, TranslatorException, JSONException, Exception {

    Parallel.run(
        () -> {
          fillLMODOperationData(type);
        },
        () -> {

          fillLMODCropData(type);
        },
        () -> {
          fillLMODResidueData(type);
        }
    );
  }

  protected void fillLMODOperationData(FORMAT type) throws Exception {

    JSONArray operations;
    JSONObject joperation = null;
    Map<String, JSONObject> fileData;
    JSONArray jvalues = new JSONArray();

    for (Event datum : eventData) {
      if (type == FORMAT.KEY) {
        jvalues.put(datum.getOperation().getName());
      } else {
        jvalues.put(datum.getOperation().getID());
      }
    }

    ModelDataServiceCall r = new ModelDataServiceCall()
        .put(NATIVE_FORMATS, true)
        .put(LIMIT, "all")
        .put(((type == FORMAT.KEY) ? NAME : ID), jvalues)
        .withDefaultLogger()
        .url(urls.getOperations())
        .call();
    if (r.serviceFinished()) {
      operations = r.getJSONObject("crlmod").getJSONArray(OPERATIONS);
      fileData = Util.mapOpResultsByName(operations, type);

      for (Event datum : eventData) {

        if (type == FORMAT.KEY) {
          joperation = fileData.get(datum.getOperation().getName());
        } else {
          joperation = fileData.get(datum.getOperation().getID());
        }

        if (joperation == null) {
          throw new ServiceException("Operation: " + datum.getOperation().getID() + " " + datum.getOperation().getName() + " is not in CR_LMOD");
        }

        // If we only had the name then we need to fill in the key
        if (type == FORMAT.KEY) {
          datum.getOperation().setID(joperation.getString(ID));
        }

        datum.getOperation().addProperty("crop", datum.getCrop());
        datum.getOperation().parseLMODData(joperation);
      }

    } else {
      throw new ServiceException("op service error: " + r.getError());
    }

  }

  protected void fillLMODCropData(FORMAT type) throws Exception {
    JSONArray crops;
    Crop crop;
    JSONObject jcrop = null;
    Map<String, JSONObject> fileData;
    JSONArray jvalues = new JSONArray();

    for (Event datum : eventData) {
      crop = datum.getCrop();
      if (crop != null) {
        if (type == FORMAT.KEY) {
          jvalues.put(datum.getCrop().getName());
        } else {
          jvalues.put(datum.getCrop().getID());
        }
      }
    }

    ModelDataServiceCall r = new ModelDataServiceCall()
        .put(NATIVE_FORMATS, true)
        .put(LIMIT, "all")
        .put(((type == FORMAT.KEY) ? NAME : ID), jvalues)
        .withDefaultLogger()
        .url(urls.getCrops())
        .call();

    if (r.serviceFinished()) {
      crops = r.getJSONObject("crlmod").getJSONArray(CROPS);
      fileData = Util.mapCropResultsByName(crops, type);
      for (Event datum : eventData) {
        if (datum.getCrop() != null) {
          if (type == FORMAT.KEY) {
            jcrop = fileData.get(datum.getCrop().getName());
          } else {
            jcrop = fileData.get(datum.getCrop().getID());
          }

          if (jcrop == null) {
            throw new ServiceException("Crop: " + datum.getCrop().getName() + " is not in CR_LMOD");
          }
          // If we only had the name then we need to fill in the key
          if (type == FORMAT.KEY) {
            datum.getCrop().setID(jcrop.getString(ID));
          }
          datum.getCrop().parseLMODData(jcrop);
        }
      }
    } else {
      throw new ServiceException("op service error: " + r.getError());
    }

  }

  protected void fillLMODResidueData(FORMAT type) throws Exception {
    Residue residue;
    JSONArray residues;
    JSONObject jresidue = null;

    Map<String, JSONObject> fileData;
    JSONArray jvalues = new JSONArray();

    for (Event datum : eventData) {
      residue = datum.getResidue();
      if (residue != null) {
        if (type == FORMAT.KEY) {
          jvalues.put(datum.getResidue().getName());
        } else {
          jvalues.put(datum.getResidue().getID());
        }
      }
    }

    ModelDataServiceCall r = new ModelDataServiceCall()
        .put(NATIVE_FORMATS, true)
        .put(LIMIT, "all")
        .put(((type == FORMAT.KEY) ? NAME : ID), jvalues)
        .withDefaultLogger()
        .url(urls.getResidue())
        .call();

    if (r.serviceFinished()) {
      residues = r.getJSONObject("lmod").getJSONArray(RESIDUES);
      fileData = Util.mapResidueResultsByName(residues, type);
      for (Event datum : eventData) {
        if (datum.getResidue() != null) {
          if (type == FORMAT.KEY) {
            jresidue = fileData.get(datum.getResidue().getName());
          } else {
            jresidue = fileData.get(datum.getResidue().getID());
          }

          if (jresidue == null) {
            throw new ServiceException("Residue: " + datum.getResidue().getName() + " is not in CR_LMOD");
          }
          // If we only had the name then we need to fill in the key
          if (type == FORMAT.KEY) {
            datum.getResidue().setID(jresidue.getString(ID));
          }

          datum.getResidue().parseLMODData(jresidue);
        }
      }
    } else {
      throw new ServiceException("op service error: " + r.getError());
    }

  }

  public void rotateEvents() {
    TreeSet<Event> eventSet = new TreeSet();
    TreeSet<Event> newEventSet = new TreeSet();
    int totalCalendarYears = 0;
    int firstYear, lastYear;
    double totalYearDiff;
    int lastOperationDayOfYear;

    for (int i = 0; i < eventData.size(); i++) {
      eventSet.add(eventData.get(i));
    }

    Event lastEvent = eventSet.last();
    Event firstEvent = eventSet.first();

    firstYear = firstEvent.getDate().getYear();
    lastYear = lastEvent.getDate().getYear();
    lastOperationDayOfYear = lastEvent.getDate().getDayOfYear();

    totalCalendarYears = lastYear - firstYear + 1;
    long daysBetween = DAYS.between(firstEvent.getDate(), lastEvent.getDate()) + 1;

    totalYearDiff = daysBetween / 365.0;

    //  Can the rotation be squished into fewer years than the years provided?
    if (Math.ceil(totalYearDiff) < totalCalendarYears) {
      if (firstEvent.getDate().getDayOfYear() > lastEvent.getDate().getDayOfYear()) {

        lastYear = (int) Math.ceil(totalYearDiff);

        //  Need to reorder the list and reset the duration...
        totalCalendarYears = lastYear;
        Iterator it = eventSet.iterator();
        //Move last operation up to firstYear.
        int count = 0;
        int formerYear = -1;

        while (it.hasNext()) {
          Event tEvent = (Event) it.next();

          //  Make sure to increment the counter to reset year values.
          if ((formerYear != tEvent.getDate().getYear()) && (count < totalCalendarYears) && (firstYear < tEvent.getDate().getYear())) {
            count++;
            formerYear = tEvent.getDate().getYear();
          }

          if (firstYear == tEvent.getDate().getYear()) {
            if (tEvent.getDate().getDayOfYear() >= lastOperationDayOfYear) {
              tEvent.setDate(tEvent.getDate().withYear(lastYear));
            }
          } else {
            tEvent.setDate(tEvent.getDate().withYear(count));
          }
          newEventSet.add(tEvent);
        }

        eventSet.clear();
        eventSet.addAll(newEventSet);
      }//  Else...don't need to do anything??
    }
    eventData.clear();
    eventData.addAll(eventSet);
    rotationYears = totalCalendarYears;
  }
}