/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.iosp.grads;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ucar.nc2.units.DateUnit;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;


/**
 * Class to hold the complexities of the GrADS time dimension
 *
 * @author Don Murray - CU/CIRES
 */
public class GradsTimeDimension extends GradsDimension {
  private static Logger logger = LoggerFactory.getLogger(GradsTimeDimension.class);

  /** time increment periods */
  private final String[] incStr = {"mn", "hr", "dy", "mo", "yr"};

  /** corresponding Calendar periods */
  private final int[] calIncs = {Calendar.MINUTE, Calendar.HOUR, Calendar.DATE, Calendar.MONTH, Calendar.YEAR};

  /** time date formats (input is lowercased so Z is z) */
  private final String[] dateFormats = {"HH:mm'z'ddMMMyyyy", "HH'z'ddMMMyyyy", "ddMMMyyyy", "MMMyyyy"};

  /** The initial time as a TimeStruct */
  private GradsTimeStruct initialTime;

  // J-
  /** time templates */
  private static final String[] timeTemplates = {"%x1", // 1 digit decade
      "%x3", // 3 digit decade
      "%y2", // 2 digit year
      "%y4", // 4 digit year
      "%m1", // 1 or 2 digit month
      "%m2", // 2 digit month (leading zero if needed)
      "%mc", // 3 character month abbreviation
      "%d1", // 1 or 2 digit day
      "%d2", // 2 digit day (leading zero if needed)
      "%h1", // 1 or 2 digit hour
      "%h2", // 2 digit hour
      "%h3", // 3 digit hour (e.g., 120 or 012)
      "%n2", // 2 digit minute; leading zero if needed
      "%f2", // 2 digit forecast hour; leading zero if needed; more digits added for hours >99; hour values increase
             // indefinitely
      "%f3", // 3 digit forecast hour; leading zeros if needed; more digits added for hours >999; hour values increase
             // indefinitely
      "%fn2", // 2 digit forecast minute; leading zero if needed; more digits added for minutes > 99; minute values
              // increase indefinitely (2.0.a9+)
      "%fhn", // forecast time expressed in hours and minutes (hhnn) where minute value (nn) is always <=59
              // and hour value (hh) increases indefinitely. If hh or nn are <=9, they are padded with a 0
              // so they are always at least 2 digits; more digits added for hours >99. (2.0.a9+)
      "%fdhn", // forecast time expressed in days, hours, and minutes (ddhhnn) where minute value (nn) is always <=59,
               // hour value (hh) is always <=23 and day value (dd) increases indefinitely. If dd, hh, or nn are <=9,
               // they are padded with a 0 so they are always at least 2 digits; more digits added for days >99.
               // (2.0.a9+)
      "%j3", // 3 digit julian day (day of year) (2.0.a7+)
      "%t1", // 1 or 2 digit time index (file names contain number sequences that begin with 1 or 01) (2.0.a7+)
      "%t2", // 2 digit time index (file names contain number sequences that begin with 01) (2.0.a7+)
      "%t3", // 3 digit time index (file names contain number sequences that begin with 001) (2.0.a7+)
      "%t4", // 4 digit time index (file names contain number sequences that begin with 0001) (2.0.a8+)
      "%t5", // 5 digit time index (file names contain number sequences that begin with 00001) (2.0.a8+)
      "%t6", // 6 digit time index (file names contain number sequences that begin with 000001) (2.0.a8+)
      "%tm1", // 1 or 2 digit time index (file names contain number sequences that begin with 0 or 00) (2.0.a7+)
      "%tm2", // 2 digit time index (file names contain number sequences that begin with 00) (2.0.a7+)
      "%tm3", // 3 digit time index (file names contain number sequences that begin with 000) (2.0.a7+)
      "%tm4", // 4 digit time index (file names contain number sequences that begin with 0000) (2.0.a8+)
      "%tm5", // 5 digit time index (file names contain number sequences that begin with 00000) (2.0.a8+)
      "%tm6", // 6 digit time index (file names contain number sequences that begin with 000000) (2.0.a8+)

      // When specifying the initial time (e.g., NWP model output), use these substitutions:

      "%ix1", // initial 1 digit decade
      "%ix3", // initial 3 digit decade
      "%iy2", // initial 2 digit year
      "%iy4", // initial 4 digit year
      "%im1", // initial 1 or 2 digit month
      "%im2", // initial 2 digit month (leading zero if needed)
      "%imc", // initial 3 character month abbreviation
      "%id1", // initial 1 or 2 digit day (leading zero if needed)
      "%id2", // initial 2 digit day
      "%ih1", // initial 1 or 2 digit hour
      "%ih2", // initial 2 digit hour
      "%ih3", // initial 3 digit hour
      "%in2" // initial 2 digit minute (leading zero if needed)

  };
  // J+

  /**
   * Create new GradsTimeDimension
   *
   * @param name the dimension name
   * @param size the dimension size
   * @param mapping the dimension mapping type
   */
  public GradsTimeDimension(String name, int size, String mapping) {
    super(name, size, mapping);
  }

  /**
   * Make the level values from the specifications
   *
   * @return the level values
   */
  protected double[] makeLevelValues() {
    List<String> levels = getLevels();
    if (levels == null) {
      return null;
    }
    if (levels.size() != getSize()) {
      // do someting
    }
    // Time is always LINEAR
    int inc = 0;
    double[] vals = new double[getSize()];
    String tstart = levels.get(0).trim().toLowerCase();
    String pattern;
    if (tstart.contains(":")) { // HH:mmZddMMMyyyy
      pattern = dateFormats[0];
    } else if (tstart.contains("z")) { // mmZddMMMyyyy
      pattern = dateFormats[1];
    } else if (Character.isLetter(tstart.charAt(0))) { // MMMyyyy
      pattern = dateFormats[3];
    } else {
      pattern = dateFormats[2]; // ddMMMyyyy
    }
    SimpleDateFormat sdf = new SimpleDateFormat(pattern);
    // sdf.setLenient(true);
    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
    ParsePosition p = new ParsePosition(0);
    Date d = sdf.parse(tstart, p);
    if (d == null) {
      logger.warn("couldn't parse at " + p.getErrorIndex());
      d = new Date(0);
    }
    // set the unit
    sdf.applyPattern("yyyy-MM-dd HH:mm:ss Z");
    setUnit("hours since " + sdf.format(d, new StringBuffer(), new FieldPosition(0)));
    // parse the increment
    // vvkk where
    // vv = an integer number, 1 or 2 digits
    // kk = mn (minute)
    // hr (hour)
    // dy (day)
    // mo (month)
    // yr (year)
    String tinc = levels.get(1).toLowerCase();
    int incIndex = 0;
    for (int i = 0; i < incStr.length; i++) {
      int index = tinc.indexOf(incStr[i]);
      if (index < 0) {
        continue;
      }
      int numOf = Integer.parseInt(tinc.substring(0, index));
      inc = numOf;
      incIndex = i;

      break;
    }
    Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    calendar.setTime(d);
    vals[0] = 0;
    initialTime = makeTimeStruct(calendar);
    int calInc = calIncs[incIndex];
    double hours = (double) 1000 * 60 * 60;
    for (int i = 1; i < getSize(); i++) {
      calendar.add(calInc, inc);
      // subtract from origin, convert to hours
      double offset = (calendar.getTime().getTime() - d.getTime()) / hours; // millis in an hour
      vals[i] = offset;
    }
    return vals;

  }

  /**
   * Make a time struct from the index.
   *
   * @param timeIndex the time value index
   *
   * @return the corresponding TimeStruct
   */
  public GradsTimeStruct makeTimeStruct(int timeIndex) {
    double tVal = getValues()[timeIndex];
    Date d = DateUnit.getStandardDate(tVal + " " + getUnit());
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
    calendar.setTime(d);
    return makeTimeStruct(calendar);
  }

  /**
   * Make a GradsTimeStruct from the calendar state
   *
   * @param calendar the calendar
   *
   * @return the corresponding GradsTimeStruct
   */
  private GradsTimeStruct makeTimeStruct(Calendar calendar) {
    GradsTimeStruct ts = new GradsTimeStruct();
    ts.year = calendar.get(Calendar.YEAR);
    ts.month = calendar.get(Calendar.MONTH) + 1; // MONTH is zero based
    ts.day = calendar.get(Calendar.DAY_OF_MONTH);
    ts.hour = calendar.get(Calendar.HOUR_OF_DAY);
    ts.minute = calendar.get(Calendar.MINUTE);
    ts.jday = calendar.get(Calendar.DAY_OF_YEAR);
    return ts;
  }

  /**
   * Replace the time template parameters in a filename
   *
   * @param filespec the file template
   * @param timeIndex the time index
   *
   * @see "http://www.iges.org/grads/gadoc/templates.html"
   *
   * @return the filled in template
   */
  public String replaceFileTemplate(String filespec, int timeIndex) {

    GradsTimeStruct ts = makeTimeStruct(timeIndex);
    String retString = filespec;
    String format;
    while (hasTimeTemplate(retString)) {
      // initial time
      if (retString.contains("%ix1")) {
        retString = retString.replaceAll("%ix1", String.format("%d", initialTime.year / 10));
      }
      if (retString.contains("%ix3")) {
        retString = retString.replaceAll("%ix3", String.format("%03d", initialTime.year / 10));
      }
      if (retString.contains("%iy2")) {
        int cent = initialTime.year / 100;
        int val = initialTime.year - cent * 100;
        retString = retString.replaceAll("%iy2", String.format("%02d", val));
      }
      if (retString.contains("%iy4")) {
        retString = retString.replaceAll("%iy4", String.format("%d", initialTime.year));
      }
      if (retString.contains("%im1")) {
        retString = retString.replaceAll("%im1", String.format("%d", initialTime.month));
      }
      if (retString.contains("%im2")) {
        retString = retString.replaceAll("%im2", String.format("%02d", initialTime.month));
      }
      if (retString.contains("%imc")) {
        retString = retString.replaceAll("%imc", GradsTimeStruct.months[initialTime.month - 1]);
      }
      if (retString.contains("%id1")) {
        retString = retString.replaceAll("%id1", String.format("%d", initialTime.day));
      }
      if (retString.contains("%id2")) {
        retString = retString.replaceAll("%id2", String.format("%02d", initialTime.day));
      }
      if (retString.contains("%ih1")) {
        retString = retString.replaceAll("%ih1", String.format("%d", initialTime.hour));
      }
      if (retString.contains("%ih2")) {
        retString = retString.replaceAll("%ih2", String.format("%02d", initialTime.hour));
      }
      if (retString.contains("%ih3")) {
        retString = retString.replaceAll("%ih3", String.format("%03d", initialTime.hour));
      }
      if (retString.contains("%in2")) {
        retString = retString.replaceAll("%in2", String.format("%02d", initialTime.minute));
      }
      // any time
      // decade
      if (retString.contains("%x1")) {
        retString = retString.replaceAll("%x1", String.format("%d", ts.year / 10));
      }
      if (retString.contains("%x3")) {
        retString = retString.replaceAll("%x3", String.format("%03d", ts.year / 10));
      }
      // year
      if (retString.contains("%y2")) {
        int cent = ts.year / 100;
        int val = ts.year - cent * 100;
        retString = retString.replaceAll("%y2", String.format("%02d", val));
      }
      if (retString.contains("%y4")) {
        retString = retString.replaceAll("%y4", String.format("%d", ts.year));
      }
      // month
      if (retString.contains("%m1")) {
        retString = retString.replaceAll("%m1", String.format("%d", ts.month));
      }
      if (retString.contains("%m2")) {
        retString = retString.replaceAll("%m2", String.format("%02d", ts.month));
      }
      if (retString.contains("%mc")) {
        retString = retString.replaceAll("%mc", GradsTimeStruct.months[ts.month - 1]);
      }
      // day
      if (retString.contains("%d1")) {
        retString = retString.replaceAll("%d1", String.format("%d", ts.day));
      }
      if (retString.contains("%d2")) {
        retString = retString.replaceAll("%d2", String.format("%02d", ts.day));
      }
      // hour
      if (retString.contains("%h1")) {
        retString = retString.replaceAll("%h1", String.format("%d", ts.hour));
      }
      if (retString.contains("%h2")) {
        retString = retString.replaceAll("%h2", String.format("%02d", ts.hour));
      }
      if (retString.contains("%h3")) {
        retString = retString.replaceAll("%h3", String.format("%03d", ts.hour));
      }
      // minute
      if (retString.contains("%n2")) {
        retString = retString.replaceAll("%n2", String.format("%02d", ts.minute));
      }
      // julian day
      if (retString.contains("%j3")) {
        retString = retString.replaceAll("%j3", String.format("%03d", ts.jday));
      }
      // time index (1 based)
      if (retString.contains("%t1")) {
        retString = retString.replaceAll("%t1", String.format("%d", timeIndex + 1));
      }
      if (retString.contains("%t2")) {
        retString = retString.replaceAll("%t2", String.format("%02d", timeIndex + 1));
      }
      if (retString.contains("%t3")) {
        retString = retString.replaceAll("%t3", String.format("%03d", timeIndex + 1));
      }
      if (retString.contains("%t4")) {
        retString = retString.replaceAll("%t4", String.format("%04d", timeIndex + 1));
      }
      if (retString.contains("%t5")) {
        retString = retString.replaceAll("%t5", String.format("%05d", timeIndex + 1));
      }
      if (retString.contains("%t6")) {
        retString = retString.replaceAll("%t6", String.format("%06d", timeIndex + 1));
      }
      // time index (0 based)
      if (retString.contains("%tm1")) {
        retString = retString.replaceAll("%tm1", String.format("%d", timeIndex));
      }
      if (retString.contains("%tm2")) {
        retString = retString.replaceAll("%tm2", String.format("%02d", timeIndex));
      }
      if (retString.contains("%tm3")) {
        retString = retString.replaceAll("%tm3", String.format("%03d", timeIndex));
      }
      if (retString.contains("%tm4")) {
        retString = retString.replaceAll("%tm4", String.format("%04d", timeIndex));
      }
      if (retString.contains("%tm5")) {
        retString = retString.replaceAll("%tm5", String.format("%05d", timeIndex));
      }
      if (retString.contains("%tm6")) {
        retString = retString.replaceAll("%tm6", String.format("%06d", timeIndex));
      }
      // forecast hours
      if (retString.contains("%f")) {
        int mins = (int) getValues()[timeIndex] * 60;
        int tdif;
        if (retString.contains("%f2")) {
          format = "%02d";
          tdif = mins / 60;
          if (tdif > 99) {
            format = "%d";
          }
          retString = retString.replaceAll("%f2", String.format(format, tdif));
        }
        if (retString.contains("%f3")) {
          format = "%03d";
          tdif = mins / 60;
          if (tdif > 999) {
            format = "%d";
          }
          retString = retString.replaceAll("%f3", String.format(format, tdif));
        }
        if (retString.contains("%fn2")) {
          format = "%02d";
          if (mins > 99) {
            format = "%d";
          }
          retString = retString.replaceAll("%fn2", String.format(format, mins));
        }
        if (retString.contains("%fhn2")) {
          tdif = mins;
          int hrs = tdif / 60;
          int mns = tdif - (hrs * 60);
          format = "%02d%02d";
          if (hrs > 99) {
            format = "%d%02d";
          }
          retString = retString.replaceAll("%fhn2", String.format(format, hrs, mns));
        }
        if (retString.contains("%fdhn2")) {
          tdif = mins;
          int dys = tdif / 1440;
          int hrs = (tdif - (dys * 1440)) / 60;
          int mns = tdif - (dys * 1440) - (hrs * 60);
          format = "%02d%02d%02d";
          if (dys > 99) {
            format = "%d%02d%02d";
          }
          retString = retString.replaceAll("%fdhn2", String.format(format, dys, hrs, mns));
        }
      }
    }
    return retString;

  }

  /**
   * Does this file definition have a time template in it?
   * 
   * @param template the file template
   * @return true if it does
   */
  public static boolean hasTimeTemplate(String template) {
    for (String timeTemplate : timeTemplates) {
      if (template.contains(timeTemplate)) {
        return true;
      }
    }
    return false;
  }

}

