package nxm.sys.prim;

import nxm.sys.inc.Units;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.Primitive;
import nxm.sys.lib.Table;

/**
  Copies data from one file to another with start, length, and decimation.
  Negative or fractional decimation performs opposite function with zero fill.

  @author Jeff Schoen
*/
public class thin extends Primitive {

  Data dbi,dbo;
  DataFile hi,ho;
  double dstart,dend,dinc;
  double fdstr,fdend,fdinc;
  int inc,bpe,stride;
  int finc,fbpe,fstride,foff,nsub;
  int off,curoff=0;
  int addressingMode;
  boolean xsub;

  /** flag to enable/disable the output pipe from restarting when certain properties (inc, start, end, and fend) are changed. */
  boolean enablerestart = true;

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

  @Override
  public int open () {

    hi = MA.getDataFile("IN");
    hi.open();

    if (state != RESTART) {
      addressingMode = MA.getAddressing();
      String startStr = MA.getU("START");
      String endStr   = MA.getU("END");
      String incStr   = MA.getU("INC");
      dstart = hi.getIndex(startStr,DataFile.ISTART, addressingMode);
      dend   = hi.getIndex(endStr,  DataFile.IEND,  dstart, addressingMode);
      dinc   = hi.getIndex(incStr,  DataFile.RDELTA, addressingMode);
      if (endStr.equals("-1")) M.warning("Setting END to -1 addresses EOF-1 not EOF. Use -0 for EOF.");

      // print warning for change in behavior of tilde addressing in 2.9.1 (can remove after two stable releases)
      if (hi.getUnits() == Units.TIME && !MA.hasAddressing()) { // for BUG 2421
        if ( ((dstart > 100e6) && startStr.startsWith("~")) ||
             ((dend   > 100e6) &&   endStr.startsWith("~")) ) {
          M.warning("Behavior change since 2.9.1 for abscissa addressing using tilde.");
          M.warning("You may need to add /ADDRESSING=timecode or /ABSC switch to get desired result.");
        }
      }
    }
    if (dend > hi.getSize()) dend = hi.getSize(); // BUG 1995: Prevent output larger than input

    inc = (int)dinc;
    if (dinc<-1) dinc = -1.0/dinc;
    else if (dinc<=0) { dinc=1; inc=1; }
    else if (dinc<1) { inc=-(int)(1/dinc+.1); }
    bpe = (int)hi.getBPE();
    if (inc<0) stride = -inc*bpe;
    else stride = inc*bpe;

    if (hi.typeClass==2) {  // check for 2D inter-frame thin
      if (state != RESTART) {
        fdstr = hi.getIndex(MA.getU("FSTART"),DataFile.JSTART, 0.0, addressingMode);
        fdend = hi.getIndex(MA.getU("FEND"),  DataFile.JEND, fdstr, addressingMode);
        fdinc = hi.getIndex(MA.getU("FINC"),  DataFile.JDELTA, 0.0, addressingMode);
      }
      finc  = Math.max(0,(int)(fdinc+.5));
      xsub  = fdstr!=0.0 || fdend!=hi.getSubSize() || finc!=1;
      nsub  = (int)Math.round((fdend-fdstr)/fdinc);
      fbpe  = hi.getBPA();
      foff  = Math.max(0,(int)fdstr)*fbpe;
      fstride = fbpe*finc;
    }

    ho = MA.getDataFile ("OUT",hi,0);
    ho.setStart(hi.getStart()+hi.getDelta()*dstart);
    ho.setDelta(hi.getDelta()*dinc);
    ho.setSize(Math.ceil((dend-dstart)/dinc+.1));
    if (xsub) ho.setXStart(hi.getXStart()+hi.getXDelta()*fdstr);
    if (xsub) ho.setXDelta(hi.getXDelta()*fdinc);
    if (xsub) ho.setSubSize(nsub);
    ho.open();

    xfer = Math.max(1, (bufSize/stride));
    if (xsub || stride>1024) xfer=1;
    xfer = MA.getL("/TL", xfer);

    setupDataBuffers();

    off = MA.getL("/OFFSET",0);
    enablerestart = MA.getState("/RESTART", true); // since 2.9.2; TODO: deprecate in future and do something like /restartprops=mask?

    hi.seek(dstart);
    hi.setSize(dend);
    return NORMAL;
  }

  private void setupDataBuffers () {
    if (xfer==1) {
      dbo = dbi = hi.getDataBuffer(1);
      if (xsub) dbo = ho.getDataBuffer(1);
    }
    else if (inc<0) {
      dbi = hi.getDataBuffer(xfer);
      dbo = ho.getDataBuffer(-inc*xfer);
    }
    else {
      dbo = dbi = hi.getDataBuffer(xfer*inc);
    }
  }

  @Override
  public int process () {

    int n = hi.read(dbi);
    if (n==0) return NOOP;
    if (n<0)  return FINISH;

    if (xfer==1)    {
      int newoff = off;
      if (inc>1) hi.skip(newoff-curoff+inc-1);
      curoff = newoff;
      if (xsub) Convert.move(dbi.buf,foff,fstride, dbo.buf,0,fbpe, fbpe,nsub);
    }
    else if (inc>1) { n=(n+inc-1)/inc; Convert.move (dbi.buf,off*bpe,stride, dbo.buf,0,bpe, bpe,n); }
    else if (inc<0) { Convert.move (dbi.buf,0,bpe, dbo.buf,0,stride, bpe,n); n=-inc*n; }

    ho.write(dbo,n);
    return NORMAL;
  }

  @Override
  public int close () {
    hi.close();
    ho.close();
    return NORMAL;
  }

  /** Set run-time start offset [0:inc]
      @param off run time offset */
  public void setOffset (int off) {
    this.off = off % inc;
  }
  /** Get run-time start offset
      @return run time offset */
  public int getOffset () {
    return off;
  }

  /** Set the increment. Non-int values have special meaning. See Explain file
      for details.<br><br>
      <b>NOTE:</b>This will trigger a restart unless /RESTART is set to false.
      @param dinc data increment
      @since NeXtMidas 2.9.0
   */
  public void setInc (String dinc) {
    // this method added in 2.9.0 for Bug 2280
    this.dinc = hi.getIndex(dinc, DataFile.RDELTA, 0.0, addressingMode);  // inc will get set off of dinc value
    if (enablerestart) setState(RESTART);
  }

  /** Get the (absolute index) increment
      @return the (absolute index) increment */
  public int getInc () { return inc; }

  /** Set the start.<br><br><b>NOTE:</b>This will trigger a restart unless /RESTART is set to false.
      @param start the start index */
  public void setStart (String start) {
    this.dstart = hi.getIndex(start, DataFile.ISTART, 0.0, addressingMode);
    if (enablerestart) setState(RESTART);
  }
  /** Get the absolute start index.
      @return absolute start index */
  public double getStart () { return dstart; }

  /** Set the end.<br><br><b>NOTE:</b>This will trigger a restart unless /RESTART is set to false.
      @param end the end of the file */
  public void setEnd (String end) {
    this.dend = hi.getIndex(end,  DataFile.IEND, dstart, addressingMode);
    if (enablerestart) setState(RESTART);
  }
  /** Get the absolute end index.
      @return absolute end index */
  public double getEnd () { return dend; }

  /** Reset the frame start, stop, and increment for Type 2000 files. If you do
      not want to set all of the values, supply a null or empty String for the unwanted ones.
      <br><br>
      NOTE: This method will always cause a RESTART. If you do not wish a RESTART,
      call {@link #setFInc(String)} and / or {@link #setFStart(String)} separately
      ({@link #setFEnd(String)} will always cause a RESTART).
      @param fstart Starting frame offset
      @param fend   Ending frame offset
      @param finc   Frame increment
      @since NeXtMidas 2.7.3
   */
  public void setFVals (String fstart, String fend, String finc) {
    if (hi.typeClass != 2) return; // only for Type 2000 files
    setFInc(finc);
    setFStart(fstart);
    setFEnd(fend);
  }

  /** Reset the frame start, stop, and increment for Type 2000 files. If you do
      not want to set all of the values, do not supply the unwanted ones.
      <pre>
       Keys:
          FSTART
          FEND
          FINC
      </pre>
      NOTE: This method will always cause a RESTART unless /RESTART is set to false. If you do not wish a RESTART,
      call {@link #setFInc(String)} and / or {@link #setFStart(String)} separately
      ({@link #setFEnd(String)} will always cause a RESTART).
      @param fvals Table with new values
      @since NeXtMidas 2.7.3
  */
  public void setFVals (Table fvals) {
    if (hi.typeClass != 2 || fvals == null) return; // Type 2000 only
    setFVals(fvals.getS("FSTART",""), fvals.getS("FEND",""), fvals.getS("FINC",""));
  }

  /** Set the frame increment (Type 2000 files only)
      @param sval frame increment (as String)
      NOTE: Does not cause a RESTART.
      @since NeXtMidas 2.7.3
   */
  public void setFInc (String sval) {
    if (hi.typeClass != 2) return; // only for Type 2000 files
    if (sval != null && !sval.equals("")) fdinc = hi.getIndex(sval, DataFile.JDELTA, addressingMode);
  }
  /** Get the frame increment (Type 2000 file only).
      @return the frame increment */
  public int getFInc () { return finc; }

  /** Set the starting frame offset (Type 2000 file only)
      NOTE: Does not cause a restart
      @param sval new FStart value
      @since NeXtMidas 2.7.3
   */
  public void setFStart (String sval) {
    if (hi.typeClass != 2) return; // only for Type 2000 files
    if (sval != null && !sval.equals("")) fdstr = hi.getIndex(sval, DataFile.JSTART, addressingMode);
  }
  /** Get the starting frame offset (Type 2000 file only).
      @return starting frame offset */
  public double getFStart () { return this.fdstr; }

  /** Set the ending frame offset (Type 2000 file only)
      NOTE: Causes a restart unless /RESTART is set to false.
      @param sval new FEnd value
      @since NeXtMidas 2.7.3
   */
  public void setFEnd (String sval) {
    if (hi.typeClass != 2) return; // only for Type 2000 files
    if (sval != null && !sval.equals("")) fdend = hi.getIndex(sval, DataFile.JEND, addressingMode);
    if (enablerestart) setState(RESTART);
  }
  /** Get the ending frame offset (Type 2000 file only)
      @return ending frame offset */
  public double getFEnd () { return this.fdend; }

}
