package nxm.sys.test;

import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.Primitive;
import nxm.sys.lib.Time;
import nxm.sys.lib.TimeLine;

/** Used for testing timeline issues. Outputs a file with a time line.
    <pre>
      IN=      - Input file (required)
      OUT=     - Output file (required)
      TSTART=  - Start time (default is outFile.getStart())
      TDELTA=  - Time delta (default is outFile.getDelta())
      JITTER=  - Max value of jitter to add (default is 0.0)
      TFREQ=   - Frequency of timecode entries (default is 1) (TFREQ=N -&gt; time every Nth transfer)

      /RT      - Real-time (for pipe output)
      /TL=     - Transfer length (default is 32KiB for Type 1000, 1 element for all others)
      /WRAP    - Number of times to wrap input data (time does keeps increasing) (default is 0)
    </pre>
    Examples:
    <pre>
      nM&gt; nxm.sys.test.timelinetester/WRAP=2 IN=apenny.prm OUT=mypenny.prm{YU=TIME} &amp;
      --&gt; TSTART=2000:01:01::00:00:00 TDELTA=0.25 JITTER=0.125
    </pre>
    Note: Since 3.7.2, to allow high precision times, Time objects are used in
    keeping track of times, rather than doubles.

    @author  J. Czechowski IV
    @since   NeXtMidas 2.5.0
*/
public class timelinetester extends Primitive {
  private DataFile inFile;            // Input file
  private DataFile outFile;           // Output file/pipe
  private Data     data;              // Output data buffer
  private boolean  rt;                // The real-time mode
  private int      wrap;              // Number of times to wrap input file
  private double   tdelta;            // File time: delta
  private Time     tcurrent;          // File time: current time
  private Time     tnext;             // System time: next transfer written (use with /RT)
  private double   jitter;            // Max value for jitter
  private int      count;             // Current count for next TimeLine entry
  private int      timeFreq;          // Frequency of timecode entries

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

  @Override
  public int open() {
    inFile = MA.getDataFile("IN");
    inFile.open();

    outFile = MA.getDataFile("OUT", inFile, 0);
    outFile.setDefaultQualifier("TYPE", inFile.getType()); // since 3.5.1 (allows user to override output type) to test BUG 2518
    outFile.open();

    if ((outFile.getType() % 1000 == 1)) {
      xfer = Math.max(1,(int)(bufSize/inFile.dbpe));
    } else {
      xfer = 1; // Default to 1 for Type 2000/3000 files
    }

    xfer      = MA.getL("/TL",xfer);
    data      = inFile.getDataBuffer(xfer);
    wrap      = MA.getL("/WRAP", 0);
    rt        = MA.getState("/RT");
    tcurrent  = new Time(MA.getD("TSTART", outFile.getStart()));
    tdelta    = MA.getD("TDELTA", outFile.getDelta());
    jitter    = MA.getD("JITTER", 0.0);
    timeFreq  = MA.getL("TFREQ", 1);
    count     = timeFreq; //  0  -> Set first time
    tnext     = new Time();      // 0.0 -> First write is immediate
    if (!MA.isPresent("/TLT")  && (outFile.getTimeLineTolerance() > 0.09)) {
      // if not specified via switch (and tolerance has been defaulted to
      // 0.1), change default to microsecond tolerance
      outFile.setTimeLineTolerance(TimeLine.MICROSECOND_TOLERANCE);
    }

    return NORMAL;
  }

  @Override
  public int process() {
    int    status = NORMAL;
    Time time   = Time.currentTime();

    if (rt && (tnext.diff(time) > 0)) {
      status = NOOP;
    } else {
      int n = inFile.read(data);

      if (n>0) {
        if (count == timeFreq) {
          data.setHeader(new Time(tcurrent));
          count = 0;
        }
        count++;
        outFile.write(data,n);

        double dt = tdelta;
        if (jitter != 0.0) {
          dt = tdelta + ((Math.random() * 2.0 - 1.0) * jitter);
        }
        tcurrent.addSec(dt*xfer);            // fixed in 3.9.4 to take into account TL
        tnext    = Time.add(time, dt*xfer);

      } else if (n==0) {
        status = NOOP;
      } else if ((wrap > 0) && (inFile.isFile() ||inFile.isJarFile())) { // Wrap file
        inFile.seek(0.0);
        wrap--;
      } else {
        status = FINISH;
      }
    }

    return status;
  }

  @Override
  public int close() {
    if (inFile  != null) inFile.close();
    if (outFile != null) outFile.close();
    return NORMAL;
  }
}
