package nxm.sys.prim;

import java.text.SimpleDateFormat;
import java.util.Date;

import nxm.sys.inc.Units;
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 a special file with real-time and circular options.

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

  private DataFile hi,ho;
  private Data data;
  private double start,offset,size,end;
  private boolean rtfile;
  private String out;

  /////// Variables below added to support /CUT in NeXtMidas 3.3.0    ////////
  /** Time should be added to the file name? */     private boolean timeBased = false;
  /** Number of elements for each file.  */         private long nelements = 0;
  /** Length of digits in the filename.  */         private int ndigits = 4;
  /** Sequence number of filenames.  */             private long seqNum = 0L;
  /** Format of the filename date/time  */          private String namescheme = "";
  /** The output filename. */                       private String fileName;
  /** Number of seconds for each file.   */         private double nseconds;
  /** Start time when splitting up per seconds. */  private double startTime;
  /** Format of the filename.  */                   private String cut;
  /** Info message when opening a new file.  */     private String cutMsg = null;

  // Supported SimpleDateFormat formats
  private static final String FORM_TYPE_1 = "yyyyMMdd_HHmmss";
  private static final String FORM_TYPE_2 = "yyMMdd";
  private static final String FORM_TYPE_3 = "HHmmss";

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

  @Override
  public int open() {

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

    out        = MA.getS("OUT");
    start      = MA.getD("START");
    size       = MA.getD("DURATION");
    cut        = MA.getS("/CUT",null);

    end = start+size;

    if (cut != null) setCutOptions(cut, MA.getTable("CUTOPTIONS"));   // fileName set to increment
    else             fileName = out;

    ho = new DataFile(M, fileName, hi, 0);
    ho.open(DataFile.OUTPUT);
    ho.setXStart(hi.getXStart() + hi.getXDelta()*hi.getOffset());

    if (size>0) ho.setSize(end);

    xfer = Math.max(1,(int)(bufSize/hi.dbpe));
    xfer = MA.getL("/TL",xfer);
    data = hi.getDataBuffer(xfer);

    offset = 0;
    rtfile = MA.getState("/RTFILE");
    // Default to the original way if the keyword is not provided
    if(nelements == 0 || nseconds == 0) {
      if (size>0) ho.seek(end); 	// set EOF
      ho.seek(start);
    }
    return (NORMAL);
  }

  /** Setup all the options and params associated with the /CUT switch    */
  private void setCutOptions (String cutVal, Table options) {
    if (options != null) {
      ndigits = Math.max(1, Math.min(16, options.getL("DIGITS")));
      namescheme = options.getS("NAMESCHEME");
      if (namescheme != null) {
        timeBased = namescheme.equalsIgnoreCase("") ? false : true;
      }
      seqNum  = options.getL("STARTNUM", 0);
      cutMsg    = options.getS("NEWFILEMSG");
    } else {
      ndigits    = 4;
      namescheme = "";
      timeBased  = false;
      cutMsg     = null;
    }

    if (cutVal.equalsIgnoreCase("ELEMENTS")||cutVal.equalsIgnoreCase("TIME")) {
      if (cutVal.equalsIgnoreCase("TIME")) {
        if ( (hi.getUnits()!=Units.TIME) || (hi.getUnits()!=Units.TIME_S) ) {
          M.warning("Input file is not time-based. Assuming Time Delta of 1.0");
        } else {
          size = size/hi.getDelta();
        }
      }
      setNelements((long)size);
      if (size>0) { hi.seek(0); end = size; }
    } else if (cutVal.equalsIgnoreCase("CLOCK_TIME")) {
      if (size > 1) {
        setNseconds(size);
        size = 0; // Ensure that the file size is not initially set
        hi.seek(0);
        double t = System.currentTimeMillis(); // Based on clock time
        setStartTime(t);
      }
    }
    fileName = getFileName(seqNum++);
  }

  @Override
  public int process() {
// System.out.println("SinkFile proc() hi="+hi.getName()+" offset="+offset+" ho="+ho.getName());
    if (nelements != 0) {               // /CUT=ELEMENTS or /CUT=TIME
      return processCutByElements();
    } else if (nseconds != 0) {         // /CUT=CLOCK_TIME
      return processCutByClockTime();
    } else {    // standard sinkfile, not using /CUT
      int n = hi.read(data);
      if (n<0) return (FINISH);
      if (n==0) return (NOOP);
      ho.write(data,n);
      offset += n;
      if (rtfile) { ho.setInByte(offset*ho.dbpe); ho.writeHeader(); }
      if (size>0 && ho.seek()>=end) ho.seek(start);

      return (NORMAL);
    }
  }

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

  @Override
  public int processMessage (nxm.sys.lib.Message msg) {
    if (msg.name.equals("CUT")) {
      if (msg.data != null) out = (String)msg.data;
      if (cutMsg==null) cutMsg = "";  // ensure notification back to user
      cutNewFile();
    }
    else {
      return super.processMessage(msg);
    }
     return NORMAL;
  }

  private int processCutByElements () {
    if((offset+xfer) < ho.getSize()) {
      int n = hi.read(data);
      if (n<0) return (FINISH);
      if (n==0) return (NOOP);
      ho.write(data, n);
      //ho.setXStart(hi.getXStart() + hi.getXDelta()*hi.getOffset());   // only set when file opens?
      offset += n;
    }
    else {
      int elementsLeft = (int)(ho.getSize() - offset);
      data = hi.getDataBuffer(elementsLeft);
      int n = hi.read(data);
      if (n<0) return (FINISH);
      if (n==0) return (NOOP);
      ho.write(data, n);
      if(hi.seek()<(hi.getSize()-1)) {
        cutNewFile();
      }
    }
    return NORMAL;
  }

  private int processCutByClockTime () {
  //TODO: Continue to grab either the file time or clock time
    long currentTime = System.currentTimeMillis();
    long lapsedTime = (long) ((currentTime - startTime) / 1000); // convert to seconds
    if(lapsedTime < nseconds) {
      int n = hi.read(data);
      if (n<0) return (FINISH);
      if (n==0) return (NOOP);
      ho.write(data, n);
      offset += n;
    }
    else {
      setStartTime(System.currentTimeMillis());
      cutNewFile();
    }
    return (NORMAL);
  }

  /** Get the filename for the current output file
   *  @return the filename for the current output file
   *  @since NeXtMidas 3.3.0
   */
  public String getFileName () {
    return getFileName(seqNum);
  }

  /** Get the predetermined filename when using /CUT. Formatted based off {@link #namescheme} and {@link #timeBased}  */
  private String getFileName (long count) {
    String fileName, timeFormat = "";
    String format = "%0" + getNdigits() + "d";

    if(timeBased) {
      SimpleDateFormat sdf = new SimpleDateFormat(retrieveFormat(namescheme));
      timeFormat = "_" + sdf.format(new Date());
    }
    String fileNum = String.format(format, count);
    fileName = out + fileNum + timeFormat;
    return fileName;
  }

  /** Close the current output file and start a new one.
      @since NeXtMidas 3.3.0
   */
  public void cutNewFile () {
    String oldName = ho.getName().getBasename();
    ho.close();
    offset = 0;
    String fileName = getFileName(seqNum++);
    ho = new DataFile(M, fileName, hi, 0);
    ho.setXStart(hi.getXStart() + hi.getXDelta()*hi.getOffset());
    if (size>0) {ho.setSize(size); ho.seek(0);}
    ho.open(DataFile.OUTPUT);
    if(cutMsg != null) {
      if (cutMsg.equals("")) M.info("SINKFILE: Closed current file: "+oldName+". Spawning new file: "+fileName);
      else                    M.info(cutMsg);   // user supplied
    }
  }

  /** Set the number of elements per file when using /CUT. Since NeXtMidas 3.3.0    */
  private void setNelements (long nelements) {
    this.nelements = nelements;
  }

  /** @param number of seconds per file when using /CUT Since NeXtMidas 3.3.0*/
  private void setNseconds (double nseconds) {
    this.nseconds = nseconds;
  }

  /** Set file time or clock time to start new files when using /CUT
   *  @param startTime file time or clock time to start new files when using /CUT Since NeXtMidas 3.3.0 */
  public void setStartTime (double startTime) {
    // Added in 3.3.0 for Bug 2640
    this.startTime = startTime;
  }

  /** Get number of digits used in the output file
   *  @return number of digits (precision) used in the output file when using /CUT. Since NeXtMidas 3.3.0 */
  private int getNdigits () {
    // Added in 3.3.0 for Bug 2640
    if (ndigits < 0)
      return 4; // Default to 4 if none is provided or zero entered
    else
      return ndigits;
  }

  /** Set number of digits used in the output file
   *  @param ndigits number of digits (precision) used in the output file when using /CUT. Since NeXtMidas 3.3.0 */
  public void setNdigits (int ndigits) {
    if(ndigits > 8)
      this.ndigits = 8; // Default to 8 if greater than 8 is given
    else
      this.ndigits = ndigits;
  }

  /** Returns the time format ({@link #FORM_TYPE_1} [DEF], {@link #FORM_TYPE_2}, or {@link #FORM_TYPE_3}
      for the filename when using /CUT. <br>
      Input String must be 'STD', 'YMD', or 'HMS'.
      @param namescheme Time Format name
      @return time format
   */
  private String retrieveFormat (String namescheme) {
    if (namescheme.equalsIgnoreCase("STD"))
      return FORM_TYPE_1;
    else if (namescheme.equalsIgnoreCase("YMD"))
      return FORM_TYPE_2;
    else if (namescheme.equalsIgnoreCase("HMS"))
      return FORM_TYPE_3;
    else
      return FORM_TYPE_1; // Default to FORM_TYPE_1 if something other than the supported types are given
  }

}
