/*
 * 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 d.climate.dssat;

import csip.Client;
import csip.Config;
import csip.ModelDataService;
import static csip.ModelDataService.ERROR;
import static csip.ModelDataService.KEY_METAINFO;
import static csip.ModelDataService.KEY_PARAMETER;
import static csip.ModelDataService.KEY_STATUS;
import csip.ServiceException;
import csip.annotations.Description;
import csip.annotations.Name;
import csip.annotations.Polling;
import csip.annotations.VersionInfo;
import csip.utils.JSONUtils;
import java.io.File;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.Path;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

/**
 *
 * @author sidereus
 */
@Name("DSSAT weather")
@Description("Gets DSSAT compliant weather data")
@VersionInfo("1.0")
@Path("d/csm_weather/1.0")
@Polling(first = 100, next = 100)
public class V1_0 extends ModelDataService {

  private final static String DATE = "date";
  private final static String DAYLENGTH = "dayl";
  private final static String PRECIP = "prcp";
  private final static String SHORTWAVE = "srad";
  private final static String SWE = "swe";
  private final static String TMAX = "tmax";
  private final static String TMIN = "tmin";
  private final static String VP = "vp";

  private static final String LONGITUDE = "longitude";
  private static final String LATITUDE = "latitude";
  private static final String START_DATE = "start_date";
  private static final String END_DATE = "end_date";
  private static final String FILE_NAME = "file_name";

  @Override
  public void doProcess() throws ServiceException {
    double longitude = parameter().getDouble(LONGITUDE);
    double latitude = parameter().getDouble(LATITUDE);
    String start_date = parameter().getString(START_DATE);
    String end_date = parameter().getString(END_DATE);
    String fileName = parameter().getString(FILE_NAME);

    // temporary for concurrent service calls debugging
    String ss = null;
    try {
      ss = (String) getMetainfo().get("uid");
    } catch (JSONException ex) {
      Logger.getLogger(V1_0.class.getName()).log(Level.SEVERE, null, ex);
    }
    JSONArray climateData = getClimate(longitude, latitude, start_date, end_date);
    File wthfile;
    try {
      wthfile = buildDSSATweather(climateData, longitude, latitude, fileName, ss);
    } catch (JSONException ex) {
      throw new RuntimeException(ex);
    }
    results().put(wthfile);
  }

  private File buildDSSATweather(JSONArray climateData, double longitude, double latitude, String fileName, String ss) throws JSONException {
    DecimalFormat df3 = new DecimalFormat("#.###");
    JSONObject elev = climateData.getJSONObject(0);
    double elevation = elev.getDouble(KEY_VALUE);
    JSONObject data = climateData.getJSONObject(1);
    JSONArray ld = data.getJSONArray(KEY_VALUE);

    List<String> fileLine = new ArrayList();

    JSONArray vars = ld.getJSONArray(0);
    Map<String, Integer> vars_ind = new HashMap();

    for (int i = 0; i < vars.length(); i++) {
      vars_ind.put(vars.getString(i), i);
    }

    LongTermAverage temperatureAvg = new LongTermAverage();

    for (int i = 1; i < ld.length(); i++) { // skip list of variables for now

      StringBuilder tmp_strbuild = new StringBuilder();
      JSONArray tmp = ld.getJSONArray(i);
      String tmpdate = tmp.getString(vars_ind.get(DATE));
      tmp_strbuild.append(getDSSATdate(tmpdate));
      double solar_radiation = shortwaveToSolar(tmp.getDouble(vars_ind.get(SHORTWAVE)),
          tmp.getDouble(vars_ind.get(DAYLENGTH)));
      tmp_strbuild.append(space7format(solar_radiation));

      double tmp_tmax = tmp.getDouble(vars_ind.get(TMAX));
      tmp_strbuild.append(space7format(tmp_tmax));

      double tmp_tmin = tmp.getDouble(vars_ind.get(TMIN));
      tmp_strbuild.append(space7format(tmp_tmin));

      int month = new Integer(tmpdate.split("-")[1]);
      temperatureAvg.add(month, tmp_tmin, tmp_tmax);

      double tmp_prec = tmp.getDouble(vars_ind.get(PRECIP));
      tmp_strbuild.append(space7format(tmp_prec));
      fileLine.add(tmp_strbuild.toString());

    }

    if (!fileName.endsWith(".WTH")) {
      fileName = fileName + ".WTH";
    }

    WTHbuilder fileBuilder = new WTHbuilder();
    return fileBuilder.getWeatherFile(fileName, Double.parseDouble(df3.format(latitude)),
        Double.parseDouble(df3.format(longitude)), elevation, temperatureAvg.getLongTermAvg(),
        temperatureAvg.getAmplitude(), fileLine.iterator(), ss);

  }

  private String space7format(double value) {
    DecimalFormat df1 = new DecimalFormat("#.#");
    return String.format("%7s", df1.format(value));
  }

  private double shortwaveToSolar(double shortwave, double daylength) {
    return shortwave * daylength / 1000000;
  }

  private String getDSSATdate(String date) {
    String[] splitDate = date.split("-");
    String formattedDayOfYear = getFormattedDayOfTheYear(splitDate);
    String year = splitDate[0].substring(2);
    return year + formattedDayOfYear;
  }

  private String getFormattedDayOfTheYear(String[] date) {
    Calendar c = Calendar.getInstance();
    Integer year = new Integer(date[0]);
    Integer month = new Integer(date[1]);
    month -= 1; // numbering start from 0
    Integer day = new Integer(date[2]);
    c.set(year, month, day);
    return String.format("%03d", c.get(Calendar.DAY_OF_YEAR));
  }

  private JSONObject getClimateRequest(double longitude, double latitude, String start_date, String end_date) throws JSONException {
    JSONObject climateRequest = new JSONObject();

    JSONObject meta = new JSONObject();
    JSONArray paramObj = new JSONArray();
    JSONObject lonObj = new JSONObject();
    JSONObject latObj = new JSONObject();
    JSONObject startdate_json = new JSONObject();
    JSONObject enddate_json = new JSONObject();

    climateRequest.put(KEY_METAINFO, meta);
    climateRequest.put(KEY_PARAMETER, paramObj);

    paramObj.put(lonObj);
    lonObj.put(KEY_NAME, LONGITUDE);
    lonObj.put(KEY_VALUE, longitude);

    paramObj.put(latObj);
    latObj.put(KEY_NAME, LATITUDE);
    latObj.put(KEY_VALUE, latitude);

    paramObj.put(startdate_json);
    startdate_json.put(KEY_NAME, START_DATE);
    startdate_json.put(KEY_VALUE, start_date);

    paramObj.put(enddate_json);
    enddate_json.put(KEY_NAME, END_DATE);
    enddate_json.put(KEY_VALUE, end_date);

    return climateRequest;
  }

  private JSONArray getClimate(double longitude, double latitude, String start_date, String end_date) {

    JSONObject climateRequest;
    try {
      climateRequest = getClimateRequest(longitude, latitude, start_date, end_date);
    } catch (JSONException ex) {
      throw new RuntimeException(ex);
    }

    JSONObject climateResponse;
    Map<String, JSONObject> resultList;

    LOG.info("CLIMATE Request: " + climateRequest.toString());
    JSONArray tmp = null;
    try {
      climateResponse = new Client().doPOST(Config.getString("climate.daymet"), climateRequest);
      LOG.info("CLIMATE Response: " + climateResponse.toString());
      JSONObject responseMeta = climateResponse.getJSONObject(KEY_METAINFO);
      if (!responseMeta.getString(KEY_STATUS).equalsIgnoreCase(FAILED)) {
        resultList = JSONUtils.getResults(climateResponse);
        tmp = JSONUtils.getJSONArrayParam(resultList, "output");
        if (tmp.length() > 2) {
          String msg = "JSON result has more than 2 elements. ";
          msg += "Please check daymet web-service.";
          throw new UnsupportedOperationException(msg);
        }

      } else {
        String serviceUrl = responseMeta.getString(KEY_SERVICE_URL);
        String error = responseMeta.getString(ERROR);
        String msg = "Error calling: " + serviceUrl;
        msg += " error: " + error;
        throw new ServiceException(msg);
      }
    } catch (ServiceException ex) {
      throw new RuntimeException(ex);
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }

    return tmp;

  }

}
