package nxm.sys.test;

import java.util.ArrayList;

import nxm.sys.inc.ProvisionalUseOnly;
import nxm.sys.lib.BaseFile;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.FileName;
import nxm.sys.lib.Primitive;
import nxm.sys.lib.Shell;
import nxm.sys.prim.thin;

/** Commutates a 1000 file to form a 2000 file with the specified number of elements<br>
    data ends up in the form:<br>
    <pre>
    i[0]       i[elem]      i[elem*2]    ...
    i[1]       i[elem+1]    i[elem*2+1]  ...
    ...        ...          ...
    i[elem-1]  i[elem*2-1]  i[elem*3+1]  ...
    </pre>
    Note: The design of using thinned files was chosen to allow a large input file
    @author Jeanette McCall
    @since  NeXtMidas 3.9.1
*/
@ProvisionalUseOnly("This is primarily for testing and educational purposes and has only been minimally tested")
public class commutate extends Primitive {

  DataFile hi, ho;
  Data dbi, dbo;
  int elements, ndata;
  int frameSize;
  double isize;
  double dbpe;
  String format;
  FileName fileName;
  boolean commutate;
  String tempPrefix = "\"commutate_\"";
  String suffix = ".tmp";
  String quals = "{AUX=RAM}";
  double xs, xd;
  ArrayList<thin> thinCmds = new ArrayList<>();
  DataFile thinnedFile;
  Data data;
  byte[] zeroBuf;
  boolean debug;
  double lastChecked;
  boolean thinComplete = false;
  boolean saveTemp; // save the temporary files (i.e. do not store them in RAM)
  private static int TIME_SINCE_WRITE = 100; // 0.1 seconds

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

  public int open () {

    hi = MA.getDataFile("IN", "1000", "S#,C#", 0);
    hi.open();
    xs = hi.getXStart();
    xd = hi.getXDelta();
    format = hi.getFormat();
    isize = hi.getSize();
    dbpe = hi.dbpe;
    fileName = hi.getFileName();
    hi.close();

    elements = MA.getL("ELEM", MA.getL("ELEMENTS", 1));
    frameSize = (int) Math.ceil(isize / elements);

    ho = MA.getDataFile("OUT", "2000", format, 0);
    ho.setSubSize(frameSize);
    ho.setXStart(xs);
    ho.setXDelta(xd * frameSize);
    ho.setInternals();
    ho.open(BaseFile.OUTPUT | BaseFile.OPTIONAL);

    tempPrefix = MA.getS("TEMPPREFIX", tempPrefix);

    saveTemp = MA.isPresent("/SAVETEMP");
    if (saveTemp) quals = "";

    debug = MA.isPresent("/DEBUG");

    int max0needed = (int) (frameSize * dbpe);
    zeroBuf = new byte[max0needed];

    if (debug) {
      System.out.println("elements:" + elements);
      System.out.println("frame size:" + frameSize);
      System.out.println("isize:" + isize);
      System.out.println("dbpe:" + dbpe);
      System.out.println("fileName:" + fileName);
      System.out.println("tempPrefix:" + tempPrefix + " quals:" + quals);
    }

    data = new Data(format, frameSize);

    startThinCommands();

    return NORMAL;
  }

  public int process () {
    if (!thinComplete) {
      // thinComplete = readyForMerge();
      thinComplete = filesNotModified();
      if (debug) System.out.println("thinComplete:" + thinComplete);
      if (!thinComplete) return NOOP;
    }

    for (int i = 0; i < elements; i++) {
      writeDataFromThinnedFile(tempPrefix + i + suffix);
    }

    return FINISH;
  }

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

  private void startThinCommands () {
    for (int i = 0; i < elements; i++) {
      String thinCmd = "thin IN=" + fileName + " OUT=\"" + tempPrefix + i + suffix + quals + "\" START=" + i + " INC="
          + elements;
      if (debug) System.out.println("thinCmd:" + thinCmd);
      thinCmds.add((thin) Shell.execBackgroundCommand(M, thinCmd));
      lastChecked = System.currentTimeMillis();
    }
  }

  private boolean filesNotModified () {
    for (int i = 0; i < elements; i++) {
      java.io.File processCheck = new java.io.File(tempPrefix + i + suffix);
      boolean exists = processCheck.exists();
      double thisModified = processCheck.lastModified();
      if (debug) System.out.println("checking " + tempPrefix + i + suffix + " modified:" + thisModified
          + " lastChecked:" + lastChecked + " exists:" + exists);
      if ((thisModified + TIME_SINCE_WRITE > lastChecked) || !exists) {
        if (debug) System.out.println("greater");
        lastChecked = System.currentTimeMillis();
        return false;
      }
    }
    return true;
  }

  private boolean readyForMerge () {
    boolean finished = true;
    for (int i = 0; i < elements; i++) {
      finished &= thinCmds.get(i).finishing();
      if (debug) System.out.println("checking " + tempPrefix + i + suffix);
      if (!finished) break;
    }
    return finished;
  }

  private void writeDataFromThinnedFile (String fileName) {
    thinnedFile = new DataFile(M, fileName);
    thinnedFile.open();
    int read = thinnedFile.read(data, frameSize); // get the FIR taps
    // write byte buffer directly to avoid reformatting
    if (read > 0) {
      ho.write(data.buf, 0, (int) (read * dbpe));
      if (read < frameSize) {
        ho.write(zeroBuf, 0, (int) ((frameSize - read) * dbpe));
      }
    }
    thinnedFile.close();
    if (debug) System.out.println("writing data from file:" + fileName + " read:" + read);
  }

}
