package nxm.dsp.test;

import nxm.sys.inc.ProvisionalUseOnly;
import nxm.sys.lib.BaseFile;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Primitive;

/** Transforms a 1000 filter to the form needed by NEWRATE.<br>
    Since NEWRATE actually uses correlation, rather than convolution,
    the filter needs to be reversed in addition to the commutation needed
    to spread the filter taps into upsampling FACTORa separate filters.<br>
    The last operation needed, for the buffer indexing to work properly in NEWRATE,
    is for an extra filter / frame to be added at the end of the 2000 file.<br>
    Note: These adjustments DO NOT take into account that you may want to zero-pad
    the front of your data by NTAPS (each sub-filter length), as NEWRATE only outputs
    the steady state data.

    <pre>
    A 17 point filter with the taps being t0 ... t16 and each row below being a frame:

    t16  t12  t8   t4  t0
    0    t13  t9   t5  t1
    0    t14  t10  t6  t2
    0    t15  t11  t7  t3
    0    t16  t12  t8  t4

    16 point filter example:

    t12  t8   t4  t0
    t13  t9   t5  t1
    t14  t10  t6  t2
    t15  t11  t7  t3
    0    t12  t8  t4

    16 point with method used by X-Midas, based on the symmetry of the filters:

    t3  t7  t11  t15
    t2  t6  t10  t14
    t1  t5  t9   t13
    t0  t4  t8   t12
    0   t3  t7   t11

    </pre>

    @author Jeanette McCall
    @since  NeXtMidas 3.9.1
 */
@ProvisionalUseOnly("Minimally tested for educational purposes")
public class filter4newrate extends Primitive {

  private DataFile inFile;      // The input file
  private DataFile outFile;     // The output file
  private Data     dataBuffer;  // The data buffer holding input data
  private Data     outBuffer;   // The data buffer holding output data
  private int      factor;      // the upsampling factor (output samples to input samples ratio)
  private int      inSize;      // number of taps in original filter
  private int      outSize;     // number of frames / filters in 2000 output (includes extra)
  private int      frameSize;   // frame size (i.e. NTAPS/filter) in output file
  private int      dbpe;
  float[]  filter1000;  // incoming 1000 filter

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

  @Override
  public int open() {

    inFile = MA.getDataFile("IN", "1000", "S#", 0); // only allow type 1000 files
    inFile.open();
    double inXDelta = inFile.getXDelta();
    double xStart = inFile.getXStart();
    String format = inFile.getFormat();
    inSize = (int)inFile.getSize();

    factor = MA.getL("FACTOR",1);

    // we will pad zeros if needed to fill out the frames that are short
    frameSize = (int)Math.ceil(inSize / (double)factor);
    dbpe   = (int)(inFile.getBPE()*frameSize);

    outFile = MA.getDataFile("OUT","2000",format,0);
    outFile.setSubSize(frameSize);
    outFile.setXStart(xStart);
    outFile.setXDelta(inXDelta*frameSize);
    outFile.setYDelta(inXDelta);
    outFile.setInternals();
    outFile.open(BaseFile.OUTPUT);

    dataBuffer = inFile.getDataBuffer(inSize,Data.DOUBLE);
    outSize = factor + 1; // account for extra frame NEWRATE needs
    outBuffer = outFile.getDataBuffer(outSize,Data.DOUBLE);

    return NORMAL;
  }

  @Override
  public int process() {

    read1000Filter();
    create2000();
    outFile.write(outBuffer,0,outSize*dbpe);

    return FINISH;
  }

  @Override
  public int close() {
    inFile.close();
    outFile.close();
    return NORMAL;
  }

  private void read1000Filter () {
    int nGot = inFile.read(dataBuffer, inSize);
    filter1000 = dataBuffer.castF(true);

    if (filter1000.length != inSize) {
      throw new MidasException("Failed to read all "+inSize+
          " elements from filter file - only got "+nGot+" from"
          + inFile.getName());
    }
  }

  /** create 2000 filter set from 1000 filter*/
  private void create2000 () {
    float[] filter2000 = outBuffer.castF(false);

    // reverse and commutate input filter
    int i = filter1000.length - 1; // start at end of input filter
    for (int tap = frameSize-1; tap >= 0; tap--) {
      // fill in each sub-filter's taps in reverse order
      for (int f = 0; f < outSize-1; f++) {
        // spread data across sub-filters / frames
        filter2000[f*frameSize+tap] = (i >= 0) ? filter1000[i--] : 0;
      }
    }

    // fill in extra filter / frame needed by NEWRATE
    // without this extra filter, the indexing into NEWRATE filter buffer will be incorrect
    // this extra filter is just the first sub-filter with a phase offset
    int idxFillerFrame = (outSize-1)*frameSize;
    filter2000[idxFillerFrame] = 0;
    for (int tap = 1; tap < frameSize; tap++) {
      filter2000[idxFillerFrame+tap] = filter2000[tap];
    }

    // ready to put values in dataBuffer
    outBuffer.uncast(filter2000, true);

  }

}
