package nxm.sys.intr;

import java.text.ParseException;

import nxm.sys.lib.BaseFile;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.Intrinsic;
import nxm.sys.lib.KeyObject;
import nxm.sys.lib.MFormat;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Time;

/**
  Handle time functions for the macro language.

  @author Jeff Schoen
*/
public class Timex extends Intrinsic {
  /** seconds in a year */
  public static final double SECS_IN_YEAR = 86400.0;
  private static final int ARG_INPUT_TIME = 1;

  /** default constructor */
  public Timex () {}

  public int process () {
    DataFile hcb;

    // get format switches
    int     dp        = MA.getL("/PREC", -1);   // -1 uses default precision
    String  inFmt     = MA.getS("/INFMT",null);
    String  outFmt    = MA.getS("/FMT",  null);
    MFormat outFormat;  // Wait until have Time to initialize - Since 3.5.2 Bug 2864

    // default to current CPU time
    Time time = Time.currentTime();
    boolean hasOutput = false;

    // loop through args and perform functions
    for (int n=1; n<=MA.numberOf(); n++) {
      String key   = MA.getKey(n);
      String label = MA.getU(n);

      boolean handled = true;
      if (key.equals("TIME") || key.equals("P"+n)) {
             if (label.equals("NOW")); // already done
        else if (label.equals("DAY"))        { time.floor(SECS_IN_YEAR);  }
        else if (label.equals("SSM"))        { time.modulo(SECS_IN_YEAR); }
        else if (label.equals("TERM"))       { M.println("Time = "+time); hasOutput=true; } // since 3.7.1 using M.println instead of M.terminal.writeln to allow logging - NXM-1813
        else if (label.equals("TERMINAL"))   { M.println("Time = "+time); hasOutput=true; }
        else if (n==ARG_INPUT_TIME)          { time = getInputTime(inFmt, time);}
        else {
          // get the outFormat as a TimeFormat, simpleDateFormat, or simpleDateFormat
          // adjusted for high precision   Since 3.5.2  Bug 2864
          outFmt = time.createHighPrecisionSimpleDateFormat(outFmt);
          outFormat = (outFmt == null)? null : MFormat.getDateFormatFor(outFmt,dp);
          if (outFormat == null)             { MR.put(label,time); hasOutput=true; }
          else                               { MR.put(label,outFormat.format(time)); hasOutput=true; }
        }
      }
      else if (key.equals("INFILE")) {
        hcb = MA.getDataFile("INFILE");
        hcb.open(); time.fromTimeCode(hcb); hcb.close();
      }
      else if (key.equals("INACQ")) {
        hcb = MA.getDataFile("INACQ");
        hcb.open(); time.fromAcqDate(hcb); hcb.close();
      }
      else if (key.equals("INQUAD")) {
        hcb = MA.getDataFile("INQUAD");
        hcb.open(); time.fromQuadwords(hcb); hcb.close();
      }
      else if (key.equals("ROUND")) {
        double sec = MA.getD("ROUND");
        time.round(sec);
      }
      else {
        handled = false;
      }
      if (handled) continue;
      hasOutput=true;
      if (n==1) {
         String extra = ("FILE,ACQ,QUAD".indexOf(key)>=0) ? "- Try IN"+key : "";
         M.error("Need an input parameter in 1st slot "+extra);
      }
      else if (key.equals("FILE")) {
        hcb = MA.getDataFile("FILE");
        hcb.open(BaseFile.INOUT); time.toTimeCode(hcb); hcb.close();
      }
      else if (key.equals("ACQ")) {
        hcb = MA.getDataFile("ACQ");
        hcb.open(BaseFile.INOUT); time.toAcqDate(hcb); hcb.close();
      }
      else if (key.equals("QUAD")) {
        hcb = MA.getDataFile("QUAD");
        hcb.open(BaseFile.INOUT); time.toQuadwords(hcb); hcb.close();
      }
      else {
             if (key.equals("SEC")) key = "MJS";
        else if (key.equals("JUL")) key = "JD";
        else if (key.equals("Y"))   key = "YEAR";
        else if (key.equals("FN"))  key = "TOFILENAME";
        Object obj = KeyObject.getKey(time,key);
        if (obj==null) M.error("Illegal time key: "+key+"="+label);
        MR.put(label,obj);
      }
    }

    // default to terminal output
    if (!hasOutput) {
      // get the outFormat as a TimeFormat, simpleDateFormat, or simpleDateFormat
      // adjusted for high precision   Since 3.5.2  Bug 2864
      outFmt = time.createHighPrecisionSimpleDateFormat(outFmt);
      outFormat = (outFmt == null)? null : MFormat.getDateFormatFor(outFmt,dp);
      // write to terminal
      if (outFormat == null) M.println("Time = "+time.toString(dp));
      else                   M.println("Time = "+outFormat.format(time));
    }

    return FINISH;
  }

  private Time parseTime (MFormat fmt, String str) {
    try {
      Time t = Time.toTime(fmt.parseObject(str));

      if (t == null) {
        throw new MidasException("Unable to parse '"+str+"' using "+fmt+".");
      }
      return t;

    } catch (ParseException e) {
      throw new MidasException("Unable to parse '"+str+"': "+e.getMessage(), e);
    }
  }

  /**
     Get Input Time Argument in Format Specified

     Input Time Format can be:
     1) A format in the Time.formatList
     2) A SimpleDateFormat (with milliseconds up to SSS)
     3) An extended version of SimpleDateFormat
        -4 or more Ss in a row stand for number of digits of fractional
         seconds, rather than millisecond

     Process for high precision (enhanced) simpleDateFormat Strings
     1) Get location of high precision fractional seconds from format string
     2) pull out fractional seconds and covert to double
     3) remove SSS...S from format string
     4) remove fractional seconds from input String
     5) process remaining incoming string with simpleDateFormat using remaining format String

     @param inFmt format of incoming Time String
     @param time current value of time
     @return new value matching input Time String
     @since 3.5.2 Bug 2864
   */
  private Time getInputTime (String inFmt, Time time) {
    String inTimeArg = MA.getRaw(MA.getKey(ARG_INPUT_TIME));
    if (inFmt != null) {
      time = new Time(inTimeArg, inFmt); // switched to using new Time constructor in NeXtMidas 3.5.4
    }
    else {
      time = MA.getTime(ARG_INPUT_TIME);
    }
    return time;
  }
}
